From fa05ae9647f9f8a7e7863254b41ff6765a53e99a Mon Sep 17 00:00:00 2001 From: Vasiliy Usov Date: Tue, 1 Feb 2022 20:06:07 +0300 Subject: [PATCH 01/28] Add Migration manager --- FreeAPS.xcodeproj/project.pbxproj | 52 ++++++++++++++ FreeAPS/Sources/Application/FreeAPSApp.swift | 28 +++++++- .../Sources/Models/TargetInformation.swift | 30 ++++++++ .../Modules/Migration/MigrationDataFlow.swift | 7 ++ .../Modules/Migration/MigrationProvider.swift | 5 ++ .../Migration/MigrationStateModel.swift | 9 +++ .../Migration/View/MigrationRootView.swift | 31 ++++++++ .../Services/Migration/MigrationManager.swift | 59 +++++++++++++++ .../Migration/MigrationPublisher.swift | 72 +++++++++++++++++++ 9 files changed, 292 insertions(+), 1 deletion(-) create mode 100644 FreeAPS/Sources/Models/TargetInformation.swift create mode 100644 FreeAPS/Sources/Modules/Migration/MigrationDataFlow.swift create mode 100644 FreeAPS/Sources/Modules/Migration/MigrationProvider.swift create mode 100644 FreeAPS/Sources/Modules/Migration/MigrationStateModel.swift create mode 100644 FreeAPS/Sources/Modules/Migration/View/MigrationRootView.swift create mode 100644 FreeAPS/Sources/Services/Migration/MigrationManager.swift create mode 100644 FreeAPS/Sources/Services/Migration/MigrationPublisher.swift diff --git a/FreeAPS.xcodeproj/project.pbxproj b/FreeAPS.xcodeproj/project.pbxproj index 166e418f4..34e6f798f 100644 --- a/FreeAPS.xcodeproj/project.pbxproj +++ b/FreeAPS.xcodeproj/project.pbxproj @@ -307,11 +307,18 @@ E974172296125A5AE99E634C /* PumpConfigRootView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 2AD22C985B79A2F0D2EA3D9D /* PumpConfigRootView.swift */; }; F5CA3DB1F9DC8B05792BBFAA /* CGMDataFlow.swift in Sources */ = {isa = PBXBuildFile; fileRef = B9B5C0607505A38F256BF99A /* CGMDataFlow.swift */; }; F5F7E6C1B7F098F59EB67EC5 /* TargetsEditorDataFlow.swift in Sources */ = {isa = PBXBuildFile; fileRef = BA49538D56989D8DA6FCF538 /* TargetsEditorDataFlow.swift */; }; + F904D3CD27A85B6800C5466F /* MigrationManager.swift in Sources */ = {isa = PBXBuildFile; fileRef = F904D3CC27A85B6800C5466F /* MigrationManager.swift */; }; F90692AA274B7AAE0037068D /* HealthKitManager.swift in Sources */ = {isa = PBXBuildFile; fileRef = F90692A9274B7AAE0037068D /* HealthKitManager.swift */; }; F90692CF274B999A0037068D /* HealthKitDataFlow.swift in Sources */ = {isa = PBXBuildFile; fileRef = F90692CE274B999A0037068D /* HealthKitDataFlow.swift */; }; F90692D1274B99B60037068D /* HealthKitProvider.swift in Sources */ = {isa = PBXBuildFile; fileRef = F90692D0274B99B60037068D /* HealthKitProvider.swift */; }; F90692D3274B9A130037068D /* AppleHealthKitRootView.swift in Sources */ = {isa = PBXBuildFile; fileRef = F90692D2274B9A130037068D /* AppleHealthKitRootView.swift */; }; F90692D6274B9A450037068D /* HealthKitStateModel.swift in Sources */ = {isa = PBXBuildFile; fileRef = F90692D5274B9A450037068D /* HealthKitStateModel.swift */; }; + F97F722127A957AF007B6620 /* MigrationPublisher.swift in Sources */ = {isa = PBXBuildFile; fileRef = F97F722027A957AF007B6620 /* MigrationPublisher.swift */; }; + F97F722427A973DF007B6620 /* MigrationDataFlow.swift in Sources */ = {isa = PBXBuildFile; fileRef = F97F722327A973DF007B6620 /* MigrationDataFlow.swift */; }; + F97F722627A973FF007B6620 /* MigrationProvider.swift in Sources */ = {isa = PBXBuildFile; fileRef = F97F722527A973FF007B6620 /* MigrationProvider.swift */; }; + F97F722827A9741C007B6620 /* MigrationStateModel.swift in Sources */ = {isa = PBXBuildFile; fileRef = F97F722727A9741C007B6620 /* MigrationStateModel.swift */; }; + F97F722B27A9743A007B6620 /* MigrationRootView.swift in Sources */ = {isa = PBXBuildFile; fileRef = F97F722A27A9743A007B6620 /* MigrationRootView.swift */; }; + F97F722D27A986F1007B6620 /* TargetInformation.swift in Sources */ = {isa = PBXBuildFile; fileRef = F97F722C27A986F1007B6620 /* TargetInformation.swift */; }; FA630397F76B582C8D8681A7 /* BasalProfileEditorProvider.swift in Sources */ = {isa = PBXBuildFile; fileRef = 42369F66CF91F30624C0B3A6 /* BasalProfileEditorProvider.swift */; }; /* End PBXBuildFile section */ @@ -713,11 +720,18 @@ E625985B47742D498CB1681A /* NotificationsConfigProvider.swift */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = sourcecode.swift; path = NotificationsConfigProvider.swift; sourceTree = ""; }; E68CDC1E5C438D1BEAD4CF24 /* LibreConfigStateModel.swift */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = sourcecode.swift; path = LibreConfigStateModel.swift; sourceTree = ""; }; E9AAB83FB6C3B41EFD1846A0 /* AddTempTargetRootView.swift */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = sourcecode.swift; path = AddTempTargetRootView.swift; sourceTree = ""; }; + F904D3CC27A85B6800C5466F /* MigrationManager.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = MigrationManager.swift; sourceTree = ""; }; F90692A9274B7AAE0037068D /* HealthKitManager.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = HealthKitManager.swift; sourceTree = ""; }; F90692CE274B999A0037068D /* HealthKitDataFlow.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = HealthKitDataFlow.swift; sourceTree = ""; }; F90692D0274B99B60037068D /* HealthKitProvider.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = HealthKitProvider.swift; sourceTree = ""; }; F90692D2274B9A130037068D /* AppleHealthKitRootView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = AppleHealthKitRootView.swift; sourceTree = ""; }; F90692D5274B9A450037068D /* HealthKitStateModel.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = HealthKitStateModel.swift; sourceTree = ""; }; + F97F722027A957AF007B6620 /* MigrationPublisher.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = MigrationPublisher.swift; sourceTree = ""; }; + F97F722327A973DF007B6620 /* MigrationDataFlow.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = MigrationDataFlow.swift; sourceTree = ""; }; + F97F722527A973FF007B6620 /* MigrationProvider.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = MigrationProvider.swift; sourceTree = ""; }; + F97F722727A9741C007B6620 /* MigrationStateModel.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = MigrationStateModel.swift; sourceTree = ""; }; + F97F722A27A9743A007B6620 /* MigrationRootView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = MigrationRootView.swift; sourceTree = ""; }; + F97F722C27A986F1007B6620 /* TargetInformation.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = TargetInformation.swift; sourceTree = ""; }; FBB3BAE7494CB771ABAC7B8B /* ISFEditorRootView.swift */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = sourcecode.swift; path = ISFEditorRootView.swift; sourceTree = ""; }; /* End PBXFileReference section */ @@ -850,6 +864,7 @@ 3811DE0325C9D31700A708ED /* Modules */ = { isa = PBXGroup; children = ( + F97F722227A9739F007B6620 /* Migration */, F90692CD274B99850037068D /* HealthKit */, 6DC5D590658EF8B8DF94F9F5 /* AddCarbs */, A9A4C88374496B3C89058A89 /* AddTempTarget */, @@ -980,6 +995,7 @@ 3811DE9125C9D88200A708ED /* Services */ = { isa = PBXGroup; children = ( + F904D3CB27A85B5000C5466F /* Migration */, F90692A8274B7A980037068D /* HealthKit */, 38E8754D275556E100975559 /* WatchManager */, 38E87406274F9AA500975559 /* UserNotifiactions */, @@ -1267,6 +1283,7 @@ 38E989DC25F5021400C0CED0 /* PumpStatus.swift */, 38BF021C25E7E3AF00579895 /* Reservoir.swift */, 3871F38625ED661C0013ECB5 /* Suggestion.swift */, + F97F722C27A986F1007B6620 /* TargetInformation.swift */, 38A0364125ED069400FCBB52 /* TempBasal.swift */, 3871F39B25ED892B0013ECB5 /* TempTarget.swift */, 3811DE8E25C9D80400A708ED /* User.swift */, @@ -1814,6 +1831,15 @@ path = CGM; sourceTree = ""; }; + F904D3CB27A85B5000C5466F /* Migration */ = { + isa = PBXGroup; + children = ( + F904D3CC27A85B6800C5466F /* MigrationManager.swift */, + F97F722027A957AF007B6620 /* MigrationPublisher.swift */, + ); + path = Migration; + sourceTree = ""; + }; F90692A8274B7A980037068D /* HealthKit */ = { isa = PBXGroup; children = ( @@ -1841,6 +1867,25 @@ path = View; sourceTree = ""; }; + F97F722227A9739F007B6620 /* Migration */ = { + isa = PBXGroup; + children = ( + F97F722927A97425007B6620 /* View */, + F97F722327A973DF007B6620 /* MigrationDataFlow.swift */, + F97F722527A973FF007B6620 /* MigrationProvider.swift */, + F97F722727A9741C007B6620 /* MigrationStateModel.swift */, + ); + path = Migration; + sourceTree = ""; + }; + F97F722927A97425007B6620 /* View */ = { + isa = PBXGroup; + children = ( + F97F722A27A9743A007B6620 /* MigrationRootView.swift */, + ); + path = View; + sourceTree = ""; + }; /* End PBXGroup section */ /* Begin PBXNativeTarget section */ @@ -2080,6 +2125,7 @@ 38D0B3B625EBE24900CB6E88 /* Battery.swift in Sources */, 38C4D33725E9A1A300D30B77 /* DispatchQueue+Extensions.swift in Sources */, F90692CF274B999A0037068D /* HealthKitDataFlow.swift in Sources */, + F904D3CD27A85B6800C5466F /* MigrationManager.swift in Sources */, 3862CC2E2743F9F700BF832C /* CalendarManager.swift in Sources */, 38B4F3C325E2A20B00E76A18 /* PumpSetupView.swift in Sources */, 38E4453C274E411700EC9A94 /* Disk+Codable.swift in Sources */, @@ -2197,6 +2243,7 @@ 9825E5E923F0B8FA80C8C7C7 /* NightscoutConfigStateModel.swift in Sources */, 38A43598262E0E4900E80935 /* FetchAnnouncementsManager.swift in Sources */, 642F76A05A4FF530463A9FD0 /* NightscoutConfigRootView.swift in Sources */, + F97F722627A973FF007B6620 /* MigrationProvider.swift in Sources */, AD3D2CD42CD01B9EB8F26522 /* PumpConfigDataFlow.swift in Sources */, 53F2382465BF74DB1A967C8B /* PumpConfigProvider.swift in Sources */, 5D16287A969E64D18CE40E44 /* PumpConfigStateModel.swift in Sources */, @@ -2220,6 +2267,7 @@ 38FEF3FA2737E42000574A46 /* BaseStateModel.swift in Sources */, 385CEA8225F23DFD002D6D5B /* NightscoutStatus.swift in Sources */, F90692AA274B7AAE0037068D /* HealthKitManager.swift in Sources */, + F97F722D27A986F1007B6620 /* TargetInformation.swift in Sources */, 38887CCE25F5725200944304 /* IOBEntry.swift in Sources */, 38E98A2425F52C9300C0CED0 /* Logger.swift in Sources */, CA370FC152BC98B3D1832968 /* BasalProfileEditorRootView.swift in Sources */, @@ -2234,11 +2282,13 @@ A33352ED40476125EBAC6EE0 /* CREditorDataFlow.swift in Sources */, 17A9D0899046B45E87834820 /* CREditorProvider.swift in Sources */, 69B9A368029F7EB39F525422 /* CREditorStateModel.swift in Sources */, + F97F722827A9741C007B6620 /* MigrationStateModel.swift in Sources */, 38E44538274E411700EC9A94 /* Disk+[Data].swift in Sources */, 98641AF4F92123DA668AB931 /* CREditorRootView.swift in Sources */, 38E4453D274E411700EC9A94 /* Disk+Errors.swift in Sources */, 38E98A2325F52C9300C0CED0 /* Signpost.swift in Sources */, F5F7E6C1B7F098F59EB67EC5 /* TargetsEditorDataFlow.swift in Sources */, + F97F722B27A9743A007B6620 /* MigrationRootView.swift in Sources */, 5075C1608E6249A51495C422 /* TargetsEditorProvider.swift in Sources */, E13B7DAB2A435F57066AF02E /* TargetsEditorStateModel.swift in Sources */, 9702FF92A09C53942F20D7EA /* TargetsEditorRootView.swift in Sources */, @@ -2273,8 +2323,10 @@ 711C0CB42CAABE788916BC9D /* ManualTempBasalDataFlow.swift in Sources */, BF1667ADE69E4B5B111CECAE /* ManualTempBasalProvider.swift in Sources */, F90692D6274B9A450037068D /* HealthKitStateModel.swift in Sources */, + F97F722427A973DF007B6620 /* MigrationDataFlow.swift in Sources */, C967DACD3B1E638F8B43BE06 /* ManualTempBasalStateModel.swift in Sources */, 38E4453B274E411700EC9A94 /* Disk+VolumeInformation.swift in Sources */, + F97F722127A957AF007B6620 /* MigrationPublisher.swift in Sources */, 7BCFACB97C821041BA43A114 /* ManualTempBasalRootView.swift in Sources */, 38E44534274E411700EC9A94 /* Disk+InternalHelpers.swift in Sources */, 38A00B2325FC2B55006BC0B0 /* LRUCache.swift in Sources */, diff --git a/FreeAPS/Sources/Application/FreeAPSApp.swift b/FreeAPS/Sources/Application/FreeAPSApp.swift index e63d19dea..e78829f55 100644 --- a/FreeAPS/Sources/Application/FreeAPSApp.swift +++ b/FreeAPS/Sources/Application/FreeAPSApp.swift @@ -1,9 +1,15 @@ +import Combine import SwiftUI import Swinject @main struct FreeAPSApp: App { @Environment(\.scenePhase) var scenePhase @UIApplicationDelegateAdaptor(AppDelegate.self) var appDelegate + @State private var isMigrating: Bool = true + + // Migration + private var migrationManager = MigrationManager() + @State private var lifetime: Lifetime = [] // Dependencies Assembler // contain all dependencies Assemblies @@ -43,14 +49,34 @@ import Swinject init() { loadServices() + _isMigrating = State(initialValue: migrationManager.isNeedMigrate) } var body: some Scene { WindowGroup { - Main.RootView(resolver: resolver) + ZStack { + if isMigrating { + Migration.RootView(resolver: resolver) + .onAppear { runMigration() } + } else { + Main.RootView(resolver: resolver) + } + } + .animation(.easeIn(duration: 0.75), value: self.isMigrating) } .onChange(of: scenePhase) { newScenePhase in debug(.default, "APPLICATION PHASE: \(newScenePhase)") } } + + private func runMigration() { + migrationManager + .publisher + .sink { [self] _ in + DispatchQueue.main.asyncAfter(deadline: .now() + 1.5) { + isMigrating = false + } + } + .store(in: &lifetime) + } } diff --git a/FreeAPS/Sources/Models/TargetInformation.swift b/FreeAPS/Sources/Models/TargetInformation.swift new file mode 100644 index 000000000..6c8a638ec --- /dev/null +++ b/FreeAPS/Sources/Models/TargetInformation.swift @@ -0,0 +1,30 @@ +import Foundation + +struct TargetInformation { + @Persisted(key: "AppTargetInformation.lastMigrationVersion") private var _lastMigrationVersion: String = "" + var lastMigrationVersion: String? { + if _lastMigrationVersion == "" { + // nil means that + // 1) app run first time after install + // or + // 2) previous run was on version <= 0.2.6 + return nil + } + return _lastMigrationVersion + } + + // App can be run first time + var isFirstExecute: Bool { + // check first app execution by preferences.json file + // if file doesn't exist - the first execution + Disk.exists(OpenAPS.Settings.preferences, in: .documents) + } + + var currentVersion: String { + Bundle.main.infoDictionary?["CFBundleVersion"] as! String + } + + mutating func actualLastMigrationVersion() { + _lastMigrationVersion = currentVersion + } +} diff --git a/FreeAPS/Sources/Modules/Migration/MigrationDataFlow.swift b/FreeAPS/Sources/Modules/Migration/MigrationDataFlow.swift new file mode 100644 index 000000000..27b666f57 --- /dev/null +++ b/FreeAPS/Sources/Modules/Migration/MigrationDataFlow.swift @@ -0,0 +1,7 @@ +import SwiftUI + +enum Migration { + enum Config {} +} + +protocol MigrationProvider: Provider {} diff --git a/FreeAPS/Sources/Modules/Migration/MigrationProvider.swift b/FreeAPS/Sources/Modules/Migration/MigrationProvider.swift new file mode 100644 index 000000000..5528687f7 --- /dev/null +++ b/FreeAPS/Sources/Modules/Migration/MigrationProvider.swift @@ -0,0 +1,5 @@ +import Combine + +extension Migration { + final class Provider: BaseProvider, MainProvider {} +} diff --git a/FreeAPS/Sources/Modules/Migration/MigrationStateModel.swift b/FreeAPS/Sources/Modules/Migration/MigrationStateModel.swift new file mode 100644 index 000000000..014fe3d85 --- /dev/null +++ b/FreeAPS/Sources/Modules/Migration/MigrationStateModel.swift @@ -0,0 +1,9 @@ +import SwiftMessages +import SwiftUI +import Swinject + +extension Migration { + final class StateModel: BaseStateModel { + @Published var animated: Bool = false + } +} diff --git a/FreeAPS/Sources/Modules/Migration/View/MigrationRootView.swift b/FreeAPS/Sources/Modules/Migration/View/MigrationRootView.swift new file mode 100644 index 000000000..d432a7f47 --- /dev/null +++ b/FreeAPS/Sources/Modules/Migration/View/MigrationRootView.swift @@ -0,0 +1,31 @@ +import SwiftUI +import Swinject + +extension Migration { + struct RootView: BaseView { + let resolver: Resolver + @StateObject var state = StateModel() + + var body: some View { + VStack { + Circle() + .trim(from: 0.1, to: 0.9) + .stroke( + AngularGradient(gradient: Gradient(colors: [.red, .yellow]), center: .center), + style: StrokeStyle(lineWidth: 15, lineCap: .round) + ) + .frame(width: 80, height: 80) + .rotationEffect(.degrees(state.animated ? 360 : 0)) + .animation(.linear(duration: 0.7).repeatForever(autoreverses: false)) + Text("Preparing data \n Please wait") + .font(.title) + .fontWeight(.bold) + .multilineTextAlignment(.center) + .padding(.top, 10) + } + .onAppear { + state.animated.toggle() + } + } + } +} diff --git a/FreeAPS/Sources/Services/Migration/MigrationManager.swift b/FreeAPS/Sources/Services/Migration/MigrationManager.swift new file mode 100644 index 000000000..88c94af15 --- /dev/null +++ b/FreeAPS/Sources/Services/Migration/MigrationManager.swift @@ -0,0 +1,59 @@ +import Combine +import Foundation +import SwiftUI + +class MigrationManager { + private var needWorkAfterUpdateIntoVersion: [String] = [] + private var appInformation: TargetInformation + var isNeedMigrate: Bool { + return true + guard !appInformation.isFirstExecute else { + return false + } + guard let lastMigrationVersion = appInformation.lastMigrationVersion else { + // if version < 0.2.6 + // in 0.2.6 was added MigrationManager + return true + } + if !needWorkAfterUpdateIntoVersion.filter({ $0 > lastMigrationVersion }).isEmpty { + // need execute handlers beetwen CurrentVersion...lastExecutedVersion, exclude lastExecutedVersion + return true + } + return false + } + + var publisher: AnyPublisher { + Publishers + .getMigrationPublisher(appInformation) + // example of migrating + // .migrate(migrateExample) + .actualLastMigrationVersion() + .eraseToAnyPublisher() + } + + init(appInformation: TargetInformation = TargetInformation()) { + self.appInformation = appInformation + // need execute one or more migration's handler after update into version 0.2.6 + // if need migrating on version 0.2.6, add + // needWorkAfterUpdateIntoVersion.append("0.2.6") + } + + private func checkNeedToRun(_ version: String) -> Bool { + guard appInformation.currentVersion >= version, + !appInformation.isFirstExecute + else { return false } + + guard let lastMigrationVersion = appInformation.lastMigrationVersion, + lastMigrationVersion == appInformation.currentVersion + else { return true } + return false + } +} + +// Migrating Example +extension MigrationManager { + func migrateExample(_: TargetInformation) { + guard checkNeedToRun("0.2.6") else { return } + print("Sample migration handler") + } +} diff --git a/FreeAPS/Sources/Services/Migration/MigrationPublisher.swift b/FreeAPS/Sources/Services/Migration/MigrationPublisher.swift new file mode 100644 index 000000000..23e4f3c0a --- /dev/null +++ b/FreeAPS/Sources/Services/Migration/MigrationPublisher.swift @@ -0,0 +1,72 @@ +import Combine +import Foundation +import SwiftUI + +/** + + Example of using DataMigrationTool + + Publishers + .dataMigrationTool(AppTargetInfo()) + .migrate { info in + // ... + } + .sink { + // ... + } + + info is AppTargetInfo instance, which contains target information (version and etc). + + */ + +extension Publishers { + static func getMigrationPublisher(_ appInfo: TargetInformation) -> MigrationPublisher { + MigrationPublisher(appInfo) + } + + class MigrationSubscription: Subscription where S.Input == TargetInformation, S.Failure == Never { + private var targetInformation: TargetInformation + private var subscriber: S? + + init(_ appInfo: TargetInformation, subscriber: S) { + targetInformation = appInfo + self.subscriber = subscriber + } + + func cancel() { + subscriber = nil + } + + func request(_: Subscribers.Demand) { + _ = subscriber?.receive(targetInformation) + subscriber?.receive(completion: .finished) + return + } + } + + struct MigrationPublisher: Publisher { + typealias Output = TargetInformation + typealias Failure = Never + + @State private var targetInformation: TargetInformation + + init(_ AppTargetInfo: TargetInformation) { + targetInformation = AppTargetInfo + } + + func receive(subscriber: S) where S: Subscriber, Never == S.Failure, TargetInformation == S.Input { + let subscription = MigrationSubscription(targetInformation, subscriber: subscriber) + subscriber.receive(subscription: subscription) + } + + func migrate(_ handler: (TargetInformation) -> Void) -> Self { + handler(targetInformation) + return self + } + + func actualLastMigrationVersion() -> Self { + targetInformation.actualLastMigrationVersion() + return self + } + } +} From d2d2b5309b74ac76d1b5f7f9a6d57d080c6e7f01 Mon Sep 17 00:00:00 2001 From: Vasiliy Usov Date: Fri, 11 Feb 2022 20:46:25 +0300 Subject: [PATCH 02/28] Refactoring migration manager/publisher --- FreeAPS.xcodeproj/project.pbxproj | 24 ++++- FreeAPS/Sources/Application/FreeAPSApp.swift | 32 +++---- .../Sources/Assemblies/ServiceAssembly.swift | 4 + .../PreferenceKeyAppLoading.swift | 9 ++ FreeAPS/Sources/Models/AppInfo.swift | 12 +++ .../Sources/Models/TargetInformation.swift | 30 ------- .../Migration/MigrationStateModel.swift | 30 ++++++- .../Migration/View/MigrationRootView.swift | 4 + FreeAPS/Sources/Router/Screen.swift | 3 + .../Services/Migration/MigrationManager.swift | 88 +++++++++---------- .../Migration/MigrationPublisher.swift | 69 ++++++++------- .../Migration/MigrationWorkItem.swift | 20 +++++ 12 files changed, 190 insertions(+), 135 deletions(-) create mode 100644 FreeAPS/Sources/Helpers/PreferenceKeys/PreferenceKeyAppLoading.swift create mode 100644 FreeAPS/Sources/Models/AppInfo.swift delete mode 100644 FreeAPS/Sources/Models/TargetInformation.swift create mode 100644 FreeAPS/Sources/Services/Migration/MigrationWorkItem.swift diff --git a/FreeAPS.xcodeproj/project.pbxproj b/FreeAPS.xcodeproj/project.pbxproj index 34e6f798f..21ecd6661 100644 --- a/FreeAPS.xcodeproj/project.pbxproj +++ b/FreeAPS.xcodeproj/project.pbxproj @@ -313,12 +313,14 @@ F90692D1274B99B60037068D /* HealthKitProvider.swift in Sources */ = {isa = PBXBuildFile; fileRef = F90692D0274B99B60037068D /* HealthKitProvider.swift */; }; F90692D3274B9A130037068D /* AppleHealthKitRootView.swift in Sources */ = {isa = PBXBuildFile; fileRef = F90692D2274B9A130037068D /* AppleHealthKitRootView.swift */; }; F90692D6274B9A450037068D /* HealthKitStateModel.swift in Sources */ = {isa = PBXBuildFile; fileRef = F90692D5274B9A450037068D /* HealthKitStateModel.swift */; }; + F97C8B2927B6A90200CB4A46 /* MigrationWorkItem.swift in Sources */ = {isa = PBXBuildFile; fileRef = F97C8B2827B6A90100CB4A46 /* MigrationWorkItem.swift */; }; + F97C8B2C27B6CA0C00CB4A46 /* PreferenceKeyAppLoading.swift in Sources */ = {isa = PBXBuildFile; fileRef = F97C8B2B27B6CA0C00CB4A46 /* PreferenceKeyAppLoading.swift */; }; F97F722127A957AF007B6620 /* MigrationPublisher.swift in Sources */ = {isa = PBXBuildFile; fileRef = F97F722027A957AF007B6620 /* MigrationPublisher.swift */; }; F97F722427A973DF007B6620 /* MigrationDataFlow.swift in Sources */ = {isa = PBXBuildFile; fileRef = F97F722327A973DF007B6620 /* MigrationDataFlow.swift */; }; F97F722627A973FF007B6620 /* MigrationProvider.swift in Sources */ = {isa = PBXBuildFile; fileRef = F97F722527A973FF007B6620 /* MigrationProvider.swift */; }; F97F722827A9741C007B6620 /* MigrationStateModel.swift in Sources */ = {isa = PBXBuildFile; fileRef = F97F722727A9741C007B6620 /* MigrationStateModel.swift */; }; F97F722B27A9743A007B6620 /* MigrationRootView.swift in Sources */ = {isa = PBXBuildFile; fileRef = F97F722A27A9743A007B6620 /* MigrationRootView.swift */; }; - F97F722D27A986F1007B6620 /* TargetInformation.swift in Sources */ = {isa = PBXBuildFile; fileRef = F97F722C27A986F1007B6620 /* TargetInformation.swift */; }; + F97F722D27A986F1007B6620 /* AppInfo.swift in Sources */ = {isa = PBXBuildFile; fileRef = F97F722C27A986F1007B6620 /* AppInfo.swift */; }; FA630397F76B582C8D8681A7 /* BasalProfileEditorProvider.swift in Sources */ = {isa = PBXBuildFile; fileRef = 42369F66CF91F30624C0B3A6 /* BasalProfileEditorProvider.swift */; }; /* End PBXBuildFile section */ @@ -726,12 +728,14 @@ F90692D0274B99B60037068D /* HealthKitProvider.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = HealthKitProvider.swift; sourceTree = ""; }; F90692D2274B9A130037068D /* AppleHealthKitRootView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = AppleHealthKitRootView.swift; sourceTree = ""; }; F90692D5274B9A450037068D /* HealthKitStateModel.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = HealthKitStateModel.swift; sourceTree = ""; }; + F97C8B2827B6A90100CB4A46 /* MigrationWorkItem.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = MigrationWorkItem.swift; sourceTree = ""; }; + F97C8B2B27B6CA0C00CB4A46 /* PreferenceKeyAppLoading.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = PreferenceKeyAppLoading.swift; sourceTree = ""; }; F97F722027A957AF007B6620 /* MigrationPublisher.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = MigrationPublisher.swift; sourceTree = ""; }; F97F722327A973DF007B6620 /* MigrationDataFlow.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = MigrationDataFlow.swift; sourceTree = ""; }; F97F722527A973FF007B6620 /* MigrationProvider.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = MigrationProvider.swift; sourceTree = ""; }; F97F722727A9741C007B6620 /* MigrationStateModel.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = MigrationStateModel.swift; sourceTree = ""; }; F97F722A27A9743A007B6620 /* MigrationRootView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = MigrationRootView.swift; sourceTree = ""; }; - F97F722C27A986F1007B6620 /* TargetInformation.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = TargetInformation.swift; sourceTree = ""; }; + F97F722C27A986F1007B6620 /* AppInfo.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = AppInfo.swift; sourceTree = ""; }; FBB3BAE7494CB771ABAC7B8B /* ISFEditorRootView.swift */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = sourcecode.swift; path = ISFEditorRootView.swift; sourceTree = ""; }; /* End PBXFileReference section */ @@ -1283,7 +1287,7 @@ 38E989DC25F5021400C0CED0 /* PumpStatus.swift */, 38BF021C25E7E3AF00579895 /* Reservoir.swift */, 3871F38625ED661C0013ECB5 /* Suggestion.swift */, - F97F722C27A986F1007B6620 /* TargetInformation.swift */, + F97F722C27A986F1007B6620 /* AppInfo.swift */, 38A0364125ED069400FCBB52 /* TempBasal.swift */, 3871F39B25ED892B0013ECB5 /* TempTarget.swift */, 3811DE8E25C9D80400A708ED /* User.swift */, @@ -1295,6 +1299,7 @@ 388E5A5A25B6F05F0019842D /* Helpers */ = { isa = PBXGroup; children = ( + F97C8B2A27B6C9FA00CB4A46 /* PreferenceKeys */, 38F37827261260DC009DB701 /* Color+Extensions.swift */, 389ECE042601144100D86C4F /* ConcurrentMap.swift */, 38192E0C261BAF980094D973 /* ConvenienceExtensions.swift */, @@ -1836,6 +1841,7 @@ children = ( F904D3CC27A85B6800C5466F /* MigrationManager.swift */, F97F722027A957AF007B6620 /* MigrationPublisher.swift */, + F97C8B2827B6A90100CB4A46 /* MigrationWorkItem.swift */, ); path = Migration; sourceTree = ""; @@ -1867,6 +1873,14 @@ path = View; sourceTree = ""; }; + F97C8B2A27B6C9FA00CB4A46 /* PreferenceKeys */ = { + isa = PBXGroup; + children = ( + F97C8B2B27B6CA0C00CB4A46 /* PreferenceKeyAppLoading.swift */, + ); + path = PreferenceKeys; + sourceTree = ""; + }; F97F722227A9739F007B6620 /* Migration */ = { isa = PBXGroup; children = ( @@ -2213,6 +2227,7 @@ 3811DE5F25C9D4D500A708ED /* ProgressBar.swift in Sources */, 38E87408274F9AD000975559 /* UserNotificationsManager.swift in Sources */, 38BF021D25E7E3AF00579895 /* Reservoir.swift in Sources */, + F97C8B2927B6A90200CB4A46 /* MigrationWorkItem.swift in Sources */, 38BF021B25E7D06400579895 /* PumpSettingsView.swift in Sources */, 3862CC05273D152B00BF832C /* CalibrationService.swift in Sources */, 3811DEEA25CA063400A708ED /* SyncAccess.swift in Sources */, @@ -2245,6 +2260,7 @@ 642F76A05A4FF530463A9FD0 /* NightscoutConfigRootView.swift in Sources */, F97F722627A973FF007B6620 /* MigrationProvider.swift in Sources */, AD3D2CD42CD01B9EB8F26522 /* PumpConfigDataFlow.swift in Sources */, + F97C8B2C27B6CA0C00CB4A46 /* PreferenceKeyAppLoading.swift in Sources */, 53F2382465BF74DB1A967C8B /* PumpConfigProvider.swift in Sources */, 5D16287A969E64D18CE40E44 /* PumpConfigStateModel.swift in Sources */, E974172296125A5AE99E634C /* PumpConfigRootView.swift in Sources */, @@ -2267,7 +2283,7 @@ 38FEF3FA2737E42000574A46 /* BaseStateModel.swift in Sources */, 385CEA8225F23DFD002D6D5B /* NightscoutStatus.swift in Sources */, F90692AA274B7AAE0037068D /* HealthKitManager.swift in Sources */, - F97F722D27A986F1007B6620 /* TargetInformation.swift in Sources */, + F97F722D27A986F1007B6620 /* AppInfo.swift in Sources */, 38887CCE25F5725200944304 /* IOBEntry.swift in Sources */, 38E98A2425F52C9300C0CED0 /* Logger.swift in Sources */, CA370FC152BC98B3D1832968 /* BasalProfileEditorRootView.swift in Sources */, diff --git a/FreeAPS/Sources/Application/FreeAPSApp.swift b/FreeAPS/Sources/Application/FreeAPSApp.swift index e78829f55..2a84d0d74 100644 --- a/FreeAPS/Sources/Application/FreeAPSApp.swift +++ b/FreeAPS/Sources/Application/FreeAPSApp.swift @@ -5,11 +5,7 @@ import Swinject @main struct FreeAPSApp: App { @Environment(\.scenePhase) var scenePhase @UIApplicationDelegateAdaptor(AppDelegate.self) var appDelegate - @State private var isMigrating: Bool = true - - // Migration - private var migrationManager = MigrationManager() - @State private var lifetime: Lifetime = [] + @State var loadingIsEnded: Bool = false // Dependencies Assembler // contain all dependencies Assemblies @@ -49,34 +45,28 @@ import Swinject init() { loadServices() - _isMigrating = State(initialValue: migrationManager.isNeedMigrate) } var body: some Scene { WindowGroup { ZStack { - if isMigrating { - Migration.RootView(resolver: resolver) - .onAppear { runMigration() } - } else { - Main.RootView(resolver: resolver) - } + rootView } - .animation(.easeIn(duration: 0.75), value: self.isMigrating) + .animation(.easeIn(duration: 0.75), value: self.loadingIsEnded) } .onChange(of: scenePhase) { newScenePhase in debug(.default, "APPLICATION PHASE: \(newScenePhase)") } } - private func runMigration() { - migrationManager - .publisher - .sink { [self] _ in - DispatchQueue.main.asyncAfter(deadline: .now() + 1.5) { - isMigrating = false + @ViewBuilder private var rootView: some View { + if !loadingIsEnded { + Screen.migration.view(resolver: resolver) + .onPreferenceChange(PreferenceKeyAppLoading.self) { + loadingIsEnded = $0 } - } - .store(in: &lifetime) + } else { + Main.RootView(resolver: resolver) + } } } diff --git a/FreeAPS/Sources/Assemblies/ServiceAssembly.swift b/FreeAPS/Sources/Assemblies/ServiceAssembly.swift index 61c0f1459..139b8477f 100644 --- a/FreeAPS/Sources/Assemblies/ServiceAssembly.swift +++ b/FreeAPS/Sources/Assemblies/ServiceAssembly.swift @@ -19,5 +19,9 @@ final class ServiceAssembly: Assembly { container.register(HealthKitManager.self) { r in BaseHealthKitManager(resolver: r) } container.register(UserNotificationsManager.self) { r in BaseUserNotificationsManager(resolver: r) } container.register(WatchManager.self) { r in BaseWatchManager(resolver: r) } + + // Migration service's + container.register(AppInfo.self) { _ in BaseAppInfo() } + container.register(MigrationManager.self) { r in BaseMigrationManager(resolver: r) } } } diff --git a/FreeAPS/Sources/Helpers/PreferenceKeys/PreferenceKeyAppLoading.swift b/FreeAPS/Sources/Helpers/PreferenceKeys/PreferenceKeyAppLoading.swift new file mode 100644 index 000000000..24acc772a --- /dev/null +++ b/FreeAPS/Sources/Helpers/PreferenceKeys/PreferenceKeyAppLoading.swift @@ -0,0 +1,9 @@ +import SwiftUI + +struct PreferenceKeyAppLoading: PreferenceKey { + typealias Value = Bool + static var defaultValue: Bool = false + static func reduce(value: inout Bool, nextValue: () -> Bool) { + value = nextValue() + } +} diff --git a/FreeAPS/Sources/Models/AppInfo.swift b/FreeAPS/Sources/Models/AppInfo.swift new file mode 100644 index 000000000..f6f84fbdf --- /dev/null +++ b/FreeAPS/Sources/Models/AppInfo.swift @@ -0,0 +1,12 @@ +import Foundation + +protocol AppInfo { + // curent target/app version + var currentVersion: String { get } +} + +class BaseAppInfo: AppInfo { + var currentVersion: String { + Bundle.main.infoDictionary?["CFBundleVersion"] as! String + } +} diff --git a/FreeAPS/Sources/Models/TargetInformation.swift b/FreeAPS/Sources/Models/TargetInformation.swift deleted file mode 100644 index 6c8a638ec..000000000 --- a/FreeAPS/Sources/Models/TargetInformation.swift +++ /dev/null @@ -1,30 +0,0 @@ -import Foundation - -struct TargetInformation { - @Persisted(key: "AppTargetInformation.lastMigrationVersion") private var _lastMigrationVersion: String = "" - var lastMigrationVersion: String? { - if _lastMigrationVersion == "" { - // nil means that - // 1) app run first time after install - // or - // 2) previous run was on version <= 0.2.6 - return nil - } - return _lastMigrationVersion - } - - // App can be run first time - var isFirstExecute: Bool { - // check first app execution by preferences.json file - // if file doesn't exist - the first execution - Disk.exists(OpenAPS.Settings.preferences, in: .documents) - } - - var currentVersion: String { - Bundle.main.infoDictionary?["CFBundleVersion"] as! String - } - - mutating func actualLastMigrationVersion() { - _lastMigrationVersion = currentVersion - } -} diff --git a/FreeAPS/Sources/Modules/Migration/MigrationStateModel.swift b/FreeAPS/Sources/Modules/Migration/MigrationStateModel.swift index 014fe3d85..8902592e7 100644 --- a/FreeAPS/Sources/Modules/Migration/MigrationStateModel.swift +++ b/FreeAPS/Sources/Modules/Migration/MigrationStateModel.swift @@ -1,9 +1,35 @@ -import SwiftMessages +import Combine import SwiftUI import Swinject extension Migration { - final class StateModel: BaseStateModel { + @MainActor final class StateModel: BaseStateModel { + @Injected() private var manager: MigrationManager! + @Published var animated: Bool = false + @Published var loadingIsEnded: Bool = false + + func runMigration() { +// try? Disk.remove(OpenAPS.FreeAPS.settings, from: .documents) +// UserDefaults.standard.removeObject(forKey: "AppInfo.lastMigrationAppVersion") +// return + debug(.businessLogic, "Migration did start on current version \(manager.appInfo.currentVersion)") + debug(.businessLogic, "Last migration did on version \(manager.lastMigrationAppVersion ?? "null")") + Publishers + .getMigrationPublisher(fromMigrationManager: manager) +// migration example +// .migrate(onVersion: "0.2.5", MigrationWorkExample.run1) +// .migrate(onVersion: "0.2.6", MigrationWorkExample.run2) +// .migrate(onVersion: "0.2.7", MigrationWorkExample.run3) + .updateLastAppMigrationVersionToCurrent() + .sink { _ in + debug(.businessLogic, "Migration did finish") + // fake pause to exclude UI-lags + DispatchQueue.main.asyncAfter(deadline: .now() + 0.5) { + self.loadingIsEnded = true + } + } + .store(in: &lifetime) + } } } diff --git a/FreeAPS/Sources/Modules/Migration/View/MigrationRootView.swift b/FreeAPS/Sources/Modules/Migration/View/MigrationRootView.swift index d432a7f47..5598cf2ef 100644 --- a/FreeAPS/Sources/Modules/Migration/View/MigrationRootView.swift +++ b/FreeAPS/Sources/Modules/Migration/View/MigrationRootView.swift @@ -1,3 +1,4 @@ +import Combine import SwiftUI import Swinject @@ -24,8 +25,11 @@ extension Migration { .padding(.top, 10) } .onAppear { + configureView() state.animated.toggle() + state.runMigration() } + .preference(key: PreferenceKeyAppLoading.self, value: state.loadingIsEnded) } } } diff --git a/FreeAPS/Sources/Router/Screen.swift b/FreeAPS/Sources/Router/Screen.swift index d79260c0b..e4d36ef2c 100644 --- a/FreeAPS/Sources/Router/Screen.swift +++ b/FreeAPS/Sources/Router/Screen.swift @@ -26,6 +26,7 @@ enum Screen: Identifiable, Hashable { case calibrations case notificationsConfig case snooze + case migration var id: Int { String(reflecting: self).hashValue } } @@ -81,6 +82,8 @@ extension Screen { NotificationsConfig.RootView(resolver: resolver) case .snooze: Snooze.RootView(resolver: resolver) + case .migration: + Migration.RootView(resolver: resolver) } } diff --git a/FreeAPS/Sources/Services/Migration/MigrationManager.swift b/FreeAPS/Sources/Services/Migration/MigrationManager.swift index 88c94af15..30abe7ac2 100644 --- a/FreeAPS/Sources/Services/Migration/MigrationManager.swift +++ b/FreeAPS/Sources/Services/Migration/MigrationManager.swift @@ -1,59 +1,59 @@ import Combine import Foundation import SwiftUI +import Swinject -class MigrationManager { - private var needWorkAfterUpdateIntoVersion: [String] = [] - private var appInformation: TargetInformation - var isNeedMigrate: Bool { - return true - guard !appInformation.isFirstExecute else { - return false - } - guard let lastMigrationVersion = appInformation.lastMigrationVersion else { - // if version < 0.2.6 - // in 0.2.6 was added MigrationManager - return true - } - if !needWorkAfterUpdateIntoVersion.filter({ $0 > lastMigrationVersion }).isEmpty { - // need execute handlers beetwen CurrentVersion...lastExecutedVersion, exclude lastExecutedVersion - return true +protocol MigrationManager { + var appInfo: AppInfo { get } + // 'true' when app run first time + var isFirstExecute: Bool { get } + // last version of app/target, which did execute + var lastMigrationAppVersion: String? { get } + // update 'lastExecutedVersion' to 'currentVersion' + func setActualLastMigrationAppVersion() + + func checkMigrationNeeded(onVersion version: String) -> Bool + + init(resolver: Resolver) +} + +class BaseMigrationManager: MigrationManager { + @Persisted(key: "AppInfo.lastMigrationAppVersion") private var _lastMigrationAppVersion: String = "" + var lastMigrationAppVersion: String? { + if _lastMigrationAppVersion == "" { + // nil means that + // 1) app execute first time after install on version >= 0.2.6 + // or + // 2) previous execution was on version <= 0.2.6 + return nil } - return false + return _lastMigrationAppVersion } - var publisher: AnyPublisher { - Publishers - .getMigrationPublisher(appInformation) - // example of migrating - // .migrate(migrateExample) - .actualLastMigrationVersion() - .eraseToAnyPublisher() + var isFirstExecute: Bool { + // check first app execution by preferences.json file + // if file doesn't exist - the first execution + !Disk.exists(OpenAPS.FreeAPS.settings, in: .documents) } - init(appInformation: TargetInformation = TargetInformation()) { - self.appInformation = appInformation - // need execute one or more migration's handler after update into version 0.2.6 - // if need migrating on version 0.2.6, add - // needWorkAfterUpdateIntoVersion.append("0.2.6") - } + var appInfo: AppInfo - private func checkNeedToRun(_ version: String) -> Bool { - guard appInformation.currentVersion >= version, - !appInformation.isFirstExecute - else { return false } + private var resolver: Resolver - guard let lastMigrationVersion = appInformation.lastMigrationVersion, - lastMigrationVersion == appInformation.currentVersion - else { return true } - return false + required init(resolver: Resolver) { + self.resolver = resolver + appInfo = resolver.resolve(AppInfo.self)! + } + + func checkMigrationNeeded(onVersion version: String) -> Bool { + guard appInfo.currentVersion >= version else { return false } + guard !isFirstExecute else { return false } + guard let last = lastMigrationAppVersion else { return true } + guard last < version else { return false } + return true } -} -// Migrating Example -extension MigrationManager { - func migrateExample(_: TargetInformation) { - guard checkNeedToRun("0.2.6") else { return } - print("Sample migration handler") + func setActualLastMigrationAppVersion() { + _lastMigrationAppVersion = appInfo.currentVersion } } diff --git a/FreeAPS/Sources/Services/Migration/MigrationPublisher.swift b/FreeAPS/Sources/Services/Migration/MigrationPublisher.swift index 23e4f3c0a..7dda05443 100644 --- a/FreeAPS/Sources/Services/Migration/MigrationPublisher.swift +++ b/FreeAPS/Sources/Services/Migration/MigrationPublisher.swift @@ -2,34 +2,17 @@ import Combine import Foundation import SwiftUI -/** - - Example of using DataMigrationTool - - Publishers - .dataMigrationTool(AppTargetInfo()) - .migrate { info in - // ... - } - .sink { - // ... - } - - info is AppTargetInfo instance, which contains target information (version and etc). - - */ - extension Publishers { - static func getMigrationPublisher(_ appInfo: TargetInformation) -> MigrationPublisher { - MigrationPublisher(appInfo) + static func getMigrationPublisher(fromMigrationManager manager: MigrationManager) -> MigrationPublisher { + MigrationPublisher(manager) } - class MigrationSubscription: Subscription where S.Input == TargetInformation, S.Failure == Never { - private var targetInformation: TargetInformation + class MigrationSubscription: Subscription where S.Input == AppInfo, S.Failure == Never { + private var manager: MigrationManager private var subscriber: S? - init(_ appInfo: TargetInformation, subscriber: S) { - targetInformation = appInfo + init(_ manager: MigrationManager, subscriber: S) { + self.manager = manager self.subscriber = subscriber } @@ -38,34 +21,52 @@ extension Publishers { } func request(_: Subscribers.Demand) { - _ = subscriber?.receive(targetInformation) + _ = subscriber?.receive(manager.appInfo) subscriber?.receive(completion: .finished) return } } struct MigrationPublisher: Publisher { - typealias Output = TargetInformation + typealias Output = AppInfo typealias Failure = Never - @State private var targetInformation: TargetInformation + private var manager: MigrationManager - init(_ AppTargetInfo: TargetInformation) { - targetInformation = AppTargetInfo + init(_ manager: MigrationManager) { + self.manager = manager } - func receive(subscriber: S) where S: Subscriber, Never == S.Failure, TargetInformation == S.Input { - let subscription = MigrationSubscription(targetInformation, subscriber: subscriber) + func receive(subscriber: S) where S: Subscriber, Never == S.Failure, AppInfo == S.Input { + let subscription = MigrationSubscription(manager, subscriber: subscriber) subscriber.receive(subscription: subscription) } - func migrate(_ handler: (TargetInformation) -> Void) -> Self { - handler(targetInformation) + func tryAsyncMigrate(onVersion version: String, _ handler: @escaping (AppInfo) async throws -> Void) throws -> Self { + Task { + if manager.checkMigrationNeeded(onVersion: version) { + try await handler(manager.appInfo) + } + } return self } - func actualLastMigrationVersion() -> Self { - targetInformation.actualLastMigrationVersion() + func migrate(onVersion version: String, _ handler: (AppInfo) -> Void) -> Self { + debug(.businessLogic, "Try to execute migration on version \(version)") + if manager.checkMigrationNeeded(onVersion: version) { + debug(.businessLogic, "Migration will start") + handler(manager.appInfo) + } else { + debug(.businessLogic, "Migration skipped") + } + return self + } + + // TODO: Add tryMigrate + // TODO: Add asyncMigrate + + func updateLastAppMigrationVersionToCurrent() -> Self { + manager.setActualLastMigrationAppVersion() return self } } diff --git a/FreeAPS/Sources/Services/Migration/MigrationWorkItem.swift b/FreeAPS/Sources/Services/Migration/MigrationWorkItem.swift new file mode 100644 index 000000000..840ab968c --- /dev/null +++ b/FreeAPS/Sources/Services/Migration/MigrationWorkItem.swift @@ -0,0 +1,20 @@ +import Foundation + +protocol MigrationWorkItem {} + +class MigrationWorkExample: MigrationWorkItem { + static func run1(appInfo _: AppInfo) { + // add here any migration logic + print("did some migration work on 0.2.5") + } + + static func run2(appInfo _: AppInfo) { + // add here any migration logic + print("did some migration work on 0.2.6") + } + + static func run3(appInfo _: AppInfo) { + // add here any migration logic + print("did some migration work on 0.2.7") + } +} From 135c218e67705bd2ca5ae0d0d6bc7806a01283e0 Mon Sep 17 00:00:00 2001 From: Vasiliy Usov Date: Fri, 11 Feb 2022 20:48:57 +0300 Subject: [PATCH 03/28] Update MigrationStateModel.swift --- FreeAPS/Sources/Modules/Migration/MigrationStateModel.swift | 5 +---- 1 file changed, 1 insertion(+), 4 deletions(-) diff --git a/FreeAPS/Sources/Modules/Migration/MigrationStateModel.swift b/FreeAPS/Sources/Modules/Migration/MigrationStateModel.swift index 8902592e7..57dcd488e 100644 --- a/FreeAPS/Sources/Modules/Migration/MigrationStateModel.swift +++ b/FreeAPS/Sources/Modules/Migration/MigrationStateModel.swift @@ -10,9 +10,6 @@ extension Migration { @Published var loadingIsEnded: Bool = false func runMigration() { -// try? Disk.remove(OpenAPS.FreeAPS.settings, from: .documents) -// UserDefaults.standard.removeObject(forKey: "AppInfo.lastMigrationAppVersion") -// return debug(.businessLogic, "Migration did start on current version \(manager.appInfo.currentVersion)") debug(.businessLogic, "Last migration did on version \(manager.lastMigrationAppVersion ?? "null")") Publishers @@ -24,7 +21,7 @@ extension Migration { .updateLastAppMigrationVersionToCurrent() .sink { _ in debug(.businessLogic, "Migration did finish") - // fake pause to exclude UI-lags + // fake pause DispatchQueue.main.asyncAfter(deadline: .now() + 0.5) { self.loadingIsEnded = true } From 3ad667b97bb755d8db40f7d419fe82ba733cfb6c Mon Sep 17 00:00:00 2001 From: Vasiliy Usov Date: Fri, 11 Feb 2022 21:05:33 +0300 Subject: [PATCH 04/28] Update MigrationRootView.swift --- .../Migration/View/MigrationRootView.swift | 21 ++++++++++++++----- 1 file changed, 16 insertions(+), 5 deletions(-) diff --git a/FreeAPS/Sources/Modules/Migration/View/MigrationRootView.swift b/FreeAPS/Sources/Modules/Migration/View/MigrationRootView.swift index 5598cf2ef..ef08afcc2 100644 --- a/FreeAPS/Sources/Modules/Migration/View/MigrationRootView.swift +++ b/FreeAPS/Sources/Modules/Migration/View/MigrationRootView.swift @@ -18,11 +18,16 @@ extension Migration { .frame(width: 80, height: 80) .rotationEffect(.degrees(state.animated ? 360 : 0)) .animation(.linear(duration: 0.7).repeatForever(autoreverses: false)) - Text("Preparing data \n Please wait") - .font(.title) - .fontWeight(.bold) - .multilineTextAlignment(.center) - .padding(.top, 10) + VStack(spacing: 0) { + Text("Preparing data") + .font(.title) + .fontWeight(.bold) + .padding(.top, 10) + Text("Please wait") + .font(.title) + .fontWeight(.bold) + .padding(.top, 10) + } } .onAppear { configureView() @@ -33,3 +38,9 @@ extension Migration { } } } + +struct MyPreviewProvider_Previews: PreviewProvider { + static var previews: some View { + Migration.RootView(resolver: FreeAPSApp.resolver) + } +} From 5ba717ef22d73142e86341ed259def675c5d3dcd Mon Sep 17 00:00:00 2001 From: Vasily Usov Date: Thu, 24 Mar 2022 11:33:48 +0300 Subject: [PATCH 05/28] Update MigrationTool logic --- .../Migration/MigrationStateModel.swift | 7 +--- .../Services/Migration/MigrationManager.swift | 32 +++++-------------- .../Migration/MigrationPublisher.swift | 28 ++++------------ .../Migration/MigrationWorkItem.swift | 25 +++++++-------- 4 files changed, 26 insertions(+), 66 deletions(-) diff --git a/FreeAPS/Sources/Modules/Migration/MigrationStateModel.swift b/FreeAPS/Sources/Modules/Migration/MigrationStateModel.swift index 57dcd488e..cddbcc906 100644 --- a/FreeAPS/Sources/Modules/Migration/MigrationStateModel.swift +++ b/FreeAPS/Sources/Modules/Migration/MigrationStateModel.swift @@ -11,14 +11,9 @@ extension Migration { func runMigration() { debug(.businessLogic, "Migration did start on current version \(manager.appInfo.currentVersion)") - debug(.businessLogic, "Last migration did on version \(manager.lastMigrationAppVersion ?? "null")") Publishers .getMigrationPublisher(fromMigrationManager: manager) -// migration example -// .migrate(onVersion: "0.2.5", MigrationWorkExample.run1) -// .migrate(onVersion: "0.2.6", MigrationWorkExample.run2) -// .migrate(onVersion: "0.2.7", MigrationWorkExample.run3) - .updateLastAppMigrationVersionToCurrent() +// .migrate(startAtVersion: "0.2.6", MigrationWorkExample()) .sink { _ in debug(.businessLogic, "Migration did finish") // fake pause diff --git a/FreeAPS/Sources/Services/Migration/MigrationManager.swift b/FreeAPS/Sources/Services/Migration/MigrationManager.swift index 30abe7ac2..5a3aaf9af 100644 --- a/FreeAPS/Sources/Services/Migration/MigrationManager.swift +++ b/FreeAPS/Sources/Services/Migration/MigrationManager.swift @@ -7,29 +7,13 @@ protocol MigrationManager { var appInfo: AppInfo { get } // 'true' when app run first time var isFirstExecute: Bool { get } - // last version of app/target, which did execute - var lastMigrationAppVersion: String? { get } - // update 'lastExecutedVersion' to 'currentVersion' - func setActualLastMigrationAppVersion() - func checkMigrationNeeded(onVersion version: String) -> Bool + func checkMigrationNeededRun(_: MigrationWorkItem, startAtVersion version: String) -> Bool init(resolver: Resolver) } class BaseMigrationManager: MigrationManager { - @Persisted(key: "AppInfo.lastMigrationAppVersion") private var _lastMigrationAppVersion: String = "" - var lastMigrationAppVersion: String? { - if _lastMigrationAppVersion == "" { - // nil means that - // 1) app execute first time after install on version >= 0.2.6 - // or - // 2) previous execution was on version <= 0.2.6 - return nil - } - return _lastMigrationAppVersion - } - var isFirstExecute: Bool { // check first app execution by preferences.json file // if file doesn't exist - the first execution @@ -45,15 +29,15 @@ class BaseMigrationManager: MigrationManager { appInfo = resolver.resolve(AppInfo.self)! } - func checkMigrationNeeded(onVersion version: String) -> Bool { + func checkMigrationNeededRun(_ migrationWorkItem: MigrationWorkItem, startAtVersion version: String) -> Bool { + // if current app version >= version of migration needed guard appInfo.currentVersion >= version else { return false } + // if migration need to run each app execution + if migrationWorkItem.repeatEachTime { return true } + // if it first run of app guard !isFirstExecute else { return false } - guard let last = lastMigrationAppVersion else { return true } - guard last < version else { return false } + // if migration did run in past + guard UserDefaults.standard.optionalBool(forKey: migrationWorkItem.uniqueIdentifier) == nil else { return false } return true } - - func setActualLastMigrationAppVersion() { - _lastMigrationAppVersion = appInfo.currentVersion - } } diff --git a/FreeAPS/Sources/Services/Migration/MigrationPublisher.swift b/FreeAPS/Sources/Services/Migration/MigrationPublisher.swift index 7dda05443..662f16a99 100644 --- a/FreeAPS/Sources/Services/Migration/MigrationPublisher.swift +++ b/FreeAPS/Sources/Services/Migration/MigrationPublisher.swift @@ -42,32 +42,16 @@ extension Publishers { subscriber.receive(subscription: subscription) } - func tryAsyncMigrate(onVersion version: String, _ handler: @escaping (AppInfo) async throws -> Void) throws -> Self { - Task { - if manager.checkMigrationNeeded(onVersion: version) { - try await handler(manager.appInfo) - } - } - return self - } - - func migrate(onVersion version: String, _ handler: (AppInfo) -> Void) -> Self { + func migrate(startAtVersion version: String, _ workItem: MigrationWorkItem) -> Self { debug(.businessLogic, "Try to execute migration on version \(version)") - if manager.checkMigrationNeeded(onVersion: version) { - debug(.businessLogic, "Migration will start") - handler(manager.appInfo) + if manager.checkMigrationNeededRun(workItem, startAtVersion: version) { + debug(.businessLogic, "Start migration \(workItem.uniqueIdentifier)") + workItem.migrationHandler(manager.appInfo) + UserDefaults.standard.set(true, forKey: workItem.uniqueIdentifier) } else { - debug(.businessLogic, "Migration skipped") + debug(.businessLogic, "Skip migration \(workItem.uniqueIdentifier)") } return self } - - // TODO: Add tryMigrate - // TODO: Add asyncMigrate - - func updateLastAppMigrationVersionToCurrent() -> Self { - manager.setActualLastMigrationAppVersion() - return self - } } } diff --git a/FreeAPS/Sources/Services/Migration/MigrationWorkItem.swift b/FreeAPS/Sources/Services/Migration/MigrationWorkItem.swift index 840ab968c..8e6d4b270 100644 --- a/FreeAPS/Sources/Services/Migration/MigrationWorkItem.swift +++ b/FreeAPS/Sources/Services/Migration/MigrationWorkItem.swift @@ -1,20 +1,17 @@ import Foundation -protocol MigrationWorkItem {} +protocol MigrationWorkItem { + // If true then migration will run each time while app is loading + var repeatEachTime: Bool { get } + var uniqueIdentifier: String { get } + func migrationHandler(_: AppInfo) +} class MigrationWorkExample: MigrationWorkItem { - static func run1(appInfo _: AppInfo) { - // add here any migration logic - print("did some migration work on 0.2.5") - } - - static func run2(appInfo _: AppInfo) { - // add here any migration logic - print("did some migration work on 0.2.6") - } - - static func run3(appInfo _: AppInfo) { - // add here any migration logic - print("did some migration work on 0.2.7") + private(set) var repeatEachTime: Bool = false + private(set) var uniqueIdentifier: String = "Migration.MigrationWorkExample" + func migrationHandler(_: AppInfo) { + debug(.businessLogic, "Migration MigrationWorkExample will start") } } + From 481adb842de8f44601abda75f810e59659357517 Mon Sep 17 00:00:00 2001 From: Vasily Usov Date: Fri, 25 Mar 2022 10:00:26 +0300 Subject: [PATCH 06/28] Update MigrationTool --- .../Services/Migration/MigrationManager.swift | 16 ++++++++++++++++ .../Services/Migration/MigrationPublisher.swift | 14 ++++++-------- .../Services/Migration/MigrationWorkItem.swift | 11 ++++++++++- 3 files changed, 32 insertions(+), 9 deletions(-) diff --git a/FreeAPS/Sources/Services/Migration/MigrationManager.swift b/FreeAPS/Sources/Services/Migration/MigrationManager.swift index 5a3aaf9af..dfa4882d1 100644 --- a/FreeAPS/Sources/Services/Migration/MigrationManager.swift +++ b/FreeAPS/Sources/Services/Migration/MigrationManager.swift @@ -1,3 +1,7 @@ +// +// Main Migration tool of App +// + import Combine import Foundation import SwiftUI @@ -9,6 +13,7 @@ protocol MigrationManager { var isFirstExecute: Bool { get } func checkMigrationNeededRun(_: MigrationWorkItem, startAtVersion version: String) -> Bool + func migrate(startAtVersion version: String, _ workItem: MigrationWorkItem) init(resolver: Resolver) } @@ -40,4 +45,15 @@ class BaseMigrationManager: MigrationManager { guard UserDefaults.standard.optionalBool(forKey: migrationWorkItem.uniqueIdentifier) == nil else { return false } return true } + + func migrate(startAtVersion version: String, _ workItem: MigrationWorkItem) { + debug(.businessLogic, "Try to execute migration on version \(version)") + if checkMigrationNeededRun(workItem, startAtVersion: version) { + debug(.businessLogic, "Start migration \(workItem.uniqueIdentifier)") + workItem.migrationHandler(appInfo) + UserDefaults.standard.set(true, forKey: workItem.uniqueIdentifier) + } else { + debug(.businessLogic, "Skip migration \(workItem.uniqueIdentifier)") + } + } } diff --git a/FreeAPS/Sources/Services/Migration/MigrationPublisher.swift b/FreeAPS/Sources/Services/Migration/MigrationPublisher.swift index 662f16a99..9c91dd62a 100644 --- a/FreeAPS/Sources/Services/Migration/MigrationPublisher.swift +++ b/FreeAPS/Sources/Services/Migration/MigrationPublisher.swift @@ -1,3 +1,8 @@ +// +// This file store code for working Migration manager with Combine +// This is only combine's wrapper for Migration manager +// + import Combine import Foundation import SwiftUI @@ -43,14 +48,7 @@ extension Publishers { } func migrate(startAtVersion version: String, _ workItem: MigrationWorkItem) -> Self { - debug(.businessLogic, "Try to execute migration on version \(version)") - if manager.checkMigrationNeededRun(workItem, startAtVersion: version) { - debug(.businessLogic, "Start migration \(workItem.uniqueIdentifier)") - workItem.migrationHandler(manager.appInfo) - UserDefaults.standard.set(true, forKey: workItem.uniqueIdentifier) - } else { - debug(.businessLogic, "Skip migration \(workItem.uniqueIdentifier)") - } + manager.migrate(startAtVersion: version, workItem) return self } } diff --git a/FreeAPS/Sources/Services/Migration/MigrationWorkItem.swift b/FreeAPS/Sources/Services/Migration/MigrationWorkItem.swift index 8e6d4b270..a3afe3a30 100644 --- a/FreeAPS/Sources/Services/Migration/MigrationWorkItem.swift +++ b/FreeAPS/Sources/Services/Migration/MigrationWorkItem.swift @@ -1,9 +1,19 @@ +// +// This file contains WorkItems with migration tasks +// Each WorkItem have to execute one migration task +// Each WorkItem can be run in Migration.StateModel.runMigration() +// ... +// .migrate(startAtVersion: "0.2.6", MigrationWorkExample()) +// ... + import Foundation protocol MigrationWorkItem { // If true then migration will run each time while app is loading var repeatEachTime: Bool { get } + // Unique identifier to store migration execute flag in UserDefaults var uniqueIdentifier: String { get } + // Migration task func migrationHandler(_: AppInfo) } @@ -14,4 +24,3 @@ class MigrationWorkExample: MigrationWorkItem { debug(.businessLogic, "Migration MigrationWorkExample will start") } } - From 4b3ac23ce67abf1c778aa25d6c60b1465d062103 Mon Sep 17 00:00:00 2001 From: Vasily Usov Date: Fri, 25 Mar 2022 10:00:26 +0300 Subject: [PATCH 07/28] Update MigrationTool --- .../Services/Migration/MigrationManager.swift | 16 ++++++++++++++++ .../Services/Migration/MigrationPublisher.swift | 14 ++++++-------- .../Services/Migration/MigrationWorkItem.swift | 11 ++++++++++- 3 files changed, 32 insertions(+), 9 deletions(-) diff --git a/FreeAPS/Sources/Services/Migration/MigrationManager.swift b/FreeAPS/Sources/Services/Migration/MigrationManager.swift index 5a3aaf9af..dfa4882d1 100644 --- a/FreeAPS/Sources/Services/Migration/MigrationManager.swift +++ b/FreeAPS/Sources/Services/Migration/MigrationManager.swift @@ -1,3 +1,7 @@ +// +// Main Migration tool of App +// + import Combine import Foundation import SwiftUI @@ -9,6 +13,7 @@ protocol MigrationManager { var isFirstExecute: Bool { get } func checkMigrationNeededRun(_: MigrationWorkItem, startAtVersion version: String) -> Bool + func migrate(startAtVersion version: String, _ workItem: MigrationWorkItem) init(resolver: Resolver) } @@ -40,4 +45,15 @@ class BaseMigrationManager: MigrationManager { guard UserDefaults.standard.optionalBool(forKey: migrationWorkItem.uniqueIdentifier) == nil else { return false } return true } + + func migrate(startAtVersion version: String, _ workItem: MigrationWorkItem) { + debug(.businessLogic, "Try to execute migration on version \(version)") + if checkMigrationNeededRun(workItem, startAtVersion: version) { + debug(.businessLogic, "Start migration \(workItem.uniqueIdentifier)") + workItem.migrationHandler(appInfo) + UserDefaults.standard.set(true, forKey: workItem.uniqueIdentifier) + } else { + debug(.businessLogic, "Skip migration \(workItem.uniqueIdentifier)") + } + } } diff --git a/FreeAPS/Sources/Services/Migration/MigrationPublisher.swift b/FreeAPS/Sources/Services/Migration/MigrationPublisher.swift index 662f16a99..9c91dd62a 100644 --- a/FreeAPS/Sources/Services/Migration/MigrationPublisher.swift +++ b/FreeAPS/Sources/Services/Migration/MigrationPublisher.swift @@ -1,3 +1,8 @@ +// +// This file store code for working Migration manager with Combine +// This is only combine's wrapper for Migration manager +// + import Combine import Foundation import SwiftUI @@ -43,14 +48,7 @@ extension Publishers { } func migrate(startAtVersion version: String, _ workItem: MigrationWorkItem) -> Self { - debug(.businessLogic, "Try to execute migration on version \(version)") - if manager.checkMigrationNeededRun(workItem, startAtVersion: version) { - debug(.businessLogic, "Start migration \(workItem.uniqueIdentifier)") - workItem.migrationHandler(manager.appInfo) - UserDefaults.standard.set(true, forKey: workItem.uniqueIdentifier) - } else { - debug(.businessLogic, "Skip migration \(workItem.uniqueIdentifier)") - } + manager.migrate(startAtVersion: version, workItem) return self } } diff --git a/FreeAPS/Sources/Services/Migration/MigrationWorkItem.swift b/FreeAPS/Sources/Services/Migration/MigrationWorkItem.swift index 8e6d4b270..a3afe3a30 100644 --- a/FreeAPS/Sources/Services/Migration/MigrationWorkItem.swift +++ b/FreeAPS/Sources/Services/Migration/MigrationWorkItem.swift @@ -1,9 +1,19 @@ +// +// This file contains WorkItems with migration tasks +// Each WorkItem have to execute one migration task +// Each WorkItem can be run in Migration.StateModel.runMigration() +// ... +// .migrate(startAtVersion: "0.2.6", MigrationWorkExample()) +// ... + import Foundation protocol MigrationWorkItem { // If true then migration will run each time while app is loading var repeatEachTime: Bool { get } + // Unique identifier to store migration execute flag in UserDefaults var uniqueIdentifier: String { get } + // Migration task func migrationHandler(_: AppInfo) } @@ -14,4 +24,3 @@ class MigrationWorkExample: MigrationWorkItem { debug(.businessLogic, "Migration MigrationWorkExample will start") } } - From 54b956b62c6d6ccd01a449ffc26df3ca0e20656f Mon Sep 17 00:00:00 2001 From: Vasily Usov Date: Fri, 25 Mar 2022 10:53:31 +0300 Subject: [PATCH 08/28] Update CarbsEntry and create CarbsMigrationWorkItem --- FreeAPS/Sources/Models/CarbsEntry.swift | 29 +++++++++++++++++++ .../Migration/MigrationStateModel.swift | 2 +- .../Migration/MigrationWorkItem.swift | 23 +++++++++++++++ 3 files changed, 53 insertions(+), 1 deletion(-) diff --git a/FreeAPS/Sources/Models/CarbsEntry.swift b/FreeAPS/Sources/Models/CarbsEntry.swift index dbcbc237f..2b26ef527 100644 --- a/FreeAPS/Sources/Models/CarbsEntry.swift +++ b/FreeAPS/Sources/Models/CarbsEntry.swift @@ -1,6 +1,7 @@ import Foundation struct CarbsEntry: JSON, Equatable, Hashable { + var id = UUID().uuidString let createdAt: Date let carbs: Decimal let enteredBy: String? @@ -17,6 +18,34 @@ struct CarbsEntry: JSON, Equatable, Hashable { } extension CarbsEntry { + private enum CodingKeys: String, CodingKey { + case id = "_id" + case createdAt = "created_at" + case carbs + case enteredBy + } +} + +// MARK: CarbsEntry till 0.2.6 + +// At this version was add id propery for working with Apple Health +struct CarbsEntryTill026: JSON, Equatable, Hashable { + let createdAt: Date + let carbs: Decimal + let enteredBy: String? + + static let manual = "freeaps-x" + + static func == (lhs: CarbsEntryTill026, rhs: CarbsEntryTill026) -> Bool { + lhs.createdAt == rhs.createdAt + } + + func hash(into hasher: inout Hasher) { + hasher.combine(createdAt) + } +} + +extension CarbsEntryTill026 { private enum CodingKeys: String, CodingKey { case createdAt = "created_at" case carbs diff --git a/FreeAPS/Sources/Modules/Migration/MigrationStateModel.swift b/FreeAPS/Sources/Modules/Migration/MigrationStateModel.swift index cddbcc906..f8d115fdc 100644 --- a/FreeAPS/Sources/Modules/Migration/MigrationStateModel.swift +++ b/FreeAPS/Sources/Modules/Migration/MigrationStateModel.swift @@ -13,7 +13,7 @@ extension Migration { debug(.businessLogic, "Migration did start on current version \(manager.appInfo.currentVersion)") Publishers .getMigrationPublisher(fromMigrationManager: manager) -// .migrate(startAtVersion: "0.2.6", MigrationWorkExample()) + .migrate(startAtVersion: "0.2.6", MigrationCarbs()) .sink { _ in debug(.businessLogic, "Migration did finish") // fake pause diff --git a/FreeAPS/Sources/Services/Migration/MigrationWorkItem.swift b/FreeAPS/Sources/Services/Migration/MigrationWorkItem.swift index a3afe3a30..19f19b065 100644 --- a/FreeAPS/Sources/Services/Migration/MigrationWorkItem.swift +++ b/FreeAPS/Sources/Services/Migration/MigrationWorkItem.swift @@ -24,3 +24,26 @@ class MigrationWorkExample: MigrationWorkItem { debug(.businessLogic, "Migration MigrationWorkExample will start") } } + +// MARK: - Migration carbs at 0.2.6 + +// New CarbsEntry class (with new id property) was create. +// This work item add new property to carbs in carbs.json + +class MigrationCarbs: MigrationWorkItem { + private(set) var repeatEachTime: Bool = false + private(set) var uniqueIdentifier: String = "Migration.MigrationCarbs" + func migrationHandler(_: AppInfo) { + let resolver = FreeAPSApp.resolver + let fileStorage = resolver.resolve(FileStorage.self)! + let carbsStorage = resolver.resolve(CarbsStorage.self)! + guard let oldCarbs = fileStorage.retrieve(OpenAPS.Monitor.carbHistory, as: [CarbsEntryTill026].self) else { return } + carbsStorage.storeCarbs(convert(carbs: oldCarbs)) + } + + private func convert(carbs: [CarbsEntryTill026]) -> [CarbsEntry] { + carbs.map { oldCarb in + CarbsEntry(createdAt: oldCarb.createdAt, carbs: oldCarb.carbs, enteredBy: oldCarb.enteredBy) + } + } +} From 3b90ecef8653e7aa609f24619a099bd772c068ee Mon Sep 17 00:00:00 2001 From: Vasily Usov Date: Sun, 27 Mar 2022 15:39:19 +0300 Subject: [PATCH 09/28] Added Carbs transfer to/from Apple Health --- FreeAPS/Sources/APS/Carbs/CarbsSource.swift | 5 + .../Sources/APS/FetchTreatmentsManager.swift | 9 +- .../Sources/APS/Storage/CarbsStorage.swift | 2 +- .../Main/en.lproj/Localizable.strings | 4 +- .../Main/ru.lproj/Localizable.strings | 4 +- FreeAPS/Sources/Models/CarbsEntry.swift | 1 + FreeAPS/Sources/Models/HealthKitSample.swift | 19 -- FreeAPS/Sources/Models/HealthKitSamples.swift | 41 +++ .../Modules/DataTable/DataTableDataFlow.swift | 22 +- .../Modules/DataTable/DataTableProvider.swift | 7 +- .../DataTable/DataTableStateModel.swift | 12 +- .../DataTable/View/DataTableRootView.swift | 4 +- .../HealthKit/HealthKitStateModel.swift | 3 +- .../View/AppleHealthKitRootView.swift | 8 +- .../Services/HealthKit/HealthKitManager.swift | 288 ++++++++++++++++-- .../Services/Network/NightscoutManager.swift | 2 +- 16 files changed, 365 insertions(+), 66 deletions(-) create mode 100644 FreeAPS/Sources/APS/Carbs/CarbsSource.swift delete mode 100644 FreeAPS/Sources/Models/HealthKitSample.swift create mode 100644 FreeAPS/Sources/Models/HealthKitSamples.swift diff --git a/FreeAPS/Sources/APS/Carbs/CarbsSource.swift b/FreeAPS/Sources/APS/Carbs/CarbsSource.swift new file mode 100644 index 000000000..ecaf3fb54 --- /dev/null +++ b/FreeAPS/Sources/APS/Carbs/CarbsSource.swift @@ -0,0 +1,5 @@ +import Combine + +protocol CarbsSource: SourceInfoProvider { + func fetchCarbs() -> AnyPublisher<[CarbsEntry], Never> +} diff --git a/FreeAPS/Sources/APS/FetchTreatmentsManager.swift b/FreeAPS/Sources/APS/FetchTreatmentsManager.swift index 81be5a6b9..9b3fe1b0c 100644 --- a/FreeAPS/Sources/APS/FetchTreatmentsManager.swift +++ b/FreeAPS/Sources/APS/FetchTreatmentsManager.swift @@ -8,6 +8,7 @@ protocol FetchTreatmentsManager {} final class BaseFetchTreatmentsManager: FetchTreatmentsManager, Injectable { private let processQueue = DispatchQueue(label: "BaseFetchTreatmentsManager.processQueue") @Injected() var nightscoutManager: NightscoutManager! + @Injected() var healthKitManager: HealthKitManager! @Injected() var tempTargetsStorage: TempTargetsStorage! @Injected() var carbsStorage: CarbsStorage! @@ -22,15 +23,17 @@ final class BaseFetchTreatmentsManager: FetchTreatmentsManager, Injectable { private func subscribe() { timer.publisher .receive(on: processQueue) - .flatMap { _ -> AnyPublisher<([CarbsEntry], [TempTarget]), Never> in + .flatMap { _ -> AnyPublisher<([CarbsEntry], [CarbsEntry], [TempTarget]), Never> in debug(.nightscout, "FetchTreatmentsManager heartbeat") debug(.nightscout, "Start fetching carbs and temptargets") - return Publishers.CombineLatest( + return Publishers.CombineLatest3( self.nightscoutManager.fetchCarbs(), + self.healthKitManager.fetchCarbs(), self.nightscoutManager.fetchTempTargets() ).eraseToAnyPublisher() } - .sink { carbs, targets in + .sink { carbsFromNS, carbsFromAH, targets in + let carbs = carbsFromAH + carbsFromNS let filteredCarbs = carbs.filter { !($0.enteredBy?.contains(CarbsEntry.manual) ?? false) } if filteredCarbs.isNotEmpty { self.carbsStorage.storeCarbs(filteredCarbs) diff --git a/FreeAPS/Sources/APS/Storage/CarbsStorage.swift b/FreeAPS/Sources/APS/Storage/CarbsStorage.swift index 16c23f10d..78ebeac2d 100644 --- a/FreeAPS/Sources/APS/Storage/CarbsStorage.swift +++ b/FreeAPS/Sources/APS/Storage/CarbsStorage.swift @@ -65,7 +65,7 @@ final class BaseCarbsStorage: CarbsStorage, Injectable { func nightscoutTretmentsNotUploaded() -> [NigtscoutTreatment] { let uploaded = storage.retrieve(OpenAPS.Nightscout.uploadedPumphistory, as: [NigtscoutTreatment].self) ?? [] - let eventsManual = recent().filter { $0.enteredBy == CarbsEntry.manual } + let eventsManual = recent().filter { $0.enteredBy == CarbsEntry.manual || $0.enteredBy == CarbsEntry.applehealth } let treatments = eventsManual.map { NigtscoutTreatment( duration: nil, diff --git a/FreeAPS/Sources/Localizations/Main/en.lproj/Localizable.strings b/FreeAPS/Sources/Localizations/Main/en.lproj/Localizable.strings index ab4956eb9..dcbc288b9 100644 --- a/FreeAPS/Sources/Localizations/Main/en.lproj/Localizable.strings +++ b/FreeAPS/Sources/Localizations/Main/en.lproj/Localizable.strings @@ -941,10 +941,10 @@ Enact a temp Basal or a temp target */ "Connect to Apple Health" = "Connect to Apple Health"; /* Show when have not permissions for writing to Health */ -"For write data to Apple Health you must give permissions in Settings > Health > Data Access" = "For write data to Apple Health you must give permissions in Settings > Health > Data Access"; +"For read/write data from/to Apple Health you must give permissions in Settings > Health > Data Access" = "For read/write data from/to Apple Health you must give permissions in Settings > Health > Data Access"; /* */ -"After you create glucose records in the Health app, please open FreeAPS X to help us guaranteed transfer changed data" = "After you create glucose records in the Health app, please open FreeAPS X to help us guaranteed transfer changed data"; +"After you create records in the Health app, please open FreeAPS X to help us guaranteed transfer changed data" = "After you create records in the Health app, please open FreeAPS X to help us guaranteed transfer changed data"; /* -------------------------------------------- */ /* diff --git a/FreeAPS/Sources/Localizations/Main/ru.lproj/Localizable.strings b/FreeAPS/Sources/Localizations/Main/ru.lproj/Localizable.strings index d18164aa1..e559ab16e 100644 --- a/FreeAPS/Sources/Localizations/Main/ru.lproj/Localizable.strings +++ b/FreeAPS/Sources/Localizations/Main/ru.lproj/Localizable.strings @@ -941,10 +941,10 @@ Enact a temp Basal or a temp target */ "Connect to Apple Health" = "Подключить к Apple Health"; /* Show when have not permissions for writing to Health */ -"For write data to Apple Health you must give permissions in Settings > Health > Data Access" = "Чтобы записывать данные в Apple Health вам необходимо дать соответствующие разрешения, перейдя к меню Настройки > Здоровье > Доступ к данным"; +"For read/write data from/to Apple Health you must give permissions in Settings > Health > Data Access" = "Чтобы считывать и записывать данные из и в Apple Health вам необходимо дать соответствующие разрешения, перейдя в меню Настройки > Здоровье > Доступ к данным"; /* */ -"After you create glucose records in the Health app, please open FreeAPS X to help us guaranteed transfer changed data" = "После ручного создания записей о глюкозы в программе Здоровье пожалуйста откройте FreeAPS X, чтобы помочь нам гарантированно загрузить измененные данные"; +"After you create records in the Health app, please open FreeAPS X to help us guaranteed transfer changed data" = "После ручного создания записей в программе Здоровье пожалуйста откройте FreeAPS X, чтобы помочь нам гарантированно загрузить измененные данные"; /* -------------------------------------------- diff --git a/FreeAPS/Sources/Models/CarbsEntry.swift b/FreeAPS/Sources/Models/CarbsEntry.swift index 2b26ef527..aa8333dbb 100644 --- a/FreeAPS/Sources/Models/CarbsEntry.swift +++ b/FreeAPS/Sources/Models/CarbsEntry.swift @@ -7,6 +7,7 @@ struct CarbsEntry: JSON, Equatable, Hashable { let enteredBy: String? static let manual = "freeaps-x" + static let applehealth = "applehealth" static func == (lhs: CarbsEntry, rhs: CarbsEntry) -> Bool { lhs.createdAt == rhs.createdAt diff --git a/FreeAPS/Sources/Models/HealthKitSample.swift b/FreeAPS/Sources/Models/HealthKitSample.swift deleted file mode 100644 index 58b3f6845..000000000 --- a/FreeAPS/Sources/Models/HealthKitSample.swift +++ /dev/null @@ -1,19 +0,0 @@ -import Foundation - -struct HealthKitSample: JSON, Hashable, Equatable { - var healthKitId: String - var date: Date - var glucose: Int - - static func == (lhs: HealthKitSample, rhs: HealthKitSample) -> Bool { - lhs.healthKitId == rhs.healthKitId - } -} - -extension HealthKitSample { - private enum CodingKeys: String, CodingKey { - case healthKitId = "healthkit_id" - case date - case glucose - } -} diff --git a/FreeAPS/Sources/Models/HealthKitSamples.swift b/FreeAPS/Sources/Models/HealthKitSamples.swift new file mode 100644 index 000000000..569dea935 --- /dev/null +++ b/FreeAPS/Sources/Models/HealthKitSamples.swift @@ -0,0 +1,41 @@ +import Foundation + +// MARK: - Blood glucose + +struct HealthKitBGSample: JSON, Hashable, Equatable { + var healthKitId: String + var date: Date + var glucose: Int + + static func == (lhs: HealthKitBGSample, rhs: HealthKitBGSample) -> Bool { + lhs.healthKitId == rhs.healthKitId + } +} + +extension HealthKitBGSample { + private enum CodingKeys: String, CodingKey { + case healthKitId = "healthkit_id" + case date + case glucose + } +} + +// MARK: - Carbs + +struct HealthKitCarbsSample: JSON, Hashable, Equatable { + var healthKitId: String + var date: Date + var carbs: Decimal + + static func == (lhs: HealthKitCarbsSample, rhs: HealthKitCarbsSample) -> Bool { + lhs.healthKitId == rhs.healthKitId + } +} + +extension HealthKitCarbsSample { + private enum CodingKeys: String, CodingKey { + case healthKitId = "healthkit_id" + case date + case carbs + } +} diff --git a/FreeAPS/Sources/Modules/DataTable/DataTableDataFlow.swift b/FreeAPS/Sources/Modules/DataTable/DataTableDataFlow.swift index 1a30a0d15..267dca125 100644 --- a/FreeAPS/Sources/Modules/DataTable/DataTableDataFlow.swift +++ b/FreeAPS/Sources/Modules/DataTable/DataTableDataFlow.swift @@ -52,7 +52,7 @@ enum DataTable { } class Treatment: Identifiable, Hashable, Equatable { - let id = UUID() + var id = UUID() let units: GlucoseUnits let type: DataType let date: Date @@ -67,6 +67,24 @@ enum DataTable { return formatter } + init( + id: UUID, + units: GlucoseUnits, + type: DataType, + date: Date, + amount: Decimal? = nil, + secondAmount: Decimal? = nil, + duration: Decimal? = nil + ) { + self.id = id + self.units = units + self.type = type + self.date = date + self.amount = amount + self.secondAmount = secondAmount + self.duration = duration + } + init( units: GlucoseUnits, type: DataType, @@ -172,6 +190,6 @@ protocol DataTableProvider: Provider { func tempTargets() -> [TempTarget] func carbs() -> [CarbsEntry] func glucose() -> [BloodGlucose] - func deleteCarbs(at date: Date) + func deleteCarbs(_ treatment: DataTable.Treatment) func deleteGlucose(id: String) } diff --git a/FreeAPS/Sources/Modules/DataTable/DataTableProvider.swift b/FreeAPS/Sources/Modules/DataTable/DataTableProvider.swift index 134031c3b..34f05bead 100644 --- a/FreeAPS/Sources/Modules/DataTable/DataTableProvider.swift +++ b/FreeAPS/Sources/Modules/DataTable/DataTableProvider.swift @@ -21,8 +21,9 @@ extension DataTable { carbsStorage.recent() } - func deleteCarbs(at date: Date) { - nightscoutManager.deleteCarbs(at: date) + func deleteCarbs(_ treatment: Treatment) { + nightscoutManager.deleteCarbs(at: treatment.date) + healthkitManager.deleteCarbs(syncID: treatment.id.uuidString) } func glucose() -> [BloodGlucose] { @@ -31,7 +32,7 @@ extension DataTable { func deleteGlucose(id: String) { glucoseStorage.removeGlucose(ids: [id]) - healthkitManager.deleteGlucise(syncID: id) + healthkitManager.deleteGlucose(syncID: id) } } } diff --git a/FreeAPS/Sources/Modules/DataTable/DataTableStateModel.swift b/FreeAPS/Sources/Modules/DataTable/DataTableStateModel.swift index 502413ed0..2f369cf57 100644 --- a/FreeAPS/Sources/Modules/DataTable/DataTableStateModel.swift +++ b/FreeAPS/Sources/Modules/DataTable/DataTableStateModel.swift @@ -24,7 +24,13 @@ extension DataTable { let units = self.settingsManager.settings.units let carbs = self.provider.carbs().map { - Treatment(units: units, type: .carbs, date: $0.createdAt, amount: $0.carbs) + Treatment( + id: UUID(uuidString: $0.id) ?? UUID(), + units: units, + type: .carbs, + date: $0.createdAt, + amount: $0.carbs + ) } let boluses = self.provider.pumpHistory() @@ -88,8 +94,8 @@ extension DataTable { } } - func deleteCarbs(at date: Date) { - provider.deleteCarbs(at: date) + func deleteCarbs(_ treatment: Treatment) { + provider.deleteCarbs(treatment) } func deleteGlucose(at index: Int) { diff --git a/FreeAPS/Sources/Modules/DataTable/View/DataTableRootView.swift b/FreeAPS/Sources/Modules/DataTable/View/DataTableRootView.swift index 1c5bb6cfc..04c185107 100644 --- a/FreeAPS/Sources/Modules/DataTable/View/DataTableRootView.swift +++ b/FreeAPS/Sources/Modules/DataTable/View/DataTableRootView.swift @@ -90,7 +90,9 @@ extension DataTable { message: Text(item.amountText), primaryButton: .destructive( Text("Delete"), - action: { state.deleteCarbs(at: item.date) } + action: { + state.deleteCarbs(item) + } ), secondaryButton: .cancel() ) diff --git a/FreeAPS/Sources/Modules/HealthKit/HealthKitStateModel.swift b/FreeAPS/Sources/Modules/HealthKit/HealthKitStateModel.swift index fa23644b2..85ddd279c 100644 --- a/FreeAPS/Sources/Modules/HealthKit/HealthKitStateModel.swift +++ b/FreeAPS/Sources/Modules/HealthKit/HealthKitStateModel.swift @@ -25,7 +25,8 @@ extension AppleHealthKit { self.healthKitManager.requestPermission { ok, error in DispatchQueue.main.async { - self.needShowInformationTextForSetPermissions = !self.healthKitManager.checkAvailabilitySaveBG() + self.needShowInformationTextForSetPermissions = !self.healthKitManager.checkAvailabilitySaveBG() || !self + .healthKitManager.checkAvailabilitySaveCarbs() } guard ok, error == nil else { diff --git a/FreeAPS/Sources/Modules/HealthKit/View/AppleHealthKitRootView.swift b/FreeAPS/Sources/Modules/HealthKit/View/AppleHealthKitRootView.swift index b7594f3d4..aa67780f8 100644 --- a/FreeAPS/Sources/Modules/HealthKit/View/AppleHealthKitRootView.swift +++ b/FreeAPS/Sources/Modules/HealthKit/View/AppleHealthKitRootView.swift @@ -13,7 +13,7 @@ extension AppleHealthKit { HStack { Image(systemName: "pencil.circle.fill") Text( - "After you create glucose records in the Health app, please open FreeAPS X to help us guaranteed transfer changed data" + "After you create records in the Health app, please open FreeAPS X to help us guaranteed transfer changed data" ) .font(.caption) } @@ -21,8 +21,10 @@ extension AppleHealthKit { if state.needShowInformationTextForSetPermissions { HStack { Image(systemName: "exclamationmark.circle.fill") - Text("For write data to Apple Health you must give permissions in Settings > Health > Data Access") - .font(.caption) + Text( + "For read/write data from/to Apple Health you must give permissions in Settings > Health > Data Access" + ) + .font(.caption) } .foregroundColor(Color.secondary) } diff --git a/FreeAPS/Sources/Services/HealthKit/HealthKitManager.swift b/FreeAPS/Sources/Services/HealthKit/HealthKitManager.swift index 869ea4df1..31729cd60 100644 --- a/FreeAPS/Sources/Services/HealthKit/HealthKitManager.swift +++ b/FreeAPS/Sources/Services/HealthKit/HealthKitManager.swift @@ -3,31 +3,38 @@ import Foundation import HealthKit import Swinject -protocol HealthKitManager: GlucoseSource { +protocol HealthKitManager: GlucoseSource, CarbsSource { /// Check all needed permissions /// Return false if one or more permissions are deny or not choosen var areAllowAllPermissions: Bool { get } /// Check availability to save data of BG type to Health store func checkAvailabilitySaveBG() -> Bool + /// Check availability to save data of Carbs type to Health store + func checkAvailabilitySaveCarbs() -> Bool /// Requests user to give permissions on using HealthKit func requestPermission(completion: ((Bool, Error?) -> Void)?) /// Save blood glucose to Health store (dublicate of bg will ignore) func saveIfNeeded(bloodGlucose: [BloodGlucose]) + /// Save carbs to Health store (dublicate of bg will ignore) + func saveIfNeeded(carbs: [CarbsEntry]) /// Create observer for data passing beetwen Health Store and FreeAPS func createObserver() /// Enable background delivering objects from Apple Health to FreeAPS func enableBackgroundDelivery() /// Delete glucose with syncID - func deleteGlucise(syncID: String) + func deleteCarbs(syncID: String) + /// Delete glucose with syncID + func deleteGlucose(syncID: String) } final class BaseHealthKitManager: HealthKitManager, Injectable { private enum Config { // unwraped HKObjects - static var permissions: Set { Set([healthBGObject].compactMap { $0 }) } + static var permissions: Set { Set([healthBGObject, healthCarbObject].compactMap { $0 }) } // link to object in HealthKit static let healthBGObject = HKObjectType.quantityType(forIdentifier: .bloodGlucose) + static let healthCarbObject = HKObjectType.quantityType(forIdentifier: .dietaryCarbohydrates) // Meta-data key of FreeASPX data in HealthStore static let freeAPSMetaKey = "fromFreeAPSX" @@ -36,25 +43,44 @@ final class BaseHealthKitManager: HealthKitManager, Injectable { @Injected() private var glucoseStorage: GlucoseStorage! @Injected() private var healthKitStore: HKHealthStore! @Injected() private var settingsManager: SettingsManager! + @Injected() private var broadcaster: Broadcaster! private let processQueue = DispatchQueue(label: "BaseHealthKitManager.processQueue") private var lifetime = Lifetime() - // BG that will be return Publisher + // BG that will be return Publisher (GlucoseSource protocol) @SyncAccess @Persisted(key: "BaseHealthKitManager.newGlucose") private var newGlucose: [BloodGlucose] = [] + // Carbs that will be return Publisher (CarbsSource protocol) + @SyncAccess @Persisted(key: "BaseHealthKitManager.newCarbs") private var newCarbs: [CarbsEntry] = [] // last anchor for HKAnchoredQuery + // BG private var lastBloodGlucoseQueryAnchor: HKQueryAnchor? { set { - persistedAnchor = try? NSKeyedArchiver.archivedData(withRootObject: newValue as Any, requiringSecureCoding: false) + persistedBGAnchor = try? NSKeyedArchiver.archivedData(withRootObject: newValue as Any, requiringSecureCoding: false) + } + get { + guard let data = persistedBGAnchor else { return nil } + return try? NSKeyedUnarchiver.unarchiveTopLevelObjectWithData(data) as? HKQueryAnchor + } + } + + @Persisted(key: "HealthKitManagerAnchor") private var persistedBGAnchor: Data? = nil + // Carbs + private var lastCarbsQueryAnchor: HKQueryAnchor? { + set { + persistedCarbsAnchor = try? NSKeyedArchiver.archivedData( + withRootObject: newValue as Any, + requiringSecureCoding: false + ) } get { - guard let data = persistedAnchor else { return nil } + guard let data = persistedCarbsAnchor else { return nil } return try? NSKeyedUnarchiver.unarchiveTopLevelObjectWithData(data) as? HKQueryAnchor } } - @Persisted(key: "HealthKitManagerAnchor") private var persistedAnchor: Data? = nil + @Persisted(key: "HealthKitManagerAnchor_Carbs") private var persistedCarbsAnchor: Data? = nil var isAvailableOnCurrentDevice: Bool { HKHealthStore.isHealthDataAvailable() @@ -67,7 +93,7 @@ final class BaseHealthKitManager: HealthKitManager, Injectable { } // NSPredicate, which use during load increment BG from Health store - private var loadBGPredicate: NSPredicate { + private var loadHealthDataPredicate: NSPredicate { // loading only daily bg let predicateByStartDate = HKQuery.predicateForSamples( withStart: Date().addingTimeInterval(-1.days.timeInterval), @@ -93,6 +119,11 @@ final class BaseHealthKitManager: HealthKitManager, Injectable { createObserver() enableBackgroundDelivery() debug(.service, "HealthKitManager did create") + subscribe() + } + + private func subscribe() { + broadcaster.register(CarbsObserver.self, observer: self) } func checkAvailabilitySave(objectTypeToHealthStore: HKObjectType) -> Bool { @@ -103,6 +134,10 @@ final class BaseHealthKitManager: HealthKitManager, Injectable { Config.healthBGObject.map { checkAvailabilitySave(objectTypeToHealthStore: $0) } ?? false } + func checkAvailabilitySaveCarbs() -> Bool { + Config.healthCarbObject.map { checkAvailabilitySave(objectTypeToHealthStore: $0) } ?? false + } + func requestPermission(completion: ((Bool, Error?) -> Void)? = nil) { guard isAvailableOnCurrentDevice else { completion?(false, HKError.notAvailableOnCurrentDevice) @@ -118,6 +153,42 @@ final class BaseHealthKitManager: HealthKitManager, Injectable { } } + func saveIfNeeded(carbs: [CarbsEntry]) { + guard settingsManager.settings.useAppleHealth, + let sampleType = Config.healthCarbObject, + checkAvailabilitySave(objectTypeToHealthStore: sampleType), + carbs.isNotEmpty + else { return } + + func save(samples: [HKSample]) { + let sampleIDs = samples.compactMap(\.syncIdentifier) + let samplesToSave = carbs + .filter { !sampleIDs.contains($0.id) } + .filter { $0.enteredBy != CarbsEntry.applehealth } + .map { + HKQuantitySample( + type: sampleType, + quantity: HKQuantity(unit: .gram(), doubleValue: Double($0.carbs)), + start: $0.createdAt, + end: $0.createdAt, + metadata: [ + HKMetadataKeyExternalUUID: $0.id, + HKMetadataKeySyncIdentifier: $0.id, + HKMetadataKeySyncVersion: 1, + Config.freeAPSMetaKey: true + ] + ) + } + + healthKitStore.save(samplesToSave) { _, _ in } + } + + loadSamplesFromHealth(sampleType: sampleType, withIDs: carbs.map(\.id)) + .receive(on: processQueue) + .sink(receiveValue: save) + .store(in: &lifetime) + } + func saveIfNeeded(bloodGlucose: [BloodGlucose]) { guard settingsManager.settings.useAppleHealth, let sampleType = Config.healthBGObject, @@ -153,24 +224,30 @@ final class BaseHealthKitManager: HealthKitManager, Injectable { .store(in: &lifetime) } + // MARK: - Observers & Background data delivery + func createObserver() { guard settingsManager.settings.useAppleHealth else { return } - guard let bgType = Config.healthBGObject else { + createBGObserver() + createCarbsObserver() + } + + private func createBGObserver() { + guard let type = Config.healthBGObject else { warning(.service, "Can not create HealthKit Observer, because unable to get the Blood Glucose type") return } - - let query = HKObserverQuery(sampleType: bgType, predicate: nil) { [weak self] _, _, observerError in + let query = HKObserverQuery(sampleType: type, predicate: nil) { [weak self] _, _, observerError in guard let self = self else { return } - debug(.service, "Execute HelathKit observer query for loading increment samples") + debug(.service, "Execute HealthKit observer query for loading increment samples") guard observerError == nil else { - warning(.service, "Error during execution of HelathKit Observer's query", error: observerError!) + warning(.service, "Error during execution of HealthKit Observer's query", error: observerError!) return } - if let incrementQuery = self.getBloodGlucoseHKQuery(predicate: self.loadBGPredicate) { - debug(.service, "Create increment query") + if let incrementQuery = self.getBloodGlucoseHKQuery(predicate: self.loadHealthDataPredicate) { + debug(.service, "Create increment query for loading bg") self.healthKitStore.execute(incrementQuery) } } @@ -178,6 +255,28 @@ final class BaseHealthKitManager: HealthKitManager, Injectable { debug(.service, "Create Observer for Blood Glucose") } + private func createCarbsObserver() { + guard let type = Config.healthCarbObject else { + warning(.service, "Can not create HealthKit Observer, because unable to get the Carbs type") + return + } + let query = HKObserverQuery(sampleType: type, predicate: nil) { [weak self] _, _, observerError in + guard let self = self else { return } + debug(.service, "Execute HealthKit observer query for loading increment samples") + guard observerError == nil else { + warning(.service, "Error during execution of HealthKit Observer's query", error: observerError!) + return + } + + if let incrementQuery = self.getCarbsHKQuery(predicate: self.loadHealthDataPredicate) { + debug(.service, "Create increment query for loading carbs") + self.healthKitStore.execute(incrementQuery) + } + } + healthKitStore.execute(query) + debug(.service, "Create Observer for Carbs") + } + func enableBackgroundDelivery() { guard settingsManager.settings.useAppleHealth else { healthKitStore.disableAllBackgroundDelivery { _, _ in } @@ -193,10 +292,26 @@ final class BaseHealthKitManager: HealthKitManager, Injectable { healthKitStore.enableBackgroundDelivery(for: bgType, frequency: .immediate) { status, error in guard error == nil else { - warning(.service, "Can not enable background delivery", error: error) + warning(.service, "Can not enable background delivery for bg", error: error) + return + } + debug(.service, "Background bg delivery status is \(status)") + } + + guard let carbsType = Config.healthCarbObject else { + warning( + .service, + "Can not create background delivery, because unable to get the Carbs type" + ) + return + } + + healthKitStore.enableBackgroundDelivery(for: carbsType, frequency: .immediate) { status, error in + guard error == nil else { + warning(.service, "Can not enable background delivery for carbs", error: error) return } - debug(.service, "Background delivery status is \(status)") + debug(.service, "Background carbs delivery status is \(status)") } } @@ -234,7 +349,7 @@ final class BaseHealthKitManager: HealthKitManager, Injectable { ) { [weak self] _, addedObjects, _, anchor, _ in guard let self = self else { return } self.processQueue.async { - debug(.service, "AnchoredQuery did execute") + debug(.service, "BG AnchoredQuery did execute") self.lastBloodGlucoseQueryAnchor = anchor @@ -242,22 +357,80 @@ final class BaseHealthKitManager: HealthKitManager, Injectable { if let bgSamples = addedObjects as? [HKQuantitySample], bgSamples.isNotEmpty { - self.prepareSamplesToPublisherFetch(bgSamples) + self.prepareBGSamplesToPublisherFetch(bgSamples) } } } return query } - private func prepareSamplesToPublisherFetch(_ samples: [HKQuantitySample]) { + private func getCarbsHKQuery(predicate: NSPredicate) -> HKQuery? { + guard let sampleType = Config.healthCarbObject else { return nil } + + let query = HKAnchoredObjectQuery( + type: sampleType, + predicate: predicate, + anchor: lastCarbsQueryAnchor, + limit: HKObjectQueryNoLimit + ) { [weak self] _, addedObjects, _, anchor, _ in + guard let self = self else { return } + self.processQueue.async { + debug(.service, "Carbs AnchoredQuery did execute") + + self.lastCarbsQueryAnchor = anchor + + // Added objects + if let samples = addedObjects as? [HKQuantitySample], + samples.isNotEmpty + { + self.prepareCarbsSamplesToPublisherFetch(samples) + } + } + } + return query + } + + private func prepareCarbsSamplesToPublisherFetch(_ samples: [HKQuantitySample]) { dispatchPrecondition(condition: .onQueue(processQueue)) - debug(.service, "Start preparing samples: \(String(describing: samples))") + debug(.service, "Start preparing carbs samples: \(String(describing: samples))") + + newCarbs += samples + .compactMap { sample -> HealthKitCarbsSample? in + let fromFAX = sample.metadata?[Config.freeAPSMetaKey] as? Bool ?? false + guard !fromFAX else { return nil } + return HealthKitCarbsSample( + healthKitId: sample.uuid.uuidString, + date: sample.startDate, + carbs: Decimal(round(sample.quantity.doubleValue(for: .gram()))) + ) + } + .map { sample in + CarbsEntry( + id: sample.healthKitId, + createdAt: sample.date, + carbs: sample.carbs, + enteredBy: "applehealth" + ) + } + .filter { $0.createdAt >= Date().addingTimeInterval(-1.days.timeInterval) } + + newCarbs = newCarbs.removeDublicates() + + debug( + .service, + "Current Carbs.Type objects will be send from Publisher during fetch: \(String(describing: newCarbs))" + ) + } + + private func prepareBGSamplesToPublisherFetch(_ samples: [HKQuantitySample]) { + dispatchPrecondition(condition: .onQueue(processQueue)) + debug(.service, "Start preparing bg samples: \(String(describing: samples))") newGlucose += samples - .compactMap { sample -> HealthKitSample? in + .compactMap { sample -> HealthKitBGSample? in let fromFAX = sample.metadata?[Config.freeAPSMetaKey] as? Bool ?? false guard !fromFAX else { return nil } - return HealthKitSample( + return HealthKitBGSample( healthKitId: sample.uuid.uuidString, date: sample.startDate, glucose: Int(round(sample.quantity.doubleValue(for: .milligramsPerDeciliter))) @@ -287,6 +460,65 @@ final class BaseHealthKitManager: HealthKitManager, Injectable { ) } + // MARK: - Carbs source + + func fetchCarbs() -> AnyPublisher<[CarbsEntry], Never> { + Future { [weak self] promise in + guard let self = self else { + promise(.success([])) + return + } + + self.processQueue.async { + debug(.service, "Start fetching carbs from HealthKitManager") + guard self.settingsManager.settings.useAppleHealth else { + debug(.service, "HealthKitManager cant return any data, because useAppleHealth option is disable") + promise(.success([])) + return + } + + // Remove old Carbs + self.newCarbs = self.newCarbs + .filter { $0.createdAt >= Date().addingTimeInterval(-1.days.timeInterval) } + // Get actual Carbs (beetwen Date() - 1 day and Date()) + let actualCarbs = self.newCarbs + .filter { $0.createdAt <= Date() } + // Update newCarbs + self.newCarbs = self.newCarbs + .filter { !actualCarbs.contains($0) } + + debug(.service, "Actual carbs is \(actualCarbs)") + + debug(.service, "Current state of newCarbs is \(self.newCarbs)") + + promise(.success(actualCarbs)) + } + } + .eraseToAnyPublisher() + } + + func deleteCarbs(syncID: String) { + guard settingsManager.settings.useAppleHealth, + let sampleType = Config.healthCarbObject, + checkAvailabilitySave(objectTypeToHealthStore: sampleType) + else { return } + + processQueue.async { + let predicate = HKQuery.predicateForObjects( + withMetadataKey: HKMetadataKeySyncIdentifier, + operatorType: .equalTo, + value: syncID + ) + + self.healthKitStore.deleteObjects(of: sampleType, predicate: predicate) { _, _, error in + guard let error = error else { return } + warning(.service, "Cannot delete sample with syncID: \(syncID)", error: error) + } + } + } + + // MARK: - Glucose source + func fetch() -> AnyPublisher<[BloodGlucose], Never> { Future { [weak self] promise in guard let self = self else { @@ -295,7 +527,7 @@ final class BaseHealthKitManager: HealthKitManager, Injectable { } self.processQueue.async { - debug(.service, "Start fetching HealthKitManager") + debug(.service, "Start fetching bloodGlucose from HealthKitManager") guard self.settingsManager.settings.useAppleHealth else { debug(.service, "HealthKitManager cant return any data, because useAppleHealth option is disable") promise(.success([])) @@ -322,7 +554,7 @@ final class BaseHealthKitManager: HealthKitManager, Injectable { .eraseToAnyPublisher() } - func deleteGlucise(syncID: String) { + func deleteGlucose(syncID: String) { guard settingsManager.settings.useAppleHealth, let sampleType = Config.healthBGObject, checkAvailabilitySave(objectTypeToHealthStore: sampleType) @@ -343,6 +575,12 @@ final class BaseHealthKitManager: HealthKitManager, Injectable { } } +extension BaseHealthKitManager: CarbsObserver { + func carbsDidUpdate(_ carbs: [CarbsEntry]) { + saveIfNeeded(carbs: carbs) + } +} + enum HealthKitPermissionRequestStatus { case needRequest case didRequest diff --git a/FreeAPS/Sources/Services/Network/NightscoutManager.swift b/FreeAPS/Sources/Services/Network/NightscoutManager.swift index c6b20767c..3a4d2ad3f 100644 --- a/FreeAPS/Sources/Services/Network/NightscoutManager.swift +++ b/FreeAPS/Sources/Services/Network/NightscoutManager.swift @@ -3,7 +3,7 @@ import Foundation import Swinject import UIKit -protocol NightscoutManager: GlucoseSource { +protocol NightscoutManager: GlucoseSource, CarbsSource { func fetchGlucose(since date: Date) -> AnyPublisher<[BloodGlucose], Never> func fetchCarbs() -> AnyPublisher<[CarbsEntry], Never> func fetchTempTargets() -> AnyPublisher<[TempTarget], Never> From 0cbdb5c94c1b6db5fd3519b94449175d03b10554 Mon Sep 17 00:00:00 2001 From: Vasily Usov Date: Sun, 27 Mar 2022 16:01:09 +0300 Subject: [PATCH 10/28] Update comments style --- FreeAPS.xcodeproj/project.pbxproj | 20 +++++++++++++++---- .../Services/Migration/MigrationManager.swift | 6 +++--- .../Migration/MigrationPublisher.swift | 8 ++++---- .../Migration/MigrationWorkItem.swift | 15 +++++++------- 4 files changed, 31 insertions(+), 18 deletions(-) diff --git a/FreeAPS.xcodeproj/project.pbxproj b/FreeAPS.xcodeproj/project.pbxproj index 21ecd6661..50d469eb8 100644 --- a/FreeAPS.xcodeproj/project.pbxproj +++ b/FreeAPS.xcodeproj/project.pbxproj @@ -297,7 +297,8 @@ E013D872273AC6FE0014109C /* GlucoseSimulatorSource.swift in Sources */ = {isa = PBXBuildFile; fileRef = E013D871273AC6FE0014109C /* GlucoseSimulatorSource.swift */; }; E06B911A275B5EEA003C04B6 /* Array+Extension.swift in Sources */ = {isa = PBXBuildFile; fileRef = E06B9119275B5EEA003C04B6 /* Array+Extension.swift */; }; E0CC2C5C275B9F0F00A7BC71 /* HealthKit.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = E0CC2C5B275B9DAE00A7BC71 /* HealthKit.framework */; }; - E0D4F80527513ECF00BDF1FE /* HealthKitSample.swift in Sources */ = {isa = PBXBuildFile; fileRef = E0D4F80427513ECF00BDF1FE /* HealthKitSample.swift */; }; + E0D4F80527513ECF00BDF1FE /* HealthKitSamples.swift in Sources */ = {isa = PBXBuildFile; fileRef = E0D4F80427513ECF00BDF1FE /* HealthKitSamples.swift */; }; + E0E9DB9727F051E700614A3F /* CarbsSource.swift in Sources */ = {isa = PBXBuildFile; fileRef = E0E9DB9627F051E700614A3F /* CarbsSource.swift */; }; E13B7DAB2A435F57066AF02E /* TargetsEditorStateModel.swift in Sources */ = {isa = PBXBuildFile; fileRef = 36F58DDD71F0E795464FA3F0 /* TargetsEditorStateModel.swift */; }; E25073BC86C11C3D6A42F5AC /* CalibrationsStateModel.swift in Sources */ = {isa = PBXBuildFile; fileRef = 47DFCE895C930F784EF11843 /* CalibrationsStateModel.swift */; }; E39E418C56A5A46B61D960EE /* ConfigEditorStateModel.swift in Sources */ = {isa = PBXBuildFile; fileRef = 5D5B4F8B4194BB7E260EF251 /* ConfigEditorStateModel.swift */; }; @@ -716,7 +717,8 @@ E013D871273AC6FE0014109C /* GlucoseSimulatorSource.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = GlucoseSimulatorSource.swift; sourceTree = ""; }; E06B9119275B5EEA003C04B6 /* Array+Extension.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "Array+Extension.swift"; sourceTree = ""; }; E0CC2C5B275B9DAE00A7BC71 /* HealthKit.framework */ = {isa = PBXFileReference; lastKnownFileType = wrapper.framework; name = HealthKit.framework; path = System/Library/Frameworks/HealthKit.framework; sourceTree = SDKROOT; }; - E0D4F80427513ECF00BDF1FE /* HealthKitSample.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = HealthKitSample.swift; sourceTree = ""; }; + E0D4F80427513ECF00BDF1FE /* HealthKitSamples.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = HealthKitSamples.swift; sourceTree = ""; }; + E0E9DB9627F051E700614A3F /* CarbsSource.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = CarbsSource.swift; sourceTree = ""; }; E26904AACA8D9C15D229D675 /* SnoozeStateModel.swift */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = sourcecode.swift; path = SnoozeStateModel.swift; sourceTree = ""; }; E2EBA7C03C26FCC67E16D798 /* LibreConfigProvider.swift */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = sourcecode.swift; path = LibreConfigProvider.swift; sourceTree = ""; }; E625985B47742D498CB1681A /* NotificationsConfigProvider.swift */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = sourcecode.swift; path = NotificationsConfigProvider.swift; sourceTree = ""; }; @@ -1115,6 +1117,7 @@ 38A43597262E0E4900E80935 /* FetchAnnouncementsManager.swift */, 38DAB289260D349500F74C1A /* FetchGlucoseManager.swift */, 38192E06261BA9960094D973 /* FetchTreatmentsManager.swift */, + E0E9DB9527F051C300614A3F /* Carbs */, 3856933F270B57A00002C50D /* CGM */, 38A504F625DDA0E200C5B9E8 /* Extensions */, 388E5A5825B6F0070019842D /* OpenAPS */, @@ -1291,7 +1294,7 @@ 38A0364125ED069400FCBB52 /* TempBasal.swift */, 3871F39B25ED892B0013ECB5 /* TempTarget.swift */, 3811DE8E25C9D80400A708ED /* User.swift */, - E0D4F80427513ECF00BDF1FE /* HealthKitSample.swift */, + E0D4F80427513ECF00BDF1FE /* HealthKitSamples.swift */, ); path = Models; sourceTree = ""; @@ -1765,6 +1768,14 @@ path = Assemblies; sourceTree = ""; }; + E0E9DB9527F051C300614A3F /* Carbs */ = { + isa = PBXGroup; + children = ( + E0E9DB9627F051E700614A3F /* CarbsSource.swift */, + ); + path = Carbs; + sourceTree = ""; + }; E42231DBF0DBE2B4B92D1B15 /* CREditor */ = { isa = PBXGroup; children = ( @@ -2323,7 +2334,7 @@ D2165E9D78EFF692C1DED1C6 /* AddTempTargetDataFlow.swift in Sources */, 38E4451E274DB04600EC9A94 /* AppDelegate.swift in Sources */, 5BFA1C2208114643B77F8CEB /* AddTempTargetProvider.swift in Sources */, - E0D4F80527513ECF00BDF1FE /* HealthKitSample.swift in Sources */, + E0D4F80527513ECF00BDF1FE /* HealthKitSamples.swift in Sources */, 919DBD08F13BAFB180DF6F47 /* AddTempTargetStateModel.swift in Sources */, 8BC2F5A29AD1ED08AC0EE013 /* AddTempTargetRootView.swift in Sources */, 38A00B1F25FC00F7006BC0B0 /* Autotune.swift in Sources */, @@ -2364,6 +2375,7 @@ B7C465E9472624D8A2BE2A6A /* CalibrationsDataFlow.swift in Sources */, 320D030F724170A637F06D50 /* CalibrationsProvider.swift in Sources */, E25073BC86C11C3D6A42F5AC /* CalibrationsStateModel.swift in Sources */, + E0E9DB9727F051E700614A3F /* CarbsSource.swift in Sources */, BA90041DC8991147E5C8C3AA /* CalibrationsRootView.swift in Sources */, E3A08AAE59538BC8A8ABE477 /* NotificationsConfigDataFlow.swift in Sources */, 0F7A65FBD2CD8D6477ED4539 /* NotificationsConfigProvider.swift in Sources */, diff --git a/FreeAPS/Sources/Services/Migration/MigrationManager.swift b/FreeAPS/Sources/Services/Migration/MigrationManager.swift index dfa4882d1..a861d9877 100644 --- a/FreeAPS/Sources/Services/Migration/MigrationManager.swift +++ b/FreeAPS/Sources/Services/Migration/MigrationManager.swift @@ -1,6 +1,6 @@ -// -// Main Migration tool of App -// +/** + Main Migration tool of App + */ import Combine import Foundation diff --git a/FreeAPS/Sources/Services/Migration/MigrationPublisher.swift b/FreeAPS/Sources/Services/Migration/MigrationPublisher.swift index 9c91dd62a..36008dbdd 100644 --- a/FreeAPS/Sources/Services/Migration/MigrationPublisher.swift +++ b/FreeAPS/Sources/Services/Migration/MigrationPublisher.swift @@ -1,7 +1,7 @@ -// -// This file store code for working Migration manager with Combine -// This is only combine's wrapper for Migration manager -// +/** + This file store code for working Migration manager with Combine + This is only combine's wrapper for Migration manager + */ import Combine import Foundation diff --git a/FreeAPS/Sources/Services/Migration/MigrationWorkItem.swift b/FreeAPS/Sources/Services/Migration/MigrationWorkItem.swift index 19f19b065..7e421b55a 100644 --- a/FreeAPS/Sources/Services/Migration/MigrationWorkItem.swift +++ b/FreeAPS/Sources/Services/Migration/MigrationWorkItem.swift @@ -1,10 +1,11 @@ -// -// This file contains WorkItems with migration tasks -// Each WorkItem have to execute one migration task -// Each WorkItem can be run in Migration.StateModel.runMigration() -// ... -// .migrate(startAtVersion: "0.2.6", MigrationWorkExample()) -// ... +/** + This file contains WorkItems with migration tasks + Each WorkItem have to execute one migration task + Each WorkItem can be run in Migration.StateModel.runMigration() + ... + .migrate(startAtVersion: "0.2.6", MigrationWorkExample()) + ... + */ import Foundation From 35da3bacf614ee131a21941c2b1b5e1ba953d4b6 Mon Sep 17 00:00:00 2001 From: Vasily Usov Date: Tue, 29 Mar 2022 12:59:30 +0300 Subject: [PATCH 11/28] Added new buttons on Add carbs screen --- .../Main/en.lproj/Localizable.strings | 12 +++++++++ .../Main/ru.lproj/Localizable.strings | 12 +++++++++ .../Modules/AddCarbs/AddCarbsStateModel.swift | 27 +++++++++++++++++++ .../AddCarbs/View/AddCarbsRootView.swift | 24 ++++++++++++++++- 4 files changed, 74 insertions(+), 1 deletion(-) diff --git a/FreeAPS/Sources/Localizations/Main/en.lproj/Localizable.strings b/FreeAPS/Sources/Localizations/Main/en.lproj/Localizable.strings index 48740e8ac..fa703e2c8 100644 --- a/FreeAPS/Sources/Localizations/Main/en.lproj/Localizable.strings +++ b/FreeAPS/Sources/Localizations/Main/en.lproj/Localizable.strings @@ -70,6 +70,18 @@ /* Add carbs header and button in Watch app. You can skip the last " " space. It's just for differentiation */ "Add Carbs " = "Add Carbs "; +/* Fast Add carbs button */ +"Fast Add" = "Fast Add"; + +/* Fast Add carbs button description */ +"Carbs will add and FreeAPX X will determine and inject bolus without your participation" = "Carbs will add and FreeAPX X will determine and inject bolus without your participation"; + +/* Simple Add carbs button */ +"Simple Add" = "Simple Add"; + +/* Simple Add carbs button description*/ +"Carbs will add without bolus" = "Carbs will add without bolus"; + /* */ "Amount Carbs" = "Amount Carbs"; diff --git a/FreeAPS/Sources/Localizations/Main/ru.lproj/Localizable.strings b/FreeAPS/Sources/Localizations/Main/ru.lproj/Localizable.strings index 6b55bfc2e..b7cf2f573 100644 --- a/FreeAPS/Sources/Localizations/Main/ru.lproj/Localizable.strings +++ b/FreeAPS/Sources/Localizations/Main/ru.lproj/Localizable.strings @@ -70,6 +70,18 @@ /* Add carbs header and button in Watch app. You can skip the last " " space. It's just for differentiation */ "Add Carbs " = "Ввод углеводов"; +/* Fast Add carbs button */ +"Fast Add" = "Добавить быстро"; + +/* Fast Add carbs button description */ +"Carbs will add and FreeAPX X will determine and inject bolus without your participation" = "Углеводы будут добавлены, а FreeAPS X автоматически определит и введет необходимую дозу инсулина"; + +/* Simple Add carbs button */ +"Simple Add" = "Просто добавить"; + +/* Simple Add carbs button description*/ +"Carbs will add without bolus" = "Углеводы будут добавлены без ввода болюса"; + /* */ "Amount Carbs" = "Кол-во углеводов"; diff --git a/FreeAPS/Sources/Modules/AddCarbs/AddCarbsStateModel.swift b/FreeAPS/Sources/Modules/AddCarbs/AddCarbsStateModel.swift index 7bac8146c..5f25500de 100644 --- a/FreeAPS/Sources/Modules/AddCarbs/AddCarbsStateModel.swift +++ b/FreeAPS/Sources/Modules/AddCarbs/AddCarbsStateModel.swift @@ -29,5 +29,32 @@ extension AddCarbs { showModal(for: .bolus(waitForSuggestion: true)) } } + + func fastAdd() { + guard carbs > 0 else { + showModal(for: nil) + return + } + + carbsStorage.storeCarbs([ + CarbsEntry(createdAt: date, carbs: carbs, enteredBy: CarbsEntry.manual) + ]) + + apsManager.determineBasalSync() + showModal(for: nil) + } + + func addWithoutbolus() { + guard carbs > 0 else { + showModal(for: nil) + return + } + + carbsStorage.storeCarbs([ + CarbsEntry(createdAt: date, carbs: carbs, enteredBy: CarbsEntry.manual) + ]) + + showModal(for: nil) + } } } diff --git a/FreeAPS/Sources/Modules/AddCarbs/View/AddCarbsRootView.swift b/FreeAPS/Sources/Modules/AddCarbs/View/AddCarbsRootView.swift index f694af8b1..59b5f7c82 100644 --- a/FreeAPS/Sources/Modules/AddCarbs/View/AddCarbsRootView.swift +++ b/FreeAPS/Sources/Modules/AddCarbs/View/AddCarbsRootView.swift @@ -35,9 +35,31 @@ extension AddCarbs { } Section { - Button { state.add() } + Button { state.fastAdd() } label: { Text("Add") } .disabled(state.carbs <= 0) + VStack(alignment: .leading, spacing: 5) { + Button { state.addWithoutbolus() } + label: { Text("Fast Add") } + .disabled(state.carbs <= 0) + Text( + "Carbs will add and FreeAPX X will determine and inject bolus without your participation" + ) + .font(.caption) + .foregroundColor(Color.secondary) + } + .padding(.top, 5) + VStack(alignment: .leading, spacing: 5) { + Button { state.addWithoutbolus() } + label: { Text("Simple Add") } + .disabled(state.carbs <= 0) + Text( + "Carbs will add without bolus" + ) + .font(.caption) + .foregroundColor(Color.secondary) + } + .padding(.top, 5) } } .onAppear(perform: configureView) From 48a01e66b8c05338bb7dc5a42dcfcc4c0404d21c Mon Sep 17 00:00:00 2001 From: Vasily Usov Date: Tue, 29 Mar 2022 13:06:01 +0300 Subject: [PATCH 12/28] Update AddCarbsRootView.swift --- FreeAPS/Sources/Modules/AddCarbs/View/AddCarbsRootView.swift | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/FreeAPS/Sources/Modules/AddCarbs/View/AddCarbsRootView.swift b/FreeAPS/Sources/Modules/AddCarbs/View/AddCarbsRootView.swift index 59b5f7c82..061b3f533 100644 --- a/FreeAPS/Sources/Modules/AddCarbs/View/AddCarbsRootView.swift +++ b/FreeAPS/Sources/Modules/AddCarbs/View/AddCarbsRootView.swift @@ -35,11 +35,11 @@ extension AddCarbs { } Section { - Button { state.fastAdd() } + Button { state.add() } label: { Text("Add") } .disabled(state.carbs <= 0) VStack(alignment: .leading, spacing: 5) { - Button { state.addWithoutbolus() } + Button { state.fastAdd()() } label: { Text("Fast Add") } .disabled(state.carbs <= 0) Text( From 2382081bc3a7e38fd440188291cc33d7d00abdc5 Mon Sep 17 00:00:00 2001 From: Vasily Usov Date: Tue, 29 Mar 2022 13:09:11 +0300 Subject: [PATCH 13/28] Update AddCarbsRootView.swift --- FreeAPS/Sources/Modules/AddCarbs/View/AddCarbsRootView.swift | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/FreeAPS/Sources/Modules/AddCarbs/View/AddCarbsRootView.swift b/FreeAPS/Sources/Modules/AddCarbs/View/AddCarbsRootView.swift index 061b3f533..b3b01570c 100644 --- a/FreeAPS/Sources/Modules/AddCarbs/View/AddCarbsRootView.swift +++ b/FreeAPS/Sources/Modules/AddCarbs/View/AddCarbsRootView.swift @@ -39,7 +39,7 @@ extension AddCarbs { label: { Text("Add") } .disabled(state.carbs <= 0) VStack(alignment: .leading, spacing: 5) { - Button { state.fastAdd()() } + Button { state.fastAdd() } label: { Text("Fast Add") } .disabled(state.carbs <= 0) Text( From d4102e7b098214dea1a2cb8da31046388498edb0 Mon Sep 17 00:00:00 2001 From: Vasily Usov Date: Tue, 29 Mar 2022 13:25:34 +0300 Subject: [PATCH 14/28] Update descriptions --- FreeAPS/Sources/Localizations/Main/en.lproj/Localizable.strings | 2 +- FreeAPS/Sources/Localizations/Main/ru.lproj/Localizable.strings | 2 +- FreeAPS/Sources/Modules/AddCarbs/View/AddCarbsRootView.swift | 2 +- 3 files changed, 3 insertions(+), 3 deletions(-) diff --git a/FreeAPS/Sources/Localizations/Main/en.lproj/Localizable.strings b/FreeAPS/Sources/Localizations/Main/en.lproj/Localizable.strings index fa703e2c8..9da7a55ce 100644 --- a/FreeAPS/Sources/Localizations/Main/en.lproj/Localizable.strings +++ b/FreeAPS/Sources/Localizations/Main/en.lproj/Localizable.strings @@ -74,7 +74,7 @@ "Fast Add" = "Fast Add"; /* Fast Add carbs button description */ -"Carbs will add and FreeAPX X will determine and inject bolus without your participation" = "Carbs will add and FreeAPX X will determine and inject bolus without your participation"; +"Carbs will add and FreeAPX X will update forecasts without bolus" = "Carbs will add and FreeAPX X will update forecasts without bolus"; /* Simple Add carbs button */ "Simple Add" = "Simple Add"; diff --git a/FreeAPS/Sources/Localizations/Main/ru.lproj/Localizable.strings b/FreeAPS/Sources/Localizations/Main/ru.lproj/Localizable.strings index b7cf2f573..5b4e85194 100644 --- a/FreeAPS/Sources/Localizations/Main/ru.lproj/Localizable.strings +++ b/FreeAPS/Sources/Localizations/Main/ru.lproj/Localizable.strings @@ -74,7 +74,7 @@ "Fast Add" = "Добавить быстро"; /* Fast Add carbs button description */ -"Carbs will add and FreeAPX X will determine and inject bolus without your participation" = "Углеводы будут добавлены, а FreeAPS X автоматически определит и введет необходимую дозу инсулина"; +"Carbs will add and FreeAPX X will update forecasts without bolus" = "Углеводы будут добавлены без ввода болюса, FreeAPS X автоматически обновит прогнозы"; /* Simple Add carbs button */ "Simple Add" = "Просто добавить"; diff --git a/FreeAPS/Sources/Modules/AddCarbs/View/AddCarbsRootView.swift b/FreeAPS/Sources/Modules/AddCarbs/View/AddCarbsRootView.swift index b3b01570c..52da812ab 100644 --- a/FreeAPS/Sources/Modules/AddCarbs/View/AddCarbsRootView.swift +++ b/FreeAPS/Sources/Modules/AddCarbs/View/AddCarbsRootView.swift @@ -43,7 +43,7 @@ extension AddCarbs { label: { Text("Fast Add") } .disabled(state.carbs <= 0) Text( - "Carbs will add and FreeAPX X will determine and inject bolus without your participation" + "Carbs will add and FreeAPX X will update forecasts without bolus" ) .font(.caption) .foregroundColor(Color.secondary) From e098d677d47d87ee638787d933476b68d792cfe8 Mon Sep 17 00:00:00 2001 From: Vasily Usov Date: Tue, 29 Mar 2022 20:15:46 +0300 Subject: [PATCH 15/28] Remove skipBolusScreenAfterCarbs setting --- FreeAPS/Sources/Modules/AddCarbs/AddCarbsStateModel.swift | 7 +------ .../PreferencesEditor/PreferencesEditorStateModel.swift | 1 - .../PreferencesEditor/View/PreferencesEditorRootView.swift | 2 -- 3 files changed, 1 insertion(+), 9 deletions(-) diff --git a/FreeAPS/Sources/Modules/AddCarbs/AddCarbsStateModel.swift b/FreeAPS/Sources/Modules/AddCarbs/AddCarbsStateModel.swift index 5f25500de..303f70c2a 100644 --- a/FreeAPS/Sources/Modules/AddCarbs/AddCarbsStateModel.swift +++ b/FreeAPS/Sources/Modules/AddCarbs/AddCarbsStateModel.swift @@ -22,12 +22,7 @@ extension AddCarbs { CarbsEntry(createdAt: date, carbs: carbs, enteredBy: CarbsEntry.manual) ]) - if settingsManager.settings.skipBolusScreenAfterCarbs { - apsManager.determineBasalSync() - showModal(for: nil) - } else { - showModal(for: .bolus(waitForSuggestion: true)) - } + showModal(for: .bolus(waitForSuggestion: true)) } func fastAdd() { diff --git a/FreeAPS/Sources/Modules/PreferencesEditor/PreferencesEditorStateModel.swift b/FreeAPS/Sources/Modules/PreferencesEditor/PreferencesEditorStateModel.swift index eab8be450..e801eaf8b 100644 --- a/FreeAPS/Sources/Modules/PreferencesEditor/PreferencesEditorStateModel.swift +++ b/FreeAPS/Sources/Modules/PreferencesEditor/PreferencesEditorStateModel.swift @@ -15,7 +15,6 @@ extension PreferencesEditor { subscribeSetting(\.allowAnnouncements, on: $allowAnnouncements) { allowAnnouncements = $0 } subscribeSetting(\.insulinReqFraction, on: $insulinReqFraction) { insulinReqFraction = $0 } - subscribeSetting(\.skipBolusScreenAfterCarbs, on: $skipBolusScreenAfterCarbs) { skipBolusScreenAfterCarbs = $0 } subscribeSetting(\.units, on: $unitsIndex.map { $0 == 0 ? GlucoseUnits.mgdL : .mmolL }) { unitsIndex = $0 == .mgdL ? 0 : 1 diff --git a/FreeAPS/Sources/Modules/PreferencesEditor/View/PreferencesEditorRootView.swift b/FreeAPS/Sources/Modules/PreferencesEditor/View/PreferencesEditorRootView.swift index aebfad189..04d458159 100644 --- a/FreeAPS/Sources/Modules/PreferencesEditor/View/PreferencesEditorRootView.swift +++ b/FreeAPS/Sources/Modules/PreferencesEditor/View/PreferencesEditorRootView.swift @@ -34,8 +34,6 @@ extension PreferencesEditor { Text("Recommended Insulin Fraction") DecimalTextField("", value: $state.insulinReqFraction, formatter: formatter) } - - Toggle("Skip Bolus screen after carbs", isOn: $state.skipBolusScreenAfterCarbs) } ForEach(state.sections.indexed(), id: \.1.id) { sectionIndex, section in From 5c54ff05e82e470d4014d58bb69c7e000cfb631e Mon Sep 17 00:00:00 2001 From: Vasiliy Usov Date: Sat, 11 Jun 2022 12:25:57 +0300 Subject: [PATCH 16/28] Add Migration manager (#176) * Add Migration manager * Refactoring migration manager/publisher * Update MigrationStateModel.swift * Update MigrationRootView.swift * Update MigrationTool logic * Update MigrationTool --- .../Sources/Services/Migration/MigrationManager.swift | 4 ---- .../Sources/Services/Migration/MigrationPublisher.swift | 5 ----- .../Sources/Services/Migration/MigrationWorkItem.swift | 9 --------- 3 files changed, 18 deletions(-) diff --git a/FreeAPS/Sources/Services/Migration/MigrationManager.swift b/FreeAPS/Sources/Services/Migration/MigrationManager.swift index a861d9877..fc7595f40 100644 --- a/FreeAPS/Sources/Services/Migration/MigrationManager.swift +++ b/FreeAPS/Sources/Services/Migration/MigrationManager.swift @@ -1,7 +1,3 @@ -/** - Main Migration tool of App - */ - import Combine import Foundation import SwiftUI diff --git a/FreeAPS/Sources/Services/Migration/MigrationPublisher.swift b/FreeAPS/Sources/Services/Migration/MigrationPublisher.swift index 36008dbdd..cda0e9439 100644 --- a/FreeAPS/Sources/Services/Migration/MigrationPublisher.swift +++ b/FreeAPS/Sources/Services/Migration/MigrationPublisher.swift @@ -1,8 +1,3 @@ -/** - This file store code for working Migration manager with Combine - This is only combine's wrapper for Migration manager - */ - import Combine import Foundation import SwiftUI diff --git a/FreeAPS/Sources/Services/Migration/MigrationWorkItem.swift b/FreeAPS/Sources/Services/Migration/MigrationWorkItem.swift index 7e421b55a..0b959d122 100644 --- a/FreeAPS/Sources/Services/Migration/MigrationWorkItem.swift +++ b/FreeAPS/Sources/Services/Migration/MigrationWorkItem.swift @@ -1,12 +1,3 @@ -/** - This file contains WorkItems with migration tasks - Each WorkItem have to execute one migration task - Each WorkItem can be run in Migration.StateModel.runMigration() - ... - .migrate(startAtVersion: "0.2.6", MigrationWorkExample()) - ... - */ - import Foundation protocol MigrationWorkItem { From afbde8040a85a1da0d6ce63a3cc5db071e778aa8 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Jon=20B=20M=C3=A5rtensson?= <53905247+Jon-b-m@users.noreply.github.com> Date: Sat, 11 Jun 2022 11:27:37 +0200 Subject: [PATCH 17/28] Crowdin (#191) * Crowdin Strings from Crowdin translators * More Crowdin updates (#453) * More Crowdin (#455) --- .../LoopKit/it.lproj/Localizable.strings | 8 +- .../LoopKit/ru.lproj/Localizable.strings | 2 +- .../LoopKit/uk.lproj/Localizable.strings | 6 +- .../LoopKitUI/it.lproj/Localizable.strings | 2 +- .../LoopKitUI/pt-PT.lproj/Localizable.strings | 14 +-- .../LoopKitUI/uk.lproj/Localizable.strings | 20 ++--- .../OmniKitUI/pt-PT.lproj/Localizable.strings | 8 +- .../it.lproj/Localizable.strings | 12 +-- .../pt-PT.lproj/Localizable.strings | 4 +- .../sk.lproj/Localizable.strings | 2 +- .../Main/da.lproj/Localizable.strings | 6 +- .../Main/de.lproj/Localizable.strings | 2 +- .../Main/es.lproj/Localizable.strings | 46 +++++----- .../Main/fi.lproj/Localizable.strings | 10 +-- .../Main/it.lproj/Localizable.strings | 86 +++++++++---------- .../Main/pl.lproj/Localizable.strings | 10 +-- .../Main/pt-BR.lproj/Localizable.strings | 4 +- .../Main/pt-PT.lproj/Localizable.strings | 2 +- .../Main/ru.lproj/Localizable.strings | 8 +- .../Main/sk.lproj/Localizable.strings | 4 +- .../Main/uk.lproj/Localizable.strings | 6 +- .../Main/zh-Hans.lproj/Localizable.strings | 12 +-- 22 files changed, 137 insertions(+), 137 deletions(-) diff --git a/Dependencies/LoopKit/LoopKit/it.lproj/Localizable.strings b/Dependencies/LoopKit/LoopKit/it.lproj/Localizable.strings index a9b6cc904..185cec2cf 100644 --- a/Dependencies/LoopKit/LoopKit/it.lproj/Localizable.strings +++ b/Dependencies/LoopKit/LoopKit/it.lproj/Localizable.strings @@ -23,16 +23,16 @@ "Connection Failure" = "Errore di connessione"; /* Generic pump error description */ -"Device Refused" = "il dispositivo e stato rifiutato"; +"Device Refused" = "Il dispositivo è stato rifiutato"; /* Recovery suggestion for a no data error */ -"Ensure carb data exists for the specified date" = "Assicurare che i dati carbo esistano per la data specificata"; +"Ensure carb data exists for the specified date" = "Assicurati che i dati dei carboidrati esistano per la data specificata"; /* Glucose trend down */ -"Falling" = "Abbassamento"; +"Falling" = "Cadendo"; /* Glucose trend down-down */ -"Falling fast" = "Abbassamento veloc"; +"Falling fast" = "In discesa veloce"; /* Glucose trend down-down-down */ "Falling very fast" = "Abbassamento molto veloce"; diff --git a/Dependencies/LoopKit/LoopKit/ru.lproj/Localizable.strings b/Dependencies/LoopKit/LoopKit/ru.lproj/Localizable.strings index 1216e1150..fc863cd0e 100644 --- a/Dependencies/LoopKit/LoopKit/ru.lproj/Localizable.strings +++ b/Dependencies/LoopKit/LoopKit/ru.lproj/Localizable.strings @@ -83,7 +83,7 @@ "U" = "ед"; /* The short unit display string for international units of insulin per hour */ -"U/hr" = "U/hr"; +"U/hr" = "Ед/ч"; /* The long unit display string for a singular international unit of insulin */ "Unit" = "Unit"; diff --git a/Dependencies/LoopKit/LoopKit/uk.lproj/Localizable.strings b/Dependencies/LoopKit/LoopKit/uk.lproj/Localizable.strings index 39dc4ee39..3d25d2542 100644 --- a/Dependencies/LoopKit/LoopKit/uk.lproj/Localizable.strings +++ b/Dependencies/LoopKit/LoopKit/uk.lproj/Localizable.strings @@ -41,7 +41,7 @@ "Flat" = "Flat"; /* The short unit display string for grams per U */ -"g/U" = "g/U"; +"g/U" = "гр/Од"; /* Generic pump error description */ "Invalid Configuration" = "Invalid Configuration"; @@ -53,7 +53,7 @@ "mg/dL/U" = "mg/dL/U"; /* The short unit display string for millimoles per liter */ -"mmol/L" = "mmol/L"; +"mmol/L" = "ммол/л"; /* The short unit display string for millimoles per liter per U */ "mmol/L/U" = "mmol/L/U"; @@ -83,7 +83,7 @@ "U" = "U"; /* The short unit display string for international units of insulin per hour */ -"U/hr" = "U/hr"; +"U/hr" = "Од/год"; /* The long unit display string for a singular international unit of insulin */ "Unit" = "Unit"; diff --git a/Dependencies/LoopKit/LoopKitUI/it.lproj/Localizable.strings b/Dependencies/LoopKit/LoopKitUI/it.lproj/Localizable.strings index 5c40bb4d0..d6b6e5b85 100644 --- a/Dependencies/LoopKit/LoopKitUI/it.lproj/Localizable.strings +++ b/Dependencies/LoopKit/LoopKitUI/it.lproj/Localizable.strings @@ -3,7 +3,7 @@ "%1$@ %2$@" = "%1$@ %2$@"; /* Accessibility format string for (1: localized volume)(2: time) */ -"%1$@ units remaining at %2$@" = "%1$@ units remaining at %2$@"; +"%1$@ units remaining at %2$@" = "%1$@ unità rimanenti alle ore %2$@"; /* The format for a glucose target range. (1: min target)(2: max target)(3: glucose unit) */ "%1$@ – %2$@ %3$@" = "%1$@ – %2$@ %3$@"; diff --git a/Dependencies/LoopKit/LoopKitUI/pt-PT.lproj/Localizable.strings b/Dependencies/LoopKit/LoopKitUI/pt-PT.lproj/Localizable.strings index bbc497d99..7b33ca9fc 100644 --- a/Dependencies/LoopKit/LoopKitUI/pt-PT.lproj/Localizable.strings +++ b/Dependencies/LoopKit/LoopKitUI/pt-PT.lproj/Localizable.strings @@ -45,7 +45,7 @@ "Basal, bolus, and correction insulin dose amounts are unaffected." = "Basal, bolus, and correction insulin dose amounts are unaffected."; /* The title of the cancel action in an action sheet */ -"Cancel" = "Cancel"; +"Cancel" = "Cancelar"; /* The text for the override cancellation button */ "Cancel Override" = "Cancel Override"; @@ -90,19 +90,19 @@ "Correction range is the blood glucose range that you would like Loop to correct to." = "Correction range is the blood glucose range that you would like Loop to correct to."; /* The text for a custom override */ -"Custom" = "Custom"; +"Custom" = "Definir"; /* The title for the custom override entry screen */ "Custom Override" = "Custom Override"; /* Title of the carb entry date picker cell */ -"Date" = "Date"; +"Date" = "Data"; /* Button title to delete all objects */ "Delete All" = "Delete All"; /* The text for the override duration setting */ -"Duration" = "Duration"; +"Duration" = "Duração"; /* The title for the override editing screen */ "Edit Override" = "Edit Override"; @@ -147,14 +147,14 @@ "More Info" = "More Info"; /* The text for the override preset name setting */ -"Name" = "Name"; +"Name" = "Nome"; /* The title for the new override preset entry screen */ "New Preset" = "New Preset"; /* Section title for no-carb food The title for override emoji miscellaneous section */ -"Other" = "Other"; +"Other" = "Outro"; /* The title text for the insulin sensitivity scaling setting */ "Overall Insulin Needs" = "Overall Insulin Needs"; @@ -188,7 +188,7 @@ /* Button text for saving glucose correction range schedule Button text for saving insulin sensitivity schedule */ -"Save" = "Save"; +"Save" = "Salvar"; /* The section header text for a scheduled override */ "SCHEDULED OVERRIDE" = "SCHEDULED OVERRIDE"; diff --git a/Dependencies/LoopKit/LoopKitUI/uk.lproj/Localizable.strings b/Dependencies/LoopKit/LoopKitUI/uk.lproj/Localizable.strings index bbc497d99..6405dec55 100644 --- a/Dependencies/LoopKit/LoopKitUI/uk.lproj/Localizable.strings +++ b/Dependencies/LoopKit/LoopKitUI/uk.lproj/Localizable.strings @@ -3,7 +3,7 @@ "%1$@ %2$@" = "%1$@ %2$@"; /* Accessibility format string for (1: localized volume)(2: time) */ -"%1$@ units remaining at %2$@" = "%1$@ units remaining at %2$@"; +"%1$@ units remaining at %2$@" = "%1$@ одиниць залишилося на %2$@"; /* The format for a glucose target range. (1: min target)(2: max target)(3: glucose unit) */ "%1$@ – %2$@ %3$@" = "%1$@ – %2$@ %3$@"; @@ -45,7 +45,7 @@ "Basal, bolus, and correction insulin dose amounts are unaffected." = "Basal, bolus, and correction insulin dose amounts are unaffected."; /* The title of the cancel action in an action sheet */ -"Cancel" = "Cancel"; +"Cancel" = "Відмінити"; /* The text for the override cancellation button */ "Cancel Override" = "Cancel Override"; @@ -84,25 +84,25 @@ "Condition" = "Condition"; /* Title of the setup button to continue */ -"Continue" = "Continue"; +"Continue" = "Продовжити"; /* The section footer of correction range schedule */ "Correction range is the blood glucose range that you would like Loop to correct to." = "Correction range is the blood glucose range that you would like Loop to correct to."; /* The text for a custom override */ -"Custom" = "Custom"; +"Custom" = "Своя"; /* The title for the custom override entry screen */ "Custom Override" = "Custom Override"; /* Title of the carb entry date picker cell */ -"Date" = "Date"; +"Date" = "Дата"; /* Button title to delete all objects */ "Delete All" = "Delete All"; /* The text for the override duration setting */ -"Duration" = "Duration"; +"Duration" = "Тривалість"; /* The title for the override editing screen */ "Edit Override" = "Edit Override"; @@ -147,14 +147,14 @@ "More Info" = "More Info"; /* The text for the override preset name setting */ -"Name" = "Name"; +"Name" = "Ім’я"; /* The title for the new override preset entry screen */ "New Preset" = "New Preset"; /* Section title for no-carb food The title for override emoji miscellaneous section */ -"Other" = "Other"; +"Other" = "Інше"; /* The title text for the insulin sensitivity scaling setting */ "Overall Insulin Needs" = "Overall Insulin Needs"; @@ -188,7 +188,7 @@ /* Button text for saving glucose correction range schedule Button text for saving insulin sensitivity schedule */ -"Save" = "Save"; +"Save" = "Зберегти"; /* The section header text for a scheduled override */ "SCHEDULED OVERRIDE" = "SCHEDULED OVERRIDE"; @@ -240,7 +240,7 @@ /* Accessibility value for an unknown value The default title to use when an entry has none */ -"Unknown" = "Unknown"; +"Unknown" = "Невідомий"; /* Label indicating validation is occurring */ "Verifying" = "Verifying"; diff --git a/Dependencies/rileylink_ios/OmniKitUI/pt-PT.lproj/Localizable.strings b/Dependencies/rileylink_ios/OmniKitUI/pt-PT.lproj/Localizable.strings index 5f8e4369c..a12af4d0c 100644 --- a/Dependencies/rileylink_ios/OmniKitUI/pt-PT.lproj/Localizable.strings +++ b/Dependencies/rileylink_ios/OmniKitUI/pt-PT.lproj/Localizable.strings @@ -60,7 +60,7 @@ "Bolus Delivery" = "Bolus Delivery"; /* The title of the cancel action in an action sheet */ -"Cancel" = "Cancel"; +"Cancel" = "Cancelar"; /* The title of the command to change pump time zone */ "Change Time Zone" = "Change Time Zone"; @@ -69,7 +69,7 @@ "Changing time…" = "Changing time…"; /* The title of the configuration section in settings */ -"Configuration" = "Configuration"; +"Configuration" = "Ajustes"; /* The title of the Insulin Type */ "Insulin Type" = "Insulin Type"; @@ -213,10 +213,10 @@ /* Title of button to save delivery limit settings Title of button to sync basal profile when no pod paired */ -"Save" = "Save"; +"Save" = "Salvar"; /* The detail text of the basal row when pod is running scheduled basal */ -"Schedule" = "Schedule"; +"Schedule" = "Agenda"; /* The title of the status section in settings */ "Status" = "Status"; diff --git a/Dependencies/rileylink_ios/RileyLinkKitUI/it.lproj/Localizable.strings b/Dependencies/rileylink_ios/RileyLinkKitUI/it.lproj/Localizable.strings index c269b15ac..185b09e1b 100644 --- a/Dependencies/rileylink_ios/RileyLinkKitUI/it.lproj/Localizable.strings +++ b/Dependencies/rileylink_ios/RileyLinkKitUI/it.lproj/Localizable.strings @@ -53,22 +53,22 @@ "Alert" = "Sveglia"; /* The title of the cell showing Low Battery Alert */ -"Low Battery Alert" = "Low Battery Alert"; +"Low Battery Alert" = "Avviso Batteria Bassa"; /* Header of list showing battery level alert options */ -"Battery level Alert" = "Battery level Alert"; +"Battery level Alert" = "Avviso livello batteria"; /* Battery level alert OFF in list of options */ -"OFF" = "OFF"; +"OFF" = "SPENTO"; /* The title of the command to update diagnostic LEDs */ -"Diagnostic LEDs" = "Diagnostic LEDs"; +"Diagnostic LEDs" = "LED Diagnostici"; /* The title of the command to fetch RileyLink statistics */ -"Get RileyLink Statistics" = "Get RileyLink Statistics"; +"Get RileyLink Statistics" = "Ottieni Statistiche Di RileyLink"; /* The title of the command to invert BLE connection LED logic */ -"Invert LED Logic" = "Invert LED Logic"; +"Invert LED Logic" = "Inverti Logica LED"; /* The header of the cells showing test commands */ "Test Commands" = "Test Commands"; diff --git a/Dependencies/rileylink_ios/RileyLinkKitUI/pt-PT.lproj/Localizable.strings b/Dependencies/rileylink_ios/RileyLinkKitUI/pt-PT.lproj/Localizable.strings index 7cb0c55b3..2a18a80e0 100644 --- a/Dependencies/rileylink_ios/RileyLinkKitUI/pt-PT.lproj/Localizable.strings +++ b/Dependencies/rileylink_ios/RileyLinkKitUI/pt-PT.lproj/Localizable.strings @@ -14,7 +14,7 @@ "Device" = "Device"; /* The title of the devices table section in RileyLink settings */ -"Devices" = "Devices"; +"Devices" = "Dispositivos"; /* The disconnected state */ "Disconnected" = "Disconnected"; @@ -29,7 +29,7 @@ "Frequency" = "Frequency"; /* The title of the cell showing device name */ -"Name" = "Name"; +"Name" = "Nome"; /* RileyLink setup description */ "RileyLink allows for communication with the pump over Bluetooth Low Energy." = "RileyLink allows for communication with the pump over Bluetooth Low Energy."; diff --git a/Dependencies/rileylink_ios/RileyLinkKitUI/sk.lproj/Localizable.strings b/Dependencies/rileylink_ios/RileyLinkKitUI/sk.lproj/Localizable.strings index dcfff92b1..1158b6b87 100644 --- a/Dependencies/rileylink_ios/RileyLinkKitUI/sk.lproj/Localizable.strings +++ b/Dependencies/rileylink_ios/RileyLinkKitUI/sk.lproj/Localizable.strings @@ -98,7 +98,7 @@ "On" = "Zapnuté"; /* Text indicating LED Mode is off */ -"Off" = "Off"; +"Off" = "Vypnuté"; /* Text indicating LED Mode is auto */ "Auto" = "Auto"; diff --git a/FreeAPS/Sources/Localizations/Main/da.lproj/Localizable.strings b/FreeAPS/Sources/Localizations/Main/da.lproj/Localizable.strings index a1a690af0..dd360f30d 100644 --- a/FreeAPS/Sources/Localizations/Main/da.lproj/Localizable.strings +++ b/FreeAPS/Sources/Localizations/Main/da.lproj/Localizable.strings @@ -513,7 +513,7 @@ Enact a temp Basal or a temp target */ "Calibrationinfo" = "Calibrationinfo"; /* */ -"Unknown" = "Unknown"; +"Unknown" = "Ukendt"; /* */ "Not paired yet" = "Not paired yet"; @@ -660,7 +660,7 @@ Enact a temp Basal or a temp target */ "Firmware" = "Firmware"; /* */ -"Connection State" = "Connection State"; +"Connection State" = "Tilslutningstilstand"; /* */ "Transmitter Type" = "Transmitter Type"; @@ -693,7 +693,7 @@ Enact a temp Basal or a temp target */ "Advanced" = "Advanced"; /* */ -"Alarms" = "Alarms"; +"Alarms" = "Alarmer"; /* */ "Glucose Settings" = "Glucose Settings"; diff --git a/FreeAPS/Sources/Localizations/Main/de.lproj/Localizable.strings b/FreeAPS/Sources/Localizations/Main/de.lproj/Localizable.strings index fc571c751..e3ac3adb8 100644 --- a/FreeAPS/Sources/Localizations/Main/de.lproj/Localizable.strings +++ b/FreeAPS/Sources/Localizations/Main/de.lproj/Localizable.strings @@ -1056,7 +1056,7 @@ Enact a temp Basal or a temp target */ "Minimum duration in minutes between two enacted SMBs" = "Minimaler Abstand (in Minuten), der zwischen zwei SMBs liegen muss"; /* "Bolus Increment" */ -"Smallest SMB / SMB increment in oref0. Minimum amount for Medtronic pumps is 0.1 U, whereas for Omnipod it’s 0.05 U. The default value is 0.1." = "Kleinstmögliche SMB-Menge in oref0. Für Medtronic ist der kleinstmögliche Betrag 0,1 U. Für Omnipod ist der kleinstmögliche Betrag 0,5 U."; +"Smallest SMB / SMB increment in oref0. Minimum amount for Medtronic pumps is 0.1 U, whereas for Omnipod it’s 0.05 U. The default value is 0.1." = "Kleinstmögliche SMB-Menge in oref0. Für Medtronic ist der kleinstmögliche Betrag 0,1 U, für Omnipod 0,5 U. Der Standardwert ist 0,1."; /* "Insulin Peak Time" */ "Time of maximum blood glucose lowering effect of insulin, in minutes. Beware: Oref assumes for ultra-rapid (Lyumjev) & rapid-acting (Fiasp) curves minimal (35 & 50 min) and maximal (100 & 120 min) applicable insulinPeakTimes. Using a custom insulinPeakTime outside these bounds will result in issues with FreeAPS-X, longer loop calculations and possible red loops." = "Benutzerspezifische Zeit des maximalen Wirkungshochs des Insulins. Achtung: Oref wendet für die ultra-rapid (Lyumjev) & rapid-acting (Fiasp) Insulinwirkungs-Kurven Unter- (35 & 50 min) und Obergrenzen (100 & 120 min) an. Falls das benutzerdefinierte Wirkungshoch außerhalb dieser Grenzen liegt, kann es zu Komplikationen mit FreeAPS-X kommen, langen Loop-Berechnungszeiten und \"red loops\"."; diff --git a/FreeAPS/Sources/Localizations/Main/es.lproj/Localizable.strings b/FreeAPS/Sources/Localizations/Main/es.lproj/Localizable.strings index f9d124a23..4e4a733e7 100644 --- a/FreeAPS/Sources/Localizations/Main/es.lproj/Localizable.strings +++ b/FreeAPS/Sources/Localizations/Main/es.lproj/Localizable.strings @@ -68,7 +68,7 @@ "Add Carbs" = "Añadir Carbohidratos"; /* Add carbs header and button in Watch app. You can skip the last " " space. It's just for differentiation */ -"Add Carbs " = "Add Carbs "; +"Add Carbs " = "Agregar Carbohidratos "; /* */ "Amount Carbs" = "Cantidad de Carbohidratos"; @@ -417,10 +417,10 @@ Enact a temp Basal or a temp target */ "Temp Targets" = "Temp Targets"; /* Delete carbs from Treatments list*/ -"Delete carbs?" = "Delete carbs?"; +"Delete carbs?" = "¿Eliminar carbohidratos?"; /* Treatments list */ -"Treatments" = "Treatments"; +"Treatments" = "Tratamientos"; /* " min" in Treatments list */ " min" = " min"; @@ -429,19 +429,19 @@ Enact a temp Basal or a temp target */ /* Calendar and Libre transmitter settings --------------- */ /* */ -"Configure Libre Transmitter" = "Configure Libre Transmitter"; +"Configure Libre Transmitter" = "Configurar Transmisor Libre"; /* */ -"Calibrations" = "Calibrations"; +"Calibrations" = "Calibraciones"; /* */ -"Create events in calendar" = "Create events in calendar"; +"Create events in calendar" = "Crear eventos en el calendario"; /* */ -"Calendar" = "Calendar"; +"Calendar" = "Calendario"; /* */ -"Other" = "Other"; +"Other" = "Otro"; /* */ "Libre Transmitter" = "Libre Transmitter"; @@ -450,16 +450,16 @@ Enact a temp Basal or a temp target */ "Libre Transmitters" = "Libre Transmitters"; /* */ -"Bluetooth Transmitters" = "Bluetooth Transmitters"; +"Bluetooth Transmitters" = "Transmisores Bluetooth"; /* */ -"Modes" = "Modes"; +"Modes" = "Modos"; /* Libre 2 Direct */ -"Libre 2 Direct" = "Libre 2 Direct"; +"Libre 2 Direct" = "Libre 2 Directo"; /* */ -"Select the third party transmitter you want to connect to" = "Select the third party transmitter you want to connect to"; +"Select the third party transmitter you want to connect to" = "Seleccione el transmisor de terceros al que desea conectarse"; /* State was restored */ "State was restored" = "State was restored"; @@ -471,7 +471,7 @@ Enact a temp Basal or a temp target */ "mg/dL" = "mg/dL"; /* */ -"Add calibration" = "Add calibration"; +"Add calibration" = "Añadir calibración"; /* When adding capillary glucose meater reading */ "Meter glucose" = "Meter glucose"; @@ -486,16 +486,16 @@ Enact a temp Basal or a temp target */ "Intercept" = "Intercept"; /* */ -"Chart" = "Chart"; +"Chart" = "Gráfica"; /* */ -"Remove" = "Remove"; +"Remove" = "Eliminar"; /* */ -"Remove Last" = "Remove Last"; +"Remove Last" = "Eliminar último"; /* */ -"Remove All" = "Remove All"; +"Remove All" = "Eliminar todos"; /* */ "About the Process" = "About the Process"; @@ -513,10 +513,10 @@ Enact a temp Basal or a temp target */ "Calibrationinfo" = "Calibrationinfo"; /* */ -"Unknown" = "Unknown"; +"Unknown" = "Desconocido"; /* */ -"Not paired yet" = "Not paired yet"; +"Not paired yet" = "No emparejado aún"; /* */ "Pair Sensor & connect" = "Pair Sensor & connect"; @@ -615,13 +615,13 @@ Enact a temp Basal or a temp target */ "Current Sensor is Ending soon! Sensor Life left in %@" = "Current Sensor is Ending soon! Sensor Life left in %@"; /* */ -"Libre Bluetooth" = "Libre Bluetooth"; +"Libre Bluetooth" = "Bluetooth Libre"; /* */ "Snooze Alerts" = "Snooze Alerts"; /* */ -"Last measurement" = "Last measurement"; +"Last measurement" = "Última medición"; /* */ "Sensor Footer checksum" = "Sensor Footer checksum"; @@ -660,7 +660,7 @@ Enact a temp Basal or a temp target */ "Firmware" = "Firmware"; /* */ -"Connection State" = "Connection State"; +"Connection State" = "Estado de Conexión"; /* */ "Transmitter Type" = "Transmitter Type"; @@ -693,7 +693,7 @@ Enact a temp Basal or a temp target */ "Advanced" = "Advanced"; /* */ -"Alarms" = "Alarms"; +"Alarms" = "Alarmas"; /* */ "Glucose Settings" = "Glucose Settings"; diff --git a/FreeAPS/Sources/Localizations/Main/fi.lproj/Localizable.strings b/FreeAPS/Sources/Localizations/Main/fi.lproj/Localizable.strings index 14b454ffa..06e2da797 100644 --- a/FreeAPS/Sources/Localizations/Main/fi.lproj/Localizable.strings +++ b/FreeAPS/Sources/Localizations/Main/fi.lproj/Localizable.strings @@ -134,7 +134,7 @@ "for" = "for"; /* Temp target set for ... minutes */ -"min" = "min"; +"min" = "min."; /* */ "Autotune" = "Autotune"; @@ -513,7 +513,7 @@ Enact a temp Basal or a temp target */ "Calibrationinfo" = "Calibrationinfo"; /* */ -"Unknown" = "Unknown"; +"Unknown" = "Tuntematon"; /* */ "Not paired yet" = "Not paired yet"; @@ -657,10 +657,10 @@ Enact a temp Basal or a temp target */ "Hardware" = "Hardware"; /* */ -"Firmware" = "Firmware"; +"Firmware" = "Laiteohjelmisto"; /* */ -"Connection State" = "Connection State"; +"Connection State" = "Yhteyden tila"; /* */ "Transmitter Type" = "Transmitter Type"; @@ -693,7 +693,7 @@ Enact a temp Basal or a temp target */ "Advanced" = "Advanced"; /* */ -"Alarms" = "Alarms"; +"Alarms" = "Hälytykset"; /* */ "Glucose Settings" = "Glucose Settings"; diff --git a/FreeAPS/Sources/Localizations/Main/it.lproj/Localizable.strings b/FreeAPS/Sources/Localizations/Main/it.lproj/Localizable.strings index 62a55bf68..a0e6565bc 100644 --- a/FreeAPS/Sources/Localizations/Main/it.lproj/Localizable.strings +++ b/FreeAPS/Sources/Localizations/Main/it.lproj/Localizable.strings @@ -131,10 +131,10 @@ "Top target" = "Target alto"; /* Temp target set for ... minutes */ -"for" = "for"; +"for" = "per"; /* Temp target set for ... minutes */ -"min" = "min"; +"min" = "minuti"; /* */ "Autotune" = "Autotune"; @@ -417,13 +417,13 @@ Enact a temp Basal or a temp target */ "Temp Targets" = "Obiettivi Temporanei"; /* Delete carbs from Treatments list*/ -"Delete carbs?" = "Delete carbs?"; +"Delete carbs?" = "Elimina carboidrati?"; /* Treatments list */ -"Treatments" = "Treatments"; +"Treatments" = "Trattamenti"; /* " min" in Treatments list */ -" min" = " min"; +" min" = " minuti"; /* Calendar and Libre transmitter settings --------------- @@ -624,7 +624,7 @@ Enact a temp Basal or a temp target */ "Last measurement" = "Ultima misurazione"; /* */ -"Sensor Footer checksum" = "Sensor Footer checksum"; +"Sensor Footer checksum" = "Somma di controllo del piè di pagina del sensore"; /* */ "Last Blood Sugar prediction" = "Ultima previsione di Zucchero nel Sangue"; @@ -822,82 +822,82 @@ Enact a temp Basal or a temp target */ "Found devices: %d" = "Dispositivi trovati: %d"; /* */ -"Backfill options" = "Backfill options"; +"Backfill options" = "Opzioni di riempimento"; /* */ -"Backfilling from trend is currently not well supported by Loop" = "Backfilling from trend is currently not well supported by Loop"; +"Backfilling from trend is currently not well supported by Loop" = "Il riempimento dalla tendenza non è attualmente ben supportato da Loop"; /* */ -"Backfill from history" = "Backfill from history"; +"Backfill from history" = "Riempi dalla cronologia"; /* */ -"Backfill from trend" = "Backfill from trend"; +"Backfill from trend" = "Riempimento dalla tendenza"; /* */ -"Debug options" = "Debug options"; +"Debug options" = "Opzioni di Debug"; /* */ -"Adds a lot of data to the Issue Report " = "Adds a lot of data to the Issue Report "; +"Adds a lot of data to the Issue Report " = "Aggiunge molti dati al Rapporto Problemi "; /* */ -"Persist sensordata" = "Persist sensordata"; +"Persist sensordata" = "Persistere i dati del sensore"; /* */ -"Battery" = "Battery"; +"Battery" = "Batteria"; /* */ -"Also add source info" = "Also add source info"; +"Also add source info" = "Aggiungi anche informazioni sorgente"; /* */ -"Carbs Required Threshold" = "Carbs Required Threshold"; +"Carbs Required Threshold" = "Soglia carboidrati necessari"; /* */ -"Carbs required: %d g" = "Carbs required: %d g"; +"Carbs required: %d g" = "Carboidrati necessari %d g"; /* */ -"To prevent LOW required %d g of carbs" = "To prevent LOW required %d g of carbs"; +"To prevent LOW required %d g of carbs" = "Per evitare glicemia BASSA servono %d g di carboidrati"; /* */ -"FreeAPS X not active" = "FreeAPS X not active"; +"FreeAPS X not active" = "FreeAPS X non attivo"; /* */ -"Last loop was more then %d min ago" = "Last loop was more then %d min ago"; +"Last loop was more then %d min ago" = "L'ultimo ciclo è stato più di %d min fa"; /* Glucose badge */ "Show glucose on the app badge" = "Mostra il glucosio sul badge dell'app"; /* */ -"Backfill glucose" = "Backfill glucose"; +"Backfill glucose" = "Riempi glicemia"; /* About this source */ -"About this source" = "About this source"; +"About this source" = "Informazioni su questa fonte"; /* */ -"Bolus failed" = "Bolus failed"; +"Bolus failed" = "Bolo fallito"; /* */ -"Bolus failed or inaccurate. Check pump history before repeating." = "Bolus failed or inaccurate. Check pump history before repeating."; +"Bolus failed or inaccurate. Check pump history before repeating." = "Bolo fallito o impreciso. Controlla la cronologia del microinfusore prima di ripetere."; /* */ -"Carbs" = "Carbs"; +"Carbs" = "Carboidrati"; /* */ -"Temp Basal" = "Temp Basal"; +"Temp Basal" = "Basale temporanea"; /* */ -"Temp Target" = "Temp Target"; +"Temp Target" = "Target Temporaneo"; /* */ -"Resume" = "Resume"; +"Resume" = "Riprendi"; /* */ -"Suspend" = "Suspend"; +"Suspend" = "Sospendi"; /* */ -"Animated Background" = "Animated Background"; +"Animated Background" = "Sfondo Animato"; /* Sensor day(s) */ -" day(s)" = " day(s)"; +" day(s)" = " giorno/i"; /* Headers for settings ----------------------- */ @@ -913,41 +913,41 @@ Enact a temp Basal or a temp target */ "Glucose Simulator" = "Glucose Simulator"; /* Restored state message */ -"Bluetooth State restored (APS restarted?). Found %d peripherals, and connected to %@ with identifier %@" = "Bluetooth State restored (APS restarted?). Found %d peripherals, and connected to %@ with identifier %@"; +"Bluetooth State restored (APS restarted?). Found %d peripherals, and connected to %@ with identifier %@" = "Stato Bluetooth ripristinato (APS riavviato?). Trovato %d periferiche e collegato a %@ con identificatore %@"; /* Shared app group */ -"Shared app group for direct connection with Libre 1 transmitters or European Libre 2 sensors" = "Shared app group for direct connection with Libre 1 transmitters or European Libre 2 sensors"; +"Shared app group for direct connection with Libre 1 transmitters or European Libre 2 sensors" = "Gruppo di app condiviso per il collegamento diretto con trasmettitori Libre 1 o sensori Europei Libre 2"; /* Native G6 app */ -"Native G6 app" = "Native G6 app"; +"Native G6 app" = "App nativa G6"; /* Native G5 app */ -"Native G5 app" = "Native G5 app"; +"Native G5 app" = "App nativa G5"; /* Minilink transmitter */ -"Minilink transmitter" = "Minilink transmitter"; +"Minilink transmitter" = "Trasmettitore Minilink"; /* Simple simulator */ -"Simple simulator" = "Simple simulator"; +"Simple simulator" = "Simulatore semplice"; /* Direct connection with Libre 1 transmitters or Libre 2 */ -"Direct connection with Libre 1 transmitters or European Libre 2 sensors" = "Direct connection with Libre 1 transmitters or European Libre 2 sensors"; +"Direct connection with Libre 1 transmitters or European Libre 2 sensors" = "Collegamento diretto con trasmettitori Libre 1 o sensori Europei Libre 2"; /* Online or internal server */ -"Online or internal server" = "Online or internal server"; +"Online or internal server" = "Server online o interno"; /* HealthKit intergration --------------------*/ /* */ "Apple Health" = "Apple Health"; /* */ -"Connect to Apple Health" = "Connect to Apple Health"; +"Connect to Apple Health" = "Connetti con Apple Health"; /* Show when have not permissions for writing to Health */ -"For write data to Apple Health you must give permissions in Settings > Health > Data Access" = "For write data to Apple Health you must give permissions in Settings > Health > Data Access"; +"For write data to Apple Health you must give permissions in Settings > Health > Data Access" = "Per scrivere dati su Apple Health devi dare il permesso in Impostazioni > Salute > Accesso dati"; /* */ -"After you create glucose records in the Health app, please open FreeAPS X to help us guaranteed transfer changed data" = "After you create glucose records in the Health app, please open FreeAPS X to help us guaranteed transfer changed data"; +"After you create glucose records in the Health app, please open FreeAPS X to help us guaranteed transfer changed data" = "Dopo aver creato record glicemici nell'app Salute, apri FreeAPS X per aiutarci a trasferire i dati modificati"; /* -------------------------------------------- @@ -1056,7 +1056,7 @@ Enact a temp Basal or a temp target */ "Minimum duration in minutes between two enacted SMBs" = "Durata minima in minuti tra due SMB sommimistrati"; /* "Bolus Increment" */ -"Smallest SMB / SMB increment in oref0. Minimum amount for Medtronic pumps is 0.1 U, whereas for Omnipod it’s 0.05 U. The default value is 0.1." = "Smallest SMB / SMB increment in oref0. Minimum amount for Medtronic pumps is 0.1 U, whereas for Omnipod it’s 0.05 U. The default value is 0.1."; +"Smallest SMB / SMB increment in oref0. Minimum amount for Medtronic pumps is 0.1 U, whereas for Omnipod it’s 0.05 U. The default value is 0.1." = "Incremento SMB / SMB più piccolo in oref0. La quantità minima per microinfusore Medtronic è 0.1 U, mentre per Omnipod è 0.05 U. Il valore predefinito è 0.1."; /* "Insulin Peak Time" */ "Time of maximum blood glucose lowering effect of insulin, in minutes. Beware: Oref assumes for ultra-rapid (Lyumjev) & rapid-acting (Fiasp) curves minimal (35 & 50 min) and maximal (100 & 120 min) applicable insulinPeakTimes. Using a custom insulinPeakTime outside these bounds will result in issues with FreeAPS-X, longer loop calculations and possible red loops." = "Tempo del massimo effetto dell'insulina di riduzione del glucosio nel sangue, in minuti. Attenzione: Oref presume le curve ultra rapide (Lyumjev) e ad azione rapida (Fiasp) minime (35 e 50 min) e massime (100 e 120 min) degli insulinPeakTimes applicabili. Usare un insulinPeakTime personalizzato al di fuori di questi limiti risulterà in problemi con FreeAPS-X, calcoli ciclici più lunghi e possibili cicli rossi."; diff --git a/FreeAPS/Sources/Localizations/Main/pl.lproj/Localizable.strings b/FreeAPS/Sources/Localizations/Main/pl.lproj/Localizable.strings index 6ad944f85..a523784f4 100644 --- a/FreeAPS/Sources/Localizations/Main/pl.lproj/Localizable.strings +++ b/FreeAPS/Sources/Localizations/Main/pl.lproj/Localizable.strings @@ -134,7 +134,7 @@ "for" = "for"; /* Temp target set for ... minutes */ -"min" = "min"; +"min" = "min."; /* */ "Autotune" = "Autotune"; @@ -515,7 +515,7 @@ Połączono z Nightscout!"; "Calibrationinfo" = "Calibrationinfo"; /* */ -"Unknown" = "Unknown"; +"Unknown" = "Nieznana"; /* */ "Not paired yet" = "Not paired yet"; @@ -659,10 +659,10 @@ Połączono z Nightscout!"; "Hardware" = "Hardware"; /* */ -"Firmware" = "Firmware"; +"Firmware" = "Oprogramowanie"; /* */ -"Connection State" = "Connection State"; +"Connection State" = "Status połączenia"; /* */ "Transmitter Type" = "Transmitter Type"; @@ -695,7 +695,7 @@ Połączono z Nightscout!"; "Advanced" = "Advanced"; /* */ -"Alarms" = "Alarms"; +"Alarms" = "Alarmy"; /* */ "Glucose Settings" = "Glucose Settings"; diff --git a/FreeAPS/Sources/Localizations/Main/pt-BR.lproj/Localizable.strings b/FreeAPS/Sources/Localizations/Main/pt-BR.lproj/Localizable.strings index f6f533615..df4f624d1 100644 --- a/FreeAPS/Sources/Localizations/Main/pt-BR.lproj/Localizable.strings +++ b/FreeAPS/Sources/Localizations/Main/pt-BR.lproj/Localizable.strings @@ -660,7 +660,7 @@ Enact a temp Basal or a temp target */ "Firmware" = "Firmware"; /* */ -"Connection State" = "Connection State"; +"Connection State" = "Estado da Conexão"; /* */ "Transmitter Type" = "Transmitter Type"; @@ -693,7 +693,7 @@ Enact a temp Basal or a temp target */ "Advanced" = "Advanced"; /* */ -"Alarms" = "Alarms"; +"Alarms" = "Alarmes"; /* */ "Glucose Settings" = "Glucose Settings"; diff --git a/FreeAPS/Sources/Localizations/Main/pt-PT.lproj/Localizable.strings b/FreeAPS/Sources/Localizations/Main/pt-PT.lproj/Localizable.strings index 7f78287be..55ef61ace 100644 --- a/FreeAPS/Sources/Localizations/Main/pt-PT.lproj/Localizable.strings +++ b/FreeAPS/Sources/Localizations/Main/pt-PT.lproj/Localizable.strings @@ -441,7 +441,7 @@ Enact a temp Basal or a temp target */ "Calendar" = "Calendar"; /* */ -"Other" = "Other"; +"Other" = "Outro"; /* */ "Libre Transmitter" = "Libre Transmitter"; diff --git a/FreeAPS/Sources/Localizations/Main/ru.lproj/Localizable.strings b/FreeAPS/Sources/Localizations/Main/ru.lproj/Localizable.strings index 1980d3f47..23a2111bb 100644 --- a/FreeAPS/Sources/Localizations/Main/ru.lproj/Localizable.strings +++ b/FreeAPS/Sources/Localizations/Main/ru.lproj/Localizable.strings @@ -909,7 +909,7 @@ Enact a temp Basal or a temp target */ "Animated Background" = "Анимированный фон"; /* Sensor day(s) */ -" day(s)" = " day(s)"; +" day(s)" = "дн."; /* Headers for settings ----------------------- */ @@ -928,7 +928,7 @@ Enact a temp Basal or a temp target */ "Bluetooth State restored (APS restarted?). Found %d peripherals, and connected to %@ with identifier %@" = "Bluetooth состояние восстановлено (APS перезапущен?). Найдено %d периферийных устройств и подключено к %@ с идентификатором %@"; /* Shared app group */ -"Shared app group for direct connection with Libre 1 transmitters or European Libre 2 sensors" = "Shared app group for direct connection with Libre 1 transmitters or European Libre 2 sensors"; +"Shared app group for direct connection with Libre 1 transmitters or European Libre 2 sensors" = "Общая группа приложений для прямой связи с трансмиттерами Libre 1 или Европейскими датчиками Libre 2"; /* Native G6 app */ "Native G6 app" = "Родное приложение G6"; @@ -943,7 +943,7 @@ Enact a temp Basal or a temp target */ "Simple simulator" = "Простой симулятор"; /* Direct connection with Libre 1 transmitters or Libre 2 */ -"Direct connection with Libre 1 transmitters or European Libre 2 sensors" = "Direct connection with Libre 1 transmitters or European Libre 2 sensors"; +"Direct connection with Libre 1 transmitters or European Libre 2 sensors" = "Прямое подключение к трансмиттерам Libre 1 или датчикам European Libre 2"; /* Online or internal server */ "Online or internal server" = "Онлайн или внутренний сервер"; @@ -1068,7 +1068,7 @@ Enact a temp Basal or a temp target */ "Minimum duration in minutes between two enacted SMBs" = "Минимальная продолжительность в минутах между подачей болюсов"; /* "Bolus Increment" */ -"Smallest SMB / SMB increment in oref0. Minimum amount for Medtronic pumps is 0.1 U, whereas for Omnipod it’s 0.05 U. The default value is 0.1." = "Smallest SMB / SMB increment in oref0. Minimum amount for Medtronic pumps is 0.1 U, whereas for Omnipod it’s 0.05 U. The default value is 0.1."; +"Smallest SMB / SMB increment in oref0. Minimum amount for Medtronic pumps is 0.1 U, whereas for Omnipod it’s 0.05 U. The default value is 0.1." = "Самый маленький SMB / прирост SMB в oref0. Минимум для помп Медтроник равен 0.1 Ед., в то время как для Omnipod это 0.05 Ед. По умолчанию 0.1."; /* "Insulin Peak Time" */ "Time of maximum blood glucose lowering effect of insulin, in minutes. Beware: Oref assumes for ultra-rapid (Lyumjev) & rapid-acting (Fiasp) curves minimal (35 & 50 min) and maximal (100 & 120 min) applicable insulinPeakTimes. Using a custom insulinPeakTime outside these bounds will result in issues with FreeAPS-X, longer loop calculations and possible red loops." = "Пиковое время инсулина в минутах. Время необходимое инсулину для достижения максимального воздействия"; diff --git a/FreeAPS/Sources/Localizations/Main/sk.lproj/Localizable.strings b/FreeAPS/Sources/Localizations/Main/sk.lproj/Localizable.strings index d965bbf27..e2a77af3c 100644 --- a/FreeAPS/Sources/Localizations/Main/sk.lproj/Localizable.strings +++ b/FreeAPS/Sources/Localizations/Main/sk.lproj/Localizable.strings @@ -660,7 +660,7 @@ Enact a temp Basal or a temp target */ "Firmware" = "Firmware"; /* */ -"Connection State" = "Connection State"; +"Connection State" = "Stav pripojenia"; /* */ "Transmitter Type" = "Transmitter Type"; @@ -693,7 +693,7 @@ Enact a temp Basal or a temp target */ "Advanced" = "Advanced"; /* */ -"Alarms" = "Alarms"; +"Alarms" = "Alarmy"; /* */ "Glucose Settings" = "Glucose Settings"; diff --git a/FreeAPS/Sources/Localizations/Main/uk.lproj/Localizable.strings b/FreeAPS/Sources/Localizations/Main/uk.lproj/Localizable.strings index 821ffc4a4..4f4124aa0 100644 --- a/FreeAPS/Sources/Localizations/Main/uk.lproj/Localizable.strings +++ b/FreeAPS/Sources/Localizations/Main/uk.lproj/Localizable.strings @@ -657,10 +657,10 @@ Enact a temp Basal or a temp target */ "Hardware" = "Hardware"; /* */ -"Firmware" = "Firmware"; +"Firmware" = "Прошивка"; /* */ -"Connection State" = "Connection State"; +"Connection State" = "Стан з'єднання"; /* */ "Transmitter Type" = "Transmitter Type"; @@ -693,7 +693,7 @@ Enact a temp Basal or a temp target */ "Advanced" = "Advanced"; /* */ -"Alarms" = "Alarms"; +"Alarms" = "Тривога"; /* */ "Glucose Settings" = "Glucose Settings"; diff --git a/FreeAPS/Sources/Localizations/Main/zh-Hans.lproj/Localizable.strings b/FreeAPS/Sources/Localizations/Main/zh-Hans.lproj/Localizable.strings index 87455d470..e39aed8d1 100644 --- a/FreeAPS/Sources/Localizations/Main/zh-Hans.lproj/Localizable.strings +++ b/FreeAPS/Sources/Localizations/Main/zh-Hans.lproj/Localizable.strings @@ -888,16 +888,16 @@ Enact a temp Basal or a temp target */ "Temp Target" = "临时目标"; /* */ -"Resume" = "Resume"; +"Resume" = "恢复"; /* */ "Suspend" = "暂停"; /* */ -"Animated Background" = "Animated Background"; +"Animated Background" = "动态背景"; /* Sensor day(s) */ -" day(s)" = " day(s)"; +" day(s)" = "天"; /* Headers for settings ----------------------- */ @@ -916,7 +916,7 @@ Enact a temp Basal or a temp target */ "Bluetooth State restored (APS restarted?). Found %d peripherals, and connected to %@ with identifier %@" = "蓝牙状态已恢复(APS 重启?) 找到 %d 个蓝牙,并连接到 %@ 标识符为 %@ 的设备"; /* Shared app group */ -"Shared app group for direct connection with Libre 1 transmitters or European Libre 2 sensors" = "Shared app group for direct connection with Libre 1 transmitters or European Libre 2 sensors"; +"Shared app group for direct connection with Libre 1 transmitters or European Libre 2 sensors" = "将连接瞬感1+发射器及直连瞬感 2 的第三方应用与 FreeAPS X 通过 AppGroup 方式共享数据"; /* Native G6 app */ "Native G6 app" = "官方G6 App"; @@ -931,7 +931,7 @@ Enact a temp Basal or a temp target */ "Simple simulator" = "简单模拟器"; /* Direct connection with Libre 1 transmitters or Libre 2 */ -"Direct connection with Libre 1 transmitters or European Libre 2 sensors" = "Direct connection with Libre 1 transmitters or European Libre 2 sensors"; +"Direct connection with Libre 1 transmitters or European Libre 2 sensors" = "直连Libre2或者Libre1发射器组合"; /* Online or internal server */ "Online or internal server" = "在线或内部服务器"; @@ -1058,7 +1058,7 @@ Enact a temp Basal or a temp target */ "Minimum duration in minutes between two enacted SMBs" = "两次自动大剂量的最短时间"; /* "Bolus Increment" */ -"Smallest SMB / SMB increment in oref0. Minimum amount for Medtronic pumps is 0.1 U, whereas for Omnipod it’s 0.05 U. The default value is 0.1." = "Smallest SMB / SMB increment in oref0. Minimum amount for Medtronic pumps is 0.1 U, whereas for Omnipod it’s 0.05 U. The default value is 0.1."; +"Smallest SMB / SMB increment in oref0. Minimum amount for Medtronic pumps is 0.1 U, whereas for Omnipod it’s 0.05 U. The default value is 0.1." = " oref0中SMB最小增量,美敦力为 0.1U, Omnipod 为 0.05U,默认值为 0.1"; /* "Insulin Peak Time" */ "Time of maximum blood glucose lowering effect of insulin, in minutes. Beware: Oref assumes for ultra-rapid (Lyumjev) & rapid-acting (Fiasp) curves minimal (35 & 50 min) and maximal (100 & 120 min) applicable insulinPeakTimes. Using a custom insulinPeakTime outside these bounds will result in issues with FreeAPS-X, longer loop calculations and possible red loops." = "胰岛素的最大降血糖作用,以分钟为单位"; From 94b441b512d8c78ddbf77c7b22f60b7389455a00 Mon Sep 17 00:00:00 2001 From: Vasiliy Usov Date: Sat, 11 Jun 2022 12:27:50 +0300 Subject: [PATCH 18/28] Fix bug with cycle error in new build system (Xcode 13.3) (#190) --- .../CGMBLEKit.xcodeproj/project.pbxproj | 2 +- .../LoopKit/LoopKit.xcodeproj/project.pbxproj | 6 +++--- .../RileyLink.xcodeproj/project.pbxproj | 18 +++++++++--------- 3 files changed, 13 insertions(+), 13 deletions(-) diff --git a/Dependencies/CGMBLEKit/CGMBLEKit.xcodeproj/project.pbxproj b/Dependencies/CGMBLEKit/CGMBLEKit.xcodeproj/project.pbxproj index b993f6cd1..472809574 100644 --- a/Dependencies/CGMBLEKit/CGMBLEKit.xcodeproj/project.pbxproj +++ b/Dependencies/CGMBLEKit/CGMBLEKit.xcodeproj/project.pbxproj @@ -274,9 +274,9 @@ isa = PBXNativeTarget; buildConfigurationList = 43CABE071C3506F100005705 /* Build configuration list for PBXNativeTarget "CGMBLEKit" */; buildPhases = ( + 43CABDF01C3506F100005705 /* Headers */, 43CABDEE1C3506F100005705 /* Sources */, 43CABDEF1C3506F100005705 /* Frameworks */, - 43CABDF01C3506F100005705 /* Headers */, 43CABDF11C3506F100005705 /* Resources */, ); buildRules = ( diff --git a/Dependencies/LoopKit/LoopKit.xcodeproj/project.pbxproj b/Dependencies/LoopKit/LoopKit.xcodeproj/project.pbxproj index 1aea860d0..0f6b4db7a 100644 --- a/Dependencies/LoopKit/LoopKit.xcodeproj/project.pbxproj +++ b/Dependencies/LoopKit/LoopKit.xcodeproj/project.pbxproj @@ -2856,9 +2856,9 @@ isa = PBXNativeTarget; buildConfigurationList = 43BA715F201E484D0058961E /* Build configuration list for PBXNativeTarget "LoopKitUI" */; buildPhases = ( + 43BA7151201E484D0058961E /* Headers */, 43BA714F201E484D0058961E /* Sources */, 43BA7150201E484D0058961E /* Frameworks */, - 43BA7151201E484D0058961E /* Headers */, 43BA7152201E484D0058961E /* Resources */, ); buildRules = ( @@ -2875,9 +2875,9 @@ isa = PBXNativeTarget; buildConfigurationList = 43D8FDDF1C728FDF0073BE78 /* Build configuration list for PBXNativeTarget "LoopKit" */; buildPhases = ( + 43D8FDC81C728FDF0073BE78 /* Headers */, 43D8FDC61C728FDF0073BE78 /* Sources */, 43D8FDC71C728FDF0073BE78 /* Frameworks */, - 43D8FDC81C728FDF0073BE78 /* Headers */, 43D8FDC91C728FDF0073BE78 /* Resources */, ); buildRules = ( @@ -2970,9 +2970,9 @@ isa = PBXNativeTarget; buildConfigurationList = A9E675ED22713F4700E25293 /* Build configuration list for PBXNativeTarget "LoopKit-watchOS" */; buildPhases = ( + A9E675E822713F4700E25293 /* Headers */, A9E6758122713F4700E25293 /* Sources */, A9E675E522713F4700E25293 /* Frameworks */, - A9E675E822713F4700E25293 /* Headers */, A9E675EA22713F4700E25293 /* Resources */, ); buildRules = ( diff --git a/Dependencies/rileylink_ios/RileyLink.xcodeproj/project.pbxproj b/Dependencies/rileylink_ios/RileyLink.xcodeproj/project.pbxproj index 121380f1c..10fd2c7cc 100644 --- a/Dependencies/rileylink_ios/RileyLink.xcodeproj/project.pbxproj +++ b/Dependencies/rileylink_ios/RileyLink.xcodeproj/project.pbxproj @@ -2767,9 +2767,9 @@ isa = PBXNativeTarget; buildConfigurationList = 431CE78A1F98564200255374 /* Build configuration list for PBXNativeTarget "RileyLinkBLEKit" */; buildPhases = ( + 431CE76C1F98564100255374 /* Headers */, 431CE76A1F98564100255374 /* Sources */, 431CE76B1F98564100255374 /* Frameworks */, - 431CE76C1F98564100255374 /* Headers */, 431CE76D1F98564100255374 /* Resources */, ); buildRules = ( @@ -2804,9 +2804,9 @@ isa = PBXNativeTarget; buildConfigurationList = 4352A72E20DEC9B700CAC200 /* Build configuration list for PBXNativeTarget "MinimedKitUI" */; buildPhases = ( + 4352A72220DEC9B700CAC200 /* Headers */, 4352A72020DEC9B700CAC200 /* Sources */, 4352A72120DEC9B700CAC200 /* Frameworks */, - 4352A72220DEC9B700CAC200 /* Headers */, 4352A72320DEC9B700CAC200 /* Resources */, ); buildRules = ( @@ -2824,9 +2824,9 @@ isa = PBXNativeTarget; buildConfigurationList = 43722FC91CB9F7640038B7F2 /* Build configuration list for PBXNativeTarget "RileyLinkKit" */; buildPhases = ( + 43722FAB1CB9F7630038B7F2 /* Headers */, 43722FA91CB9F7630038B7F2 /* Sources */, 43722FAA1CB9F7630038B7F2 /* Frameworks */, - 43722FAB1CB9F7630038B7F2 /* Headers */, 43722FAC1CB9F7630038B7F2 /* Resources */, ); buildRules = ( @@ -2843,9 +2843,9 @@ isa = PBXNativeTarget; buildConfigurationList = 43C2469E1D8918AE0031F8D1 /* Build configuration list for PBXNativeTarget "Crypto" */; buildPhases = ( + 43C246901D8918AE0031F8D1 /* Headers */, 43C2468E1D8918AE0031F8D1 /* Sources */, 43C2468F1D8918AE0031F8D1 /* Frameworks */, - 43C246901D8918AE0031F8D1 /* Headers */, 43C246911D8918AE0031F8D1 /* Resources */, ); buildRules = ( @@ -2861,9 +2861,9 @@ isa = PBXNativeTarget; buildConfigurationList = 43D5E7971FAF7BFB004ACDB7 /* Build configuration list for PBXNativeTarget "RileyLinkKitUI" */; buildPhases = ( + 43D5E78B1FAF7BFB004ACDB7 /* Headers */, 43D5E7891FAF7BFB004ACDB7 /* Sources */, 43D5E78A1FAF7BFB004ACDB7 /* Frameworks */, - 43D5E78B1FAF7BFB004ACDB7 /* Headers */, 43D5E78C1FAF7BFB004ACDB7 /* Resources */, ); buildRules = ( @@ -2880,9 +2880,9 @@ isa = PBXNativeTarget; buildConfigurationList = C10D9BD81C8269D600378342 /* Build configuration list for PBXNativeTarget "MinimedKit" */; buildPhases = ( + C10D9BBE1C8269D500378342 /* Headers */, C10D9BBC1C8269D500378342 /* Sources */, C10D9BBD1C8269D500378342 /* Frameworks */, - C10D9BBE1C8269D500378342 /* Headers */, C10D9BBF1C8269D500378342 /* Resources */, ); buildRules = ( @@ -2995,9 +2995,9 @@ isa = PBXNativeTarget; buildConfigurationList = C1B383221CD0665D00CE7782 /* Build configuration list for PBXNativeTarget "NightscoutUploadKit" */; buildPhases = ( + C1B383081CD0665D00CE7782 /* Headers */, C1B383061CD0665D00CE7782 /* Sources */, C1B383071CD0665D00CE7782 /* Frameworks */, - C1B383081CD0665D00CE7782 /* Headers */, C1B383091CD0665D00CE7782 /* Resources */, ); buildRules = ( @@ -3051,9 +3051,9 @@ isa = PBXNativeTarget; buildConfigurationList = C1FFAF93213323CD00C50C1D /* Build configuration list for PBXNativeTarget "OmniKit" */; buildPhases = ( + C1FFAF75213323CC00C50C1D /* Headers */, C1FFAF73213323CC00C50C1D /* Sources */, C1FFAF74213323CC00C50C1D /* Frameworks */, - C1FFAF75213323CC00C50C1D /* Headers */, C1FFAF76213323CC00C50C1D /* Resources */, ); buildRules = ( @@ -3089,9 +3089,9 @@ isa = PBXNativeTarget; buildConfigurationList = C1FFAFF0213323FA00C50C1D /* Build configuration list for PBXNativeTarget "OmniKitUI" */; buildPhases = ( + C1FFAFD6213323F900C50C1D /* Headers */, C1FFAFD4213323F900C50C1D /* Sources */, C1FFAFD5213323F900C50C1D /* Frameworks */, - C1FFAFD6213323F900C50C1D /* Headers */, C1FFAFD7213323F900C50C1D /* Resources */, ); buildRules = ( From 8cf75e5d8d197e1ef76475ca81b095217bb2f922 Mon Sep 17 00:00:00 2001 From: Ivan Valkou Date: Sat, 25 Dec 2021 00:07:32 +0300 Subject: [PATCH 19/28] Garmin ConnectIQ --- .../ConnectIQ.xcframework/Info.plist | 41 + .../ConnectIQ.framework/ConnectIQ | Bin 0 -> 6022892 bytes .../ConnectIQ.framework/Headers/ConnectIQ.h | 237 +++++ .../ConnectIQ.framework/Headers/IQApp.h | 34 + .../ConnectIQ.framework/Headers/IQAppStatus.h | 20 + .../ConnectIQ.framework/Headers/IQConstants.h | 63 ++ .../ConnectIQ.framework/Headers/IQDevice.h | 61 ++ .../ConnectIQ.framework/Info.plist | Bin 0 -> 738 bytes .../Modules/module.modulemap | 6 + .../ar.lproj/IQLocalizable.strings | Bin 0 -> 226 bytes .../cs.lproj/IQLocalizable.strings | Bin 0 -> 254 bytes .../da.lproj/IQLocalizable.strings | Bin 0 -> 235 bytes .../de.lproj/IQLocalizable.strings | Bin 0 -> 171 bytes .../el.lproj/IQLocalizable.strings | Bin 0 -> 290 bytes .../en.lproj/IQLocalizable.strings | Bin 0 -> 153 bytes .../es.lproj/IQLocalizable.strings | Bin 0 -> 226 bytes .../fi.lproj/IQLocalizable.strings | Bin 0 -> 207 bytes .../fr.lproj/IQLocalizable.strings | Bin 0 -> 236 bytes .../he.lproj/IQLocalizable.strings | Bin 0 -> 182 bytes .../hr.lproj/IQLocalizable.strings | Bin 0 -> 163 bytes .../hu.lproj/IQLocalizable.strings | Bin 0 -> 256 bytes .../id.lproj/IQLocalizable.strings | Bin 0 -> 151 bytes .../it.lproj/IQLocalizable.strings | Bin 0 -> 194 bytes .../ja.lproj/IQLocalizable.strings | Bin 0 -> 166 bytes .../ko.lproj/IQLocalizable.strings | Bin 0 -> 176 bytes .../ms.lproj/IQLocalizable.strings | Bin 0 -> 154 bytes .../nb.lproj/IQLocalizable.strings | Bin 0 -> 220 bytes .../nl.lproj/IQLocalizable.strings | Bin 0 -> 223 bytes .../pl.lproj/IQLocalizable.strings | Bin 0 -> 172 bytes .../pt-PT.lproj/IQLocalizable.strings | Bin 0 -> 208 bytes .../pt.lproj/IQLocalizable.strings | Bin 0 -> 222 bytes .../ru.lproj/IQLocalizable.strings | Bin 0 -> 258 bytes .../sk.lproj/IQLocalizable.strings | Bin 0 -> 248 bytes .../sv.lproj/IQLocalizable.strings | Bin 0 -> 224 bytes .../th.lproj/IQLocalizable.strings | Bin 0 -> 200 bytes .../tr.lproj/IQLocalizable.strings | Bin 0 -> 187 bytes .../zh-Hans.lproj/IQLocalizable.strings | Bin 0 -> 150 bytes .../zh-Hant.lproj/IQLocalizable.strings | Bin 0 -> 148 bytes .../ConnectIQ.framework/ConnectIQ | Bin 0 -> 919312 bytes .../ConnectIQ.framework/Headers/ConnectIQ.h | 237 +++++ .../ConnectIQ.framework/Headers/IQApp.h | 34 + .../ConnectIQ.framework/Headers/IQAppStatus.h | 20 + .../ConnectIQ.framework/Headers/IQConstants.h | 63 ++ .../ConnectIQ.framework/Headers/IQDevice.h | 61 ++ .../ConnectIQ.framework/Info.plist | Bin 0 -> 765 bytes .../Modules/module.modulemap | 6 + .../_CodeSignature/CodeResources | 830 ++++++++++++++++++ .../ar.lproj/IQLocalizable.strings | Bin 0 -> 226 bytes .../cs.lproj/IQLocalizable.strings | Bin 0 -> 254 bytes .../da.lproj/IQLocalizable.strings | Bin 0 -> 235 bytes .../de.lproj/IQLocalizable.strings | Bin 0 -> 171 bytes .../el.lproj/IQLocalizable.strings | Bin 0 -> 290 bytes .../en.lproj/IQLocalizable.strings | Bin 0 -> 153 bytes .../es.lproj/IQLocalizable.strings | Bin 0 -> 226 bytes .../fi.lproj/IQLocalizable.strings | Bin 0 -> 207 bytes .../fr.lproj/IQLocalizable.strings | Bin 0 -> 236 bytes .../he.lproj/IQLocalizable.strings | Bin 0 -> 182 bytes .../hr.lproj/IQLocalizable.strings | Bin 0 -> 163 bytes .../hu.lproj/IQLocalizable.strings | Bin 0 -> 256 bytes .../id.lproj/IQLocalizable.strings | Bin 0 -> 151 bytes .../it.lproj/IQLocalizable.strings | Bin 0 -> 194 bytes .../ja.lproj/IQLocalizable.strings | Bin 0 -> 166 bytes .../ko.lproj/IQLocalizable.strings | Bin 0 -> 176 bytes .../ms.lproj/IQLocalizable.strings | Bin 0 -> 154 bytes .../nb.lproj/IQLocalizable.strings | Bin 0 -> 220 bytes .../nl.lproj/IQLocalizable.strings | Bin 0 -> 223 bytes .../pl.lproj/IQLocalizable.strings | Bin 0 -> 172 bytes .../pt-PT.lproj/IQLocalizable.strings | Bin 0 -> 208 bytes .../pt.lproj/IQLocalizable.strings | Bin 0 -> 222 bytes .../ru.lproj/IQLocalizable.strings | Bin 0 -> 258 bytes .../sk.lproj/IQLocalizable.strings | Bin 0 -> 248 bytes .../sv.lproj/IQLocalizable.strings | Bin 0 -> 224 bytes .../th.lproj/IQLocalizable.strings | Bin 0 -> 200 bytes .../tr.lproj/IQLocalizable.strings | Bin 0 -> 187 bytes .../zh-Hans.lproj/IQLocalizable.strings | Bin 0 -> 150 bytes .../zh-Hant.lproj/IQLocalizable.strings | Bin 0 -> 148 bytes .../ExampleApp.xcodeproj/project.pbxproj | 782 +++++++++++++++++ .../contents.xcworkspacedata | 7 + .../xcshareddata/IDEWorkspaceChecks.plist | 8 + .../ExampleApp/ExampleApp/AppDelegate.h | 14 + .../ExampleApp/ExampleApp/AppDelegate.m | 73 ++ .../ExampleApp/ExampleApp/AppInfo.h | 22 + .../ExampleApp/ExampleApp/AppInfo.m | 65 ++ .../ExampleApp/AppMessageViewController.h | 15 + .../ExampleApp/AppMessageViewController.m | 218 +++++ .../ExampleApp/AppMessageViewController.xib | 48 + .../ExampleApp/ExampleApp/AppTableViewCell.h | 16 + .../ExampleApp/ExampleApp/AppTableViewCell.m | 52 ++ .../ExampleApp/AppTableViewCell.xib | 50 ++ .../ExampleApp/ExampleApp/Constants.h | 10 + .../ExampleApp/ExampleApp/Constants.m | 10 + .../ExampleApp/DeviceAppListViewController.h | 15 + .../ExampleApp/DeviceAppListViewController.m | 190 ++++ .../DeviceAppListViewController.xib | 56 ++ .../ExampleApp/DeviceListViewController.h | 13 + .../ExampleApp/DeviceListViewController.m | 163 ++++ .../ExampleApp/DeviceListViewController.xib | 56 ++ .../ExampleApp/ExampleApp/DeviceManager.h | 31 + .../ExampleApp/ExampleApp/DeviceManager.m | 117 +++ .../ExampleApp/DeviceTableViewCell.h | 17 + .../ExampleApp/DeviceTableViewCell.m | 57 ++ .../ExampleApp/DeviceTableViewCell.xib | 70 ++ .../ExampleApp/ExampleApp-Info.plist | 61 ++ .../ExampleApp/ExampleApp-Prefix.pch | 16 + .../AppIcon.appiconset/Contents.json | 38 + .../ExampleApp/Images.xcassets/Contents.json | 6 + .../ExampleApp/LaunchStoryboard.storyboard | 27 + .../ExampleApp/ExampleApp/TableEntry.h | 17 + .../ExampleApp/ExampleApp/TableEntry.m | 47 + .../ExampleApp/cs.lproj/InfoPlist.strings | 2 + .../ExampleApp/da.lproj/InfoPlist.strings | 2 + .../ExampleApp/de.lproj/InfoPlist.strings | 2 + .../ExampleApp/el.lproj/InfoPlist.strings | 2 + .../ExampleApp/en.lproj/InfoPlist.strings | 2 + .../ExampleApp/es.lproj/InfoPlist.strings | 2 + .../ExampleApp/fi.lproj/InfoPlist.strings | 2 + .../ExampleApp/fr.lproj/InfoPlist.strings | 2 + .../ExampleApp/he.lproj/InfoPlist.strings | 2 + .../ExampleApp/hr.lproj/InfoPlist.strings | 2 + .../ExampleApp/hu.lproj/InfoPlist.strings | 2 + .../ExampleApp/id.lproj/InfoPlist.strings | 2 + .../ExampleApp/it.lproj/InfoPlist.strings | 2 + .../ExampleApp/ja.lproj/InfoPlist.strings | 2 + .../ExampleApp/ko.lproj/InfoPlist.strings | 2 + .../ExampleApp/ExampleApp/main.m | 17 + .../ExampleApp/ms.lproj/InfoPlist.strings | 2 + .../ExampleApp/nb.lproj/InfoPlist.strings | 2 + .../ExampleApp/nl.lproj/InfoPlist.strings | 2 + .../ExampleApp/pl.lproj/InfoPlist.strings | 2 + .../ExampleApp/pt-PT.lproj/InfoPlist.strings | 2 + .../ExampleApp/pt.lproj/InfoPlist.strings | 2 + .../ExampleApp/ru.lproj/InfoPlist.strings | 2 + .../ExampleApp/sk.lproj/InfoPlist.strings | 2 + .../ExampleApp/sv.lproj/InfoPlist.strings | 2 + .../ExampleApp/th.lproj/InfoPlist.strings | 2 + .../zh-Hans.lproj/InfoPlist.strings | 2 + .../zh-Hant.lproj/InfoPlist.strings | 2 + .../ExampleAppTests-Info.plist | 22 + .../ExampleAppTests/ExampleAppTests.m | 33 + .../cs.lproj/InfoPlist.strings | 2 + .../da.lproj/InfoPlist.strings | 2 + .../de.lproj/InfoPlist.strings | 2 + .../el.lproj/InfoPlist.strings | 2 + .../en.lproj/InfoPlist.strings | 2 + .../es.lproj/InfoPlist.strings | 2 + .../fi.lproj/InfoPlist.strings | 2 + .../fr.lproj/InfoPlist.strings | 2 + .../he.lproj/InfoPlist.strings | 2 + .../hr.lproj/InfoPlist.strings | 2 + .../hu.lproj/InfoPlist.strings | 2 + .../id.lproj/InfoPlist.strings | 2 + .../it.lproj/InfoPlist.strings | 2 + .../ja.lproj/InfoPlist.strings | 2 + .../ko.lproj/InfoPlist.strings | 2 + .../ms.lproj/InfoPlist.strings | 2 + .../nb.lproj/InfoPlist.strings | 2 + .../nl.lproj/InfoPlist.strings | 2 + .../pl.lproj/InfoPlist.strings | 2 + .../pt-PT.lproj/InfoPlist.strings | 2 + .../pt.lproj/InfoPlist.strings | 2 + .../ru.lproj/InfoPlist.strings | 2 + .../sk.lproj/InfoPlist.strings | 2 + .../sv.lproj/InfoPlist.strings | 2 + .../th.lproj/InfoPlist.strings | 2 + .../zh-Hans.lproj/InfoPlist.strings | 2 + .../zh-Hant.lproj/InfoPlist.strings | 2 + .../documentation/ConnectIQ_iOS_SDK.html | 315 +++++++ .../resources/images/connect_iq_logo.png | Bin 0 -> 16888 bytes .../documentation/resources/images/image1.png | Bin 0 -> 51944 bytes .../resources/images/image10.jpeg | Bin 0 -> 285460 bytes .../documentation/resources/images/image2.png | Bin 0 -> 34310 bytes .../documentation/resources/images/image3.png | Bin 0 -> 35653 bytes .../documentation/resources/images/image4.png | Bin 0 -> 36824 bytes .../documentation/resources/images/image5.png | Bin 0 -> 52932 bytes .../documentation/resources/images/image6.png | Bin 0 -> 65597 bytes .../documentation/resources/images/image7.png | Bin 0 -> 28323 bytes .../documentation/resources/images/image8.png | Bin 0 -> 231072 bytes .../documentation/resources/images/image9.png | Bin 0 -> 189817 bytes .../documentation/resources/style.css | 140 +++ FreeAPS.xcodeproj/project.pbxproj | 44 + FreeAPS/Resources/Info.plist | 3 + FreeAPS/Sources/Application/FreeAPSApp.swift | 11 + .../Sources/Assemblies/ServiceAssembly.swift | 1 + .../GarminConfig/GarminConfigDataFlow.swift | 5 + .../GarminConfig/GarminConfigProvider.swift | 3 + .../GarminConfig/GarminConfigStateModel.swift | 20 + .../View/GarminConfigRootView.swift | 30 + .../Settings/View/SettingsRootView.swift | 1 + FreeAPS/Sources/Router/Screen.swift | 3 + .../Services/WatchManager/GarminManager.swift | 164 ++++ .../Services/WatchManager/WatchManager.swift | 21 +- .../DataFlow.swift | 4 + 192 files changed, 5043 insertions(+), 2 deletions(-) create mode 100644 Dependencies/connectiq-mobile-sdk-ios-1.4/ConnectIQ.xcframework/Info.plist create mode 100755 Dependencies/connectiq-mobile-sdk-ios-1.4/ConnectIQ.xcframework/ios-armv7_arm64/ConnectIQ.framework/ConnectIQ create mode 100644 Dependencies/connectiq-mobile-sdk-ios-1.4/ConnectIQ.xcframework/ios-armv7_arm64/ConnectIQ.framework/Headers/ConnectIQ.h create mode 100644 Dependencies/connectiq-mobile-sdk-ios-1.4/ConnectIQ.xcframework/ios-armv7_arm64/ConnectIQ.framework/Headers/IQApp.h create mode 100644 Dependencies/connectiq-mobile-sdk-ios-1.4/ConnectIQ.xcframework/ios-armv7_arm64/ConnectIQ.framework/Headers/IQAppStatus.h create mode 100644 Dependencies/connectiq-mobile-sdk-ios-1.4/ConnectIQ.xcframework/ios-armv7_arm64/ConnectIQ.framework/Headers/IQConstants.h create mode 100644 Dependencies/connectiq-mobile-sdk-ios-1.4/ConnectIQ.xcframework/ios-armv7_arm64/ConnectIQ.framework/Headers/IQDevice.h create mode 100644 Dependencies/connectiq-mobile-sdk-ios-1.4/ConnectIQ.xcframework/ios-armv7_arm64/ConnectIQ.framework/Info.plist create mode 100644 Dependencies/connectiq-mobile-sdk-ios-1.4/ConnectIQ.xcframework/ios-armv7_arm64/ConnectIQ.framework/Modules/module.modulemap create mode 100644 Dependencies/connectiq-mobile-sdk-ios-1.4/ConnectIQ.xcframework/ios-armv7_arm64/ConnectIQ.framework/ar.lproj/IQLocalizable.strings create mode 100644 Dependencies/connectiq-mobile-sdk-ios-1.4/ConnectIQ.xcframework/ios-armv7_arm64/ConnectIQ.framework/cs.lproj/IQLocalizable.strings create mode 100644 Dependencies/connectiq-mobile-sdk-ios-1.4/ConnectIQ.xcframework/ios-armv7_arm64/ConnectIQ.framework/da.lproj/IQLocalizable.strings create mode 100644 Dependencies/connectiq-mobile-sdk-ios-1.4/ConnectIQ.xcframework/ios-armv7_arm64/ConnectIQ.framework/de.lproj/IQLocalizable.strings create mode 100644 Dependencies/connectiq-mobile-sdk-ios-1.4/ConnectIQ.xcframework/ios-armv7_arm64/ConnectIQ.framework/el.lproj/IQLocalizable.strings create mode 100644 Dependencies/connectiq-mobile-sdk-ios-1.4/ConnectIQ.xcframework/ios-armv7_arm64/ConnectIQ.framework/en.lproj/IQLocalizable.strings create mode 100644 Dependencies/connectiq-mobile-sdk-ios-1.4/ConnectIQ.xcframework/ios-armv7_arm64/ConnectIQ.framework/es.lproj/IQLocalizable.strings create mode 100644 Dependencies/connectiq-mobile-sdk-ios-1.4/ConnectIQ.xcframework/ios-armv7_arm64/ConnectIQ.framework/fi.lproj/IQLocalizable.strings create mode 100644 Dependencies/connectiq-mobile-sdk-ios-1.4/ConnectIQ.xcframework/ios-armv7_arm64/ConnectIQ.framework/fr.lproj/IQLocalizable.strings create mode 100644 Dependencies/connectiq-mobile-sdk-ios-1.4/ConnectIQ.xcframework/ios-armv7_arm64/ConnectIQ.framework/he.lproj/IQLocalizable.strings create mode 100644 Dependencies/connectiq-mobile-sdk-ios-1.4/ConnectIQ.xcframework/ios-armv7_arm64/ConnectIQ.framework/hr.lproj/IQLocalizable.strings create mode 100644 Dependencies/connectiq-mobile-sdk-ios-1.4/ConnectIQ.xcframework/ios-armv7_arm64/ConnectIQ.framework/hu.lproj/IQLocalizable.strings create mode 100644 Dependencies/connectiq-mobile-sdk-ios-1.4/ConnectIQ.xcframework/ios-armv7_arm64/ConnectIQ.framework/id.lproj/IQLocalizable.strings create mode 100644 Dependencies/connectiq-mobile-sdk-ios-1.4/ConnectIQ.xcframework/ios-armv7_arm64/ConnectIQ.framework/it.lproj/IQLocalizable.strings create mode 100644 Dependencies/connectiq-mobile-sdk-ios-1.4/ConnectIQ.xcframework/ios-armv7_arm64/ConnectIQ.framework/ja.lproj/IQLocalizable.strings create mode 100644 Dependencies/connectiq-mobile-sdk-ios-1.4/ConnectIQ.xcframework/ios-armv7_arm64/ConnectIQ.framework/ko.lproj/IQLocalizable.strings create mode 100644 Dependencies/connectiq-mobile-sdk-ios-1.4/ConnectIQ.xcframework/ios-armv7_arm64/ConnectIQ.framework/ms.lproj/IQLocalizable.strings create mode 100644 Dependencies/connectiq-mobile-sdk-ios-1.4/ConnectIQ.xcframework/ios-armv7_arm64/ConnectIQ.framework/nb.lproj/IQLocalizable.strings create mode 100644 Dependencies/connectiq-mobile-sdk-ios-1.4/ConnectIQ.xcframework/ios-armv7_arm64/ConnectIQ.framework/nl.lproj/IQLocalizable.strings create mode 100644 Dependencies/connectiq-mobile-sdk-ios-1.4/ConnectIQ.xcframework/ios-armv7_arm64/ConnectIQ.framework/pl.lproj/IQLocalizable.strings create mode 100644 Dependencies/connectiq-mobile-sdk-ios-1.4/ConnectIQ.xcframework/ios-armv7_arm64/ConnectIQ.framework/pt-PT.lproj/IQLocalizable.strings create mode 100644 Dependencies/connectiq-mobile-sdk-ios-1.4/ConnectIQ.xcframework/ios-armv7_arm64/ConnectIQ.framework/pt.lproj/IQLocalizable.strings create mode 100644 Dependencies/connectiq-mobile-sdk-ios-1.4/ConnectIQ.xcframework/ios-armv7_arm64/ConnectIQ.framework/ru.lproj/IQLocalizable.strings create mode 100644 Dependencies/connectiq-mobile-sdk-ios-1.4/ConnectIQ.xcframework/ios-armv7_arm64/ConnectIQ.framework/sk.lproj/IQLocalizable.strings create mode 100644 Dependencies/connectiq-mobile-sdk-ios-1.4/ConnectIQ.xcframework/ios-armv7_arm64/ConnectIQ.framework/sv.lproj/IQLocalizable.strings create mode 100644 Dependencies/connectiq-mobile-sdk-ios-1.4/ConnectIQ.xcframework/ios-armv7_arm64/ConnectIQ.framework/th.lproj/IQLocalizable.strings create mode 100644 Dependencies/connectiq-mobile-sdk-ios-1.4/ConnectIQ.xcframework/ios-armv7_arm64/ConnectIQ.framework/tr.lproj/IQLocalizable.strings create mode 100644 Dependencies/connectiq-mobile-sdk-ios-1.4/ConnectIQ.xcframework/ios-armv7_arm64/ConnectIQ.framework/zh-Hans.lproj/IQLocalizable.strings create mode 100644 Dependencies/connectiq-mobile-sdk-ios-1.4/ConnectIQ.xcframework/ios-armv7_arm64/ConnectIQ.framework/zh-Hant.lproj/IQLocalizable.strings create mode 100755 Dependencies/connectiq-mobile-sdk-ios-1.4/ConnectIQ.xcframework/ios-i386_x86_64-simulator/ConnectIQ.framework/ConnectIQ create mode 100644 Dependencies/connectiq-mobile-sdk-ios-1.4/ConnectIQ.xcframework/ios-i386_x86_64-simulator/ConnectIQ.framework/Headers/ConnectIQ.h create mode 100644 Dependencies/connectiq-mobile-sdk-ios-1.4/ConnectIQ.xcframework/ios-i386_x86_64-simulator/ConnectIQ.framework/Headers/IQApp.h create mode 100644 Dependencies/connectiq-mobile-sdk-ios-1.4/ConnectIQ.xcframework/ios-i386_x86_64-simulator/ConnectIQ.framework/Headers/IQAppStatus.h create mode 100644 Dependencies/connectiq-mobile-sdk-ios-1.4/ConnectIQ.xcframework/ios-i386_x86_64-simulator/ConnectIQ.framework/Headers/IQConstants.h create mode 100644 Dependencies/connectiq-mobile-sdk-ios-1.4/ConnectIQ.xcframework/ios-i386_x86_64-simulator/ConnectIQ.framework/Headers/IQDevice.h create mode 100644 Dependencies/connectiq-mobile-sdk-ios-1.4/ConnectIQ.xcframework/ios-i386_x86_64-simulator/ConnectIQ.framework/Info.plist create mode 100644 Dependencies/connectiq-mobile-sdk-ios-1.4/ConnectIQ.xcframework/ios-i386_x86_64-simulator/ConnectIQ.framework/Modules/module.modulemap create mode 100644 Dependencies/connectiq-mobile-sdk-ios-1.4/ConnectIQ.xcframework/ios-i386_x86_64-simulator/ConnectIQ.framework/_CodeSignature/CodeResources create mode 100644 Dependencies/connectiq-mobile-sdk-ios-1.4/ConnectIQ.xcframework/ios-i386_x86_64-simulator/ConnectIQ.framework/ar.lproj/IQLocalizable.strings create mode 100644 Dependencies/connectiq-mobile-sdk-ios-1.4/ConnectIQ.xcframework/ios-i386_x86_64-simulator/ConnectIQ.framework/cs.lproj/IQLocalizable.strings create mode 100644 Dependencies/connectiq-mobile-sdk-ios-1.4/ConnectIQ.xcframework/ios-i386_x86_64-simulator/ConnectIQ.framework/da.lproj/IQLocalizable.strings create mode 100644 Dependencies/connectiq-mobile-sdk-ios-1.4/ConnectIQ.xcframework/ios-i386_x86_64-simulator/ConnectIQ.framework/de.lproj/IQLocalizable.strings create mode 100644 Dependencies/connectiq-mobile-sdk-ios-1.4/ConnectIQ.xcframework/ios-i386_x86_64-simulator/ConnectIQ.framework/el.lproj/IQLocalizable.strings create mode 100644 Dependencies/connectiq-mobile-sdk-ios-1.4/ConnectIQ.xcframework/ios-i386_x86_64-simulator/ConnectIQ.framework/en.lproj/IQLocalizable.strings create mode 100644 Dependencies/connectiq-mobile-sdk-ios-1.4/ConnectIQ.xcframework/ios-i386_x86_64-simulator/ConnectIQ.framework/es.lproj/IQLocalizable.strings create mode 100644 Dependencies/connectiq-mobile-sdk-ios-1.4/ConnectIQ.xcframework/ios-i386_x86_64-simulator/ConnectIQ.framework/fi.lproj/IQLocalizable.strings create mode 100644 Dependencies/connectiq-mobile-sdk-ios-1.4/ConnectIQ.xcframework/ios-i386_x86_64-simulator/ConnectIQ.framework/fr.lproj/IQLocalizable.strings create mode 100644 Dependencies/connectiq-mobile-sdk-ios-1.4/ConnectIQ.xcframework/ios-i386_x86_64-simulator/ConnectIQ.framework/he.lproj/IQLocalizable.strings create mode 100644 Dependencies/connectiq-mobile-sdk-ios-1.4/ConnectIQ.xcframework/ios-i386_x86_64-simulator/ConnectIQ.framework/hr.lproj/IQLocalizable.strings create mode 100644 Dependencies/connectiq-mobile-sdk-ios-1.4/ConnectIQ.xcframework/ios-i386_x86_64-simulator/ConnectIQ.framework/hu.lproj/IQLocalizable.strings create mode 100644 Dependencies/connectiq-mobile-sdk-ios-1.4/ConnectIQ.xcframework/ios-i386_x86_64-simulator/ConnectIQ.framework/id.lproj/IQLocalizable.strings create mode 100644 Dependencies/connectiq-mobile-sdk-ios-1.4/ConnectIQ.xcframework/ios-i386_x86_64-simulator/ConnectIQ.framework/it.lproj/IQLocalizable.strings create mode 100644 Dependencies/connectiq-mobile-sdk-ios-1.4/ConnectIQ.xcframework/ios-i386_x86_64-simulator/ConnectIQ.framework/ja.lproj/IQLocalizable.strings create mode 100644 Dependencies/connectiq-mobile-sdk-ios-1.4/ConnectIQ.xcframework/ios-i386_x86_64-simulator/ConnectIQ.framework/ko.lproj/IQLocalizable.strings create mode 100644 Dependencies/connectiq-mobile-sdk-ios-1.4/ConnectIQ.xcframework/ios-i386_x86_64-simulator/ConnectIQ.framework/ms.lproj/IQLocalizable.strings create mode 100644 Dependencies/connectiq-mobile-sdk-ios-1.4/ConnectIQ.xcframework/ios-i386_x86_64-simulator/ConnectIQ.framework/nb.lproj/IQLocalizable.strings create mode 100644 Dependencies/connectiq-mobile-sdk-ios-1.4/ConnectIQ.xcframework/ios-i386_x86_64-simulator/ConnectIQ.framework/nl.lproj/IQLocalizable.strings create mode 100644 Dependencies/connectiq-mobile-sdk-ios-1.4/ConnectIQ.xcframework/ios-i386_x86_64-simulator/ConnectIQ.framework/pl.lproj/IQLocalizable.strings create mode 100644 Dependencies/connectiq-mobile-sdk-ios-1.4/ConnectIQ.xcframework/ios-i386_x86_64-simulator/ConnectIQ.framework/pt-PT.lproj/IQLocalizable.strings create mode 100644 Dependencies/connectiq-mobile-sdk-ios-1.4/ConnectIQ.xcframework/ios-i386_x86_64-simulator/ConnectIQ.framework/pt.lproj/IQLocalizable.strings create mode 100644 Dependencies/connectiq-mobile-sdk-ios-1.4/ConnectIQ.xcframework/ios-i386_x86_64-simulator/ConnectIQ.framework/ru.lproj/IQLocalizable.strings create mode 100644 Dependencies/connectiq-mobile-sdk-ios-1.4/ConnectIQ.xcframework/ios-i386_x86_64-simulator/ConnectIQ.framework/sk.lproj/IQLocalizable.strings create mode 100644 Dependencies/connectiq-mobile-sdk-ios-1.4/ConnectIQ.xcframework/ios-i386_x86_64-simulator/ConnectIQ.framework/sv.lproj/IQLocalizable.strings create mode 100644 Dependencies/connectiq-mobile-sdk-ios-1.4/ConnectIQ.xcframework/ios-i386_x86_64-simulator/ConnectIQ.framework/th.lproj/IQLocalizable.strings create mode 100644 Dependencies/connectiq-mobile-sdk-ios-1.4/ConnectIQ.xcframework/ios-i386_x86_64-simulator/ConnectIQ.framework/tr.lproj/IQLocalizable.strings create mode 100644 Dependencies/connectiq-mobile-sdk-ios-1.4/ConnectIQ.xcframework/ios-i386_x86_64-simulator/ConnectIQ.framework/zh-Hans.lproj/IQLocalizable.strings create mode 100644 Dependencies/connectiq-mobile-sdk-ios-1.4/ConnectIQ.xcframework/ios-i386_x86_64-simulator/ConnectIQ.framework/zh-Hant.lproj/IQLocalizable.strings create mode 100644 Dependencies/connectiq-mobile-sdk-ios-1.4/ExampleApp/ExampleApp.xcodeproj/project.pbxproj create mode 100644 Dependencies/connectiq-mobile-sdk-ios-1.4/ExampleApp/ExampleApp.xcodeproj/project.xcworkspace/contents.xcworkspacedata create mode 100644 Dependencies/connectiq-mobile-sdk-ios-1.4/ExampleApp/ExampleApp.xcodeproj/project.xcworkspace/xcshareddata/IDEWorkspaceChecks.plist create mode 100644 Dependencies/connectiq-mobile-sdk-ios-1.4/ExampleApp/ExampleApp/AppDelegate.h create mode 100644 Dependencies/connectiq-mobile-sdk-ios-1.4/ExampleApp/ExampleApp/AppDelegate.m create mode 100644 Dependencies/connectiq-mobile-sdk-ios-1.4/ExampleApp/ExampleApp/AppInfo.h create mode 100644 Dependencies/connectiq-mobile-sdk-ios-1.4/ExampleApp/ExampleApp/AppInfo.m create mode 100644 Dependencies/connectiq-mobile-sdk-ios-1.4/ExampleApp/ExampleApp/AppMessageViewController.h create mode 100644 Dependencies/connectiq-mobile-sdk-ios-1.4/ExampleApp/ExampleApp/AppMessageViewController.m create mode 100644 Dependencies/connectiq-mobile-sdk-ios-1.4/ExampleApp/ExampleApp/AppMessageViewController.xib create mode 100644 Dependencies/connectiq-mobile-sdk-ios-1.4/ExampleApp/ExampleApp/AppTableViewCell.h create mode 100644 Dependencies/connectiq-mobile-sdk-ios-1.4/ExampleApp/ExampleApp/AppTableViewCell.m create mode 100644 Dependencies/connectiq-mobile-sdk-ios-1.4/ExampleApp/ExampleApp/AppTableViewCell.xib create mode 100644 Dependencies/connectiq-mobile-sdk-ios-1.4/ExampleApp/ExampleApp/Constants.h create mode 100644 Dependencies/connectiq-mobile-sdk-ios-1.4/ExampleApp/ExampleApp/Constants.m create mode 100644 Dependencies/connectiq-mobile-sdk-ios-1.4/ExampleApp/ExampleApp/DeviceAppListViewController.h create mode 100644 Dependencies/connectiq-mobile-sdk-ios-1.4/ExampleApp/ExampleApp/DeviceAppListViewController.m create mode 100644 Dependencies/connectiq-mobile-sdk-ios-1.4/ExampleApp/ExampleApp/DeviceAppListViewController.xib create mode 100644 Dependencies/connectiq-mobile-sdk-ios-1.4/ExampleApp/ExampleApp/DeviceListViewController.h create mode 100644 Dependencies/connectiq-mobile-sdk-ios-1.4/ExampleApp/ExampleApp/DeviceListViewController.m create mode 100644 Dependencies/connectiq-mobile-sdk-ios-1.4/ExampleApp/ExampleApp/DeviceListViewController.xib create mode 100644 Dependencies/connectiq-mobile-sdk-ios-1.4/ExampleApp/ExampleApp/DeviceManager.h create mode 100644 Dependencies/connectiq-mobile-sdk-ios-1.4/ExampleApp/ExampleApp/DeviceManager.m create mode 100644 Dependencies/connectiq-mobile-sdk-ios-1.4/ExampleApp/ExampleApp/DeviceTableViewCell.h create mode 100644 Dependencies/connectiq-mobile-sdk-ios-1.4/ExampleApp/ExampleApp/DeviceTableViewCell.m create mode 100644 Dependencies/connectiq-mobile-sdk-ios-1.4/ExampleApp/ExampleApp/DeviceTableViewCell.xib create mode 100644 Dependencies/connectiq-mobile-sdk-ios-1.4/ExampleApp/ExampleApp/ExampleApp-Info.plist create mode 100644 Dependencies/connectiq-mobile-sdk-ios-1.4/ExampleApp/ExampleApp/ExampleApp-Prefix.pch create mode 100644 Dependencies/connectiq-mobile-sdk-ios-1.4/ExampleApp/ExampleApp/Images.xcassets/AppIcon.appiconset/Contents.json create mode 100644 Dependencies/connectiq-mobile-sdk-ios-1.4/ExampleApp/ExampleApp/Images.xcassets/Contents.json create mode 100644 Dependencies/connectiq-mobile-sdk-ios-1.4/ExampleApp/ExampleApp/LaunchStoryboard.storyboard create mode 100644 Dependencies/connectiq-mobile-sdk-ios-1.4/ExampleApp/ExampleApp/TableEntry.h create mode 100644 Dependencies/connectiq-mobile-sdk-ios-1.4/ExampleApp/ExampleApp/TableEntry.m create mode 100644 Dependencies/connectiq-mobile-sdk-ios-1.4/ExampleApp/ExampleApp/cs.lproj/InfoPlist.strings create mode 100644 Dependencies/connectiq-mobile-sdk-ios-1.4/ExampleApp/ExampleApp/da.lproj/InfoPlist.strings create mode 100644 Dependencies/connectiq-mobile-sdk-ios-1.4/ExampleApp/ExampleApp/de.lproj/InfoPlist.strings create mode 100644 Dependencies/connectiq-mobile-sdk-ios-1.4/ExampleApp/ExampleApp/el.lproj/InfoPlist.strings create mode 100644 Dependencies/connectiq-mobile-sdk-ios-1.4/ExampleApp/ExampleApp/en.lproj/InfoPlist.strings create mode 100644 Dependencies/connectiq-mobile-sdk-ios-1.4/ExampleApp/ExampleApp/es.lproj/InfoPlist.strings create mode 100644 Dependencies/connectiq-mobile-sdk-ios-1.4/ExampleApp/ExampleApp/fi.lproj/InfoPlist.strings create mode 100644 Dependencies/connectiq-mobile-sdk-ios-1.4/ExampleApp/ExampleApp/fr.lproj/InfoPlist.strings create mode 100644 Dependencies/connectiq-mobile-sdk-ios-1.4/ExampleApp/ExampleApp/he.lproj/InfoPlist.strings create mode 100644 Dependencies/connectiq-mobile-sdk-ios-1.4/ExampleApp/ExampleApp/hr.lproj/InfoPlist.strings create mode 100644 Dependencies/connectiq-mobile-sdk-ios-1.4/ExampleApp/ExampleApp/hu.lproj/InfoPlist.strings create mode 100644 Dependencies/connectiq-mobile-sdk-ios-1.4/ExampleApp/ExampleApp/id.lproj/InfoPlist.strings create mode 100644 Dependencies/connectiq-mobile-sdk-ios-1.4/ExampleApp/ExampleApp/it.lproj/InfoPlist.strings create mode 100644 Dependencies/connectiq-mobile-sdk-ios-1.4/ExampleApp/ExampleApp/ja.lproj/InfoPlist.strings create mode 100644 Dependencies/connectiq-mobile-sdk-ios-1.4/ExampleApp/ExampleApp/ko.lproj/InfoPlist.strings create mode 100644 Dependencies/connectiq-mobile-sdk-ios-1.4/ExampleApp/ExampleApp/main.m create mode 100644 Dependencies/connectiq-mobile-sdk-ios-1.4/ExampleApp/ExampleApp/ms.lproj/InfoPlist.strings create mode 100644 Dependencies/connectiq-mobile-sdk-ios-1.4/ExampleApp/ExampleApp/nb.lproj/InfoPlist.strings create mode 100644 Dependencies/connectiq-mobile-sdk-ios-1.4/ExampleApp/ExampleApp/nl.lproj/InfoPlist.strings create mode 100644 Dependencies/connectiq-mobile-sdk-ios-1.4/ExampleApp/ExampleApp/pl.lproj/InfoPlist.strings create mode 100644 Dependencies/connectiq-mobile-sdk-ios-1.4/ExampleApp/ExampleApp/pt-PT.lproj/InfoPlist.strings create mode 100644 Dependencies/connectiq-mobile-sdk-ios-1.4/ExampleApp/ExampleApp/pt.lproj/InfoPlist.strings create mode 100644 Dependencies/connectiq-mobile-sdk-ios-1.4/ExampleApp/ExampleApp/ru.lproj/InfoPlist.strings create mode 100644 Dependencies/connectiq-mobile-sdk-ios-1.4/ExampleApp/ExampleApp/sk.lproj/InfoPlist.strings create mode 100644 Dependencies/connectiq-mobile-sdk-ios-1.4/ExampleApp/ExampleApp/sv.lproj/InfoPlist.strings create mode 100644 Dependencies/connectiq-mobile-sdk-ios-1.4/ExampleApp/ExampleApp/th.lproj/InfoPlist.strings create mode 100644 Dependencies/connectiq-mobile-sdk-ios-1.4/ExampleApp/ExampleApp/zh-Hans.lproj/InfoPlist.strings create mode 100644 Dependencies/connectiq-mobile-sdk-ios-1.4/ExampleApp/ExampleApp/zh-Hant.lproj/InfoPlist.strings create mode 100644 Dependencies/connectiq-mobile-sdk-ios-1.4/ExampleApp/ExampleAppTests/ExampleAppTests-Info.plist create mode 100644 Dependencies/connectiq-mobile-sdk-ios-1.4/ExampleApp/ExampleAppTests/ExampleAppTests.m create mode 100644 Dependencies/connectiq-mobile-sdk-ios-1.4/ExampleApp/ExampleAppTests/cs.lproj/InfoPlist.strings create mode 100644 Dependencies/connectiq-mobile-sdk-ios-1.4/ExampleApp/ExampleAppTests/da.lproj/InfoPlist.strings create mode 100644 Dependencies/connectiq-mobile-sdk-ios-1.4/ExampleApp/ExampleAppTests/de.lproj/InfoPlist.strings create mode 100644 Dependencies/connectiq-mobile-sdk-ios-1.4/ExampleApp/ExampleAppTests/el.lproj/InfoPlist.strings create mode 100644 Dependencies/connectiq-mobile-sdk-ios-1.4/ExampleApp/ExampleAppTests/en.lproj/InfoPlist.strings create mode 100644 Dependencies/connectiq-mobile-sdk-ios-1.4/ExampleApp/ExampleAppTests/es.lproj/InfoPlist.strings create mode 100644 Dependencies/connectiq-mobile-sdk-ios-1.4/ExampleApp/ExampleAppTests/fi.lproj/InfoPlist.strings create mode 100644 Dependencies/connectiq-mobile-sdk-ios-1.4/ExampleApp/ExampleAppTests/fr.lproj/InfoPlist.strings create mode 100644 Dependencies/connectiq-mobile-sdk-ios-1.4/ExampleApp/ExampleAppTests/he.lproj/InfoPlist.strings create mode 100644 Dependencies/connectiq-mobile-sdk-ios-1.4/ExampleApp/ExampleAppTests/hr.lproj/InfoPlist.strings create mode 100644 Dependencies/connectiq-mobile-sdk-ios-1.4/ExampleApp/ExampleAppTests/hu.lproj/InfoPlist.strings create mode 100644 Dependencies/connectiq-mobile-sdk-ios-1.4/ExampleApp/ExampleAppTests/id.lproj/InfoPlist.strings create mode 100644 Dependencies/connectiq-mobile-sdk-ios-1.4/ExampleApp/ExampleAppTests/it.lproj/InfoPlist.strings create mode 100644 Dependencies/connectiq-mobile-sdk-ios-1.4/ExampleApp/ExampleAppTests/ja.lproj/InfoPlist.strings create mode 100644 Dependencies/connectiq-mobile-sdk-ios-1.4/ExampleApp/ExampleAppTests/ko.lproj/InfoPlist.strings create mode 100644 Dependencies/connectiq-mobile-sdk-ios-1.4/ExampleApp/ExampleAppTests/ms.lproj/InfoPlist.strings create mode 100644 Dependencies/connectiq-mobile-sdk-ios-1.4/ExampleApp/ExampleAppTests/nb.lproj/InfoPlist.strings create mode 100644 Dependencies/connectiq-mobile-sdk-ios-1.4/ExampleApp/ExampleAppTests/nl.lproj/InfoPlist.strings create mode 100644 Dependencies/connectiq-mobile-sdk-ios-1.4/ExampleApp/ExampleAppTests/pl.lproj/InfoPlist.strings create mode 100644 Dependencies/connectiq-mobile-sdk-ios-1.4/ExampleApp/ExampleAppTests/pt-PT.lproj/InfoPlist.strings create mode 100644 Dependencies/connectiq-mobile-sdk-ios-1.4/ExampleApp/ExampleAppTests/pt.lproj/InfoPlist.strings create mode 100644 Dependencies/connectiq-mobile-sdk-ios-1.4/ExampleApp/ExampleAppTests/ru.lproj/InfoPlist.strings create mode 100644 Dependencies/connectiq-mobile-sdk-ios-1.4/ExampleApp/ExampleAppTests/sk.lproj/InfoPlist.strings create mode 100644 Dependencies/connectiq-mobile-sdk-ios-1.4/ExampleApp/ExampleAppTests/sv.lproj/InfoPlist.strings create mode 100644 Dependencies/connectiq-mobile-sdk-ios-1.4/ExampleApp/ExampleAppTests/th.lproj/InfoPlist.strings create mode 100644 Dependencies/connectiq-mobile-sdk-ios-1.4/ExampleApp/ExampleAppTests/zh-Hans.lproj/InfoPlist.strings create mode 100644 Dependencies/connectiq-mobile-sdk-ios-1.4/ExampleApp/ExampleAppTests/zh-Hant.lproj/InfoPlist.strings create mode 100644 Dependencies/connectiq-mobile-sdk-ios-1.4/documentation/ConnectIQ_iOS_SDK.html create mode 100644 Dependencies/connectiq-mobile-sdk-ios-1.4/documentation/resources/images/connect_iq_logo.png create mode 100644 Dependencies/connectiq-mobile-sdk-ios-1.4/documentation/resources/images/image1.png create mode 100644 Dependencies/connectiq-mobile-sdk-ios-1.4/documentation/resources/images/image10.jpeg create mode 100644 Dependencies/connectiq-mobile-sdk-ios-1.4/documentation/resources/images/image2.png create mode 100644 Dependencies/connectiq-mobile-sdk-ios-1.4/documentation/resources/images/image3.png create mode 100644 Dependencies/connectiq-mobile-sdk-ios-1.4/documentation/resources/images/image4.png create mode 100644 Dependencies/connectiq-mobile-sdk-ios-1.4/documentation/resources/images/image5.png create mode 100644 Dependencies/connectiq-mobile-sdk-ios-1.4/documentation/resources/images/image6.png create mode 100644 Dependencies/connectiq-mobile-sdk-ios-1.4/documentation/resources/images/image7.png create mode 100644 Dependencies/connectiq-mobile-sdk-ios-1.4/documentation/resources/images/image8.png create mode 100644 Dependencies/connectiq-mobile-sdk-ios-1.4/documentation/resources/images/image9.png create mode 100644 Dependencies/connectiq-mobile-sdk-ios-1.4/documentation/resources/style.css create mode 100644 FreeAPS/Sources/Modules/GarminConfig/GarminConfigDataFlow.swift create mode 100644 FreeAPS/Sources/Modules/GarminConfig/GarminConfigProvider.swift create mode 100644 FreeAPS/Sources/Modules/GarminConfig/GarminConfigStateModel.swift create mode 100644 FreeAPS/Sources/Modules/GarminConfig/View/GarminConfigRootView.swift create mode 100644 FreeAPS/Sources/Services/WatchManager/GarminManager.swift diff --git a/Dependencies/connectiq-mobile-sdk-ios-1.4/ConnectIQ.xcframework/Info.plist b/Dependencies/connectiq-mobile-sdk-ios-1.4/ConnectIQ.xcframework/Info.plist new file mode 100644 index 000000000..1925319c5 --- /dev/null +++ b/Dependencies/connectiq-mobile-sdk-ios-1.4/ConnectIQ.xcframework/Info.plist @@ -0,0 +1,41 @@ + + + + + AvailableLibraries + + + LibraryIdentifier + ios-armv7_arm64 + LibraryPath + ConnectIQ.framework + SupportedArchitectures + + armv7 + arm64 + + SupportedPlatform + ios + + + LibraryIdentifier + ios-i386_x86_64-simulator + LibraryPath + ConnectIQ.framework + SupportedArchitectures + + i386 + x86_64 + + SupportedPlatform + ios + SupportedPlatformVariant + simulator + + + CFBundlePackageType + XFWK + XCFrameworkFormatVersion + 1.0 + + diff --git a/Dependencies/connectiq-mobile-sdk-ios-1.4/ConnectIQ.xcframework/ios-armv7_arm64/ConnectIQ.framework/ConnectIQ b/Dependencies/connectiq-mobile-sdk-ios-1.4/ConnectIQ.xcframework/ios-armv7_arm64/ConnectIQ.framework/ConnectIQ new file mode 100755 index 0000000000000000000000000000000000000000..2874b6d2250fd54fa75bbbfaac082acf82493a8a GIT binary patch literal 6022892 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%0AW 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