Просмотр исходного кода

refactoring

fix duplicate NS entries

fix duration update of uploaded overrides

fix cancelling of multiple overrides from home view

fix logic in saveCustomOverride function

use async let for better performance

fix superbolus calculation

fix TINS calculation

Co-Authored-By: polscm32 aka Marvout <marvin-polscheit@t-online.de>
polscm32 aka Marvout 1 год назад
Родитель
Сommit
7c09c94eb4

+ 5 - 3
FreeAPS/Sources/APS/Storage/OverrideStorage.swift

@@ -169,7 +169,7 @@ final class BaseOverrideStorage: OverrideStorage, Injectable {
         newOverride.smbIsOff = override.smbIsOff
         newOverride.smbIsOff = override.smbIsOff
         newOverride.name = override.name
         newOverride.name = override.name
         newOverride.isPreset = false // no Preset
         newOverride.isPreset = false // no Preset
-        newOverride.date = Date()
+        newOverride.date = override.date
         newOverride.enabled = override.enabled
         newOverride.enabled = override.enabled
         newOverride.target = override.target
         newOverride.target = override.target
         newOverride.advancedSettings = override.advancedSettings
         newOverride.advancedSettings = override.advancedSettings
@@ -220,7 +220,8 @@ final class BaseOverrideStorage: OverrideStorage, Injectable {
                     eventType: OverrideStored.EventType.nsExercise,
                     eventType: OverrideStored.EventType.nsExercise,
                     createdAt: override.date ?? Date(),
                     createdAt: override.date ?? Date(),
                     enteredBy: NightscoutExercise.local,
                     enteredBy: NightscoutExercise.local,
-                    notes: override.name ?? "Custom Override"
+                    notes: override.name ?? "Custom Override",
+                    id: UUID(uuidString: override.id ?? UUID().uuidString)
                 )
                 )
             }
             }
         }
         }
@@ -248,7 +249,8 @@ final class BaseOverrideStorage: OverrideStorage, Injectable {
                     eventType: OverrideStored.EventType.nsExercise,
                     eventType: OverrideStored.EventType.nsExercise,
                     createdAt: (overrideRun.startDate ?? overrideRun.override?.date) ?? Date(),
                     createdAt: (overrideRun.startDate ?? overrideRun.override?.date) ?? Date(),
                     enteredBy: NightscoutExercise.local,
                     enteredBy: NightscoutExercise.local,
-                    notes: overrideRun.name ?? "Custom Override"
+                    notes: overrideRun.name ?? "Custom Override",
+                    id: overrideRun.id
                 )
                 )
             }
             }
         }
         }

+ 1 - 0
FreeAPS/Sources/Models/NightscoutExercise.swift

@@ -6,6 +6,7 @@ struct NightscoutExercise: JSON, Hashable, Equatable {
     var createdAt: Date
     var createdAt: Date
     var enteredBy: String?
     var enteredBy: String?
     var notes: String?
     var notes: String?
+    var id: UUID?
 
 
     static let local = "Trio"
     static let local = "Trio"
 
 

+ 23 - 7
FreeAPS/Sources/Modules/Bolus/BolusStateModel.swift

@@ -140,25 +140,41 @@ extension Bolus {
         func getCurrentBasal() {
         func getCurrentBasal() {
             let basalEntries = provider.getProfile()
             let basalEntries = provider.getProfile()
             let now = Date()
             let now = Date()
+            let calendar = Calendar.current
             let dateFormatter = DateFormatter()
             let dateFormatter = DateFormatter()
             dateFormatter.dateFormat = "HH:mm:ss"
             dateFormatter.dateFormat = "HH:mm:ss"
+            dateFormatter.timeZone = TimeZone.current
 
 
-            // iterate over basal entries
             for (index, entry) in basalEntries.enumerated() {
             for (index, entry) in basalEntries.enumerated() {
-                guard let entryStartTime = dateFormatter.date(from: entry.start) else { continue }
+                guard let entryTime = dateFormatter.date(from: entry.start) else {
+                    print("Invalid entry start time: \(entry.start)")
+                    continue
+                }
+
+                // Combine the current date with the time from entry.start
+                let entryStartTime = calendar.date(
+                    bySettingHour: calendar.component(.hour, from: entryTime),
+                    minute: calendar.component(.minute, from: entryTime),
+                    second: calendar.component(.second, from: entryTime),
+                    of: now
+                )!
 
 
                 let entryEndTime: Date
                 let entryEndTime: Date
                 if index < basalEntries.count - 1,
                 if index < basalEntries.count - 1,
-                   let nextEntryStartTime = dateFormatter.date(from: basalEntries[index + 1].start)
+                   let nextEntryTime = dateFormatter.date(from: basalEntries[index + 1].start)
                 {
                 {
-                    // end of current entry should equal start of next entry
+                    let nextEntryStartTime = calendar.date(
+                        bySettingHour: calendar.component(.hour, from: nextEntryTime),
+                        minute: calendar.component(.minute, from: nextEntryTime),
+                        second: calendar.component(.second, from: nextEntryTime),
+                        of: now
+                    )!
                     entryEndTime = nextEntryStartTime
                     entryEndTime = nextEntryStartTime
                 } else {
                 } else {
-                    // if it is the last entry use current time as end of entry
-                    entryEndTime = now
+                    // If it's the last entry, use the same start time plus one day as the end time
+                    entryEndTime = calendar.date(byAdding: .day, value: 1, to: entryStartTime)!
                 }
                 }
 
 
-                // proof if current time is between start and end of entry
                 if now >= entryStartTime, now < entryEndTime {
                 if now >= entryStartTime, now < entryEndTime {
                     currentBasal = entry.rate
                     currentBasal = entry.rate
                     break
                     break

+ 47 - 14
FreeAPS/Sources/Modules/Home/HomeStateModel.swift

@@ -72,7 +72,6 @@ extension Home {
         @Published var enactedAndNonEnactedDeterminations: [OrefDetermination] = []
         @Published var enactedAndNonEnactedDeterminations: [OrefDetermination] = []
         @Published var insulinFromPersistence: [PumpEventStored] = []
         @Published var insulinFromPersistence: [PumpEventStored] = []
         @Published var tempBasals: [PumpEventStored] = []
         @Published var tempBasals: [PumpEventStored] = []
-        var boluses: [PumpEventStored] = []
         @Published var suspensions: [PumpEventStored] = []
         @Published var suspensions: [PumpEventStored] = []
         @Published var batteryFromPersistence: [OpenAPS_Battery] = []
         @Published var batteryFromPersistence: [OpenAPS_Battery] = []
         @Published var lastPumpBolus: PumpEventStored?
         @Published var lastPumpBolus: PumpEventStored?
@@ -82,6 +81,8 @@ extension Home {
         let context = CoreDataStack.shared.newTaskContext()
         let context = CoreDataStack.shared.newTaskContext()
         let viewContext = CoreDataStack.shared.persistentContainer.viewContext
         let viewContext = CoreDataStack.shared.persistentContainer.viewContext
 
 
+        typealias PumpEvent = PumpEventStored.EventType
+
         override func subscribe() {
         override func subscribe() {
             setupNotification()
             setupNotification()
             setupGlucoseArray()
             setupGlucoseArray()
@@ -224,7 +225,7 @@ extension Home {
             }
             }
         }
         }
 
 
-        @MainActor func cancelProfile(withID id: NSManagedObjectID) async {
+        @MainActor func cancelOverride(withID id: NSManagedObjectID) async {
             do {
             do {
                 let profileToCancel = try viewContext.existingObject(with: id) as? OverrideStored
                 let profileToCancel = try viewContext.existingObject(with: id) as? OverrideStored
                 profileToCancel?.enabled = false
                 profileToCancel?.enabled = false
@@ -233,25 +234,60 @@ extension Home {
 
 
                 guard viewContext.hasChanges else { return }
                 guard viewContext.hasChanges else { return }
                 try viewContext.save()
                 try viewContext.save()
+
+                Foundation.NotificationCenter.default.post(name: .didUpdateOverrideConfiguration, object: nil)
             } catch {
             } catch {
                 debugPrint("\(DebuggingIdentifiers.failed) \(#file) \(#function) Failed to cancel Profile")
                 debugPrint("\(DebuggingIdentifiers.failed) \(#file) \(#function) Failed to cancel Profile")
             }
             }
         }
         }
 
 
         func calculateTINS() -> String {
         func calculateTINS() -> String {
+            let startTime = calculateStartTime(hours: Int(hours))
+
+            let totalBolus = calculateTotalBolus(from: insulinFromPersistence, since: startTime)
+            let totalBasal = calculateTotalBasal(from: insulinFromPersistence, since: startTime)
+
+            let totalInsulin = totalBolus + totalBasal
+
+            return formatInsulinAmount(totalInsulin)
+        }
+
+        private func calculateStartTime(hours: Int) -> Date {
             let date = Date()
             let date = Date()
             let calendar = Calendar.current
             let calendar = Calendar.current
-            let offset = hours
-
             var offsetComponents = DateComponents()
             var offsetComponents = DateComponents()
-            offsetComponents.hour = -Int(offset)
-            let startTime = calendar.date(byAdding: offsetComponents, to: date)!
+            offsetComponents.hour = -hours
+            return calendar.date(byAdding: offsetComponents, to: date)!
+        }
 
 
-            let bolusesForCurrentDay = boluses.filter { $0.timestamp ?? .distantPast >= startTime }
-            let totalBolus = bolusesForCurrentDay.map { Double(truncating: $0.bolus?.amount ?? 0.0) }.reduce(0.0, +)
-            roundedTotalBolus = Decimal(round(100 * Double(totalBolus)) / 100).formatted()
+        private func calculateTotalBolus(from events: [PumpEventStored], since startTime: Date) -> Double {
+            let bolusEvents = events.filter { $0.timestamp ?? .distantPast >= startTime && $0.type == PumpEvent.bolus.rawValue }
+            return bolusEvents.compactMap { $0.bolus?.amount?.doubleValue }.reduce(0, +)
+        }
+
+        private func calculateTotalBasal(from events: [PumpEventStored], since startTime: Date) -> Double {
+            let basalEvents = events
+                .filter { $0.timestamp ?? .distantPast >= startTime && $0.type == PumpEvent.tempBasal.rawValue }
+                .sorted { $0.timestamp ?? .distantPast < $1.timestamp ?? .distantPast }
+
+            var basalDurations: [Double] = []
+            for (index, basalEntry) in basalEvents.enumerated() {
+                if index + 1 < basalEvents.count {
+                    let nextEntry = basalEvents[index + 1]
+                    let durationInSeconds = nextEntry.timestamp?.timeIntervalSince(basalEntry.timestamp ?? Date()) ?? 0
+                    basalDurations.append(durationInSeconds / 3600) // Conversion to hours
+                }
+            }
+
+            return zip(basalEvents, basalDurations).map { entry, duration in
+                guard let rate = entry.tempBasal?.rate?.doubleValue else { return 0 }
+                return rate * duration
+            }.reduce(0, +)
+        }
 
 
-            return roundedTotalBolus
+        private func formatInsulinAmount(_ amount: Double) -> String {
+            let roundedAmount = Decimal(round(100 * amount) / 100)
+            return roundedAmount.formatted()
         }
         }
 
 
         private func setupPumpSettings() {
         private func setupPumpSettings() {
@@ -925,11 +961,8 @@ extension Home.StateModel {
                 newOverrideRunStored.override = object
                 newOverrideRunStored.override = object
                 newOverrideRunStored.isUploadedToNS = false
                 newOverrideRunStored.isUploadedToNS = false
 
 
-                guard self.viewContext.hasChanges else { return }
-                try self.viewContext.save()
-
             } catch {
             } catch {
-                print(error.localizedDescription)
+                debugPrint("\(DebuggingIdentifiers.failed) \(#file) \(#function) Failed to initialize a new Override Run Object")
             }
             }
         }
         }
     }
     }

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

@@ -238,7 +238,7 @@ extension MainChartView {
                 }
                 }
             }
             }
             .id("MainChart")
             .id("MainChart")
-            .onChange(of: state.boluses) { _ in
+            .onChange(of: state.insulinFromPersistence) { _ in
                 state.roundedTotalBolus = state.calculateTINS()
                 state.roundedTotalBolus = state.calculateTINS()
             }
             }
             .onChange(of: tempTargets) { _ in
             .onChange(of: tempTargets) { _ in

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

@@ -232,7 +232,7 @@ extension Home {
                     /// Do not show the Override anymore
                     /// Do not show the Override anymore
                     Task {
                     Task {
                         guard let objectID = self.latestOverride.first?.objectID else { return }
                         guard let objectID = self.latestOverride.first?.objectID else { return }
-                        await state.cancelProfile(withID: objectID)
+                        await state.cancelOverride(withID: objectID)
                     }
                     }
                 }
                 }
             }
             }
@@ -564,7 +564,7 @@ extension Home {
                             Button("Yes", role: .destructive) {
                             Button("Yes", role: .destructive) {
                                 Task {
                                 Task {
                                     guard let objectID = latestOverride.first?.objectID else { return }
                                     guard let objectID = latestOverride.first?.objectID else { return }
-                                    await state.cancelProfile(withID: objectID)
+                                    await state.cancelOverride(withID: objectID)
                                 }
                                 }
                             }
                             }
                         }, message: { Text("This will change settings back to your normal profile.") }
                         }, message: { Text("This will change settings back to your normal profile.") }

+ 39 - 51
FreeAPS/Sources/Modules/OverrideProfilesConfig/OverrideProfilesStateModel.swift

@@ -94,50 +94,18 @@ extension OverrideProfilesConfig {
 // MARK: - Setup Notifications
 // MARK: - Setup Notifications
 
 
 extension OverrideProfilesConfig.StateModel {
 extension OverrideProfilesConfig.StateModel {
-    /// listens for the notifications sent when the managedObjectContext has saved!
+    // Custom Notification to update View when an Override has been cancelled via Home View
     func setupNotification() {
     func setupNotification() {
         Foundation.NotificationCenter.default.addObserver(
         Foundation.NotificationCenter.default.addObserver(
             self,
             self,
-            selector: #selector(contextDidSave(_:)),
-            name: Notification.Name.NSManagedObjectContextDidSave,
+            selector: #selector(handleOverrideConfigurationUpdate),
+            name: .didUpdateOverrideConfiguration,
             object: nil
             object: nil
         )
         )
-
-        /// listens for notifications sent when a Preset was added
-        Foundation.NotificationCenter.default.addObserver(
-            self,
-            selector: #selector(handlePresetsUpdate),
-            name: .didUpdateOverridePresets,
-            object: nil
-        )
-    }
-
-    @objc private func handlePresetsUpdate() {
-        setupOverridePresetsArray()
     }
     }
 
 
-    /// determine the actions when the context has changed
-    /// its done on a background thread and after that the UI gets updated on the main thread
-    @objc private func contextDidSave(_ notification: Notification) {
-        guard let userInfo = notification.userInfo else { return }
-
-        Task { [weak self] in
-            await self?.processUpdates(userInfo: userInfo)
-        }
-    }
-
-    private func processUpdates(userInfo: [AnyHashable: Any]) async {
-        var objects = Set((userInfo[NSInsertedObjectsKey] as? Set<NSManagedObject>) ?? [])
-        objects.formUnion((userInfo[NSUpdatedObjectsKey] as? Set<NSManagedObject>) ?? [])
-        objects.formUnion((userInfo[NSDeletedObjectsKey] as? Set<NSManagedObject>) ?? [])
-
-        let overrideUpdates = objects.filter { $0 is OverrideStored }
-
-        DispatchQueue.global(qos: .background).async {
-            if overrideUpdates.isNotEmpty {
-                self.updateLatestOverrideConfiguration()
-            }
-        }
+    @objc private func handleOverrideConfigurationUpdate() {
+        updateLatestOverrideConfiguration()
     }
     }
 
 
     // MARK: - Enact Overrides
     // MARK: - Enact Overrides
@@ -152,6 +120,9 @@ extension OverrideProfilesConfig.StateModel {
         do {
         do {
             guard viewContext.hasChanges else { return }
             guard viewContext.hasChanges else { return }
             try viewContext.save()
             try viewContext.save()
+
+            // Update Presets View
+            setupOverridePresetsArray()
         } catch {
         } catch {
             debugPrint(
             debugPrint(
                 "\(DebuggingIdentifiers.failed) \(#file) \(#function) Failed to save after reordering Override Presets with error: \(error.localizedDescription)"
                 "\(DebuggingIdentifiers.failed) \(#file) \(#function) Failed to save after reordering Override Presets with error: \(error.localizedDescription)"
@@ -166,6 +137,7 @@ extension OverrideProfilesConfig.StateModel {
             let overrideToEnact = try viewContext.existingObject(with: id) as? OverrideStored
             let overrideToEnact = try viewContext.existingObject(with: id) as? OverrideStored
             overrideToEnact?.enabled = true
             overrideToEnact?.enabled = true
             overrideToEnact?.date = Date()
             overrideToEnact?.date = Date()
+            overrideToEnact?.isUploadedToNS = false
 
 
             /// Update the 'Cancel Override' button state
             /// Update the 'Cancel Override' button state
             isEnabled = true
             isEnabled = true
@@ -178,6 +150,9 @@ extension OverrideProfilesConfig.StateModel {
 
 
             guard viewContext.hasChanges else { return }
             guard viewContext.hasChanges else { return }
             try viewContext.save()
             try viewContext.save()
+
+            // Update View
+            updateLatestOverrideConfiguration()
         } catch {
         } catch {
             debugPrint("\(DebuggingIdentifiers.failed) \(#file) \(#function) Failed to enact Override Preset")
             debugPrint("\(DebuggingIdentifiers.failed) \(#file) \(#function) Failed to enact Override Preset")
         }
         }
@@ -225,6 +200,9 @@ extension OverrideProfilesConfig.StateModel {
                 // Save the context if there are changes
                 // Save the context if there are changes
                 if self.viewContext.hasChanges {
                 if self.viewContext.hasChanges {
                     try self.viewContext.save()
                     try self.viewContext.save()
+
+                    // Update the View
+                    self.updateLatestOverrideConfiguration()
                 }
                 }
             } catch {
             } catch {
                 debugPrint(
                 debugPrint(
@@ -262,12 +240,17 @@ extension OverrideProfilesConfig.StateModel {
             uamMinutes: uamMinutes
             uamMinutes: uamMinutes
         )
         )
 
 
-        if currentActiveOverride != nil {
-            await disableAllActiveOverrides(except: currentActiveOverride?.objectID, createOverrideRunEntry: true)
-        }
+        // First disable all Overrides
+        await disableAllActiveOverrides(createOverrideRunEntry: true)
+
+        // Then save and activate a new custom Override and reset the State variables
+        async let storeOverride: () = overrideStorage.storeOverride(override: override)
+        async let resetState: () = resetStateVariables()
 
 
-        await overrideStorage.storeOverride(override: override)
-        await resetStateVariables()
+        _ = await (storeOverride, resetState)
+
+        // Update View
+        updateLatestOverrideConfiguration()
     }
     }
 
 
     // Save Presets
     // Save Presets
@@ -296,13 +279,13 @@ extension OverrideProfilesConfig.StateModel {
             uamMinutes: uamMinutes
             uamMinutes: uamMinutes
         )
         )
 
 
-        await overrideStorage.storeOverride(override: preset)
+        async let storeOverride: () = overrideStorage.storeOverride(override: preset)
+        async let resetState: () = resetStateVariables()
 
 
-        // Custom Notification to update Presets View
-        Foundation.NotificationCenter.default.post(name: .didUpdateOverridePresets, object: nil)
+        _ = await (storeOverride, resetState)
 
 
-        // Prevent showing the current config of the recently added Preset
-        await resetStateVariables()
+        // Update Presets View
+        setupOverridePresetsArray()
     }
     }
 
 
     // MARK: - Setup Override Presets Array
     // MARK: - Setup Override Presets Array
@@ -332,8 +315,9 @@ extension OverrideProfilesConfig.StateModel {
 
 
     func invokeOverridePresetDeletion(_ objectID: NSManagedObjectID) async {
     func invokeOverridePresetDeletion(_ objectID: NSManagedObjectID) async {
         await overrideStorage.deleteOverridePreset(objectID)
         await overrideStorage.deleteOverridePreset(objectID)
-        // Custom Notification to update Presets View
-        Foundation.NotificationCenter.default.post(name: .didUpdateOverridePresets, object: nil)
+
+        // Update Presets View
+        setupOverridePresetsArray()
     }
     }
 
 
     // MARK: - Setup the State variables with the last Override configuration
     // MARK: - Setup the State variables with the last Override configuration
@@ -344,8 +328,10 @@ extension OverrideProfilesConfig.StateModel {
     func updateLatestOverrideConfiguration() {
     func updateLatestOverrideConfiguration() {
         Task {
         Task {
             let id = await overrideStorage.loadLatestOverrideConfigurations(fetchLimit: 1)
             let id = await overrideStorage.loadLatestOverrideConfigurations(fetchLimit: 1)
-            await updateLatestOverrideConfigurationOfState(from: id)
-            await setCurrentOverride(from: id)
+            async let updateState: () = updateLatestOverrideConfigurationOfState(from: id)
+            async let setOverride: () = setCurrentOverride(from: id)
+
+            _ = await (updateState, setOverride)
         }
         }
     }
     }
 
 
@@ -403,6 +389,8 @@ extension OverrideProfilesConfig.StateModel {
                 try self.viewContext.save()
                 try self.viewContext.save()
             }
             }
 
 
+            // Update View
+            // TODO: -
             if let overrideToEdit = try viewContext.existingObject(with: duplidateId) as? OverrideStored
             if let overrideToEdit = try viewContext.existingObject(with: duplidateId) as? OverrideStored
             {
             {
                 currentActiveOverride = overrideToEdit
                 currentActiveOverride = overrideToEdit

+ 4 - 1
FreeAPS/Sources/Modules/OverrideProfilesConfig/View/EditOverrideForm.swift

@@ -299,10 +299,13 @@ struct EditOverrideForm: View {
                             Task {
                             Task {
                                 await state.disableAllActiveOverrides(
                                 await state.disableAllActiveOverrides(
                                     except: currentActiveOverride.objectID,
                                     except: currentActiveOverride.objectID,
-                                    createOverrideRunEntry: true
+                                    createOverrideRunEntry: false
                                 )
                                 )
                             }
                             }
                         }
                         }
+
+                        // Update View
+                        state.updateLatestOverrideConfiguration()
                         hasChanges = false
                         hasChanges = false
                         presentationMode.wrappedValue.dismiss()
                         presentationMode.wrappedValue.dismiss()
                     } catch {
                     } catch {

+ 1 - 1
Model/Helper/CustomNotification.swift

@@ -5,5 +5,5 @@ extension Notification.Name {
     static let didPerformBatchUpdate = Notification.Name("didPerformBatchUpdate")
     static let didPerformBatchUpdate = Notification.Name("didPerformBatchUpdate")
     static let didPerformBatchDelete = Notification.Name("didPerformBatchDelete")
     static let didPerformBatchDelete = Notification.Name("didPerformBatchDelete")
     static let didUpdateDetermination = Notification.Name("didUpdateDetermination")
     static let didUpdateDetermination = Notification.Name("didUpdateDetermination")
-    static let didUpdateOverridePresets = Notification.Name("didUpdateOverridePresets")
+    static let didUpdateOverrideConfiguration = Notification.Name("didUpdateOverrideConfiguration")
 }
 }