Procházet zdrojové kódy

Release/0.1.18 (#40)

* Fix basal rate rounding

* units migration p1

* units migration p2

* Log app cycle

* reset pump updating state when deleted

* clear bolus reporter from DeviceManager

* Remote announcements refctored

* Bump version
Ivan před 5 roky
rodič
revize
d9eccdf1b7

+ 4 - 0
FreeAPS.xcodeproj/project.pbxproj

@@ -164,6 +164,7 @@
 		38A0363B25ECF07E00FCBB52 /* GlucoseStorage.swift in Sources */ = {isa = PBXBuildFile; fileRef = 38A0363A25ECF07E00FCBB52 /* GlucoseStorage.swift */; };
 		38A0364225ED069400FCBB52 /* TempBasal.swift in Sources */ = {isa = PBXBuildFile; fileRef = 38A0364125ED069400FCBB52 /* TempBasal.swift */; };
 		38A13D3225E28B4B00EAA382 /* PumpHistoryEvent.swift in Sources */ = {isa = PBXBuildFile; fileRef = 38A13D3125E28B4B00EAA382 /* PumpHistoryEvent.swift */; };
+		38A43598262E0E4900E80935 /* FetchAnnouncementsManager.swift in Sources */ = {isa = PBXBuildFile; fileRef = 38A43597262E0E4900E80935 /* FetchAnnouncementsManager.swift */; };
 		38A504A425DD9C4000C5B9E8 /* UserDefaultsExtensions.swift in Sources */ = {isa = PBXBuildFile; fileRef = 38A5049125DD9C4000C5B9E8 /* UserDefaultsExtensions.swift */; };
 		38A9260525F012D8009E3739 /* CarbRatios.swift in Sources */ = {isa = PBXBuildFile; fileRef = 38A9260425F012D8009E3739 /* CarbRatios.swift */; };
 		38AAF85525FFF846004AF583 /* CurrentGlucoseView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 38AAF85425FFF846004AF583 /* CurrentGlucoseView.swift */; };
@@ -448,6 +449,7 @@
 		38A0363A25ECF07E00FCBB52 /* GlucoseStorage.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = GlucoseStorage.swift; sourceTree = "<group>"; };
 		38A0364125ED069400FCBB52 /* TempBasal.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = TempBasal.swift; sourceTree = "<group>"; };
 		38A13D3125E28B4B00EAA382 /* PumpHistoryEvent.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = PumpHistoryEvent.swift; sourceTree = "<group>"; };
+		38A43597262E0E4900E80935 /* FetchAnnouncementsManager.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = FetchAnnouncementsManager.swift; sourceTree = "<group>"; };
 		38A5049125DD9C4000C5B9E8 /* UserDefaultsExtensions.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = UserDefaultsExtensions.swift; sourceTree = "<group>"; };
 		38A9260425F012D8009E3739 /* CarbRatios.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = CarbRatios.swift; sourceTree = "<group>"; };
 		38AAF85425FFF846004AF583 /* CurrentGlucoseView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = CurrentGlucoseView.swift; sourceTree = "<group>"; };
@@ -970,6 +972,7 @@
 			children = (
 				3811DF0F25CAAAE200A708ED /* APSManager.swift */,
 				38BF021E25E7F0DE00579895 /* DeviceDataManager.swift */,
+				38A43597262E0E4900E80935 /* FetchAnnouncementsManager.swift */,
 				38DAB289260D349500F74C1A /* FetchGlucoseManager.swift */,
 				38192E06261BA9960094D973 /* FetchTreatmentsManager.swift */,
 				38A504F625DDA0E200C5B9E8 /* Extensions */,
@@ -1758,6 +1761,7 @@
 				38E98A3025F52FF700C0CED0 /* Config.swift in Sources */,
 				BD2B464E0745FBE7B79913F4 /* NightscoutConfigProvider.swift in Sources */,
 				9825E5E923F0B8FA80C8C7C7 /* NightscoutConfigViewModel.swift in Sources */,
+				38A43598262E0E4900E80935 /* FetchAnnouncementsManager.swift in Sources */,
 				642F76A05A4FF530463A9FD0 /* NightscoutConfigRootView.swift in Sources */,
 				3340E0D14D4701342D459C95 /* PumpConfigBuilder.swift in Sources */,
 				AD3D2CD42CD01B9EB8F26522 /* PumpConfigDataFlow.swift in Sources */,

+ 1 - 1
FreeAPS/Resources/Config.xcconfig

@@ -1 +1 @@
-BUILD_VERSION = 0.1.17
+BUILD_VERSION = 0.1.18

+ 41 - 37
FreeAPS/Sources/APS/APSManager.swift

@@ -24,6 +24,7 @@ protocol APSManager {
     func roundBolus(amount: Decimal) -> Decimal
     var lastError: CurrentValueSubject<Error?, Never> { get }
     func cancelBolus()
+    func enactAnnouncement(_ announcement: Announcement)
 }
 
 enum APSError: LocalizedError {
@@ -110,7 +111,7 @@ final class BaseAPSManager: APSManager, Injectable {
         deviceDataManager.recommendsLoop
             .receive(on: processQueue)
             .sink { [weak self] in
-                self?.fetchAndLoop()
+                self?.loop()
             }
             .store(in: &lifetime)
         pumpManager?.addStatusObserver(self, queue: processQueue)
@@ -125,8 +126,12 @@ final class BaseAPSManager: APSManager, Injectable {
 
         deviceDataManager.bolusTrigger
             .receive(on: processQueue)
-            .sink {
-                self.createBolusReporter()
+            .sink { bolusing in
+                if bolusing {
+                    self.createBolusReporter()
+                } else {
+                    self.clearBolusReporter()
+                }
             }
             .store(in: &lifetime)
     }
@@ -135,26 +140,6 @@ final class BaseAPSManager: APSManager, Injectable {
         deviceDataManager.heartbeat(date: date, force: force)
     }
 
-    private func fetchAndLoop() {
-        if settings.allowAnnouncements {
-            nightscout.fetchAnnouncements()
-                .sink { [weak self] in
-                    guard let self = self else { return }
-                    guard self.pumpManager != nil,
-                          let recent = self.announcementsStorage.recent(),
-                          recent.action != nil
-                    else {
-                        self.loop()
-                        return
-                    }
-                    self.enactAnnouncement(recent)
-                }
-                .store(in: &lifetime)
-        } else {
-            loop()
-        }
-    }
-
     private func loop() {
         debug(.apsManager, "Starting loop")
         isLooping.send(true)
@@ -370,46 +355,60 @@ final class BaseAPSManager: APSManager, Injectable {
         openAPS.autotune().eraseToAnyPublisher()
     }
 
-    private func enactAnnouncement(_ announcement: Announcement) {
+    func enactAnnouncement(_ announcement: Announcement) {
         guard let action = announcement.action else {
-            debug(.apsManager, "Invalid Announcement action")
+            warning(.apsManager, "Invalid Announcement action")
             return
         }
+
+        guard let pump = pumpManager else {
+            warning(.apsManager, "Pump is not set")
+            return
+        }
+
+        debug(.apsManager, "Start enact announcement: \(action)")
+
         switch action {
         case let .bolus(amount):
             guard verifyStatus() else {
                 return
             }
-            pumpManager?.enactBolus(units: Double(amount), automatic: false) { result in
+            let roundedAmount = pump.roundToSupportedBolusVolume(units: Double(amount))
+            pump.enactBolus(units: roundedAmount, automatic: false) { result in
                 switch result {
                 case .success:
                     debug(.apsManager, "Announcement Bolus succeeded")
                     self.announcementsStorage.storeAnnouncements([announcement], enacted: true)
                 case let .failure(error):
-                    debug(.apsManager, "Announcement Bolus failed with error: \(error.localizedDescription)")
+                    warning(.apsManager, "Announcement Bolus failed with error: \(error.localizedDescription)")
                 }
             }
         case let .pump(pumpAction):
             switch pumpAction {
             case .suspend:
-                guard verifyStatus() else {
+                guard verifyStatus(), !pump.status.pumpStatus.suspended else {
                     return
                 }
-                pumpManager?.suspendDelivery { error in
+                pump.suspendDelivery { error in
                     if let error = error {
                         debug(.apsManager, "Pump not suspended by Announcement: \(error.localizedDescription)")
                     } else {
                         debug(.apsManager, "Pump suspended by Announcement")
                         self.announcementsStorage.storeAnnouncements([announcement], enacted: true)
+                        self.nightscout.uploadStatus()
                     }
                 }
             case .resume:
-                pumpManager?.resumeDelivery { error in
+                guard pump.status.pumpStatus.suspended else {
+                    return
+                }
+                pump.resumeDelivery { error in
                     if let error = error {
-                        debug(.apsManager, "Pump not resumed by Announcement: \(error.localizedDescription)")
+                        warning(.apsManager, "Pump not resumed by Announcement: \(error.localizedDescription)")
                     } else {
                         debug(.apsManager, "Pump resumed by Announcement")
                         self.announcementsStorage.storeAnnouncements([announcement], enacted: true)
+                        self.nightscout.uploadStatus()
                     }
                 }
             }
@@ -418,16 +417,17 @@ final class BaseAPSManager: APSManager, Injectable {
             debug(.apsManager, "Closed loop \(closedLoop) by Announcement")
             announcementsStorage.storeAnnouncements([announcement], enacted: true)
         case let .tempbasal(rate, duration):
-            guard verifyStatus() else {
+            guard verifyStatus(), !settings.closedLoop else {
                 return
             }
-            pumpManager?.enactTempBasal(unitsPerHour: Double(rate), for: TimeInterval(duration) * 60) { result in
+            let roundedRate = pump.roundToSupportedBasalRate(unitsPerHour: Double(rate))
+            pump.enactTempBasal(unitsPerHour: roundedRate, for: TimeInterval(duration) * 60) { result in
                 switch result {
                 case .success:
                     debug(.apsManager, "Announcement TempBasal succeeded")
                     self.announcementsStorage.storeAnnouncements([announcement], enacted: true)
                 case let .failure(error):
-                    debug(.apsManager, "Announcement TempBasal failed with error: \(error.localizedDescription)")
+                    warning(.apsManager, "Announcement TempBasal failed with error: \(error.localizedDescription)")
                 }
             }
         }
@@ -543,6 +543,12 @@ final class BaseAPSManager: APSManager, Injectable {
         bolusReporter = pumpManager?.createBolusProgressReporter(reportingOn: processQueue)
         bolusReporter?.addObserver(self)
     }
+
+    private func clearBolusReporter() {
+        bolusReporter?.removeObserver(self)
+        bolusReporter = nil
+        bolusProgress.send(nil)
+    }
 }
 
 private extension PumpManager {
@@ -629,9 +635,7 @@ extension BaseAPSManager: DoseProgressObserver {
     func doseProgressReporterDidUpdate(_ doseProgressReporter: DoseProgressReporter) {
         bolusProgress.send(Decimal(doseProgressReporter.progress.percentComplete))
         if doseProgressReporter.progress.isComplete {
-            bolusReporter?.removeObserver(self)
-            bolusReporter = nil
-            bolusProgress.send(nil)
+            clearBolusReporter()
         }
     }
 }

+ 8 - 5
FreeAPS/Sources/APS/DeviceDataManager.swift

@@ -13,7 +13,7 @@ protocol DeviceDataManager {
     var pumpManager: PumpManagerUI? { get set }
     var pumpDisplayState: CurrentValueSubject<PumpDisplayState?, Never> { get }
     var recommendsLoop: PassthroughSubject<Void, Never> { get }
-    var bolusTrigger: PassthroughSubject<Void, Never> { get }
+    var bolusTrigger: PassthroughSubject<Bool, Never> { get }
     var errorSubject: PassthroughSubject<Error, Never> { get }
     var pumpName: CurrentValueSubject<String, Never> { get }
     var pumpExpiresAtDate: CurrentValueSubject<Date?, Never> { get }
@@ -45,8 +45,9 @@ final class BaseDeviceDataManager: DeviceDataManager, Injectable {
         .distantPast
 
     let recommendsLoop = PassthroughSubject<Void, Never>()
-    let bolusTrigger = PassthroughSubject<Void, Never>()
+    let bolusTrigger = PassthroughSubject<Bool, Never>()
     let errorSubject = PassthroughSubject<Error, Never>()
+    let pumpNewStatus = PassthroughSubject<Void, Never>()
 
     var pumpManager: PumpManagerUI? {
         didSet {
@@ -185,7 +186,9 @@ extension BaseDeviceDataManager: PumpManagerDelegate {
         debug(.deviceManager, "New pump status Basal: \(String(describing: status.basalDeliveryState))")
 
         if case .inProgress = status.bolusState {
-            bolusTrigger.send()
+            bolusTrigger.send(true)
+        } else {
+            bolusTrigger.send(false)
         }
 
         let batteryPercent = Int((status.pumpBatteryChargeRemaining ?? 1) * 100)
@@ -218,6 +221,7 @@ extension BaseDeviceDataManager: PumpManagerDelegate {
 
     func pumpManagerWillDeactivate(_: PumpManager) {
         pumpManager = nil
+        pumpUpdateInProgress = false
     }
 
     func pumpManager(_: PumpManager, didUpdatePumpRecordsBasalProfileStartEvents _: Bool) {}
@@ -315,7 +319,7 @@ extension BaseDeviceDataManager: DeviceManagerDelegate {
         message: String,
         completion _: ((Error?) -> Void)?
     ) {
-        debug(.deviceManager, message)
+        debug(.deviceManager, "Device message: \(message)")
     }
 }
 
@@ -323,7 +327,6 @@ extension BaseDeviceDataManager: DeviceManagerDelegate {
 
 extension BaseDeviceDataManager: AlertPresenter {
     func issueAlert(_: Alert) {}
-
     func retractAlert(identifier _: Alert.Identifier) {}
 }
 

+ 47 - 0
FreeAPS/Sources/APS/FetchAnnouncementsManager.swift

@@ -0,0 +1,47 @@
+import Combine
+import Foundation
+import SwiftDate
+import Swinject
+
+protocol FetchAnnouncementsManager {}
+
+final class BaseFetchAnnouncementsManager: FetchAnnouncementsManager, Injectable {
+    private let processQueue = DispatchQueue(label: "BaseFetchAnnouncementsManager.processQueue")
+    @Injected() var announcementsStorage: AnnouncementsStorage!
+    @Injected() var nightscoutManager: NightscoutManager!
+    @Injected() var apsManager: APSManager!
+    @Injected() var settingsManager: SettingsManager!
+
+    private var lifetime = Set<AnyCancellable>()
+    private let timer = DispatchTimer(timeInterval: 1.minutes.timeInterval)
+
+    init(resolver: Resolver) {
+        injectServices(resolver)
+        subscribe()
+    }
+
+    private func subscribe() {
+        timer.publisher
+            .receive(on: processQueue)
+            .flatMap { _ -> AnyPublisher<[Announcement], Never> in
+                guard self.settingsManager.settings.allowAnnouncements else {
+                    return Just([]).eraseToAnyPublisher()
+                }
+                debug(.nightscout, "FetchAnnouncementsManager heartbeat")
+                debug(.nightscout, "Start fetching announcements")
+                return self.nightscoutManager.fetchAnnouncements()
+            }
+            .sink { announcements in
+                guard announcements.filter({ $0.createdAt > self.announcementsStorage.syncDate() }).isNotEmpty else { return }
+                self.announcementsStorage.storeAnnouncements(announcements, enacted: false)
+                if self.settingsManager.settings.allowAnnouncements, let recent = self.announcementsStorage.recent(),
+                   recent.action != nil
+                {
+                    debug(.nightscout, "New announcements found")
+                    self.apsManager.enactAnnouncement(recent)
+                }
+            }
+            .store(in: &lifetime)
+        timer.resume()
+    }
+}

+ 4 - 4
FreeAPS/Sources/APS/Storage/AnnouncementsStorage.swift

@@ -34,12 +34,12 @@ final class BaseAnnouncementsStorage: AnnouncementsStorage, Injectable {
     }
 
     func syncDate() -> Date {
-        guard let events = storage.retrieve(OpenAPS.FreeAPS.announcements, as: [Announcement].self),
-              let recent = events.filter({ $0.enteredBy != Announcement.remote }).first
+        guard let events = storage.retrieve(OpenAPS.FreeAPS.announcementsEnacted, as: [Announcement].self),
+              let recentEnacted = events.filter({ $0.enteredBy == Announcement.remote }).first
         else {
-            return Date().addingTimeInterval(-1.days.timeInterval)
+            return Date().addingTimeInterval(-Config.recentInterval)
         }
-        return recent.createdAt.addingTimeInterval(-6.minutes.timeInterval)
+        return recentEnacted.createdAt.addingTimeInterval(Config.recentInterval)
     }
 
     func recent() -> Announcement? {

+ 19 - 2
FreeAPS/Sources/Application/FreeAPSApp.swift

@@ -19,6 +19,8 @@ private extension Swinject.Resolver {
 }
 
 @main struct FreeAPSApp: App {
+    @Environment(\.scenePhase) var scenePhase
+
     static let resolver = Container(defaultObjectScope: .container) { container in
         for dep in dependencies {
             dep.register(container: container)
@@ -31,14 +33,29 @@ private extension Swinject.Resolver {
         _ = resolver.resolve(APSManager.self)!
         _ = resolver.resolve(FetchGlucoseManager.self)!
         _ = resolver.resolve(FetchTreatmentsManager.self)!
+        _ = resolver.resolve(FetchAnnouncementsManager.self)!
     }
 
-    var body: some Scene {
+    init() {
         FreeAPSApp.resolver.setup()
         FreeAPSApp.loadServices()
+    }
 
-        return WindowGroup {
+    var body: some Scene {
+        WindowGroup {
             Main.Builder(resolver: FreeAPSApp.resolver).buildView()
         }
+        .onChange(of: scenePhase) { newScenePhase in
+            switch newScenePhase {
+            case .active:
+                debug(.default, "APPLICATION is active")
+            case .inactive:
+                debug(.default, "APPLICATION is inactive")
+            case .background:
+                debug(.default, "APPLICATION is in background")
+            @unknown default:
+                debug(.default, "APPLICATION: Received an unexpected scenePhase.")
+            }
+        }
     }
 }

+ 1 - 0
FreeAPS/Sources/Containers/APSContainer.swift

@@ -9,5 +9,6 @@ enum APSContainer: DependeciesContainer {
         container.register(APSManager.self) { _ in BaseAPSManager(resolver: resolver) }
         container.register(FetchGlucoseManager.self) { _ in BaseFetchGlucoseManager(resolver: resolver) }
         container.register(FetchTreatmentsManager.self) { _ in BaseFetchTreatmentsManager(resolver: resolver) }
+        container.register(FetchAnnouncementsManager.self) { _ in BaseFetchAnnouncementsManager(resolver: resolver) }
     }
 }

+ 1 - 1
FreeAPS/Sources/Modules/BasalProfileEditor/BasalProfileEditorDataFlow.swift

@@ -26,6 +26,6 @@ enum BasalProfileEditor {
 
 protocol BasalProfileEditorProvider: Provider {
     var profile: [BasalProfileEntry] { get }
-    var supportedBasalRates: [Double]? { get }
+    var supportedBasalRates: [Decimal]? { get }
     func saveProfile(_ profile: [BasalProfileEntry]) -> AnyPublisher<Void, Error>
 }

+ 2 - 2
FreeAPS/Sources/Modules/BasalProfileEditor/BasalProfileEditorProvider.swift

@@ -12,8 +12,8 @@ extension BasalProfileEditor {
                 ?? []
         }
 
-        var supportedBasalRates: [Double]? {
-            deviceManager.pumpManager?.supportedBasalRates
+        var supportedBasalRates: [Decimal]? {
+            deviceManager.pumpManager?.supportedBasalRates.map { Decimal($0) }
         }
 
         func saveProfile(_ profile: [BasalProfileEntry]) -> AnyPublisher<Void, Error> {

+ 4 - 4
FreeAPS/Sources/Modules/BasalProfileEditor/BasalProfileEditorViewModel.swift

@@ -7,7 +7,7 @@ extension BasalProfileEditor {
 
         let timeValues = stride(from: 0.0, to: 1.days.timeInterval, by: 30.minutes.timeInterval).map { $0 }
 
-        private(set) var rateValues: [Double] = []
+        private(set) var rateValues: [Decimal] = []
 
         var canAdd: Bool {
             guard let lastItem = items.last else { return true }
@@ -15,10 +15,10 @@ extension BasalProfileEditor {
         }
 
         override func subscribe() {
-            rateValues = provider.supportedBasalRates ?? stride(from: 0.05, to: 10.01, by: 0.05).map { $0 }
+            rateValues = provider.supportedBasalRates ?? stride(from: 0.05, to: 10.01, by: 0.05).map { Decimal($0) }
             items = provider.profile.map { value in
                 let timeIndex = timeValues.firstIndex(of: Double(value.minutes * 60)) ?? 0
-                let rateIndex = rateValues.firstIndex(of: Double(value.rate)) ?? 0
+                let rateIndex = rateValues.firstIndex(of: value.rate) ?? 0
                 return Item(rateIndex: rateIndex, timeIndex: timeIndex)
             }
         }
@@ -44,7 +44,7 @@ extension BasalProfileEditor {
                 fotmatter.dateFormat = "HH:mm:ss"
                 let date = Date(timeIntervalSince1970: self.timeValues[item.timeIndex])
                 let minutes = Int(date.timeIntervalSince1970 / 60)
-                let rate = Decimal(self.rateValues[item.rateIndex])
+                let rate = self.rateValues[item.rateIndex]
                 return BasalProfileEntry(start: fotmatter.string(from: date), minutes: minutes, rate: rate)
             }
             provider.saveProfile(profile)

+ 2 - 1
FreeAPS/Sources/Modules/Home/HomeViewModel.swift

@@ -42,7 +42,7 @@ extension Home {
         @Published var bolusProgress: Decimal?
 
         @Published var allowManualTemp = false
-        private(set) var units: GlucoseUnits = .mmolL
+        @Published var units: GlucoseUnits = .mmolL
 
         override func subscribe() {
             setupGlucose()
@@ -292,6 +292,7 @@ extension Home.ViewModel:
     func settingsDidChange(_ settings: FreeAPSSettings) {
         allowManualTemp = !settings.closedLoop
         closedLoop = settingsManager.settings.closedLoop
+        units = settingsManager.settings.units
     }
 
     func pumpHistoryDidUpdate(_: [PumpHistoryEvent]) {

+ 1 - 1
FreeAPS/Sources/Modules/Home/View/Chart/MainChartView.swift

@@ -45,7 +45,7 @@ struct MainChartView: View {
     @Binding var tempTargets: [TempTarget]
     @Binding var carbs: [CarbsEntry]
     @Binding var timerDate: Date
-    let units: GlucoseUnits
+    @Binding var units: GlucoseUnits
 
     @State var didAppearTrigger = false
     @State private var glucoseDots: [CGRect] = []

+ 1 - 1
FreeAPS/Sources/Modules/Home/View/Header/CurrentGlucoseView.swift

@@ -3,7 +3,7 @@ import SwiftUI
 struct CurrentGlucoseView: View {
     @Binding var recentGlucose: BloodGlucose?
     @Binding var delta: Int?
-    let units: GlucoseUnits
+    @Binding var units: GlucoseUnits
 
     private var glucoseFormatter: NumberFormatter {
         let formatter = NumberFormatter()

+ 2 - 2
FreeAPS/Sources/Modules/Home/View/HomeRootView.swift

@@ -46,7 +46,7 @@ extension Home {
                 CurrentGlucoseView(
                     recentGlucose: $viewModel.recentGlucose,
                     delta: $viewModel.glucoseDelta,
-                    units: viewModel.units
+                    units: $viewModel.units
                 )
                 .onTapGesture {
                     viewModel.openCGM()
@@ -202,7 +202,7 @@ extension Home {
                         tempTargets: $viewModel.tempTargets,
                         carbs: $viewModel.carbs,
                         timerDate: $viewModel.timerDate,
-                        units: viewModel.units
+                        units: $viewModel.units
                     )
                     .padding(.bottom)
                     .modal(for: .dataTable, from: self)

+ 1 - 0
FreeAPS/Sources/Modules/PreferencesEditor/PreferencesEditorDataFlow.swift

@@ -28,6 +28,7 @@ enum PreferencesEditor {
 protocol PreferencesEditorProvider: Provider {
     var preferences: Preferences { get }
     func savePreferences(_ preferences: Preferences)
+    func migrateUnits()
 }
 
 protocol PreferencesSettable: AnyObject {

+ 68 - 0
FreeAPS/Sources/Modules/PreferencesEditor/PreferencesEditorProvider.swift

@@ -16,5 +16,73 @@ extension PreferencesEditor {
                 self.storage.save(prefs, as: OpenAPS.Settings.preferences)
             }
         }
+
+        func migrateUnits() {
+            migrateTargets()
+            migrateISF()
+        }
+
+        private func migrateTargets() {
+            let profile = storage.retrieve(OpenAPS.Settings.bgTargets, as: BGTargets.self)
+                ?? BGTargets(from: OpenAPS.defaults(for: OpenAPS.Settings.bgTargets))
+                ?? BGTargets(units: .mmolL, userPrefferedUnits: .mmolL, targets: [])
+
+            let units = settingsManager.settings.units
+            guard units != profile.units else { return }
+
+            let targets = profile.targets.map { target -> BGTargetEntry in
+                if units == .mmolL {
+                    return BGTargetEntry(
+                        low: Decimal(round(Double(target.low.asMmolL) * 10) / 10),
+                        high: Decimal(round(Double(target.high.asMmolL) * 10) / 10),
+                        start: target.start,
+                        offset: target.offset
+                    )
+                } else {
+                    return BGTargetEntry(
+                        low: Decimal(round(Double(target.low.asMgdL))),
+                        high: Decimal(round(Double(target.high.asMgdL))),
+                        start: target.start,
+                        offset: target.offset
+                    )
+                }
+            }
+
+            let newProfile = BGTargets(units: units, userPrefferedUnits: units, targets: targets)
+            storage.save(newProfile, as: OpenAPS.Settings.bgTargets)
+        }
+
+        private func migrateISF() {
+            let profile = storage.retrieve(OpenAPS.Settings.insulinSensitivities, as: InsulinSensitivities.self)
+                ?? InsulinSensitivities(from: OpenAPS.defaults(for: OpenAPS.Settings.insulinSensitivities))
+                ?? InsulinSensitivities(
+                    units: .mmolL,
+                    userPrefferedUnits: .mmolL,
+                    sensitivities: []
+                )
+            let units = settingsManager.settings.units
+            guard units != profile.units else { return }
+
+            let sensitivities = profile.sensitivities.map { item -> InsulinSensitivityEntry in
+
+                if units == .mmolL {
+                    return InsulinSensitivityEntry(
+                        sensitivity: Decimal(round(Double(item.sensitivity.asMmolL) * 10) / 10),
+                        offset: item.offset,
+                        start: item.start
+                    )
+                } else {
+                    return InsulinSensitivityEntry(
+                        sensitivity: Decimal(round(Double(item.sensitivity.asMgdL))),
+                        offset: item.offset,
+                        start: item.start
+                    )
+                }
+            }
+
+            let newProfile = InsulinSensitivities(units: units, userPrefferedUnits: units, sensitivities: sensitivities)
+
+            storage.save(newProfile, as: OpenAPS.Settings.insulinSensitivities)
+        }
     }
 }

+ 1 - 0
FreeAPS/Sources/Modules/PreferencesEditor/PreferencesEditorViewModel.swift

@@ -33,6 +33,7 @@ extension PreferencesEditor {
                 .removeDuplicates()
                 .sink { [weak self] index in
                     self?.settingsManager.settings.units = index == 0 ? .mgdL : .mmolL
+                    self?.provider.migrateUnits()
                 }
                 .store(in: &lifetime)
 

+ 4 - 7
FreeAPS/Sources/Services/Network/NightscoutManager.swift

@@ -7,7 +7,7 @@ protocol NightscoutManager {
     func fetchGlucose() -> AnyPublisher<[BloodGlucose], Never>
     func fetchCarbs() -> AnyPublisher<[CarbsEntry], Never>
     func fetchTempTargets() -> AnyPublisher<[TempTarget], Never>
-    func fetchAnnouncements() -> AnyPublisher<Void, Never>
+    func fetchAnnouncements() -> AnyPublisher<[Announcement], Never>
     func deleteCarbs(at date: Date)
     func uploadStatus()
     var cgmURL: URL? { get }
@@ -113,18 +113,15 @@ final class BaseNightscoutManager: NightscoutManager, Injectable {
             .eraseToAnyPublisher()
     }
 
-    func fetchAnnouncements() -> AnyPublisher<Void, Never> {
+    func fetchAnnouncements() -> AnyPublisher<[Announcement], Never> {
         guard let nightscout = nightscoutAPI, isNetworkReachable else {
-            return Just(()).eraseToAnyPublisher()
+            return Just([]).eraseToAnyPublisher()
         }
 
         let since = announcementsStorage.syncDate()
         return nightscout.fetchAnnouncement(sinceDate: since)
             .replaceError(with: [])
-            .map {
-                self.announcementsStorage.storeAnnouncements($0, enacted: false)
-                return ()
-            }.eraseToAnyPublisher()
+            .eraseToAnyPublisher()
     }
 
     func deleteCarbs(at date: Date) {