Bläddra i källkod

Revert "Merge branch 'core-data-sync-trio' into override-update"

This reverts commit 7851f66a4fda243887d5cc0488d22902369397f8.
Robert 1 år sedan
förälder
incheckning
158fa82bcc
53 ändrade filer med 726 tillägg och 812 borttagningar
  1. 4 4
      FreeAPS.xcodeproj/project.pbxproj
  2. 31 0
      FreeAPS/Sources/APS/OpenAPS/OpenAPS.swift
  3. 10 9
      FreeAPS/Sources/APS/Storage/DeterminationStorage.swift
  4. 1 6
      FreeAPS/Sources/Helpers/Color+Extensions.swift
  5. 21 22
      FreeAPS/Sources/Modules/AlgorithmAdvancedSettings/AlgorithmAdvancedSettingsStateModel.swift
  6. 1 1
      FreeAPS/Sources/Modules/AlgorithmAdvancedSettings/View/AlgorithmAdvancedSettingsRootView.swift
  7. 7 8
      FreeAPS/Sources/Modules/AutosensSettings/AutosensSettingsStateModel.swift
  8. 1 1
      FreeAPS/Sources/Modules/AutosensSettings/View/AutosensSettingsRootView.swift
  9. 8 9
      FreeAPS/Sources/Modules/BasalProfileEditor/BasalProfileEditorStateModel.swift
  10. 1 1
      FreeAPS/Sources/Modules/BasalProfileEditor/View/BasalProfileEditorRootView.swift
  11. 103 102
      FreeAPS/Sources/Modules/Bolus/BolusStateModel.swift
  12. 0 1
      FreeAPS/Sources/Modules/Bolus/View/AddMealPresetView.swift
  13. 2 2
      FreeAPS/Sources/Modules/Bolus/View/BolusRootView.swift
  14. 14 23
      FreeAPS/Sources/Modules/Bolus/View/ForecastChart.swift
  15. 1 1
      FreeAPS/Sources/Modules/Bolus/View/MealPresetView.swift
  16. 1 1
      FreeAPS/Sources/Modules/Bolus/View/PopupView.swift
  17. 10 10
      FreeAPS/Sources/Modules/Calibrations/CalibrationsStateModel.swift
  18. 1 1
      FreeAPS/Sources/Modules/Calibrations/View/CalibrationsChart.swift
  19. 2 2
      FreeAPS/Sources/Modules/Calibrations/View/CalibrationsRootView.swift
  20. 18 19
      FreeAPS/Sources/Modules/DataTable/DataTableStateModel.swift
  21. 1 3
      FreeAPS/Sources/Modules/DataTable/View/DataTableRootView.swift
  22. 12 13
      FreeAPS/Sources/Modules/DynamicSettings/DynamicSettingsStateModel.swift
  23. 1 1
      FreeAPS/Sources/Modules/DynamicSettings/View/DynamicSettingsRootView.swift
  24. 5 3
      FreeAPS/Sources/Modules/Home/HomeStateModel+Setup/ChartAxisSetup.swift
  25. 3 8
      FreeAPS/Sources/Modules/Home/HomeStateModel+Setup/ForecastSetup.swift
  26. 85 105
      FreeAPS/Sources/Modules/Home/HomeStateModel.swift
  27. 2 2
      FreeAPS/Sources/Modules/Home/View/Chart/BasalChart.swift
  28. 17 17
      FreeAPS/Sources/Modules/Home/View/Chart/MainChartView.swift
  29. 10 9
      FreeAPS/Sources/Modules/Home/View/Header/CurrentGlucoseView.swift
  30. 6 6
      FreeAPS/Sources/Modules/Home/View/Header/LoopView.swift
  31. 7 7
      FreeAPS/Sources/Modules/Home/View/Header/PumpView.swift
  32. 34 35
      FreeAPS/Sources/Modules/Home/View/HomeRootView.swift
  33. 8 9
      FreeAPS/Sources/Modules/ISFEditor/ISFEditorStateModel.swift
  34. 1 1
      FreeAPS/Sources/Modules/ISFEditor/View/ISFEditorRootView.swift
  35. 4 5
      FreeAPS/Sources/Modules/ManualTempBasal/ManualTempBasalStateModel.swift
  36. 1 1
      FreeAPS/Sources/Modules/ManualTempBasal/View/ManualTempBasalRootView.swift
  37. 66 61
      FreeAPS/Sources/Modules/OverrideConfig/OverrideStateModel.swift
  38. 2 2
      FreeAPS/Sources/Modules/OverrideConfig/View/EditOverrideForm.swift
  39. 1 1
      FreeAPS/Sources/Modules/OverrideConfig/View/OverrideRootView.swift
  40. 18 19
      FreeAPS/Sources/Modules/SMBSettings/SMBSettingsStateModel.swift
  41. 1 1
      FreeAPS/Sources/Modules/SMBSettings/View/SMBSettingsRootView.swift
  42. 4 5
      FreeAPS/Sources/Modules/Snooze/SnoozeStateModel.swift
  43. 1 1
      FreeAPS/Sources/Modules/Snooze/View/SnoozeRootView.swift
  44. 9 10
      FreeAPS/Sources/Modules/Stat/StatStateModel.swift
  45. 16 16
      FreeAPS/Sources/Modules/Stat/View/ChartsView.swift
  46. 46 46
      FreeAPS/Sources/Modules/Stat/View/StatRootView.swift
  47. 12 12
      FreeAPS/Sources/Modules/Stat/View/StatsView.swift
  48. 31 31
      FreeAPS/Sources/Services/HealthKit/HealthKitManager.swift
  49. 15 16
      FreeAPS/Sources/Services/LiveActivity/LiveActivityBridge.swift
  50. 11 10
      FreeAPS/Sources/Services/Network/NightscoutManager.swift
  51. 57 108
      FreeAPS/Sources/Shortcuts/Override/OverridePresetsIntentRequest.swift
  52. 2 6
      FreeAPS/Sources/Views/TextFieldWithToolBar.swift
  53. 0 19
      Model/Helper/CustomNotification.swift

+ 4 - 4
FreeAPS.xcodeproj/project.pbxproj

@@ -4028,7 +4028,7 @@
 				SWIFT_OPTIMIZATION_LEVEL = "-Onone";
 				SWIFT_VERSION = 5.0;
 				TARGETED_DEVICE_FAMILY = 4;
-				WATCHOS_DEPLOYMENT_TARGET = 10;
+				WATCHOS_DEPLOYMENT_TARGET = 8.0;
 			};
 			name = Debug;
 		};
@@ -4065,7 +4065,7 @@
 				SWIFT_OBJC_BRIDGING_HEADER = "Model/Classes+Properties/FreeAPSWatch-Bridging-Header.h";
 				SWIFT_VERSION = 5.0;
 				TARGETED_DEVICE_FAMILY = 4;
-				WATCHOS_DEPLOYMENT_TARGET = 10;
+				WATCHOS_DEPLOYMENT_TARGET = 8.0;
 			};
 			name = Release;
 		};
@@ -4105,7 +4105,7 @@
 				SWIFT_EMIT_LOC_STRINGS = YES;
 				SWIFT_VERSION = 5.0;
 				TARGETED_DEVICE_FAMILY = 4;
-				WATCHOS_DEPLOYMENT_TARGET = 10;
+				WATCHOS_DEPLOYMENT_TARGET = 8.0;
 			};
 			name = Debug;
 		};
@@ -4145,7 +4145,7 @@
 				SWIFT_EMIT_LOC_STRINGS = YES;
 				SWIFT_VERSION = 5.0;
 				TARGETED_DEVICE_FAMILY = 4;
-				WATCHOS_DEPLOYMENT_TARGET = 10;
+				WATCHOS_DEPLOYMENT_TARGET = 8.0;
 			};
 			name = Release;
 		};

+ 31 - 0
FreeAPS/Sources/APS/OpenAPS/OpenAPS.swift

@@ -86,6 +86,37 @@ final class OpenAPS {
 
         // First save the current Determination to Core Data
         await attemptToSaveContext()
+
+        // After that check for changes in iob and cob and if there are any post a custom Notification
+        /// this is currently used to update Live Activity so that it stays up to date and not one loop cycle behind
+        await checkForCobIobUpdate(determination)
+    }
+
+    func checkForCobIobUpdate(_ determination: Determination) async {
+        let results = await CoreDataStack.shared.fetchEntitiesAsync(
+            ofType: OrefDetermination.self,
+            onContext: context,
+            predicate: NSPredicate.predicateFor30MinAgoForDetermination,
+            key: "deliverAt",
+            ascending: false,
+            fetchLimit: 2
+        )
+
+        await context.perform {
+            guard let previousDeterminations = results as? [OrefDetermination] else {
+                return
+            }
+
+            // We need to get the second last Determination for this comparison because we have saved the current Determination already to Core Data
+            if let previousDetermination = previousDeterminations.dropFirst().first {
+                let iobChanged = previousDetermination.iob != self.decimalToNSDecimalNumber(determination.iob)
+                let cobChanged = previousDetermination.cob != Int16(Int(determination.cob ?? 0))
+
+                if iobChanged || cobChanged {
+                    Foundation.NotificationCenter.default.post(name: .didUpdateCobIob, object: nil)
+                }
+            }
+        }
     }
 
     func attemptToSaveContext() async {

+ 10 - 9
FreeAPS/Sources/APS/Storage/DeterminationStorage.swift

@@ -82,11 +82,11 @@ final class BaseDeterminationStorage: DeterminationStorage, Injectable {
         for data: (id: UUID, forecastID: NSManagedObjectID, forecastValueIDs: [NSManagedObjectID]),
         in context: NSManagedObjectContext
     ) async -> (UUID, Forecast?, [ForecastValue]) {
-        return await context.perform {
-            var forecast: Forecast?
-            var forecastValues: [ForecastValue] = []
+        var forecast: Forecast?
+        var forecastValues: [ForecastValue] = []
 
-            do {
+        do {
+            try await context.perform {
                 // Fetch the forecast object
                 forecast = try context.existingObject(with: data.forecastID) as? Forecast
 
@@ -96,13 +96,14 @@ final class BaseDeterminationStorage: DeterminationStorage, Injectable {
                         forecastValues.append(forecastValue)
                     }
                 }
-            } catch {
-                debugPrint(
-                    "\(DebuggingIdentifiers.failed) \(#file) \(#function) Failed to fetch forecast Values with error: \(error.localizedDescription)"
-                )
             }
-            return (data.id, forecast, forecastValues)
+        } catch {
+            debugPrint(
+                "\(DebuggingIdentifiers.failed) \(#file) \(#function) Failed to fetch forecast Values with error: \(error.localizedDescription)"
+            )
         }
+
+        return (data.id, forecast, forecastValues)
     }
 
     // Convert NSDecimalNumber to Decimal

+ 1 - 6
FreeAPS/Sources/Helpers/Color+Extensions.swift

@@ -8,12 +8,7 @@ extension Color {
 
     static let glucose = Color("glucose")
 
-    static let insulin =
-        // workaround for 'No color named 'Insulin' found in asset catalog' error which is most likely a bug
-        Color(
-            UIColor(named: "Insulin") ??
-                UIColor(red: 0.118, green: 0.588, blue: 0.988, alpha: 1.0) // these are RGB of our insulin color
-        )
+    static let insulin = Color("Insulin")
 
     // The loopAccent color is intended to be use as the app accent color.
     public static let loopAccent = Color("accent")

+ 21 - 22
FreeAPS/Sources/Modules/AlgorithmAdvancedSettings/AlgorithmAdvancedSettingsStateModel.swift

@@ -1,29 +1,28 @@
 import Combine
-import Observation
 import SwiftUI
 
 extension AlgorithmAdvancedSettings {
-    @Observable final class StateModel: BaseStateModel<Provider> {
-        @ObservationIgnored @Injected() var settings: SettingsManager!
-        @ObservationIgnored @Injected() var storage: FileStorage!
-        @ObservationIgnored @Injected() var nightscout: NightscoutManager!
-
-        var units: GlucoseUnits = .mgdL
-
-        var maxDailySafetyMultiplier: Decimal = 3
-        var currentBasalSafetyMultiplier: Decimal = 4
-        var useCustomPeakTime: Bool = false
-        var insulinPeakTime: Decimal = 75
-        var skipNeutralTemps: Bool = false
-        var unsuspendIfNoTemp: Bool = false
-        var suspendZerosIOB: Bool = false
-        var min5mCarbimpact: Decimal = 8
-        var autotuneISFAdjustmentFraction: Decimal = 1.0
-        var remainingCarbsFraction: Decimal = 1.0
-        var remainingCarbsCap: Decimal = 90
-        var noisyCGMTargetMultiplier: Decimal = 1.3
-
-        var insulinActionCurve: Decimal = 6
+    final class StateModel: BaseStateModel<Provider> {
+        @Injected() var settings: SettingsManager!
+        @Injected() var storage: FileStorage!
+        @Injected() var nightscout: NightscoutManager!
+
+        @Published var units: GlucoseUnits = .mgdL
+
+        @Published var maxDailySafetyMultiplier: Decimal = 3
+        @Published var currentBasalSafetyMultiplier: Decimal = 4
+        @Published var useCustomPeakTime: Bool = false
+        @Published var insulinPeakTime: Decimal = 75
+        @Published var skipNeutralTemps: Bool = false
+        @Published var unsuspendIfNoTemp: Bool = false
+        @Published var suspendZerosIOB: Bool = false
+        @Published var min5mCarbimpact: Decimal = 8
+        @Published var autotuneISFAdjustmentFraction: Decimal = 1.0
+        @Published var remainingCarbsFraction: Decimal = 1.0
+        @Published var remainingCarbsCap: Decimal = 90
+        @Published var noisyCGMTargetMultiplier: Decimal = 1.3
+
+        @Published var insulinActionCurve: Decimal = 6
 
         var preferences: Preferences {
             settingsManager.preferences

+ 1 - 1
FreeAPS/Sources/Modules/AlgorithmAdvancedSettings/View/AlgorithmAdvancedSettingsRootView.swift

@@ -4,7 +4,7 @@ import Swinject
 extension AlgorithmAdvancedSettings {
     struct RootView: BaseView {
         let resolver: Resolver
-        @State var state = StateModel()
+        @StateObject var state = StateModel()
         @State private var shouldDisplayHint: Bool = false
         @State var hintDetent = PresentationDetent.large
         @State var selectedVerboseHint: String?

+ 7 - 8
FreeAPS/Sources/Modules/AutosensSettings/AutosensSettingsStateModel.swift

@@ -1,16 +1,15 @@
-import Observation
 import SwiftUI
 
 extension AutosensSettings {
-    @Observable final class StateModel: BaseStateModel<Provider> {
-        @ObservationIgnored @Injected() var settings: SettingsManager!
-        @ObservationIgnored @Injected() var storage: FileStorage!
+    final class StateModel: BaseStateModel<Provider> {
+        @Injected() var settings: SettingsManager!
+        @Injected() var storage: FileStorage!
 
-        var units: GlucoseUnits = .mgdL
+        @Published var units: GlucoseUnits = .mgdL
 
-        var autosensMax: Decimal = 1.2
-        var autosensMin: Decimal = 0.7
-        var rewindResetsAutosens: Bool = true
+        @Published var autosensMax: Decimal = 1.2
+        @Published var autosensMin: Decimal = 0.7
+        @Published var rewindResetsAutosens: Bool = true
 
         var preferences: Preferences {
             settingsManager.preferences

+ 1 - 1
FreeAPS/Sources/Modules/AutosensSettings/View/AutosensSettingsRootView.swift

@@ -4,7 +4,7 @@ import Swinject
 extension AutosensSettings {
     struct RootView: BaseView {
         let resolver: Resolver
-        @State var state = StateModel()
+        @StateObject var state = StateModel()
         @State private var shouldDisplayHint: Bool = false
         @State var hintDetent = PresentationDetent.large
         @State var selectedVerboseHint: String?

+ 8 - 9
FreeAPS/Sources/Modules/BasalProfileEditor/BasalProfileEditorStateModel.swift

@@ -1,15 +1,14 @@
-import Observation
 import SwiftUI
 
 extension BasalProfileEditor {
-    @Observable final class StateModel: BaseStateModel<Provider> {
-        @ObservationIgnored @Injected() private var nightscout: NightscoutManager!
-
-        var syncInProgress: Bool = false
-        var initialItems: [Item] = []
-        var items: [Item] = []
-        var total: Decimal = 0.0
-        var showAlert: Bool = false
+    final class StateModel: BaseStateModel<Provider> {
+        @Injected() private var nightscout: NightscoutManager!
+
+        @Published var syncInProgress: Bool = false
+        @Published var initialItems: [Item] = []
+        @Published var items: [Item] = []
+        @Published var total: Decimal = 0.0
+        @Published var showAlert: Bool = false
 
         let timeValues = stride(from: 0.0, to: 1.days.timeInterval, by: 30.minutes.timeInterval).map { $0 }
 

+ 1 - 1
FreeAPS/Sources/Modules/BasalProfileEditor/View/BasalProfileEditorRootView.swift

@@ -4,7 +4,7 @@ import Swinject
 extension BasalProfileEditor {
     struct RootView: BaseView {
         let resolver: Resolver
-        @State var state = StateModel()
+        @StateObject var state = StateModel()
         @State private var editMode = EditMode.inactive
 
         @Environment(\.colorScheme) var colorScheme

+ 103 - 102
FreeAPS/Sources/Modules/Bolus/BolusStateModel.swift

@@ -2,116 +2,117 @@ import Combine
 import CoreData
 import Foundation
 import LoopKit
-import Observation
 import SwiftUI
 import Swinject
 
 extension Bolus {
-    @Observable final class StateModel: BaseStateModel<Provider> {
-        @ObservationIgnored @Injected() var unlockmanager: UnlockManager!
-        @ObservationIgnored @Injected() var apsManager: APSManager!
-        @ObservationIgnored @Injected() var broadcaster: Broadcaster!
-        @ObservationIgnored @Injected() var pumpHistoryStorage: PumpHistoryStorage!
-        @ObservationIgnored @Injected() var settings: SettingsManager!
-        @ObservationIgnored @Injected() var nsManager: NightscoutManager!
-        @ObservationIgnored @Injected() var carbsStorage: CarbsStorage!
-        @ObservationIgnored @Injected() var glucoseStorage: GlucoseStorage!
-        @ObservationIgnored @Injected() var determinationStorage: DeterminationStorage!
-
-        var lowGlucose: Decimal = 70
-        var highGlucose: Decimal = 180
-        var glucoseColorScheme: GlucoseColorScheme = .staticColor
-
-        var predictions: Predictions?
-        var amount: Decimal = 0
-        var insulinRecommended: Decimal = 0
-        var insulinRequired: Decimal = 0
-        var units: GlucoseUnits = .mgdL
-        var threshold: Decimal = 0
-        var maxBolus: Decimal = 0
+    final class StateModel: BaseStateModel<Provider> {
+        @Injected() var unlockmanager: UnlockManager!
+        @Injected() var apsManager: APSManager!
+        @Injected() var broadcaster: Broadcaster!
+        @Injected() var pumpHistoryStorage: PumpHistoryStorage!
+        // added for bolus calculator
+        @Injected() var settings: SettingsManager!
+        @Injected() var nsManager: NightscoutManager!
+        @Injected() var carbsStorage: CarbsStorage!
+        @Injected() var glucoseStorage: GlucoseStorage!
+        @Injected() var determinationStorage: DeterminationStorage!
+
+        @Published var lowGlucose: Decimal = 70
+        @Published var highGlucose: Decimal = 180
+        @Published var glucoseColorScheme: GlucoseColorScheme = .staticColor
+
+        @Published var predictions: Predictions?
+        @Published var amount: Decimal = 0
+        @Published var insulinRecommended: Decimal = 0
+        @Published var insulinRequired: Decimal = 0
+        @Published var units: GlucoseUnits = .mgdL
+        @Published var threshold: Decimal = 0
+        @Published var maxBolus: Decimal = 0
         var maxExternal: Decimal { maxBolus * 3 }
-        var errorString: Decimal = 0
-        var evBG: Decimal = 0
-        var insulin: Decimal = 0
-        var isf: Decimal = 0
-        var error: Bool = false
-        var minGuardBG: Decimal = 0
-        var minDelta: Decimal = 0
-        var expectedDelta: Decimal = 0
-        var minPredBG: Decimal = 0
-        var waitForSuggestion: Bool = false
-        var carbRatio: Decimal = 0
-
-        var addButtonPressed: Bool = false
+        @Published var errorString: Decimal = 0
+        @Published var evBG: Decimal = 0
+        @Published var insulin: Decimal = 0
+        @Published var isf: Decimal = 0
+        @Published var error: Bool = false
+        @Published var minGuardBG: Decimal = 0
+        @Published var minDelta: Decimal = 0
+        @Published var expectedDelta: Decimal = 0
+        @Published var minPredBG: Decimal = 0
+        @Published var waitForSuggestion: Bool = false
+        @Published var carbRatio: Decimal = 0
+
+        @Published var addButtonPressed: Bool = false
 
         var waitForSuggestionInitial: Bool = false
 
-        var target: Decimal = 0
-        var cob: Int16 = 0
-        var iob: Decimal = 0
-
-        var currentBG: Decimal = 0
-        var fifteenMinInsulin: Decimal = 0
-        var deltaBG: Decimal = 0
-        var targetDifferenceInsulin: Decimal = 0
-        var targetDifference: Decimal = 0
-        var wholeCob: Decimal = 0
-        var wholeCobInsulin: Decimal = 0
-        var iobInsulinReduction: Decimal = 0
-        var wholeCalc: Decimal = 0
-        var insulinCalculated: Decimal = 0
-        var fraction: Decimal = 0
-        var basal: Decimal = 0
-        var fattyMeals: Bool = false
-        var fattyMealFactor: Decimal = 0
-        var useFattyMealCorrectionFactor: Bool = false
-        var displayPresets: Bool = true
-
-        var currentBasal: Decimal = 0
-        var currentCarbRatio: Decimal = 0
-        var currentBGTarget: Decimal = 0
-        var currentISF: Decimal = 0
-
-        var sweetMeals: Bool = false
-        var sweetMealFactor: Decimal = 0
-        var useSuperBolus: Bool = false
-        var superBolusInsulin: Decimal = 0
-
-        var meal: [CarbsEntry]?
-        var carbs: Decimal = 0
-        var fat: Decimal = 0
-        var protein: Decimal = 0
-        var note: String = ""
-
-        var date = Date()
-
-        var carbsRequired: Decimal?
-        var useFPUconversion: Bool = false
-        var dish: String = ""
-        var selection: MealPresetStored?
-        var summation: [String] = []
-        var maxCarbs: Decimal = 0
-        var maxFat: Decimal = 0
-        var maxProtein: Decimal = 0
-
-        var id_: String = ""
-        var summary: String = ""
-
-        var externalInsulin: Bool = false
-        var showInfo: Bool = false
-        var glucoseFromPersistence: [GlucoseStored] = []
-        var determination: [OrefDetermination] = []
-        var preprocessedData: [(id: UUID, forecast: Forecast, forecastValue: ForecastValue)] = []
-        var predictionsForChart: Predictions?
-        var simulatedDetermination: Determination?
-        var determinationObjectIDs: [NSManagedObjectID] = []
-
-        var minForecast: [Int] = []
-        var maxForecast: [Int] = []
-        var minCount: Int = 12 // count of Forecasts drawn in 5 min distances, i.e. 12 means a min of 1 hour
-        var forecastDisplayType: ForecastDisplayType = .cone
-        var isSmoothingEnabled: Bool = false
-        var stops: [Gradient.Stop] = []
+        // added for bolus calculator
+        @Published var target: Decimal = 0
+        @Published var cob: Int16 = 0
+        @Published var iob: Decimal = 0
+
+        @Published var currentBG: Decimal = 0
+        @Published var fifteenMinInsulin: Decimal = 0
+        @Published var deltaBG: Decimal = 0
+        @Published var targetDifferenceInsulin: Decimal = 0
+        @Published var targetDifference: Decimal = 0
+        @Published var wholeCob: Decimal = 0
+        @Published var wholeCobInsulin: Decimal = 0
+        @Published var iobInsulinReduction: Decimal = 0
+        @Published var wholeCalc: Decimal = 0
+        @Published var insulinCalculated: Decimal = 0
+        @Published var fraction: Decimal = 0
+        @Published var basal: Decimal = 0
+        @Published var fattyMeals: Bool = false
+        @Published var fattyMealFactor: Decimal = 0
+        @Published var useFattyMealCorrectionFactor: Bool = false
+        @Published var displayPresets: Bool = true
+
+        @Published var currentBasal: Decimal = 0
+        @Published var currentCarbRatio: Decimal = 0
+        @Published var currentBGTarget: Decimal = 0
+        @Published var currentISF: Decimal = 0
+
+        @Published var sweetMeals: Bool = false
+        @Published var sweetMealFactor: Decimal = 0
+        @Published var useSuperBolus: Bool = false
+        @Published var superBolusInsulin: Decimal = 0
+
+        @Published var meal: [CarbsEntry]?
+        @Published var carbs: Decimal = 0
+        @Published var fat: Decimal = 0
+        @Published var protein: Decimal = 0
+        @Published var note: String = ""
+
+        @Published var date = Date()
+
+        @Published var carbsRequired: Decimal?
+        @Published var useFPUconversion: Bool = false
+        @Published var dish: String = ""
+        @Published var selection: MealPresetStored?
+        @Published var summation: [String] = []
+        @Published var maxCarbs: Decimal = 0
+        @Published var maxFat: Decimal = 0
+        @Published var maxProtein: Decimal = 0
+
+        @Published var id_: String = ""
+        @Published var summary: String = ""
+
+        @Published var externalInsulin: Bool = false
+        @Published var showInfo: Bool = false
+        @Published var glucoseFromPersistence: [GlucoseStored] = []
+        @Published var determination: [OrefDetermination] = []
+        @Published var preprocessedData: [(id: UUID, forecast: Forecast, forecastValue: ForecastValue)] = []
+        @Published var predictionsForChart: Predictions?
+        @Published var simulatedDetermination: Determination?
+        @Published var determinationObjectIDs: [NSManagedObjectID] = []
+
+        @Published var minForecast: [Int] = []
+        @Published var maxForecast: [Int] = []
+        @Published var minCount: Int = 12 // count of Forecasts drawn in 5 min distances, i.e. 12 means a min of 1 hour
+        @Published var forecastDisplayType: ForecastDisplayType = .cone
+        @Published var isSmoothingEnabled: Bool = false
+        @Published var stops: [Gradient.Stop] = []
 
         let now = Date.now
 

+ 0 - 1
FreeAPS/Sources/Modules/Bolus/View/AddMealPresetView.swift

@@ -1,6 +1,5 @@
 import CoreData
 import Foundation
-import Observation
 import SwiftUI
 
 struct AddMealPresetView: View {

+ 2 - 2
FreeAPS/Sources/Modules/Bolus/View/BolusRootView.swift

@@ -17,7 +17,7 @@ extension Bolus {
 
         let resolver: Resolver
 
-        @State var state = StateModel()
+        @StateObject var state = StateModel()
 
         @State private var showPresetSheet = false
         @State private var autofocus: Bool = true
@@ -175,7 +175,7 @@ extension Bolus {
                 VStack {
                     List {
                         Section {
-                            ForecastChart(state: state)
+                            ForecastChart(state: state, units: $state.units)
                                 .padding(.vertical)
                         }.listRowBackground(Color.chart)
 

+ 14 - 23
FreeAPS/Sources/Modules/Bolus/View/ForecastChart.swift

@@ -4,8 +4,9 @@ import Foundation
 import SwiftUI
 
 struct ForecastChart: View {
-    var state: Bolus.StateModel
+    @StateObject var state: Bolus.StateModel
     @Environment(\.colorScheme) var colorScheme
+    @Binding var units: GlucoseUnits
 
     @State private var startMarker = Date(timeIntervalSinceNow: -4 * 60 * 60)
 
@@ -22,7 +23,7 @@ struct ForecastChart: View {
         let formatter = NumberFormatter()
         formatter.numberStyle = .decimal
 
-        if state.units == .mmolL {
+        if units == .mmolL {
             formatter.maximumFractionDigits = 1
             formatter.minimumFractionDigits = 1
             formatter.roundingMode = .halfUp
@@ -77,21 +78,11 @@ struct ForecastChart: View {
                 if let simulatedDetermination = state.simulatedDetermination, let eventualBG = simulatedDetermination.eventualBG {
                     HStack {
                         Text(
-                            state.units == .mgdL ? Decimal(eventualBG).description : eventualBG.formattedAsMmolL
+                            (units == .mgdL ? Decimal(eventualBG).description : eventualBG.formattedAsMmolL) + units.rawValue
                         )
-                        .font(.footnote)
-                        .foregroundStyle(.primary)
-                        Text("\(state.units.rawValue)")
-                            .font(.footnote)
-                            .foregroundStyle(.secondary)
                     }
                 } else {
                     Text("---")
-                        .font(.footnote)
-                        .foregroundStyle(.primary)
-                    Text("\(state.units.rawValue)")
-                        .font(.footnote)
-                        .foregroundStyle(.secondary)
                 }
             }
             .font(.footnote)
@@ -118,7 +109,7 @@ struct ForecastChart: View {
         .chartXAxis { forecastChartXAxis }
         .chartXScale(domain: startMarker ... endMarker)
         .chartYAxis { forecastChartYAxis }
-        .chartYScale(domain: state.units == .mgdL ? 0 ... 300 : 0.asMmolL ... 300.asMmolL)
+        .chartYScale(domain: units == .mgdL ? 0 ... 300 : 0.asMmolL ... 300.asMmolL)
         .backport.chartForegroundStyleScale(state: state)
     }
 
@@ -127,8 +118,8 @@ struct ForecastChart: View {
             let glucoseToDisplay = state.units == .mgdL ? Decimal(item.glucose) : Decimal(item.glucose).asMmolL
 
             // low and high glucose is parsed in state to mmol/L; parse it back to mg/dl here for comparison
-            let lowGlucose = state.units == .mgdL ? state.lowGlucose : state.lowGlucose.asMgdL
-            let highGlucose = state.units == .mgdL ? state.highGlucose : state.highGlucose.asMgdL
+            let lowGlucose = units == .mgdL ? state.lowGlucose : state.lowGlucose.asMgdL
+            let highGlucose = units == .mgdL ? state.highGlucose : state.highGlucose.asMgdL
             let targetGlucose = (state.determination.first?.currentTarget ?? state.currentBGTarget as NSDecimalNumber) as Decimal
 
             let pointMarkColor: Color = FreeAPS.getDynamicGlucoseColor(
@@ -177,17 +168,17 @@ struct ForecastChart: View {
 
                 // if distance between respective min and max is 0, provide a default range
                 if yMinMaxDelta == 0 {
-                    let yMinValue = state.units == .mgdL ? Decimal(state.minForecast[index] - 1) :
+                    let yMinValue = units == .mgdL ? Decimal(state.minForecast[index] - 1) :
                         Decimal(state.minForecast[index] - 1)
                         .asMmolL
-                    let yMaxValue = state.units == .mgdL ? Decimal(state.minForecast[index] + 1) :
+                    let yMaxValue = units == .mgdL ? Decimal(state.minForecast[index] + 1) :
                         Decimal(state.minForecast[index] + 1)
                         .asMmolL
 
                     AreaMark(
                         x: .value("Time", xValue <= endMarker ? xValue : endMarker),
-                        yStart: .value("Min Value", state.units == .mgdL ? yMinValue : yMinValue.asMmolL),
-                        yEnd: .value("Max Value", state.units == .mgdL ? yMaxValue : yMaxValue.asMmolL)
+                        yStart: .value("Min Value", units == .mgdL ? yMinValue : yMinValue.asMmolL),
+                        yEnd: .value("Max Value", units == .mgdL ? yMaxValue : yMaxValue.asMmolL)
                     )
                     .foregroundStyle(Color.blue.opacity(0.5))
                     .interpolationMethod(.catmullRom)
@@ -198,8 +189,8 @@ struct ForecastChart: View {
 
                     AreaMark(
                         x: .value("Time", timeForIndex(Int32(index)) <= endMarker ? timeForIndex(Int32(index)) : endMarker),
-                        yStart: .value("Min Value", state.units == .mgdL ? yMinValue : yMinValue.asMmolL),
-                        yEnd: .value("Max Value", state.units == .mgdL ? yMaxValue : yMaxValue.asMmolL)
+                        yStart: .value("Min Value", units == .mgdL ? yMinValue : yMinValue.asMmolL),
+                        yEnd: .value("Max Value", units == .mgdL ? yMaxValue : yMaxValue.asMmolL)
                     )
                     .foregroundStyle(Color.blue.opacity(0.5))
                     .interpolationMethod(.catmullRom)
@@ -224,7 +215,7 @@ struct ForecastChart: View {
                 ForEach(values.indices, id: \.self) { index in
                     LineMark(
                         x: .value("Time", timeForIndex(Int32(index))),
-                        y: .value("Value", state.units == .mgdL ? Decimal(values[index]) : Decimal(values[index]).asMmolL)
+                        y: .value("Value", units == .mgdL ? Decimal(values[index]) : Decimal(values[index]).asMmolL)
                     )
                     .foregroundStyle(by: .value("Prediction Type", name))
                 }

+ 1 - 1
FreeAPS/Sources/Modules/Bolus/View/MealPresetView.swift

@@ -3,7 +3,7 @@ import Foundation
 import SwiftUI
 
 struct MealPresetView: View {
-    @Bindable var state: Bolus.StateModel
+    @StateObject var state: Bolus.StateModel
     @Environment(\.colorScheme) var colorScheme
     @Environment(\.dismiss) var dismiss
     @Environment(\.managedObjectContext) var moc

+ 1 - 1
FreeAPS/Sources/Modules/Bolus/View/PopupView.swift

@@ -1,7 +1,7 @@
 import SwiftUI
 
 struct PopupView: View {
-    var state: Bolus.StateModel
+    @StateObject var state: Bolus.StateModel
     @Environment(\.colorScheme) var colorScheme
 
     private var fractionDigits: Int {

+ 10 - 10
FreeAPS/Sources/Modules/Calibrations/CalibrationsStateModel.swift

@@ -1,21 +1,21 @@
-import Observation
 import SwiftDate
 import SwiftUI
 
 extension Calibrations {
-    @Observable final class StateModel: BaseStateModel<Provider> {
-        @ObservationIgnored @Injected() var glucoseStorage: GlucoseStorage!
-        @ObservationIgnored @Injected() var calibrationService: CalibrationService!
+    final class StateModel: BaseStateModel<Provider> {
+        @Injected() var glucoseStorage: GlucoseStorage!
+        @Injected() var calibrationService: CalibrationService!
 
-        var slope: Double = 1
-        var intercept: Double = 1
-        var newCalibration: Decimal = 0
-        var calibrations: [Calibration] = []
-        var calibrate: (Int) -> Double = { Double($0) }
-        var items: [Item] = []
+        @Published var slope: Double = 1
+        @Published var intercept: Double = 1
+        @Published var newCalibration: Decimal = 0
+        @Published var calibrations: [Calibration] = []
+        @Published var calibrate: (Int) -> Double = { Double($0) }
+        @Published var items: [Item] = []
 
         var units: GlucoseUnits = .mgdL
 
+        // TODO: - test if we need to use the viewContext here
         private let context = CoreDataStack.shared.newTaskContext()
 
         override func subscribe() {

+ 1 - 1
FreeAPS/Sources/Modules/Calibrations/View/CalibrationsChart.swift

@@ -1,7 +1,7 @@
 import SwiftUI
 
 struct CalibrationsChart: View {
-    var state: Calibrations.StateModel
+    @EnvironmentObject var state: Calibrations.StateModel
 
     private var dateFormatter: DateFormatter {
         let formatter = DateFormatter()

+ 2 - 2
FreeAPS/Sources/Modules/Calibrations/View/CalibrationsRootView.swift

@@ -4,7 +4,7 @@ import Swinject
 extension Calibrations {
     struct RootView: BaseView {
         let resolver: Resolver
-        @State var state = StateModel()
+        @StateObject var state = StateModel()
 
         @Environment(\.colorScheme) var colorScheme
         var color: LinearGradient {
@@ -101,7 +101,7 @@ extension Calibrations {
 
                     if state.calibrations.isNotEmpty {
                         Section(header: Text("Chart")) {
-                            CalibrationsChart(state: state)
+                            CalibrationsChart().environmentObject(state)
                                 .frame(minHeight: geo.size.width)
                         }.listRowBackground(Color.chart)
                     }

+ 18 - 19
FreeAPS/Sources/Modules/DataTable/DataTableStateModel.swift

@@ -1,31 +1,30 @@
 import CoreData
 import HealthKit
-import Observation
 import SwiftUI
 
 extension DataTable {
-    @Observable final class StateModel: BaseStateModel<Provider> {
-        @ObservationIgnored @Injected() var broadcaster: Broadcaster!
-        @ObservationIgnored @Injected() var apsManager: APSManager!
-        @ObservationIgnored @Injected() var unlockmanager: UnlockManager!
-        @ObservationIgnored @Injected() private var storage: FileStorage!
-        @ObservationIgnored @Injected() var pumpHistoryStorage: PumpHistoryStorage!
-        @ObservationIgnored @Injected() var glucoseStorage: GlucoseStorage!
-        @ObservationIgnored @Injected() var healthKitManager: HealthKitManager!
-        @ObservationIgnored @Injected() var carbsStorage: CarbsStorage!
+    final class StateModel: BaseStateModel<Provider> {
+        @Injected() var broadcaster: Broadcaster!
+        @Injected() var apsManager: APSManager!
+        @Injected() var unlockmanager: UnlockManager!
+        @Injected() private var storage: FileStorage!
+        @Injected() var pumpHistoryStorage: PumpHistoryStorage!
+        @Injected() var glucoseStorage: GlucoseStorage!
+        @Injected() var healthKitManager: HealthKitManager!
+        @Injected() var carbsStorage: CarbsStorage!
 
         let coredataContext = CoreDataStack.shared.newTaskContext()
 
-        var mode: Mode = .treatments
-        var treatments: [Treatment] = []
-        var glucose: [Glucose] = []
-        var meals: [Treatment] = []
-        var manualGlucose: Decimal = 0
-        var maxBolus: Decimal = 0
-        var waitForSuggestion: Bool = false
+        @Published var mode: Mode = .treatments
+        @Published var treatments: [Treatment] = []
+        @Published var glucose: [Glucose] = []
+        @Published var meals: [Treatment] = []
+        @Published var manualGlucose: Decimal = 0
+        @Published var maxBolus: Decimal = 0
+        @Published var waitForSuggestion: Bool = false
 
-        var insulinEntryDeleted: Bool = false
-        var carbEntryDeleted: Bool = false
+        @Published var insulinEntryDeleted: Bool = false
+        @Published var carbEntryDeleted: Bool = false
 
         var units: GlucoseUnits = .mgdL
 

+ 1 - 3
FreeAPS/Sources/Modules/DataTable/View/DataTableRootView.swift

@@ -5,9 +5,7 @@ import Swinject
 extension DataTable {
     struct RootView: BaseView {
         let resolver: Resolver
-
-        @State var state = StateModel()
-
+        @StateObject var state = StateModel()
         @State private var isRemoveHistoryItemAlertPresented: Bool = false
         @State private var alertTitle: String = ""
         @State private var alertMessage: String = ""

+ 12 - 13
FreeAPS/Sources/Modules/DynamicSettings/DynamicSettingsStateModel.swift

@@ -1,20 +1,19 @@
-import Observation
 import SwiftUI
 
 extension DynamicSettings {
-    @Observable final class StateModel: BaseStateModel<Provider> {
-        @ObservationIgnored @Injected() var settings: SettingsManager!
-        @ObservationIgnored @Injected() var storage: FileStorage!
+    final class StateModel: BaseStateModel<Provider> {
+        @Injected() var settings: SettingsManager!
+        @Injected() var storage: FileStorage!
 
-        var useNewFormula: Bool = false
-        var enableDynamicCR: Bool = false
-        var sigmoid: Bool = false
-        var adjustmentFactor: Decimal = 0.8
-        var adjustmentFactorSigmoid: Decimal = 0.5
-        var weightPercentage: Decimal = 0.65
-        var tddAdjBasal: Bool = false
-        var threshold_setting: Decimal = 60
-        var units: GlucoseUnits = .mgdL
+        @Published var useNewFormula: Bool = false
+        @Published var enableDynamicCR: Bool = false
+        @Published var sigmoid: Bool = false
+        @Published var adjustmentFactor: Decimal = 0.8
+        @Published var adjustmentFactorSigmoid: Decimal = 0.5
+        @Published var weightPercentage: Decimal = 0.65
+        @Published var tddAdjBasal: Bool = false
+        @Published var threshold_setting: Decimal = 60
+        @Published var units: GlucoseUnits = .mgdL
 
         var preferences: Preferences {
             settingsManager.preferences

+ 1 - 1
FreeAPS/Sources/Modules/DynamicSettings/View/DynamicSettingsRootView.swift

@@ -4,7 +4,7 @@ import Swinject
 extension DynamicSettings {
     struct RootView: BaseView {
         let resolver: Resolver
-        @State var state = StateModel()
+        @StateObject var state = StateModel()
         @State private var shouldDisplayHint: Bool = false
         @State var hintDetent = PresentationDetent.large
         @State var selectedVerboseHint: String?

+ 5 - 3
FreeAPS/Sources/Modules/Home/HomeStateModel+Setup/ChartAxisSetup.swift

@@ -17,7 +17,9 @@ extension Home.StateModel {
                 let maxForecast = forecastValues.max()
 
                 // Ensure all values exist, otherwise set default values
-                guard let minGlucose = minGlucose, let maxGlucose = maxGlucose else {
+                guard let minGlucose = minGlucose, let maxGlucose = maxGlucose,
+                      let minForecast = minForecast, let maxForecast = maxForecast
+                else {
                     Task {
                         await self.updateChartBounds(minValue: 39, maxValue: 300)
                     }
@@ -25,8 +27,8 @@ extension Home.StateModel {
                 }
 
                 // Adjust max forecast to be no more than 100 over max glucose
-                let adjustedMaxForecast = min(maxForecast ?? maxGlucose + 100, maxGlucose + 100)
-                let minOverall = min(minGlucose, minForecast ?? minGlucose)
+                let adjustedMaxForecast = min(maxForecast, maxGlucose + 100)
+                let minOverall = min(minGlucose, minForecast)
                 let maxOverall = max(maxGlucose, adjustedMaxForecast)
 
                 // Update the chart bounds on the main thread

+ 3 - 8
FreeAPS/Sources/Modules/Home/HomeStateModel+Setup/ForecastSetup.swift

@@ -80,24 +80,19 @@ extension Home.StateModel {
             return
         }
 
-        // Update minCount on the Main Thread
         minCount = max(12, allForecastValues.map(\.count).min() ?? 0)
-
-        // Safely read minCount for use inside the detached task
-        let localMinCount = minCount
-
-        guard localMinCount > 0 else { return }
+        guard minCount > 0 else { return }
 
         // Copy allForecastValues to a local constant for thread safety
         let localAllForecastValues = allForecastValues
 
         // Calculate min and max forecast values in a background task
         let (minResult, maxResult) = await Task.detached {
-            let minForecast = (0 ..< localMinCount).map { index in
+            let minForecast = (0 ..< self.minCount).map { index in
                 localAllForecastValues.compactMap { $0.indices.contains(index) ? $0[index] : nil }.min() ?? 0
             }
 
-            let maxForecast = (0 ..< localMinCount).map { index in
+            let maxForecast = (0 ..< self.minCount).map { index in
                 localAllForecastValues.compactMap { $0.indices.contains(index) ? $0[index] : nil }.max() ?? 0
             }
 

+ 85 - 105
FreeAPS/Sources/Modules/Home/HomeStateModel.swift

@@ -2,107 +2,103 @@ import Combine
 import CoreData
 import Foundation
 import LoopKitUI
-import Observation
 import SwiftDate
 import SwiftUI
 
 extension Home {
-    @Observable final class StateModel: BaseStateModel<Provider> {
-        @ObservationIgnored @Injected() var broadcaster: Broadcaster!
-        @ObservationIgnored @Injected() var apsManager: APSManager!
-        @ObservationIgnored @Injected() var fetchGlucoseManager: FetchGlucoseManager!
-        @ObservationIgnored @Injected() var nightscoutManager: NightscoutManager!
-        @ObservationIgnored @Injected() var determinationStorage: DeterminationStorage!
-        @ObservationIgnored @Injected() var glucoseStorage: GlucoseStorage!
-        @ObservationIgnored @Injected() var carbsStorage: CarbsStorage!
-        @ObservationIgnored @Injected() var tempTargetStorage: TempTargetsStorage!
+    final class StateModel: BaseStateModel<Provider> {
+        @Injected() var broadcaster: Broadcaster!
+        @Injected() var apsManager: APSManager!
+        @Injected() var fetchGlucoseManager: FetchGlucoseManager!
+        @Injected() var nightscoutManager: NightscoutManager!
+        @Injected() var determinationStorage: DeterminationStorage!
+        @Injected() var glucoseStorage: GlucoseStorage!
+        @Injected() var tempTargetStorage: TempTargetsStorage!
+        @Injected() var carbsStorage: CarbsStorage!
         private let timer = DispatchTimer(timeInterval: 5)
         private(set) var filteredHours = 24
-        var manualGlucose: [BloodGlucose] = []
-        var announcement: [Announcement] = []
-        var uploadStats = false
-        var recentGlucose: BloodGlucose?
-        var maxBasal: Decimal = 2
-        var autotunedBasalProfile: [BasalProfileEntry] = []
-        var basalProfile: [BasalProfileEntry] = []
-        var tempTargets: [TempTarget] = []
-        var timerDate = Date()
-        var closedLoop = false
-        var pumpSuspended = false
-        var isLooping = false
-        var statusTitle = ""
-        var lastLoopDate: Date = .distantPast
-        var battery: Battery?
-        var reservoir: Decimal?
-        var pumpName = ""
-        var pumpExpiresAtDate: Date?
-        var tempTarget: TempTarget?
-        var setupPump = false
-        var errorMessage: String?
-        var errorDate: Date?
-        var bolusProgress: Decimal?
-        var eventualBG: Int?
-        var allowManualTemp = false
-        var units: GlucoseUnits = .mgdL
-        var pumpDisplayState: PumpDisplayState?
-        var alarm: GlucoseAlarm?
-        var manualTempBasal = false
-        var isSmoothingEnabled = false
-        var maxValue: Decimal = 1.2
-        var lowGlucose: Decimal = 70
-        var highGlucose: Decimal = 180
-        var currentGlucoseTarget: Decimal = 100
-        var glucoseColorScheme: GlucoseColorScheme = .staticColor
-        var overrideUnit: Bool = false
-        var displayXgridLines: Bool = false
-        var displayYgridLines: Bool = false
-        var thresholdLines: Bool = false
-        var timeZone: TimeZone?
-        var hours: Int16 = 6
-        var totalBolus: Decimal = 0
-        var isStatusPopupPresented: Bool = false
-        var isLegendPresented: Bool = false
-        var legendSheetDetent = PresentationDetent.large
-        var totalInsulinDisplayType: TotalInsulinDisplayType = .totalDailyDose
-        var roundedTotalBolus: String = ""
-        var selectedTab: Int = 0
-        var waitForSuggestion: Bool = false
-        var glucoseFromPersistence: [GlucoseStored] = []
-        var latestTwoGlucoseValues: [GlucoseStored] = []
-        var carbsFromPersistence: [CarbEntryStored] = []
-        var fpusFromPersistence: [CarbEntryStored] = []
-        var determinationsFromPersistence: [OrefDetermination] = []
-        var enactedAndNonEnactedDeterminations: [OrefDetermination] = []
-        var insulinFromPersistence: [PumpEventStored] = []
-        var tempBasals: [PumpEventStored] = []
-        var suspensions: [PumpEventStored] = []
-        var batteryFromPersistence: [OpenAPS_Battery] = []
-        var lastPumpBolus: PumpEventStored?
-        var overrides: [OverrideStored] = []
-        var overrideRunStored: [OverrideRunStored] = []
-        var isOverrideCancelled: Bool = false
-        var preprocessedData: [(id: UUID, forecast: Forecast, forecastValue: ForecastValue)] = []
-        var pumpStatusHighlightMessage: String?
-        var cgmAvailable: Bool = false
-        var showCarbsRequiredBadge: Bool = true
-        var tempTargetStored: [TempTargetStored] = []
-        var tempTargetRunStored: [TempTargetRunStored] = []
+        @Published var manualGlucose: [BloodGlucose] = []
+        @Published var uploadStats = false
+        @Published var recentGlucose: BloodGlucose?
+        @Published var maxBasal: Decimal = 2
+        @Published var autotunedBasalProfile: [BasalProfileEntry] = []
+        @Published var basalProfile: [BasalProfileEntry] = []
+        @Published var timerDate = Date()
+        @Published var closedLoop = false
+        @Published var pumpSuspended = false
+        @Published var isLooping = false
+        @Published var statusTitle = ""
+        @Published var lastLoopDate: Date = .distantPast
+        @Published var battery: Battery?
+        @Published var reservoir: Decimal?
+        @Published var pumpName = ""
+        @Published var pumpExpiresAtDate: Date?
+        @Published var setupPump = false
+        @Published var errorMessage: String? = nil
+        @Published var errorDate: Date? = nil
+        @Published var bolusProgress: Decimal?
+        @Published var eventualBG: Int?
+        @Published var allowManualTemp = false
+        @Published var units: GlucoseUnits = .mgdL
+        @Published var pumpDisplayState: PumpDisplayState?
+        @Published var alarm: GlucoseAlarm?
+        @Published var manualTempBasal = false
+        @Published var isSmoothingEnabled = false
+        @Published var maxValue: Decimal = 1.2
+        @Published var lowGlucose: Decimal = 70
+        @Published var highGlucose: Decimal = 180
+        @Published var currentGlucoseTarget: Decimal = 100
+        @Published var overrideUnit: Bool = false
+        @Published var glucoseColorScheme: GlucoseColorScheme = .staticColor
+        @Published var displayXgridLines: Bool = false
+        @Published var displayYgridLines: Bool = false
+        @Published var thresholdLines: Bool = false
+        @Published var timeZone: TimeZone?
+        @Published var hours: Int16 = 6
+        @Published var totalBolus: Decimal = 0
+        @Published var isStatusPopupPresented: Bool = false
+        @Published var isLegendPresented: Bool = false
+        @Published var legendSheetDetent = PresentationDetent.large
+        @Published var totalInsulinDisplayType: TotalInsulinDisplayType = .totalDailyDose
+        @Published var roundedTotalBolus: String = ""
+        @Published var selectedTab: Int = 0
+        @Published var waitForSuggestion: Bool = false
+        @Published var glucoseFromPersistence: [GlucoseStored] = []
+        @Published var latestTwoGlucoseValues: [GlucoseStored] = []
+        @Published var carbsFromPersistence: [CarbEntryStored] = []
+        @Published var fpusFromPersistence: [CarbEntryStored] = []
+        @Published var determinationsFromPersistence: [OrefDetermination] = []
+        @Published var enactedAndNonEnactedDeterminations: [OrefDetermination] = []
+        @Published var insulinFromPersistence: [PumpEventStored] = []
+        @Published var tempBasals: [PumpEventStored] = []
+        @Published var suspensions: [PumpEventStored] = []
+        @Published var batteryFromPersistence: [OpenAPS_Battery] = []
+        @Published var lastPumpBolus: PumpEventStored?
+        @Published var overrides: [OverrideStored] = []
+        @Published var overrideRunStored: [OverrideRunStored] = []
+        @Published var tempTargetStored: [TempTargetStored] = []
+        @Published var tempTargetRunStored: [TempTargetRunStored] = []
+        @Published var isOverrideCancelled: Bool = false
+        @Published var preprocessedData: [(id: UUID, forecast: Forecast, forecastValue: ForecastValue)] = []
+        @Published var pumpStatusHighlightMessage: String? = nil
+        @Published var cgmAvailable: Bool = false
+        @Published var showCarbsRequiredBadge: Bool = true
         private(set) var setupPumpType: PumpConfig.PumpType = .minimed
         @Published var currentBGTarget: Decimal = 0
 
-        var minForecast: [Int] = []
-        var maxForecast: [Int] = []
-        var minCount: Int = 12 // count of Forecasts drawn in 5 min distances, i.e. 12 means a min of 1 hour
-        var forecastDisplayType: ForecastDisplayType = .cone
+        @Published var minForecast: [Int] = []
+        @Published var maxForecast: [Int] = []
+        @Published var minCount: Int = 12 // count of Forecasts drawn in 5 min distances, i.e. 12 means a min of 1 hour
+        @Published var forecastDisplayType: ForecastDisplayType = .cone
 
-        var minYAxisValue: Decimal = 39
-        var maxYAxisValue: Decimal = 300
+        @Published var minYAxisValue: Decimal = 39
+        @Published var maxYAxisValue: Decimal = 300
 
-        var minValueCobChart: Decimal = 0
-        var maxValueCobChart: Decimal = 20
+        @Published var minValueCobChart: Decimal = 0
+        @Published var maxValueCobChart: Decimal = 20
 
-        var minValueIobChart: Decimal = 0
-        var maxValueIobChart: Decimal = 5
+        @Published var minValueIobChart: Decimal = 0
+        @Published var maxValueIobChart: Decimal = 5
 
         let taskContext = CoreDataStack.shared.newTaskContext()
         let glucoseFetchContext = CoreDataStack.shared.newTaskContext()
@@ -454,22 +450,6 @@ extension Home {
             }
         }
 
-        @MainActor func cancelOverride(withID id: NSManagedObjectID) async {
-            do {
-                let profileToCancel = try viewContext.existingObject(with: id) as? OverrideStored
-                profileToCancel?.enabled = false
-
-                await saveToOverrideRunStored(withID: id)
-
-                guard viewContext.hasChanges else { return }
-                try viewContext.save()
-
-                Foundation.NotificationCenter.default.post(name: .willUpdateOverrideConfiguration, object: nil)
-            } catch {
-                debugPrint("\(DebuggingIdentifiers.failed) \(#file) \(#function) Failed to cancel Profile")
-            }
-        }
-
         func calculateTINS() -> String {
             let startTime = calculateStartTime(hours: Int(hours))
 

+ 2 - 2
FreeAPS/Sources/Modules/Home/View/Chart/BasalChart.swift

@@ -25,11 +25,11 @@ extension MainChartView {
                 drawTempBasals(dummy: false)
                 drawBasalProfile()
                 drawSuspensions()
-            }.onChange(of: state.tempBasals) {
+            }.onChange(of: state.tempBasals) { _ in
                 calculateBasals()
                 calculateTempBasalsInBackground()
             }
-            .onChange(of: state.maxBasal) {
+            .onChange(of: state.maxBasal) { _ in
                 calculateBasals()
             }
             .frame(minHeight: geo.size.height * 0.05)

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

@@ -7,18 +7,18 @@ let calendar = Calendar.current
 
 struct MainChartView: View {
     var geo: GeometryProxy
-    var units: GlucoseUnits
-    var hours: Int
-    var tempTargets: [TempTarget]
-    var highGlucose: Decimal
-    var lowGlucose: Decimal
-    var currentGlucoseTarget: Decimal
-    var glucoseColorScheme: GlucoseColorScheme
-    var screenHours: Int16
-    var displayXgridLines: Bool
-    var displayYgridLines: Bool
-    var thresholdLines: Bool
-    var state: Home.StateModel
+    @Binding var units: GlucoseUnits
+    @Binding var hours: Int
+    @Binding var highGlucose: Decimal
+    @Binding var lowGlucose: Decimal
+    @Binding var currentGlucoseTarget: Decimal
+    @Binding var screenHours: Int16
+    @Binding var glucoseColorScheme: GlucoseColorScheme
+    @Binding var displayXgridLines: Bool
+    @Binding var displayYgridLines: Bool
+    @Binding var thresholdLines: Bool
+
+    @StateObject var state: Home.StateModel
 
     @State var basalProfiles: [BasalProfile] = []
     @State var preparedTempBasals: [(start: Date, end: Date, rate: Double)] = []
@@ -96,17 +96,17 @@ struct MainChartView: View {
                                 iobChart
                             }
 
-                        }.onChange(of: screenHours) {
+                        }.onChange(of: screenHours) { _ in
                             scroller.scrollTo("MainChart", anchor: .trailing)
                         }
-                        .onChange(of: state.glucoseFromPersistence.last?.glucose) {
+                        .onChange(of: state.glucoseFromPersistence.last?.glucose) { _ in
                             scroller.scrollTo("MainChart", anchor: .trailing)
                             updateStartEndMarkers()
                         }
-                        .onChange(of: state.enactedAndNonEnactedDeterminations.first?.deliverAt) {
+                        .onChange(of: state.enactedAndNonEnactedDeterminations.first?.deliverAt) { _ in
                             scroller.scrollTo("MainChart", anchor: .trailing)
                         }
-                        .onChange(of: units) {
+                        .onChange(of: units) { _ in
                             // TODO: - Refactor this to only update the Y Axis Scale
                             state.setupGlucoseArray()
                         }
@@ -224,7 +224,7 @@ extension MainChartView {
                 }
             }
             .id("MainChart")
-            .onChange(of: state.insulinFromPersistence) {
+            .onChange(of: state.insulinFromPersistence) { _ in
                 state.roundedTotalBolus = state.calculateTINS()
             }
             .frame(minHeight: geo.size.height * 0.28)

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

@@ -2,15 +2,16 @@ import CoreData
 import SwiftUI
 
 struct CurrentGlucoseView: View {
-    let timerDate: Date
-    let units: GlucoseUnits
-    let alarm: GlucoseAlarm?
-    let lowGlucose: Decimal
-    let highGlucose: Decimal
-    let cgmAvailable: Bool
-    var currentGlucoseTarget: Decimal
-    let glucoseColorScheme: GlucoseColorScheme
-    let glucose: [GlucoseStored] // This contains the last two glucose values, no matter if its manual or a cgm reading
+    @Binding var timerDate: Date
+    @Binding var units: GlucoseUnits
+    @Binding var alarm: GlucoseAlarm?
+    @Binding var lowGlucose: Decimal
+    @Binding var highGlucose: Decimal
+    @Binding var cgmAvailable: Bool
+    @Binding var currentGlucoseTarget: Decimal
+    @Binding var glucoseColorScheme: GlucoseColorScheme
+
+    var glucose: [GlucoseStored] // This contains the last two glucose values, no matter if its manual or a cgm reading
 
     @State private var rotationDegrees: Double = 0.0
     @State private var angularGradient = AngularGradient(colors: [

+ 6 - 6
FreeAPS/Sources/Modules/Home/View/Header/LoopView.swift

@@ -8,13 +8,13 @@ struct LoopView: View {
         static let lag: TimeInterval = 30
     }
 
-    let closedLoop: Bool
-    let timerDate: Date
-    let isLooping: Bool
-    let lastLoopDate: Date
-    let manualTempBasal: Bool
+    @Binding var closedLoop: Bool
+    @Binding var timerDate: Date
+    @Binding var isLooping: Bool
+    @Binding var lastLoopDate: Date
+    @Binding var manualTempBasal: Bool
 
-    let determination: [OrefDetermination]
+    var determination: [OrefDetermination]
 
     private var dateFormatter: DateFormatter {
         let formatter = DateFormatter()

+ 7 - 7
FreeAPS/Sources/Modules/Home/View/Header/PumpView.swift

@@ -2,13 +2,13 @@ import CoreData
 import SwiftUI
 
 struct PumpView: View {
-    let reservoir: Decimal?
-    let name: String
-    let expiresAtDate: Date?
-    let timerDate: Date
-    let timeZone: TimeZone?
-    let pumpStatusHighlightMessage: String?
-    let battery: [OpenAPS_Battery]
+    @Binding var reservoir: Decimal?
+    @Binding var name: String
+    @Binding var expiresAtDate: Date?
+    @Binding var timerDate: Date
+    @Binding var timeZone: TimeZone?
+    @Binding var pumpStatusHighlightMessage: String?
+    @Binding var battery: [OpenAPS_Battery]
 
     @Environment(\.colorScheme) var colorScheme
 

+ 34 - 35
FreeAPS/Sources/Modules/Home/View/HomeRootView.swift

@@ -8,7 +8,7 @@ extension Home {
     struct RootView: BaseView {
         let resolver: Resolver
 
-        @State var state = StateModel()
+        @StateObject var state = StateModel()
         @State var isStatusPopupPresented = false
         @State var showCancelAlert = false
         @State var showCancelConfirmDialog = false
@@ -52,6 +52,7 @@ extension Home {
             ascending: false,
             fetchLimit: 1
         )) var latestTempTarget: FetchedResults<TempTargetStored>
+
         var bolusProgressFormatter: NumberFormatter {
             let formatter = NumberFormatter()
             formatter.numberStyle = .decimal
@@ -126,14 +127,14 @@ extension Home {
 
         var glucoseView: some View {
             CurrentGlucoseView(
-                timerDate: state.timerDate,
-                units: state.units,
-                alarm: state.alarm,
-                lowGlucose: state.lowGlucose,
-                highGlucose: state.highGlucose,
-                cgmAvailable: state.cgmAvailable,
-                currentGlucoseTarget: state.currentGlucoseTarget,
-                glucoseColorScheme: state.glucoseColorScheme,
+                timerDate: $state.timerDate,
+                units: $state.units,
+                alarm: $state.alarm,
+                lowGlucose: $state.lowGlucose,
+                highGlucose: $state.highGlucose,
+                cgmAvailable: $state.cgmAvailable,
+                currentGlucoseTarget: $state.currentGlucoseTarget,
+                glucoseColorScheme: $state.glucoseColorScheme,
                 glucose: state.latestTwoGlucoseValues
             ).scaleEffect(0.9)
                 .onTapGesture {
@@ -148,13 +149,13 @@ extension Home {
 
         var pumpView: some View {
             PumpView(
-                reservoir: state.reservoir,
-                name: state.pumpName,
-                expiresAtDate: state.pumpExpiresAtDate,
-                timerDate: state.timerDate,
-                timeZone: state.timeZone,
-                pumpStatusHighlightMessage: state.pumpStatusHighlightMessage,
-                battery: state.batteryFromPersistence
+                reservoir: $state.reservoir,
+                name: $state.pumpName,
+                expiresAtDate: $state.pumpExpiresAtDate,
+                timerDate: $state.timerDate,
+                timeZone: $state.timeZone,
+                pumpStatusHighlightMessage: $state.pumpStatusHighlightMessage,
+                battery: $state.batteryFromPersistence
             ).onTapGesture {
                 if state.pumpDisplayState == nil {
                     // shows user confirmation dialog with pump model choices, then proceeds to setup
@@ -352,17 +353,16 @@ extension Home {
             ZStack {
                 MainChartView(
                     geo: geo,
-                    units: state.units,
-                    hours: state.filteredHours,
-                    tempTargets: state.tempTargets,
-                    highGlucose: state.highGlucose,
-                    lowGlucose: state.lowGlucose,
-                    currentGlucoseTarget: state.currentGlucoseTarget,
-                    glucoseColorScheme: state.glucoseColorScheme,
-                    screenHours: state.hours,
-                    displayXgridLines: state.displayXgridLines,
-                    displayYgridLines: state.displayYgridLines,
-                    thresholdLines: state.thresholdLines,
+                    units: $state.units,
+                    hours: .constant(state.filteredHours),
+                    highGlucose: $state.highGlucose,
+                    lowGlucose: $state.lowGlucose,
+                    currentGlucoseTarget: $state.currentGlucoseTarget,
+                    screenHours: $state.hours,
+                    glucoseColorScheme: $state.glucoseColorScheme,
+                    displayXgridLines: $state.displayXgridLines,
+                    displayYgridLines: $state.displayYgridLines,
+                    thresholdLines: $state.thresholdLines,
                     state: state
                 )
             }
@@ -379,11 +379,11 @@ extension Home {
             VStack(alignment: .leading, spacing: 20) {
                 /// Loop view at bottomLeading
                 LoopView(
-                    closedLoop: state.closedLoop,
-                    timerDate: state.timerDate,
-                    isLooping: state.isLooping,
-                    lastLoopDate: state.lastLoopDate,
-                    manualTempBasal: state.manualTempBasal,
+                    closedLoop: $state.closedLoop,
+                    timerDate: $state.timerDate,
+                    isLooping: $state.isLooping,
+                    lastLoopDate: $state.lastLoopDate,
+                    manualTempBasal: $state.manualTempBasal,
                     determination: state.determinationsFromPersistence
                 ).onTapGesture {
                     state.isStatusPopupPresented = true
@@ -445,9 +445,8 @@ extension Home {
                         .foregroundColor(.loopYellow)
                     Text(
                         (
-                            numberFormatter.string(
-                                from: NSNumber(value: state.enactedAndNonEnactedDeterminations.first?.cob ?? 0)
-                            ) ?? "0"
+                            numberFormatter
+                                .string(from: (state.enactedAndNonEnactedDeterminations.first?.cob ?? 0) as NSNumber) ?? "0"
                         ) +
                             NSLocalizedString(" g", comment: "gram of carbs")
                     )

+ 8 - 9
FreeAPS/Sources/Modules/ISFEditor/ISFEditorStateModel.swift

@@ -1,19 +1,18 @@
 import CoreData
-import Observation
 import SwiftUI
 
 extension ISFEditor {
-    @Observable final class StateModel: BaseStateModel<Provider> {
-        @ObservationIgnored @Injected() var determinationStorage: DeterminationStorage!
-        @ObservationIgnored @Injected() private var nightscout: NightscoutManager!
+    final class StateModel: BaseStateModel<Provider> {
+        @Injected() var determinationStorage: DeterminationStorage!
+        @Injected() private var nightscout: NightscoutManager!
 
-        var items: [Item] = []
-        var initialItems: [Item] = []
-        var shouldDisplaySaving: Bool = false
+        @Published var items: [Item] = []
+        @Published var initialItems: [Item] = []
+        @Published var shouldDisplaySaving: Bool = false
         private(set) var autosensISF: Decimal?
         private(set) var autosensRatio: Decimal = 0
-        var autotune: Autotune?
-        var determinationsFromPersistence: [OrefDetermination] = []
+        @Published var autotune: Autotune?
+        @Published var determinationsFromPersistence: [OrefDetermination] = []
 
         let context = CoreDataStack.shared.newTaskContext()
         let viewContext = CoreDataStack.shared.persistentContainer.viewContext

+ 1 - 1
FreeAPS/Sources/Modules/ISFEditor/View/ISFEditorRootView.swift

@@ -4,7 +4,7 @@ import Swinject
 extension ISFEditor {
     struct RootView: BaseView {
         let resolver: Resolver
-        @State var state = StateModel()
+        @StateObject var state = StateModel()
         @State private var editMode = EditMode.inactive
 
         @Environment(\.colorScheme) var colorScheme

+ 4 - 5
FreeAPS/Sources/Modules/ManualTempBasal/ManualTempBasalStateModel.swift

@@ -1,11 +1,10 @@
-import Observation
 import SwiftUI
 
 extension ManualTempBasal {
-    @Observable final class StateModel: BaseStateModel<Provider> {
-        @ObservationIgnored @Injected() var apsManager: APSManager!
-        var rate: Decimal = 0
-        var durationIndex = 0
+    final class StateModel: BaseStateModel<Provider> {
+        @Injected() var apsManager: APSManager!
+        @Published var rate: Decimal = 0
+        @Published var durationIndex = 0
 
         let durationValues = stride(from: 30.0, to: 720.1, by: 30.0).map { $0 }
 

+ 1 - 1
FreeAPS/Sources/Modules/ManualTempBasal/View/ManualTempBasalRootView.swift

@@ -4,7 +4,7 @@ import Swinject
 extension ManualTempBasal {
     struct RootView: BaseView {
         let resolver: Resolver
-        @State var state = StateModel()
+        @StateObject var state = StateModel()
 
         @Environment(\.colorScheme) var colorScheme
         var color: LinearGradient {

+ 66 - 61
FreeAPS/Sources/Modules/OverrideConfig/OverrideStateModel.swift

@@ -1,64 +1,62 @@
-import Combine
 import CoreData
-import Observation
 import SwiftUI
 
 extension OverrideConfig {
-    @Observable final class StateModel: BaseStateModel<Provider> {
-        @ObservationIgnored @Injected() var broadcaster: Broadcaster!
-        @ObservationIgnored @Injected() var storage: TempTargetsStorage!
-        @ObservationIgnored @Injected() var apsManager: APSManager!
-        @ObservationIgnored @Injected() var overrideStorage: OverrideStorage!
-
-        var overridePercentage: Double = 100
-        var isEnabled = false
-        var indefinite = true
-        var overrideDuration: Decimal = 0
-        var target: Decimal = 100
-        var shouldOverrideTarget: Bool = false
-        var smbIsOff: Bool = false
-        var id = ""
-        var overrideName: String = ""
-        var isPreset: Bool = false
-        var overridePresets: [OverrideStored] = []
-        var advancedSettings: Bool = false
-        var isfAndCr: Bool = true
-        var isf: Bool = true
-        var cr: Bool = true
-        var smbIsScheduledOff: Bool = false
-        var start: Decimal = 0
-        var end: Decimal = 0
-        var smbMinutes: Decimal = 0
-        var uamMinutes: Decimal = 0
-        var defaultSmbMinutes: Decimal = 0
-        var defaultUamMinutes: Decimal = 0
-        var selectedTab: Tab = .overrides
-        var activeOverrideName: String = ""
-        var currentActiveOverride: OverrideStored?
-        var showOverrideEditSheet = false
-        var showTempTargetEditSheet = false
-        var currentActiveTempTarget: TempTargetStored?
-        var currentActiveOverride: OverrideStored?
-        var activeTempTargetName: String = ""
+    final class StateModel: BaseStateModel<Provider> {
+        @Injected() var broadcaster: Broadcaster!
+        @Injected() var tempTargetStorage: TempTargetsStorage!
+        @Injected() var apsManager: APSManager!
+        @Injected() var overrideStorage: OverrideStorage!
+
+        @Published var overrideSliderPercentage: Double = 100
+        @Published var isEnabled = false
+        @Published var indefinite = true
+        @Published var overrideDuration: Decimal = 0
+        @Published var target: Decimal = 0
+        @Published var shouldOverrideTarget: Bool = false
+        @Published var smbIsOff: Bool = false
+        @Published var id = ""
+        @Published var overrideName: String = ""
+        @Published var isPreset: Bool = false
+        @Published var overridePresets: [OverrideStored] = []
+        @Published var advancedSettings: Bool = false
+        @Published var isfAndCr: Bool = true
+        @Published var isf: Bool = true
+        @Published var cr: Bool = true
+        @Published var smbIsAlwaysOff: Bool = false
+        @Published var start: Decimal = 0
+        @Published var end: Decimal = 23
+        @Published var smbMinutes: Decimal = 0
+        @Published var uamMinutes: Decimal = 0
+        @Published var defaultSmbMinutes: Decimal = 0
+        @Published var defaultUamMinutes: Decimal = 0
+        @Published var selectedTab: Tab = .overrides
+        @Published var activeOverrideName: String = ""
+        @Published var activeTempTargetName: String = ""
+        @Published var currentActiveOverride: OverrideStored?
+        @Published var currentActiveTempTarget: TempTargetStored?
+        @Published var showOverrideEditSheet = false
+        @Published var showTempTargetEditSheet = false
+        @Published var showInvalidTargetAlert = false
 
         var units: GlucoseUnits = .mgdL
 
         // temp target stuff
-        var tempTargetDuration: Decimal = 0
-        var tempTargetName: String = ""
-        var tempTargetTarget: Decimal = 0 // lel
-        var isTempTargetEnabled: Bool = false
-        var date = Date()
-        var newPresetName = ""
-        var tempTargetPresets: [TempTargetStored] = []
-        var percentage = 100.0
-        var maxValue: Decimal = 1.2
-        var minValue: Decimal = 0.15
-        var viewPercantage = false
-        var halfBasalTarget: Decimal = 160
-        var settingHalfBasalTarget: Decimal = 160
-        var didSaveSettings: Bool = false
-        var didAdjustSens: Bool = false {
+        @Published var tempTargetDuration: Decimal = 0
+        @Published var tempTargetName: String = ""
+        @Published var tempTargetTarget: Decimal = 0 // lel
+        @Published var isTempTargetEnabled: Bool = false
+        @Published var date = Date()
+        @Published var newPresetName = ""
+        @Published var tempTargetPresets: [TempTargetStored] = []
+        @Published var percentage = 100.0
+        @Published var maxValue: Decimal = 1.2
+        @Published var minValue: Decimal = 0.15
+        @Published var viewPercantage = false
+        @Published var halfBasalTarget: Decimal = 160
+        @Published var settingHalfBasalTarget: Decimal = 160
+        @Published var didSaveSettings: Bool = false
+        @Published var didAdjustSens: Bool = false {
             didSet {
                 handleAdjustSensToggle()
             }
@@ -72,8 +70,6 @@ extension OverrideConfig {
             return "Please enter a valid target between" + " \(target)."
         }
 
-        private var cancellables = Set<AnyCancellable>()
-
         override func subscribe() {
             setupNotification()
             setupSettings()
@@ -133,12 +129,12 @@ extension OverrideConfig {
 extension OverrideConfig.StateModel {
     // Custom Notification to update View when an Override has been cancelled via Home View
     func setupNotification() {
-        Foundation.NotificationCenter.default.publisher(for: .willUpdateOverrideConfiguration)
-            .sink { [weak self] _ in
-                guard let self = self else { return }
-                self.updateLatestOverrideConfiguration()
-            }
-            .store(in: &cancellables)
+        Foundation.NotificationCenter.default.addObserver(
+            self,
+            selector: #selector(handleOverrideConfigurationUpdate),
+            name: .didUpdateOverrideConfiguration,
+            object: nil
+        )
 
         // Custom Notification to update View when an Temp Target has been cancelled via Home View
         Foundation.NotificationCenter.default.addObserver(
@@ -148,6 +144,15 @@ extension OverrideConfig.StateModel {
             object: nil
         )
     }
+
+    @objc private func handleOverrideConfigurationUpdate() {
+        updateLatestOverrideConfiguration()
+    }
+
+    @objc private func handleTempTargetConfigurationUpdate() {
+        updateLatestTempTargetConfiguration()
+    }
+
     // MARK: - Enact Overrides
 
     func reorderOverride(from source: IndexSet, to destination: Int) {

+ 2 - 2
FreeAPS/Sources/Modules/OverrideConfig/View/EditOverrideForm.swift

@@ -5,7 +5,7 @@ struct EditOverrideForm: View {
     @ObservedObject var override: OverrideStored
     @Environment(\.presentationMode) var presentationMode
     @Environment(\.colorScheme) var colorScheme
-    @Bindable var state: OverrideConfig.StateModel
+    @StateObject var state: OverrideConfig.StateModel
 
     @State private var name: String
     @State private var percentage: Double
@@ -30,7 +30,7 @@ struct EditOverrideForm: View {
 
     init(overrideToEdit: OverrideStored, state: OverrideConfig.StateModel) {
         override = overrideToEdit
-        _state = Bindable(wrappedValue: state)
+        _state = StateObject(wrappedValue: state)
         _name = State(initialValue: overrideToEdit.name ?? "")
         _percentage = State(initialValue: overrideToEdit.percentage)
         _indefinite = State(initialValue: overrideToEdit.indefinite)

+ 1 - 1
FreeAPS/Sources/Modules/OverrideConfig/View/OverrideRootView.swift

@@ -6,7 +6,7 @@ extension OverrideConfig {
     struct RootView: BaseView {
         let resolver: Resolver
 
-        @State var state = StateModel()
+        @StateObject var state = StateModel()
 
         @State private var isEditing = false
         @State private var showOverrideCreationSheet = false

+ 18 - 19
FreeAPS/Sources/Modules/SMBSettings/SMBSettingsStateModel.swift

@@ -1,27 +1,26 @@
-import Observation
 import SwiftUI
 
 extension SMBSettings {
-    @Observable final class StateModel: BaseStateModel<Provider> {
-        @ObservationIgnored @Injected() var settings: SettingsManager!
-        @ObservationIgnored @Injected() var storage: FileStorage!
+    final class StateModel: BaseStateModel<Provider> {
+        @Injected() var settings: SettingsManager!
+        @Injected() var storage: FileStorage!
 
-        var units: GlucoseUnits = .mgdL
+        @Published var units: GlucoseUnits = .mgdL
 
-        var enableSMBAlways: Bool = false
-        var maxDeltaBGthreshold: Decimal = 0.2
-        var enableSMBWithCOB: Bool = false
-        var enableSMBWithTemptarget: Bool = false
-        var enableSMBAfterCarbs: Bool = false
-        var allowSMBWithHighTemptarget: Bool = false
-        var enableSMB_high_bg: Bool = false
-        var enableSMB_high_bg_target: Decimal = 100
-        var maxSMBBasalMinutes: Decimal = 30
-        var smbDeliveryRatio: Decimal = 0.5
-        var smbInterval: Decimal = 3
-        var bolusIncrement: Decimal = 0.1 // get this from pump, dafuq?: Bool = false
-        var enableUAM: Bool = false
-        var maxUAMSMBBasalMinutes: Decimal = 30
+        @Published var enableSMBAlways: Bool = false
+        @Published var maxDeltaBGthreshold: Decimal = 0.2
+        @Published var enableSMBWithCOB: Bool = false
+        @Published var enableSMBWithTemptarget: Bool = false
+        @Published var enableSMBAfterCarbs: Bool = false
+        @Published var allowSMBWithHighTemptarget: Bool = false
+        @Published var enableSMB_high_bg: Bool = false
+        @Published var enableSMB_high_bg_target: Decimal = 100
+        @Published var maxSMBBasalMinutes: Decimal = 30
+        @Published var smbDeliveryRatio: Decimal = 0.5
+        @Published var smbInterval: Decimal = 3
+        @Published var bolusIncrement: Decimal = 0.1 // get this from pump, dafuq?: Bool = false
+        @Published var enableUAM: Bool = false
+        @Published var maxUAMSMBBasalMinutes: Decimal = 30
 
         var preferences: Preferences {
             settingsManager.preferences

+ 1 - 1
FreeAPS/Sources/Modules/SMBSettings/View/SMBSettingsRootView.swift

@@ -4,7 +4,7 @@ import Swinject
 extension SMBSettings {
     struct RootView: BaseView {
         let resolver: Resolver
-        @State var state = StateModel()
+        @StateObject var state = StateModel()
         @State private var shouldDisplayHint: Bool = false
         @State var hintDetent = PresentationDetent.large
         @State var selectedVerboseHint: String?

+ 4 - 5
FreeAPS/Sources/Modules/Snooze/SnoozeStateModel.swift

@@ -1,12 +1,11 @@
-import Observation
 import SwiftUI
 
 extension Snooze {
-    @Observable final class StateModel: BaseStateModel<Provider> {
-        @ObservationIgnored @Persisted(key: "UserNotificationsManager.snoozeUntilDate") var snoozeUntilDate: Date = .distantPast
-        @ObservationIgnored @Injected() var glucoseStogare: GlucoseStorage!
+    final class StateModel: BaseStateModel<Provider> {
+        @Persisted(key: "UserNotificationsManager.snoozeUntilDate") var snoozeUntilDate: Date = .distantPast
+        @Injected() var glucoseStogare: GlucoseStorage!
 
-        var alarm: GlucoseAlarm?
+        @Published var alarm: GlucoseAlarm?
 
         override func subscribe() {
             alarm = glucoseStogare.alarm

+ 1 - 1
FreeAPS/Sources/Modules/Snooze/View/SnoozeRootView.swift

@@ -5,7 +5,7 @@ import Swinject
 extension Snooze {
     struct RootView: BaseView {
         let resolver: Resolver
-        @State var state = StateModel()
+        @StateObject var state = StateModel()
 
         @Environment(\.colorScheme) var colorScheme
         var color: LinearGradient {

+ 9 - 10
FreeAPS/Sources/Modules/Stat/StatStateModel.swift

@@ -1,20 +1,19 @@
 import CoreData
 import Foundation
-import Observation
 import SwiftUI
 import Swinject
 
 extension Stat {
-    @Observable final class StateModel: BaseStateModel<Provider> {
-        @ObservationIgnored @Injected() var settings: SettingsManager!
-        var highLimit: Decimal = 10 / 0.0555
-        var lowLimit: Decimal = 4 / 0.0555
-        var overrideUnit: Bool = false
-        var layingChart: Bool = false
-        var units: GlucoseUnits = .mgdL
-        var glucoseFromPersistence: [GlucoseStored] = []
+    final class StateModel: BaseStateModel<Provider> {
+        @Injected() var settings: SettingsManager!
+        @Published var highLimit: Decimal = 10 / 0.0555
+        @Published var lowLimit: Decimal = 4 / 0.0555
+        @Published var overrideUnit: Bool = false
+        @Published var layingChart: Bool = false
+        @Published var units: GlucoseUnits = .mgdL
+        @Published var glucoseFromPersistence: [GlucoseStored] = []
 
-        var selectedDuration: Duration = .Today
+        @Published var selectedDuration: Duration = .Today
 
         private let context = CoreDataStack.shared.newTaskContext()
         private let viewContext = CoreDataStack.shared.persistentContainer.viewContext

+ 16 - 16
FreeAPS/Sources/Modules/Stat/View/ChartsView.swift

@@ -4,13 +4,13 @@ import SwiftDate
 import SwiftUI
 
 struct ChartsView: View {
-    let highLimit: Decimal
-    let lowLimit: Decimal
-    let units: GlucoseUnits
-    let overrideUnit: Bool
-    let standing: Bool
+    @Binding var highLimit: Decimal
+    @Binding var lowLimit: Decimal
+    @Binding var units: GlucoseUnits
+    @Binding var overrideUnit: Bool
+    @Binding var standing: Bool
 
-    let glucose: [GlucoseStored]
+    var glucose: [GlucoseStored]
 
     @State var headline: Color = .secondary
 
@@ -36,18 +36,18 @@ struct ChartsView: View {
     }
 
     init(
-        highLimit: Decimal,
-        lowLimit: Decimal,
-        units: GlucoseUnits,
-        overrideUnit: Bool,
-        standing: Bool,
+        _ highLimit: Binding<Decimal>,
+        _ lowLimit: Binding<Decimal>,
+        _ units: Binding<GlucoseUnits>,
+        _ overrideUnit: Binding<Bool>,
+        _ standing: Binding<Bool>,
         glucose: [GlucoseStored]
     ) {
-        self.highLimit = highLimit
-        self.lowLimit = lowLimit
-        self.units = units
-        self.overrideUnit = overrideUnit
-        self.standing = standing
+        _highLimit = highLimit
+        _lowLimit = lowLimit
+        _units = units
+        _overrideUnit = overrideUnit
+        _standing = standing
         self.glucose = glucose
     }
 

+ 46 - 46
FreeAPS/Sources/Modules/Stat/View/StatRootView.swift

@@ -7,7 +7,7 @@ import Swinject
 extension Stat {
     struct RootView: BaseView {
         let resolver: Resolver
-        @State var state = StateModel()
+        @StateObject var state = StateModel()
 
         @Environment(\.colorScheme) var colorScheme
 
@@ -42,42 +42,42 @@ extension Stat {
                 case .Today:
                     StatsView(
                         filter: filter.today,
-                        highLimit: state.highLimit,
-                        lowLimit: state.lowLimit,
-                        units: state.units,
-                        overrideUnit: state.overrideUnit
+                        $state.highLimit,
+                        $state.lowLimit,
+                        $state.units,
+                        $state.overrideUnit
                     )
                 case .Day:
                     StatsView(
                         filter: filter.day,
-                        highLimit: state.highLimit,
-                        lowLimit: state.lowLimit,
-                        units: state.units,
-                        overrideUnit: state.overrideUnit
+                        $state.highLimit,
+                        $state.lowLimit,
+                        $state.units,
+                        $state.overrideUnit
                     )
                 case .Week:
                     StatsView(
                         filter: filter.week,
-                        highLimit: state.highLimit,
-                        lowLimit: state.lowLimit,
-                        units: state.units,
-                        overrideUnit: state.overrideUnit
+                        $state.highLimit,
+                        $state.lowLimit,
+                        $state.units,
+                        $state.overrideUnit
                     )
                 case .Month:
                     StatsView(
                         filter: filter.month,
-                        highLimit: state.highLimit,
-                        lowLimit: state.lowLimit,
-                        units: state.units,
-                        overrideUnit: state.overrideUnit
+                        $state.highLimit,
+                        $state.lowLimit,
+                        $state.units,
+                        $state.overrideUnit
                     )
                 case .Total:
                     StatsView(
                         filter: filter.total,
-                        highLimit: state.highLimit,
-                        lowLimit: state.lowLimit,
-                        units: state.units,
-                        overrideUnit: state.overrideUnit
+                        $state.highLimit,
+                        $state.lowLimit,
+                        $state.units,
+                        $state.overrideUnit
                     )
                 }
             }
@@ -87,47 +87,47 @@ extension Stat {
             switch state.selectedDuration {
             case .Today:
                 ChartsView(
-                    highLimit: state.highLimit,
-                    lowLimit: state.lowLimit,
-                    units: state.units,
-                    overrideUnit: state.overrideUnit,
-                    standing: state.layingChart,
+                    $state.highLimit,
+                    $state.lowLimit,
+                    $state.units,
+                    $state.overrideUnit,
+                    $state.layingChart,
                     glucose: state.glucoseFromPersistence
                 )
             case .Day:
                 ChartsView(
-                    highLimit: state.highLimit,
-                    lowLimit: state.lowLimit,
-                    units: state.units,
-                    overrideUnit: state.overrideUnit,
-                    standing: state.layingChart,
+                    $state.highLimit,
+                    $state.lowLimit,
+                    $state.units,
+                    $state.overrideUnit,
+                    $state.layingChart,
                     glucose: state.glucoseFromPersistence
                 )
             case .Week:
                 ChartsView(
-                    highLimit: state.highLimit,
-                    lowLimit: state.lowLimit,
-                    units: state.units,
-                    overrideUnit: state.overrideUnit,
-                    standing: state.layingChart,
+                    $state.highLimit,
+                    $state.lowLimit,
+                    $state.units,
+                    $state.overrideUnit,
+                    $state.layingChart,
                     glucose: state.glucoseFromPersistence
                 )
             case .Month:
                 ChartsView(
-                    highLimit: state.highLimit,
-                    lowLimit: state.lowLimit,
-                    units: state.units,
-                    overrideUnit: state.overrideUnit,
-                    standing: state.layingChart,
+                    $state.highLimit,
+                    $state.lowLimit,
+                    $state.units,
+                    $state.overrideUnit,
+                    $state.layingChart,
                     glucose: state.glucoseFromPersistence
                 )
             case .Total:
                 ChartsView(
-                    highLimit: state.highLimit,
-                    lowLimit: state.lowLimit,
-                    units: state.units,
-                    overrideUnit: state.overrideUnit,
-                    standing: state.layingChart,
+                    $state.highLimit,
+                    $state.lowLimit,
+                    $state.units,
+                    $state.overrideUnit,
+                    $state.layingChart,
                     glucose: state.glucoseFromPersistence
                 )
             }

+ 12 - 12
FreeAPS/Sources/Modules/Stat/View/StatsView.swift

@@ -8,10 +8,10 @@ struct StatsView: View {
 
     @State var headline: Color = .secondary
 
-    var highLimit: Decimal
-    var lowLimit: Decimal
-    var units: GlucoseUnits
-    var overrideUnit: Bool
+    @Binding var highLimit: Decimal
+    @Binding var lowLimit: Decimal
+    @Binding var units: GlucoseUnits
+    @Binding var overrideUnit: Bool
 
     private let conversionFactor = 0.0555
 
@@ -27,10 +27,10 @@ struct StatsView: View {
 
     init(
         filter: NSDate,
-        highLimit: Decimal,
-        lowLimit: Decimal,
-        units: GlucoseUnits,
-        overrideUnit: Bool
+        _ highLimit: Binding<Decimal>,
+        _ lowLimit: Binding<Decimal>,
+        _ units: Binding<GlucoseUnits>,
+        _ overrideUnit: Binding<Bool>
     ) {
         _fetchRequest = FetchRequest<LoopStatRecord>(
             sortDescriptors: [NSSortDescriptor(key: "start", ascending: false)],
@@ -42,10 +42,10 @@ struct StatsView: View {
             predicate: NSPredicate(format: "glucose > 0 AND date > %@", filter)
         )
 
-        self.highLimit = highLimit
-        self.lowLimit = lowLimit
-        self.units = units
-        self.overrideUnit = overrideUnit
+        _highLimit = highLimit
+        _lowLimit = lowLimit
+        _units = units
+        _overrideUnit = overrideUnit
     }
 
     var loops: some View {

+ 31 - 31
FreeAPS/Sources/Services/HealthKit/HealthKitManager.swift

@@ -341,14 +341,15 @@ final class BaseHealthKitManager: HealthKitManager, Injectable {
         await uploadInsulin(pumpHistoryStorage.getPumpHistoryNotYetUploadedToHealth())
     }
 
-    func uploadInsulin(_ insulinEvents: [PumpHistoryEvent]) async {
+    func uploadInsulin(_ insulin: [PumpHistoryEvent]) async {
         guard settingsManager.settings.useAppleHealth,
               let sampleType = AppleHealthConfig.healthInsulinObject,
               checkWriteToHealthPermissions(objectTypeToHealthStore: sampleType),
-              insulinEvents.isNotEmpty else { return }
+              insulin.isNotEmpty
+        else { return }
 
         // Fetch existing temp basal entries from Core Data for the last 24 hours
-        let fetchedInsulinEntries = await CoreDataStack.shared.fetchEntitiesAsync(
+        let results = await CoreDataStack.shared.fetchEntitiesAsync(
             ofType: PumpEventStored.self,
             onContext: backgroundContext,
             predicate: NSCompoundPredicate(andPredicateWithSubpredicates: [
@@ -360,12 +361,21 @@ final class BaseHealthKitManager: HealthKitManager, Injectable {
             batchSize: 50
         )
 
+        // Initialize an array to hold the HealthKit samples to be uploaded
         var insulinSamples: [HKQuantitySample] = []
 
+        // Perform the data processing on the background context
         await backgroundContext.perform {
-            guard let existingTempBasalEntries = fetchedInsulinEntries as? [PumpEventStored] else { return }
+            // Ensure that the fetched results are of the expected type
+            guard let existingTempBasalEntries = results as? [PumpEventStored] else { return }
+
+            // Create a mapping from timestamps to indices for quick access to existing entries
+            let existingEntriesByTimestamp = Dictionary(
+                uniqueKeysWithValues: existingTempBasalEntries.enumerated()
+                    .map { ($0.element.timestamp, $0.offset) }
+            )
 
-            for event in insulinEvents {
+            for event in insulin {
                 switch event.type {
                 case .bolus:
                     // For bolus events, create a HealthKit sample directly
@@ -373,21 +383,24 @@ final class BaseHealthKitManager: HealthKitManager, Injectable {
                         debug(.service, "Created HealthKit sample for bolus entry: \(sample)")
                         insulinSamples.append(sample)
                     }
+
                 case .tempBasal:
                     // For temp basal events, process them and adjust overlapping durations if necessary
                     guard let duration = event.duration, let amount = event.amount else { continue }
 
+                    // Calculate the total insulin delivered during the temp basal period
                     let value = (Decimal(duration) / 60.0) * amount
                     let valueRounded = self.deviceDataManager?.pumpManager?
                         .roundToSupportedBolusVolume(units: Double(value)) ?? Double(value)
 
-                    // Use binary search for efficient lookup of matching entry
-                    if let matchingIndex = self.binarySearch(entries: existingTempBasalEntries, timestamp: event.timestamp) {
-                        let predecessorIndex = matchingIndex - 1
-
+                    // Check if there's a matching existing temp basal entry
+                    if let matchingEntryIndex = existingEntriesByTimestamp[event.timestamp] {
+                        let predecessorIndex = matchingEntryIndex - 1
                         if predecessorIndex >= 0 {
+                            // Get the predecessor entry to handle overlapping temp basal events
                             let predecessorEntry = existingTempBasalEntries[predecessorIndex]
 
+                            // Adjust the predecessor entry if it overlaps with the current event
                             if let adjustedSample = self.processPredecessorEntry(
                                 predecessorEntry,
                                 nextEventTimestamp: event.timestamp,
@@ -397,6 +410,7 @@ final class BaseHealthKitManager: HealthKitManager, Injectable {
                             }
                         }
 
+                        // Create a new PumpHistoryEvent with the calculated insulin value
                         let newEvent = PumpHistoryEvent(
                             id: event.id,
                             type: .tempBasal,
@@ -405,6 +419,7 @@ final class BaseHealthKitManager: HealthKitManager, Injectable {
                             duration: event.duration
                         )
 
+                        // Create a HealthKit sample for the current temp basal event
                         if let sample = self.createSample(for: newEvent, sampleType: sampleType) {
                             debug(.service, "Created HealthKit sample for initial temp basal entry: \(sample)")
                             insulinSamples.append(sample)
@@ -412,46 +427,31 @@ final class BaseHealthKitManager: HealthKitManager, Injectable {
                     }
 
                 default:
+                    // Ignore other event types
                     break
                 }
             }
         }
 
+        // Save the processed insulin samples to HealthKit
         do {
             guard insulinSamples.isNotEmpty else {
                 debug(.service, "No insulin samples available for upload.")
                 return
             }
 
+            // Attempt to save the samples to HealthKit
             try await healthKitStore.save(insulinSamples)
             debug(.service, "Successfully stored \(insulinSamples.count) insulin samples in HealthKit.")
-            await updateInsulinAsUploaded(insulinEvents)
+
+            // Mark the insulin events as uploaded
+            await updateInsulinAsUploaded(insulin)
+
         } catch {
             debug(.service, "Failed to upload insulin samples to HealthKit: \(error.localizedDescription)")
         }
     }
 
-    // Helper function to perform binary search on the sorted entries by timestamp
-    private func binarySearch(entries: [PumpEventStored], timestamp: Date) -> Int? {
-        var lowerBound = 0
-        var upperBound = entries.count - 1
-
-        while lowerBound <= upperBound {
-            let midIndex = (lowerBound + upperBound) / 2
-            guard let midTimestamp = entries[midIndex].timestamp else { return nil }
-
-            if midTimestamp == timestamp {
-                return midIndex
-            } else if midTimestamp < timestamp {
-                lowerBound = midIndex + 1
-            } else {
-                upperBound = midIndex - 1
-            }
-        }
-
-        return nil
-    }
-
     // Helper function to create a HealthKit sample from a PumpHistoryEvent
     private func createSample(
         for event: PumpHistoryEvent,

+ 15 - 16
FreeAPS/Sources/Services/LiveActivity/LiveActivityBridge.swift

@@ -68,17 +68,14 @@ import UIKit
 
     private func setupNotifications() {
         let notificationCenter = Foundation.NotificationCenter.default
+        notificationCenter.addObserver(self, selector: #selector(cobOrIobDidUpdate), name: .didUpdateCobIob, object: nil)
         notificationCenter
             .addObserver(forName: UIApplication.didEnterBackgroundNotification, object: nil, queue: nil) { [weak self] _ in
-                Task { @MainActor in
-                    self?.forceActivityUpdate()
-                }
+                self?.forceActivityUpdate()
             }
         notificationCenter
             .addObserver(forName: UIApplication.didBecomeActiveNotification, object: nil, queue: nil) { [weak self] _ in
-                Task { @MainActor in
-                    self?.forceActivityUpdate()
-                }
+                self?.forceActivityUpdate()
             }
     }
 
@@ -88,11 +85,6 @@ import UIKit
             guard let self = self else { return }
             self.overridesDidUpdate()
         }.store(in: &subscriptions)
-
-        coreDataPublisher?.filterByEntityName("OrefDetermination").sink { [weak self] _ in
-            guard let self = self else { return }
-            self.cobOrIobDidUpdate()
-        }.store(in: &subscriptions)
     }
 
     private func registerSubscribers() {
@@ -105,7 +97,7 @@ import UIKit
             .store(in: &subscriptions)
     }
 
-    private func cobOrIobDidUpdate() {
+    @objc private func cobOrIobDidUpdate() {
         Task {
             await fetchAndMapDetermination()
             if let determination = determination {
@@ -114,7 +106,7 @@ import UIKit
         }
     }
 
-    private func overridesDidUpdate() {
+    @objc private func overridesDidUpdate() {
         Task {
             await fetchAndMapOverride()
             if let determination = determination {
@@ -128,8 +120,15 @@ import UIKit
             // Fetch and map glucose to GlucoseData struct
             await fetchAndMapGlucose()
 
+            // Fetch and map Determination to DeterminationData struct
+            await fetchAndMapDetermination()
+
+            // Fetch and map Override to OverrideData struct
+            /// shows if there is an active Override
+            await fetchAndMapOverride()
+
             // Push the update to the Live Activity
-            await glucoseDidUpdate(glucoseFromPersistence ?? [])
+            glucoseDidUpdate(glucoseFromPersistence ?? [])
         }
     }
 
@@ -147,7 +146,7 @@ import UIKit
 
     /// creates and tries to present a new activity update from the current GlucoseStorage values if live activities are enabled in settings
     /// Ends existing live activities if live activities are not enabled in settings
-    @MainActor private func forceActivityUpdate() {
+    private func forceActivityUpdate() {
         // just before app resigns active, show a new activity
         // only do this if there is no current activity or the current activity is older than 1h
         if settings.useLiveActivity {
@@ -252,7 +251,7 @@ import UIKit
 
 @available(iOS 16.2, *)
 extension LiveActivityBridge {
-    @MainActor func glucoseDidUpdate(_ glucose: [GlucoseData]) {
+    func glucoseDidUpdate(_ glucose: [GlucoseData]) {
         guard settings.useLiveActivity else {
             if currentActivity != nil {
                 Task {

+ 11 - 10
FreeAPS/Sources/Services/Network/NightscoutManager.swift

@@ -152,17 +152,18 @@ final class BaseNightscoutManager: NightscoutManager, Injectable {
     }
 
     func setupNotification() {
-        Foundation.NotificationCenter.default.publisher(for: .willUpdateOverrideConfiguration)
-            .sink { [weak self] _ in
-                guard let self = self else { return }
-                Task {
-                    await self.uploadOverrides()
+        Foundation.NotificationCenter.default.addObserver(
+            self,
+            selector: #selector(handleOverrideConfigurationUpdate),
+            name: .didUpdateOverrideConfiguration,
+            object: nil
+        )
+    }
 
-                    // Post a notification indicating that the upload has finished and that we can end the background task in the OverridePresetsIntentRequest
-                    Foundation.NotificationCenter.default.post(name: .didUpdateOverrideConfiguration, object: nil)
-                }
-            }
-            .store(in: &subscriptions)
+    @objc private func handleOverrideConfigurationUpdate() {
+        Task.detached {
+            await self.uploadOverrides()
+        }
     }
 
     func sourceInfo() -> [String: Any]? {

+ 57 - 108
FreeAPS/Sources/Shortcuts/Override/OverridePresetsIntentRequest.swift

@@ -1,6 +1,5 @@
 import CoreData
 import Foundation
-import UIKit
 
 @available(iOS 16.0, *) final class OverridePresetsIntentRequest: BaseIntentsRequest {
     enum overridePresetsError: Error {
@@ -78,145 +77,95 @@ import UIKit
     }
 
     @MainActor func enactOverride(_ preset: OverridePreset) async -> Bool {
-        // Start background task
-        var backgroundTaskID: UIBackgroundTaskIdentifier = .invalid
-        backgroundTaskID = await UIApplication.shared.beginBackgroundTask(withName: "Override Upload") {
-            guard backgroundTaskID != .invalid else { return }
-            Task {
-                // End background task when the time is about to expire
-                await UIApplication.shared.endBackgroundTask(backgroundTaskID)
-            }
-            backgroundTaskID = .invalid
-        }
-
-        // Defer block to end background task when function exits
-        defer {
-            if backgroundTaskID != .invalid {
-                Task {
-                    await UIApplication.shared.endBackgroundTask(backgroundTaskID)
-                }
-                backgroundTaskID = .invalid
-            }
-        }
-
         do {
-            // Get NSManagedObjectID of Preset
             guard let overrideID = await fetchOverrideID(preset),
-                  let overrideObject = try viewContext.existingObject(with: overrideID) as? OverrideStored
-            else { return false }
+                  let overrideObject = try viewContext.existingObject(with: overrideID) as? OverrideStored else { return false }
 
-            // Enable Override
             overrideObject.enabled = true
             overrideObject.date = Date()
             overrideObject.isUploadedToNS = false
 
-            // Disable previous overrides if necessary, without starting a background task
-            await disableAllActiveOverrides(except: overrideID, createOverrideRunEntry: true, shouldStartBackgroundTask: false)
+            // Disable previous Overrides
+            /// do not create a OverrideRunEntry because we only want that if we cancel a running Override, not when enacting a Preset
+            await disableAllActiveOverrides(except: overrideID, createOverrideRunEntry: true)
 
             if viewContext.hasChanges {
                 try viewContext.save()
 
                 // Update State variables in OverrideView
-                Foundation.NotificationCenter.default.post(name: .willUpdateOverrideConfiguration, object: nil)
-
-                // Await the notification
-                print("Waiting for notification...")
-                await awaitNotification(.didUpdateOverrideConfiguration)
-                print("Notification received, continuing...")
+                Foundation.NotificationCenter.default.post(name: .didUpdateOverrideConfiguration, object: nil)
 
                 return true
             }
-        } catch {
-            // Handle error and ensure background task is ended
-            debugPrint("Failed to enact Override: \(error.localizedDescription)")
+        } catch let error as NSError {
+            debugPrint(
+                "\(DebuggingIdentifiers.failed) \(#file) \(#function) Failed to enact Override: \(error.localizedDescription)"
+            )
         }
-
         return false
     }
 
     func cancelOverride() async {
-        await disableAllActiveOverrides(createOverrideRunEntry: true, shouldStartBackgroundTask: true)
+        await disableAllActiveOverrides(createOverrideRunEntry: true)
     }
 
     @MainActor func disableAllActiveOverrides(
         except overrideID: NSManagedObjectID? = nil,
-        createOverrideRunEntry: Bool,
-        shouldStartBackgroundTask: Bool = true
+        createOverrideRunEntry _: Bool
     ) async {
-        var backgroundTaskID: UIBackgroundTaskIdentifier = .invalid
-
-        if shouldStartBackgroundTask {
-            // Start background task
-            backgroundTaskID = await UIApplication.shared.beginBackgroundTask(withName: "Override Cancel") {
-                guard backgroundTaskID != .invalid else { return }
-                Task {
-                    // End background task when the time is about to expire
-                    await UIApplication.shared.endBackgroundTask(backgroundTaskID)
-                }
-                backgroundTaskID = .invalid
-            }
-        }
-
-        // Defer block to end background task when function exits, only if it was started
-        defer {
-            if shouldStartBackgroundTask, backgroundTaskID != .invalid {
-                Task {
-                    await UIApplication.shared.endBackgroundTask(backgroundTaskID)
-                }
-                backgroundTaskID = .invalid
-            }
-        }
-
-        // Get NSManagedObjectID of all active overrides
+        // Get ALL NSManagedObject IDs of ALL active Overrides to cancel every single Override
         let ids = await overrideStorage.loadLatestOverrideConfigurations(fetchLimit: 0) // 0 = no fetch limit
 
-        do {
-            // Fetch existing OverrideStored objects
-            let results = try ids.compactMap { id in
-                try self.viewContext.existingObject(with: id) as? OverrideStored
-            }
-
-            // Return early if no results
-            guard !results.isEmpty else { return }
-
-            // Create OverrideRunStored entry if needed
-            if createOverrideRunEntry, let canceledOverride = results.first {
-                let newOverrideRunStored = OverrideRunStored(context: viewContext)
-                newOverrideRunStored.id = UUID()
-                newOverrideRunStored.name = canceledOverride.name
-                newOverrideRunStored.startDate = canceledOverride.date ?? .distantPast
-                newOverrideRunStored.endDate = Date()
-                newOverrideRunStored.target = NSDecimalNumber(
-                    decimal: overrideStorage.calculateTarget(override: canceledOverride)
-                )
-                newOverrideRunStored.override = canceledOverride
-                newOverrideRunStored.isUploadedToNS = false
-            }
+        await viewContext.perform {
+            do {
+                // Fetch the existing OverrideStored objects from the context
+                let results = try ids.compactMap { id in
+                    try self.viewContext.existingObject(with: id) as? OverrideStored
+                }
 
-            // Disable all overrides except the one specified
-            for overrideToCancel in results {
-                if overrideToCancel.objectID != overrideID {
-                    overrideToCancel.enabled = false
-                    overrideToCancel.isUploadedToNS = false
+                // If there are no results, return early
+                guard !results.isEmpty else { return }
+
+                // Check if we also need to create a corresponding OverrideRunStored entry, i.e. when the User uses the Cancel Button in Override View
+                // Auggie - commented out this if statment, we always need to do this for overrides
+                // if createOverrideRunEntry {
+                // Use the first override to create a new OverrideRunStored entry
+                if let canceledOverride = results.first {
+                    let newOverrideRunStored = OverrideRunStored(context: self.viewContext)
+                    newOverrideRunStored.id = UUID()
+                    newOverrideRunStored.name = canceledOverride.name
+                    newOverrideRunStored.startDate = canceledOverride.date ?? .distantPast
+                    newOverrideRunStored.endDate = Date()
+                    newOverrideRunStored
+                        .target = NSDecimalNumber(
+                            decimal: self.overrideStorage
+                                .calculateTarget(override: canceledOverride)
+                        )
+                    newOverrideRunStored.override = canceledOverride
+                    newOverrideRunStored.isUploadedToNS = false
+                }
+                // }
+
+                // Disable all override except the one with overrideID
+                for overrideToCancel in results {
+                    if overrideToCancel.objectID != overrideID {
+                        overrideToCancel.enabled = false
+                        overrideToCancel.isUploadedToNS = false
+                    }
                 }
-            }
 
-            if viewContext.hasChanges {
-                try viewContext.save()
+                // Save the context if there are changes
+                if self.viewContext.hasChanges {
+                    try self.viewContext.save()
 
-                // Update State variables in OverrideView
-                Foundation.NotificationCenter.default.post(name: .willUpdateOverrideConfiguration, object: nil)
+                    // Update State variables in OverrideView
+                    Foundation.NotificationCenter.default.post(name: .didUpdateOverrideConfiguration, object: nil)
+                }
+            } catch {
+                debugPrint(
+                    "\(DebuggingIdentifiers.failed) \(#file) \(#function) Failed to disable active Overrides with error: \(error.localizedDescription)"
+                )
             }
-
-            // Await the notification
-            print("Waiting for notification...")
-            await awaitNotification(.didUpdateOverrideConfiguration)
-            print("Notification received, continuing...")
-
-        } catch {
-            debugPrint(
-                "\(DebuggingIdentifiers.failed) \(#file) \(#function) Failed to disable active Overrides with error: \(error.localizedDescription)"
-            )
         }
     }
 }

+ 2 - 6
FreeAPS/Sources/Views/TextFieldWithToolBar.swift

@@ -225,14 +225,10 @@ extension TextFieldWithToolBar.Coordinator: UITextFieldDelegate {
                 let hasTrailingZeros = (hasDecimalSeparator && proposedText[lastCharIndex] == "0") || isDecimalSeparator
                 if !hasTrailingZeros
                 {
-                    DispatchQueue.main.async {
-                        self.parent.text = number.decimalValue
-                    }
+                    parent.text = number.decimalValue
                 }
             } else {
-                DispatchQueue.main.async {
-                    self.parent.text = 0
-                }
+                parent.text = 0
             }
         }
 

+ 0 - 19
Model/Helper/CustomNotification.swift

@@ -1,26 +1,7 @@
-import Combine
 import Foundation
 
 extension Notification.Name {
-    static let willUpdateOverrideConfiguration = Notification.Name("willUpdateOverrideConfiguration")
     static let didUpdateOverrideConfiguration = Notification.Name("didUpdateOverrideConfiguration")
     static let didUpdateTempTargetConfiguration = Notification.Name("didUpdateTempTargetConfiguration")
     static let didUpdateCobIob = Notification.Name("didUpdateCobIob")
 }
-
-func awaitNotification(_ name: Notification.Name) async {
-    await withCheckedContinuation { continuation in
-        var cancellable: AnyCancellable?
-
-        // Create a Combine publisher that listens for notifications
-        cancellable = Foundation.NotificationCenter.default
-            .publisher(for: name)
-            .sink { _ in
-                // When the notification is received, resume the awaiting task
-                continuation.resume()
-
-                // Cancel the subscription after the continuation has resumed
-                cancellable?.cancel()
-            }
-    }
-}