polscm32 пре 1 година
родитељ
комит
db45bef6d5

+ 32 - 0
FreeAPS.xcodeproj/project.pbxproj

@@ -17,6 +17,11 @@
 		110AEDEC2C51A0AE00615CC9 /* ShortcutsConfigDataFlow.swift in Sources */ = {isa = PBXBuildFile; fileRef = 110AEDE72C51A0AE00615CC9 /* ShortcutsConfigDataFlow.swift */; };
 		110AEDED2C51A0AE00615CC9 /* ShortcutsConfigProvider.swift in Sources */ = {isa = PBXBuildFile; fileRef = 110AEDE82C51A0AE00615CC9 /* ShortcutsConfigProvider.swift */; };
 		110AEDEE2C51A0AE00615CC9 /* ShortcutsConfigStateModel.swift in Sources */ = {isa = PBXBuildFile; fileRef = 110AEDE92C51A0AE00615CC9 /* ShortcutsConfigStateModel.swift */; };
+		118DF76A2C5ECBC60067FEB7 /* ApplyOverridePresetIntent.swift in Sources */ = {isa = PBXBuildFile; fileRef = 118DF7642C5ECBC60067FEB7 /* ApplyOverridePresetIntent.swift */; };
+		118DF76B2C5ECBC60067FEB7 /* CancelOverrideIntent.swift in Sources */ = {isa = PBXBuildFile; fileRef = 118DF7652C5ECBC60067FEB7 /* CancelOverrideIntent.swift */; };
+		118DF76C2C5ECBC60067FEB7 /* ListOverridePresetIntent.swift in Sources */ = {isa = PBXBuildFile; fileRef = 118DF7662C5ECBC60067FEB7 /* ListOverridePresetIntent.swift */; };
+		118DF76D2C5ECBC60067FEB7 /* OverridePresetEntity.swift in Sources */ = {isa = PBXBuildFile; fileRef = 118DF7672C5ECBC60067FEB7 /* OverridePresetEntity.swift */; };
+		118DF76E2C5ECBC60067FEB7 /* OverridePresetsIntentRequest.swift in Sources */ = {isa = PBXBuildFile; fileRef = 118DF7682C5ECBC60067FEB7 /* OverridePresetsIntentRequest.swift */; };
 		17A9D0899046B45E87834820 /* CarbRatioEditorProvider.swift in Sources */ = {isa = PBXBuildFile; fileRef = 9C8D5F457B5AFF763F8CF3DF /* CarbRatioEditorProvider.swift */; };
 		19012CDC291D2CB900FB8210 /* LoopStats.swift in Sources */ = {isa = PBXBuildFile; fileRef = 19012CDB291D2CB900FB8210 /* LoopStats.swift */; };
 		190EBCC429FF136900BA767D /* UserInterfaceSettingsDataFlow.swift in Sources */ = {isa = PBXBuildFile; fileRef = 190EBCC329FF136900BA767D /* UserInterfaceSettingsDataFlow.swift */; };
@@ -250,6 +255,7 @@
 		5864E8592C42CFAE00294306 /* DeterminationStorage.swift in Sources */ = {isa = PBXBuildFile; fileRef = 5864E8582C42CFAE00294306 /* DeterminationStorage.swift */; };
 		587DA1F62B77F3DD00B28F8A /* SettingsRowView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 587DA1F52B77F3DD00B28F8A /* SettingsRowView.swift */; };
 		5887527C2BD986E1008B081D /* OpenAPSBattery.swift in Sources */ = {isa = PBXBuildFile; fileRef = 5887527B2BD986E1008B081D /* OpenAPSBattery.swift */; };
+		58CE8B892C8C6B62007A6A10 /* GradientStops.swift in Sources */ = {isa = PBXBuildFile; fileRef = 58CE8B882C8C6B62007A6A10 /* GradientStops.swift */; };
 		58F107742BD1A4D000B1A680 /* Determination+helper.swift in Sources */ = {isa = PBXBuildFile; fileRef = 58F107732BD1A4D000B1A680 /* Determination+helper.swift */; };
 		5A2325522BFCBF55003518CA /* NightscoutUploadView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 5A2325512BFCBF55003518CA /* NightscoutUploadView.swift */; };
 		5A2325542BFCBF66003518CA /* NightscoutFetchView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 5A2325532BFCBF65003518CA /* NightscoutFetchView.swift */; };
@@ -592,6 +598,11 @@
 		110AEDE72C51A0AE00615CC9 /* ShortcutsConfigDataFlow.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = ShortcutsConfigDataFlow.swift; sourceTree = "<group>"; };
 		110AEDE82C51A0AE00615CC9 /* ShortcutsConfigProvider.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = ShortcutsConfigProvider.swift; sourceTree = "<group>"; };
 		110AEDE92C51A0AE00615CC9 /* ShortcutsConfigStateModel.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = ShortcutsConfigStateModel.swift; sourceTree = "<group>"; };
+		118DF7642C5ECBC60067FEB7 /* ApplyOverridePresetIntent.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = ApplyOverridePresetIntent.swift; sourceTree = "<group>"; };
+		118DF7652C5ECBC60067FEB7 /* CancelOverrideIntent.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = CancelOverrideIntent.swift; sourceTree = "<group>"; };
+		118DF7662C5ECBC60067FEB7 /* ListOverridePresetIntent.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = ListOverridePresetIntent.swift; sourceTree = "<group>"; };
+		118DF7672C5ECBC60067FEB7 /* OverridePresetEntity.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = OverridePresetEntity.swift; sourceTree = "<group>"; };
+		118DF7682C5ECBC60067FEB7 /* OverridePresetsIntentRequest.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = OverridePresetsIntentRequest.swift; sourceTree = "<group>"; };
 		19012CDB291D2CB900FB8210 /* LoopStats.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = LoopStats.swift; sourceTree = "<group>"; };
 		190EBCC329FF136900BA767D /* UserInterfaceSettingsDataFlow.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = UserInterfaceSettingsDataFlow.swift; sourceTree = "<group>"; };
 		190EBCC529FF138000BA767D /* UserInterfaceSettingsProvider.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = UserInterfaceSettingsProvider.swift; sourceTree = "<group>"; };
@@ -887,6 +898,7 @@
 		5864E8582C42CFAE00294306 /* DeterminationStorage.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = DeterminationStorage.swift; sourceTree = "<group>"; };
 		587DA1F52B77F3DD00B28F8A /* SettingsRowView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SettingsRowView.swift; sourceTree = "<group>"; };
 		5887527B2BD986E1008B081D /* OpenAPSBattery.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = OpenAPSBattery.swift; sourceTree = "<group>"; };
+		58CE8B882C8C6B62007A6A10 /* GradientStops.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = GradientStops.swift; sourceTree = "<group>"; };
 		58F107732BD1A4D000B1A680 /* Determination+helper.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "Determination+helper.swift"; sourceTree = "<group>"; };
 		5A2325512BFCBF55003518CA /* NightscoutUploadView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = NightscoutUploadView.swift; sourceTree = "<group>"; };
 		5A2325532BFCBF65003518CA /* NightscoutFetchView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = NightscoutFetchView.swift; sourceTree = "<group>"; };
@@ -1254,6 +1266,18 @@
 			path = ShortcutsConfig;
 			sourceTree = "<group>";
 		};
+		118DF7692C5ECBC60067FEB7 /* Override */ = {
+			isa = PBXGroup;
+			children = (
+				118DF7642C5ECBC60067FEB7 /* ApplyOverridePresetIntent.swift */,
+				118DF7652C5ECBC60067FEB7 /* CancelOverrideIntent.swift */,
+				118DF7662C5ECBC60067FEB7 /* ListOverridePresetIntent.swift */,
+				118DF7672C5ECBC60067FEB7 /* OverridePresetEntity.swift */,
+				118DF7682C5ECBC60067FEB7 /* OverridePresetsIntentRequest.swift */,
+			);
+			path = Override;
+			sourceTree = "<group>";
+		};
 		18B49BC9587A59E3A347C1CD /* View */ = {
 			isa = PBXGroup;
 			children = (
@@ -1923,6 +1947,7 @@
 				581516A32BCED84A00BF67D7 /* DebuggingIdentifiers.swift */,
 				DD1DB7CB2BECCA1F0048B367 /* BuildDetails.swift */,
 				DD6B7CB12C7B6F0800B75029 /* Rounding.swift */,
+				58CE8B882C8C6B62007A6A10 /* GradientStops.swift */,
 			);
 			path = Helpers;
 			sourceTree = "<group>";
@@ -2355,6 +2380,7 @@
 		CE7CA3422A064973004BE681 /* Shortcuts */ = {
 			isa = PBXGroup;
 			children = (
+				118DF7692C5ECBC60067FEB7 /* Override */,
 				110AEDE22C5193D100615CC9 /* Bolus */,
 				CE1856F32ADC4835007E39C7 /* Carbs */,
 				CE7CA3432A064973004BE681 /* AppShortcuts.swift */,
@@ -3130,6 +3156,7 @@
 				DD1745262C55526F00211FAC /* SMBSettingsRootView.swift in Sources */,
 				3811DE3025C9D49500A708ED /* HomeStateModel.swift in Sources */,
 				38BF021725E7CBBC00579895 /* PumpManagerExtensions.swift in Sources */,
+				118DF76E2C5ECBC60067FEB7 /* OverridePresetsIntentRequest.swift in Sources */,
 				CEE9A6552BBB418300EB5194 /* CalibrationsProvider.swift in Sources */,
 				19F95FF529F10FCF00314DDC /* StatProvider.swift in Sources */,
 				38F3B2EF25ED8E2A005C48AA /* TempTargetsStorage.swift in Sources */,
@@ -3246,6 +3273,7 @@
 				DD57C4C72C4C7103001A5B28 /* CarbEntryStored+CoreDataProperties.swift in Sources */,
 				DD57C4C82C4C7103001A5B28 /* OpenAPS_Battery+CoreDataClass.swift in Sources */,
 				DD57C4C92C4C7103001A5B28 /* OpenAPS_Battery+CoreDataProperties.swift in Sources */,
+				118DF76D2C5ECBC60067FEB7 /* OverridePresetEntity.swift in Sources */,
 				DD57C4CA2C4C7103001A5B28 /* GlucoseStored+CoreDataClass.swift in Sources */,
 				DD57C4CB2C4C7103001A5B28 /* GlucoseStored+CoreDataProperties.swift in Sources */,
 				DD57C4CC2C4C7103001A5B28 /* OverrideStored+CoreDataClass.swift in Sources */,
@@ -3274,6 +3302,7 @@
 				CEE9A6592BBB418300EB5194 /* CalibrationsDataFlow.swift in Sources */,
 				3811DE3525C9D49500A708ED /* HomeRootView.swift in Sources */,
 				38E98A2925F52C9300C0CED0 /* Error+Extensions.swift in Sources */,
+				118DF76C2C5ECBC60067FEB7 /* ListOverridePresetIntent.swift in Sources */,
 				38EA05DA261F6E7C0064E39B /* SimpleLogReporter.swift in Sources */,
 				3811DE6125C9D4D500A708ED /* ViewModifiers.swift in Sources */,
 				3811DEAC25C9D88300A708ED /* NightscoutManager.swift in Sources */,
@@ -3418,6 +3447,7 @@
 				38FEF413273B317A00574A46 /* HKUnit.swift in Sources */,
 				A33352ED40476125EBAC6EE0 /* CarbRatioEditorDataFlow.swift in Sources */,
 				17A9D0899046B45E87834820 /* CarbRatioEditorProvider.swift in Sources */,
+				118DF76B2C5ECBC60067FEB7 /* CancelOverrideIntent.swift in Sources */,
 				69B9A368029F7EB39F525422 /* CarbRatioEditorStateModel.swift in Sources */,
 				38E44538274E411700EC9A94 /* Disk+[Data].swift in Sources */,
 				98641AF4F92123DA668AB931 /* CarbRatioEditorRootView.swift in Sources */,
@@ -3473,7 +3503,9 @@
 				BD1661312B82ADAB00256551 /* CustomProgressView.swift in Sources */,
 				C967DACD3B1E638F8B43BE06 /* ManualTempBasalStateModel.swift in Sources */,
 				38E4453B274E411700EC9A94 /* Disk+VolumeInformation.swift in Sources */,
+				118DF76A2C5ECBC60067FEB7 /* ApplyOverridePresetIntent.swift in Sources */,
 				7BCFACB97C821041BA43A114 /* ManualTempBasalRootView.swift in Sources */,
+				58CE8B892C8C6B62007A6A10 /* GradientStops.swift in Sources */,
 				38E44534274E411700EC9A94 /* Disk+InternalHelpers.swift in Sources */,
 				38A00B2325FC2B55006BC0B0 /* LRUCache.swift in Sources */,
 				DDD163122C4C689900CD525A /* OverrideStateModel.swift in Sources */,

+ 53 - 0
FreeAPS/Sources/Helpers/GradientStops.swift

@@ -0,0 +1,53 @@
+import Foundation
+import SwiftUI
+
+struct GradientStops {
+    static func calculateGradientStops(
+        lowGlucose: Decimal,
+        highGlucose: Decimal,
+        glucoseValues: [Decimal]
+    ) async -> [Gradient.Stop] {
+        let low = Double(lowGlucose)
+        let high = Double(highGlucose)
+
+        let minimum = glucoseValues.min() ?? 0.0
+        let maximum = glucoseValues.max() ?? 0.0
+
+        // Handle edge case where minimum and maximum are equal
+        guard minimum != maximum else {
+            return [
+                Gradient.Stop(color: .green, location: 0.0),
+                Gradient.Stop(color: .green, location: 1.0)
+            ]
+        }
+
+        // Calculate positions for gradient
+        let lowPosition = (low - Double(truncating: minimum as NSNumber)) /
+            (Double(truncating: maximum as NSNumber) - Double(truncating: minimum as NSNumber))
+        let highPosition = (high - Double(truncating: minimum as NSNumber)) /
+            (Double(truncating: maximum as NSNumber) - Double(truncating: minimum as NSNumber))
+
+        // Ensure positions are in bounds [0, 1]
+        let clampedLowPosition = max(0.0, min(lowPosition, 1.0))
+        let clampedHighPosition = max(0.0, min(highPosition, 1.0))
+
+        // Ensure lowPosition is less than highPosition
+        let epsilon: CGFloat = 0.0001
+        let sortedPositions = [clampedLowPosition, clampedHighPosition].sorted()
+        var adjustedHighPosition = sortedPositions[1]
+
+        if adjustedHighPosition - sortedPositions[0] < epsilon {
+            adjustedHighPosition = min(1.0, sortedPositions[0] + epsilon)
+        }
+
+        return [
+            Gradient.Stop(color: .red, location: 0.0),
+            Gradient.Stop(color: .red, location: sortedPositions[0]), // draw red gradient till lowGlucose
+            Gradient.Stop(color: .green, location: sortedPositions[0] + epsilon),
+            // draw green above lowGlucose till highGlucose
+            Gradient.Stop(color: .green, location: adjustedHighPosition),
+            Gradient.Stop(color: .orange, location: adjustedHighPosition + epsilon), // draw orange above highGlucose
+            Gradient.Stop(color: .orange, location: 1.0)
+        ]
+    }
+}

+ 48 - 79
FreeAPS/Sources/Modules/Bolus/BolusStateModel.swift

@@ -126,39 +126,8 @@ extension Bolus {
             coreDataObserver = CoreDataObserver()
             registerHandlers()
             setupGlucoseArray()
-
-            Task {
-                async let getAllSettingsDefaults: () = getAllSettingsValues()
-                async let setupDeterminations: () = setupDeterminationsArray()
-
-                await getAllSettingsDefaults
-                await setupDeterminations
-
-                // Determination has updated, so we can use this to draw the initial Forecast Chart
-                let forecastData = await mapForecastsForChart()
-                await updateForecasts(with: forecastData)
-            }
-
-            broadcaster.register(DeterminationObserver.self, observer: self)
-            broadcaster.register(BolusFailureObserver.self, observer: self)
-            units = settingsManager.settings.units
-            fraction = settings.settings.overrideFactor
-            fattyMeals = settings.settings.fattyMeals
-            fattyMealFactor = settings.settings.fattyMealFactor
-            sweetMeals = settings.settings.sweetMeals
-            sweetMealFactor = settings.settings.sweetMealFactor
-            displayPresets = settings.settings.displayPresets
-
-            forecastDisplayType = settings.settings.forecastDisplayType
-
-            lowGlucose = units == .mgdL ? settingsManager.settings.low : settingsManager.settings.low.asMmolL
-            highGlucose = units == .mgdL ? settingsManager.settings.high : settingsManager.settings.high.asMmolL
-
-            maxCarbs = settings.settings.maxCarbs
-            maxFat = settings.settings.maxFat
-            maxProtein = settings.settings.maxProtein
-            useFPUconversion = settingsManager.settings.useFPUconversion
-            smooth = settingsManager.settings.smoothGlucose
+            setupDeterminationsAndForecasts()
+            setupSettings()
 
             if waitForSuggestionInitial {
                 Task {
@@ -204,6 +173,43 @@ extension Bolus {
             }
         }
 
+        private func setupDeterminationsAndForecasts() {
+            Task {
+                async let getAllSettingsDefaults: () = getAllSettingsValues()
+                async let setupDeterminations: () = setupDeterminationsArray()
+
+                await getAllSettingsDefaults
+                await setupDeterminations
+
+                // Determination has updated, so we can use this to draw the initial Forecast Chart
+                let forecastData = await mapForecastsForChart()
+                await updateForecasts(with: forecastData)
+            }
+        }
+
+        private func setupObservers() {
+            broadcaster.register(DeterminationObserver.self, observer: self)
+            broadcaster.register(BolusFailureObserver.self, observer: self)
+        }
+
+        private func setupSettings() {
+            units = settingsManager.settings.units
+            fraction = settings.settings.overrideFactor
+            fattyMeals = settings.settings.fattyMeals
+            fattyMealFactor = settings.settings.fattyMealFactor
+            sweetMeals = settings.settings.sweetMeals
+            sweetMealFactor = settings.settings.sweetMealFactor
+            displayPresets = settings.settings.displayPresets
+            forecastDisplayType = settings.settings.forecastDisplayType
+            lowGlucose = units == .mgdL ? settingsManager.settings.low : settingsManager.settings.low.asMmolL
+            highGlucose = units == .mgdL ? settingsManager.settings.high : settingsManager.settings.high.asMmolL
+            maxCarbs = settings.settings.maxCarbs
+            maxFat = settings.settings.maxFat
+            maxProtein = settings.settings.maxProtein
+            useFPUconversion = settingsManager.settings.useFPUconversion
+            smooth = settingsManager.settings.smoothGlucose
+        }
+
         private func getCurrentSettingValue(for type: SettingType) async {
             let now = Date()
             let calendar = Calendar.current
@@ -439,52 +445,19 @@ extension Bolus {
             }
         }
 
-        private func calculateGradientStops() async -> [Gradient.Stop] {
-            let low = Double(lowGlucose)
-            let high = Double(highGlucose)
-
+        private func calculateGradientStops() async {
             let glucoseValues = glucoseFromPersistence
                 .map { units == .mgdL ? Decimal($0.glucose) : Decimal($0.glucose).asMmolL }
 
-            let minimum = glucoseValues.min() ?? 0.0
-            let maximum = glucoseValues.max() ?? 0.0
-
-            // Handle edge case where minimum and maximum are equal
-            guard minimum != maximum else {
-                return [
-                    Gradient.Stop(color: .green, location: 0.0),
-                    Gradient.Stop(color: .green, location: 1.0)
-                ]
-            }
-
-            // Calculate positions for gradient
-            let lowPosition = (low - Double(truncating: minimum as NSNumber)) /
-                (Double(truncating: maximum as NSNumber) - Double(truncating: minimum as NSNumber))
-            let highPosition = (high - Double(truncating: minimum as NSNumber)) /
-                (Double(truncating: maximum as NSNumber) - Double(truncating: minimum as NSNumber))
-
-            // Ensure positions are in bounds [0, 1]
-            let clampedLowPosition = max(0.0, min(lowPosition, 1.0))
-            let clampedHighPosition = max(0.0, min(highPosition, 1.0))
-
-            // Ensure lowPosition is less than highPosition
-            let epsilon: CGFloat = 0.0001
-            let sortedPositions = [clampedLowPosition, clampedHighPosition].sorted()
-            var adjustedHighPosition = sortedPositions[1]
+            let calculatedStops = await GradientStops.calculateGradientStops(
+                lowGlucose: lowGlucose,
+                highGlucose: highGlucose,
+                glucoseValues: glucoseValues
+            )
 
-            if adjustedHighPosition - sortedPositions[0] < epsilon {
-                adjustedHighPosition = min(1.0, sortedPositions[0] + epsilon)
+            await MainActor.run {
+                stops = calculatedStops
             }
-
-            return [
-                Gradient.Stop(color: .red, location: 0.0),
-                Gradient.Stop(color: .red, location: sortedPositions[0]), // draw red gradient till lowGlucose
-                Gradient.Stop(color: .green, location: sortedPositions[0] + epsilon),
-                // draw green above lowGlucose till highGlucose
-                Gradient.Stop(color: .green, location: adjustedHighPosition),
-                Gradient.Stop(color: .orange, location: adjustedHighPosition + epsilon), // draw orange above highGlucose
-                Gradient.Stop(color: .orange, location: 1.0)
-            ]
         }
 
         // MARK: - Carbs
@@ -626,11 +599,7 @@ extension Bolus.StateModel {
             await updateGlucoseArray(with: glucoseObjects)
 
             if smooth {
-                let newStops = await self.calculateGradientStops()
-
-                await MainActor.run {
-                    self.stops = newStops
-                }
+                await self.calculateGradientStops()
             }
         }
     }

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

@@ -171,7 +171,7 @@ extension Bolus {
                 VStack {
                     Form {
                         Section {
-                            ForeCastChart(state: state, units: $state.units, stops: state.stops)
+                            ForeCastChart(state: state, units: $state.units)
                                 .padding(.vertical)
                         }.listRowBackground(Color.chart)
 

+ 1 - 2
FreeAPS/Sources/Modules/Bolus/View/ForeCastChart.swift

@@ -7,7 +7,6 @@ struct ForeCastChart: View {
     @StateObject var state: Bolus.StateModel
     @Environment(\.colorScheme) var colorScheme
     @Binding var units: GlucoseUnits
-    var stops: [Gradient.Stop]
 
     @State private var startMarker = Date(timeIntervalSinceNow: -4 * 60 * 60)
 
@@ -130,7 +129,7 @@ struct ForeCastChart: View {
                     y: .value("Value", glucoseToDisplay)
                 )
                 .foregroundStyle(
-                    .linearGradient(stops: stops, startPoint: .bottom, endPoint: .top)
+                    .linearGradient(stops: state.stops, startPoint: .bottom, endPoint: .top)
                 )
                 .symbol(.circle).symbolSize(34)
             } else {

+ 88 - 59
FreeAPS/Sources/Modules/Home/HomeStateModel.swift

@@ -87,6 +87,7 @@ extension Home {
         @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 gradientStops: [Gradient.Stop] = []
 
         let context = CoreDataStack.shared.newTaskContext()
         let viewContext = CoreDataStack.shared.persistentContainer.viewContext
@@ -115,30 +116,51 @@ extension Home {
             setupCurrentPumpTimezone()
             setupOverrides()
             setupOverrideRunStored()
+            setupSettings()
+            registerObservers()
+        }
 
-            // TODO: isUploadEnabled the right var here??
-            uploadStats = settingsManager.settings.isUploadEnabled
-            units = settingsManager.settings.units
-            allowManualTemp = !settingsManager.settings.closedLoop
-            closedLoop = settingsManager.settings.closedLoop
-            lastLoopDate = apsManager.lastLoopDate
-            alarm = provider.glucoseStorage.alarm
-            manualTempBasal = apsManager.isManualTempBasal
-            setupCurrentTempTarget()
-            smooth = settingsManager.settings.smoothGlucose
-            maxValue = settingsManager.preferences.autosensMax
-            lowGlucose = units == .mgdL ? settingsManager.settings.low : settingsManager.settings.low.asMmolL
-            highGlucose = units == .mgdL ? settingsManager.settings.high : settingsManager.settings.high.asMmolL
-            overrideUnit = settingsManager.settings.overrideHbA1cUnit
-            displayXgridLines = settingsManager.settings.xGridLines
-            displayYgridLines = settingsManager.settings.yGridLines
-            thresholdLines = settingsManager.settings.rulerMarks
-            totalInsulinDisplayType = settingsManager.settings.totalInsulinDisplayType
-            cgmAvailable = fetchGlucoseManager.cgmGlucoseSourceType != CGMType.none
-            showCarbsRequiredBadge = settingsManager.settings.showCarbsRequiredBadge
+        private func registerHandlers() {
+            coreDataObserver?.registerHandler(for: "OrefDetermination") { [weak self] in
+                guard let self = self else { return }
+                self.setupDeterminationsArray()
+            }
 
-            forecastDisplayType = settingsManager.settings.forecastDisplayType
+            coreDataObserver?.registerHandler(for: "GlucoseStored") { [weak self] in
+                guard let self = self else { return }
+                self.setupGlucoseArray()
+                self.setupManualGlucoseArray()
+            }
 
+            coreDataObserver?.registerHandler(for: "CarbEntryStored") { [weak self] in
+                guard let self = self else { return }
+                self.setupCarbsArray()
+            }
+
+            coreDataObserver?.registerHandler(for: "PumpEventStored") { [weak self] in
+                guard let self = self else { return }
+                self.setupInsulinArray()
+                self.setupLastBolus()
+                self.displayPumpStatusHighlightMessage()
+            }
+
+            coreDataObserver?.registerHandler(for: "OpenAPS_Battery") { [weak self] in
+                guard let self = self else { return }
+                self.setupBatteryArray()
+            }
+
+            coreDataObserver?.registerHandler(for: "OverrideStored") { [weak self] in
+                guard let self = self else { return }
+                self.setupOverrides()
+            }
+
+            coreDataObserver?.registerHandler(for: "OverrideRunStored") { [weak self] in
+                guard let self = self else { return }
+                self.setupOverrideRunStored()
+            }
+        }
+
+        private func registerObservers() {
             broadcaster.register(GlucoseObserver.self, observer: self)
             broadcaster.register(DeterminationObserver.self, observer: self)
             broadcaster.register(SettingsObserver.self, observer: self)
@@ -213,44 +235,28 @@ extension Home {
                 .store(in: &lifetime)
         }
 
-        private func registerHandlers() {
-            coreDataObserver?.registerHandler(for: "OrefDetermination") { [weak self] in
-                guard let self = self else { return }
-                self.setupDeterminationsArray()
-            }
-
-            coreDataObserver?.registerHandler(for: "GlucoseStored") { [weak self] in
-                guard let self = self else { return }
-                self.setupGlucoseArray()
-                self.setupManualGlucoseArray()
-            }
-
-            coreDataObserver?.registerHandler(for: "CarbEntryStored") { [weak self] in
-                guard let self = self else { return }
-                self.setupCarbsArray()
-            }
-
-            coreDataObserver?.registerHandler(for: "PumpEventStored") { [weak self] in
-                guard let self = self else { return }
-                self.setupInsulinArray()
-                self.setupLastBolus()
-                self.displayPumpStatusHighlightMessage()
-            }
-
-            coreDataObserver?.registerHandler(for: "OpenAPS_Battery") { [weak self] in
-                guard let self = self else { return }
-                self.setupBatteryArray()
-            }
-
-            coreDataObserver?.registerHandler(for: "OverrideStored") { [weak self] in
-                guard let self = self else { return }
-                self.setupOverrides()
-            }
-
-            coreDataObserver?.registerHandler(for: "OverrideRunStored") { [weak self] in
-                guard let self = self else { return }
-                self.setupOverrideRunStored()
-            }
+        private func setupSettings() {
+            // TODO: isUploadEnabled the right var here??
+            uploadStats = settingsManager.settings.isUploadEnabled
+            units = settingsManager.settings.units
+            allowManualTemp = !settingsManager.settings.closedLoop
+            closedLoop = settingsManager.settings.closedLoop
+            lastLoopDate = apsManager.lastLoopDate
+            alarm = provider.glucoseStorage.alarm
+            manualTempBasal = apsManager.isManualTempBasal
+            setupCurrentTempTarget()
+            smooth = settingsManager.settings.smoothGlucose
+            maxValue = settingsManager.preferences.autosensMax
+            lowGlucose = units == .mgdL ? settingsManager.settings.low : settingsManager.settings.low.asMmolL
+            highGlucose = units == .mgdL ? settingsManager.settings.high : settingsManager.settings.high.asMmolL
+            overrideUnit = settingsManager.settings.overrideHbA1cUnit
+            displayXgridLines = settingsManager.settings.xGridLines
+            displayYgridLines = settingsManager.settings.yGridLines
+            thresholdLines = settingsManager.settings.rulerMarks
+            totalInsulinDisplayType = settingsManager.settings.totalInsulinDisplayType
+            cgmAvailable = fetchGlucoseManager.cgmGlucoseSourceType != CGMType.none
+            showCarbsRequiredBadge = settingsManager.settings.showCarbsRequiredBadge
+            forecastDisplayType = settingsManager.settings.forecastDisplayType
         }
 
         func addPump(_ type: PumpConfig.PumpType) {
@@ -274,6 +280,21 @@ extension Home {
             }
         }
 
+        private func calculateGradientStops() async {
+            let glucoseValues = glucoseFromPersistence
+                .map { units == .mgdL ? Decimal($0.glucose) : Decimal($0.glucose).asMmolL }
+
+            let calculatedStops = await GradientStops.calculateGradientStops(
+                lowGlucose: lowGlucose,
+                highGlucose: highGlucose,
+                glucoseValues: glucoseValues
+            )
+
+            await MainActor.run {
+                self.gradientStops = calculatedStops
+            }
+        }
+
         func runLoop() {
             provider.heartbeatNow()
         }
@@ -557,6 +578,10 @@ extension Home.StateModel {
             let ids = await self.fetchGlucose()
             let glucoseObjects: [GlucoseStored] = await CoreDataStack.shared.getNSManagedObject(with: ids, context: viewContext)
             await updateGlucoseArray(with: glucoseObjects)
+
+            if smooth {
+                await calculateGradientStops()
+            }
         }
     }
 
@@ -588,6 +613,10 @@ extension Home.StateModel {
             let manualGlucoseObjects: [GlucoseStored] = await CoreDataStack.shared
                 .getNSManagedObject(with: ids, context: viewContext)
             await updateManualGlucoseArray(with: manualGlucoseObjects)
+
+            if smooth {
+                await calculateGradientStops()
+            }
         }
     }
 

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

@@ -554,39 +554,6 @@ extension MainChartView {
         }
     }
 
-    private var stops: [Gradient.Stop] {
-        let low = Double(lowGlucose)
-        let high = Double(highGlucose)
-
-        let glucoseValues = state.glucoseFromPersistence
-            .map { units == .mgdL ? Decimal($0.glucose) : Decimal($0.glucose).asMmolL }
-
-        let minimum = glucoseValues.min() ?? 0.0
-        let maximum = glucoseValues.max() ?? 0.0
-
-        // Calculate positions for gradient
-        let lowPosition = (low - Double(truncating: minimum as NSNumber)) /
-            (Double(truncating: maximum as NSNumber) - Double(truncating: minimum as NSNumber))
-        let highPosition = (high - Double(truncating: minimum as NSNumber)) /
-            (Double(truncating: maximum as NSNumber) - Double(truncating: minimum as NSNumber))
-
-        // Ensure positions are in bounds [0, 1]
-        let clampedLowPosition = max(0.0, min(lowPosition, 1.0))
-        let clampedHighPosition = max(0.0, min(highPosition, 1.0))
-
-        // Ensure lowPosition is less than highPosition
-        let sortedPositions = [clampedLowPosition, clampedHighPosition].sorted()
-
-        return [
-            Gradient.Stop(color: .red, location: 0.0),
-            Gradient.Stop(color: .red, location: sortedPositions[0]), // draw red gradient till lowGlucose
-            Gradient.Stop(color: .green, location: sortedPositions[0] + 0.0001), // draw green above lowGlucose till highGlucose
-            Gradient.Stop(color: .green, location: sortedPositions[1]),
-            Gradient.Stop(color: .orange, location: sortedPositions[1] + 0.0001), // draw orange above highGlucose
-            Gradient.Stop(color: .orange, location: 1.0)
-        ]
-    }
-
     private func drawGlucose(dummy _: Bool) -> some ChartContent {
         /// glucose point mark
         /// filtering for high and low bounds in settings
@@ -596,7 +563,7 @@ extension MainChartView {
             if smooth {
                 LineMark(x: .value("Time", item.date ?? Date()), y: .value("Value", glucoseToDisplay))
                     .foregroundStyle(
-                        .linearGradient(stops: stops, startPoint: .bottom, endPoint: .top)
+                        .linearGradient(stops: state.gradientStops, startPoint: .bottom, endPoint: .top)
                     )
                     .symbol(.circle).symbolSize(34)
             } else {

+ 40 - 8
FreeAPS/Sources/Modules/Home/View/HomeRootView.swift

@@ -362,7 +362,7 @@ extension Home {
                     state: state
                 )
             }
-            .padding(.bottom)
+            .padding(.bottom, UIDevice.adjustPadding(min: 0, max: nil))
         }
 
         func highlightButtons() {
@@ -574,7 +574,7 @@ extension Home {
                             showCancelAlert = true
                         }
                     }
-            }.padding(.horizontal, 10).padding(.bottom, 10)
+            }.padding(.horizontal, 10).padding(.bottom, UIDevice.adjustPadding(min: nil, max: 10))
                 .overlay {
                     /// just show temp target if no profile is already active
                     if overrideString == nil, let tempTargetString = tempTargetString {
@@ -606,7 +606,7 @@ extension Home {
                                     .font(.subheadline)
                                 Spacer()
                             }.padding(.horizontal, 10)
-                        }.padding(.horizontal, 10).padding(.bottom, 10)
+                        }.padding(.horizontal, 10).padding(.bottom, UIDevice.adjustPadding(min: nil, max: 10))
                     }
                 }
         }
@@ -688,7 +688,7 @@ extension Home {
                     }.padding(.horizontal, 10)
                         .padding(.trailing, 8)
 
-                }.padding(.horizontal, 10).padding(.bottom, 10)
+                }.padding(.horizontal, 10).padding(.bottom, UIDevice.adjustPadding(min: nil, max: 10))
                     .overlay(alignment: .bottom) {
                         bolusProgressBar(progress).padding(.horizontal, 18).offset(y: 48)
                     }.clipShape(RoundedRectangle(cornerRadius: 15))
@@ -715,16 +715,18 @@ extension Home {
                         }.padding(.leading, 20)
                     }.padding(.top, 10)
 
-                    mealPanel(geo).padding(.top, 30).padding(.bottom, 20)
+                    mealPanel(geo).padding(.top, UIDevice.adjustPadding(min: nil, max: 30))
+                        .padding(.bottom, UIDevice.adjustPadding(min: nil, max: 20))
 
                     mainChart(geo: geo)
 
-                    timeInterval.padding(.top, 12).padding(.bottom, 12)
+                    timeInterval.padding(.top, UIDevice.adjustPadding(min: 0, max: 12))
+                        .padding(.bottom, UIDevice.adjustPadding(min: 0, max: 12))
 
                     if let progress = state.bolusProgress {
-                        bolusView(geo: geo, progress).padding(.bottom, 40)
+                        bolusView(geo: geo, progress).padding(.bottom, UIDevice.adjustPadding(min: nil, max: 40))
                     } else {
-                        profileView(geo: geo).padding(.bottom, 40)
+                        profileView(geo: geo).padding(.bottom, UIDevice.adjustPadding(min: nil, max: 40))
                     }
                 }
                 .background(color)
@@ -1003,3 +1005,33 @@ extension Home {
         }
     }
 }
+
+extension UIDevice {
+    public enum DeviceSize: CGFloat {
+        case smallDevice = 667 // Height for 4" iPhone SE
+        case largeDevice = 852 // Height for 6.1" iPhone 15 Pro
+    }
+
+    @usableFromInline static func adjustPadding(min: CGFloat? = nil, max: CGFloat? = nil) -> CGFloat? {
+        if UIScreen.screenHeight > UIDevice.DeviceSize.smallDevice.rawValue {
+            if UIScreen.screenHeight >= UIDevice.DeviceSize.largeDevice.rawValue {
+                return max
+            } else {
+                return min != nil ?
+                    (max != nil ? max! * (UIScreen.screenHeight / UIDevice.DeviceSize.largeDevice.rawValue) : nil) : nil
+            }
+        } else {
+            return min
+        }
+    }
+}
+
+extension UIScreen {
+    static var screenHeight: CGFloat {
+        UIScreen.main.bounds.height
+    }
+
+    static var screenWidth: CGFloat {
+        UIScreen.main.bounds.width
+    }
+}

+ 16 - 0
FreeAPS/Sources/Services/Network/NightscoutManager.swift

@@ -79,6 +79,7 @@ final class BaseNightscoutManager: NightscoutManager, Injectable {
         subscribe()
         coreDataObserver = CoreDataObserver()
         registerHandlers()
+        setupNotification()
     }
 
     private func subscribe() {
@@ -126,6 +127,21 @@ final class BaseNightscoutManager: NightscoutManager, Injectable {
         }
     }
 
+    func setupNotification() {
+        Foundation.NotificationCenter.default.addObserver(
+            self,
+            selector: #selector(handleOverrideConfigurationUpdate),
+            name: .didUpdateOverrideConfiguration,
+            object: nil
+        )
+    }
+
+    @objc private func handleOverrideConfigurationUpdate() {
+        Task.detached {
+            await self.uploadOverrides()
+        }
+    }
+
     func sourceInfo() -> [String: Any]? {
         if let ping = ping {
             return [GlucoseSourceKey.nightscoutPing.rawValue: ping]

+ 14 - 0
FreeAPS/Sources/Shortcuts/AppShortcuts.swift

@@ -31,5 +31,19 @@ import Foundation
                 "\(.applicationName) allows to add carbs"
             ]
         )
+        AppShortcut(
+            intent: ApplyOverridePresetIntent(),
+            phrases: [
+                "Activate \(.applicationName) override",
+                "Activates an available \(.applicationName) override"
+            ]
+        )
+        AppShortcut(
+            intent: CancelOverrideIntent(),
+            phrases: [
+                "Cancel \(.applicationName) override",
+                "Cancels an active \(.applicationName) override"
+            ]
+        )
     }
 }

+ 2 - 0
FreeAPS/Sources/Shortcuts/BaseIntentsRequest.swift

@@ -13,10 +13,12 @@ import Swinject
     @Injected() var carbsStorage: CarbsStorage!
     @Injected() var glucoseStorage: GlucoseStorage!
     @Injected() var apsManager: APSManager!
+    @Injected() var overrideStorage: OverrideStorage!
 
     let resolver: Resolver
 
     let coredataContext = CoreDataStack.shared.newTaskContext()
+    let viewContext = CoreDataStack.shared.persistentContainer.viewContext
 
     override init() {
         resolver = FreeAPSApp.resolver

+ 88 - 0
FreeAPS/Sources/Shortcuts/Override/ApplyOverridePresetIntent.swift

@@ -0,0 +1,88 @@
+import AppIntents
+import Foundation
+
+@available(iOS 16.0, *) struct ApplyOverridePresetIntent: AppIntent {
+    // Title of the action in the Shortcuts app
+    static var title = LocalizedStringResource("Activate an override", table: "ShortcutsDetail")
+
+    // Description of the action in the Shortcuts app
+    static var description = IntentDescription(.init("Activate an override", table: "ShortcutsDetail"))
+
+    internal var intentRequest: OverridePresetsIntentRequest
+
+    init() {
+        intentRequest = OverridePresetsIntentRequest()
+    }
+
+    @Parameter(
+        title: LocalizedStringResource("Override", table: "ShortcutsDetail"),
+        description: LocalizedStringResource("Override choice", table: "ShortcutsDetail")
+    ) var preset: OverridePreset?
+
+    @Parameter(
+        title: LocalizedStringResource("Confirm Before applying", table: "ShortcutsDetail"),
+        description: LocalizedStringResource("If toggled, you will need to confirm before applying", table: "ShortcutsDetail"),
+        default: true
+    ) var confirmBeforeApplying: Bool
+
+    static var parameterSummary: some ParameterSummary {
+        When(\ApplyOverridePresetIntent.$confirmBeforeApplying, .equalTo, true, {
+            Summary("Applying \(\.$preset) override", table: "ShortcutsDetail") {
+                \.$confirmBeforeApplying
+            }
+        }, otherwise: {
+            Summary("Immediately applying \(\.$preset) override", table: "ShortcutsDetail") {
+                \.$confirmBeforeApplying
+            }
+        })
+    }
+
+    @MainActor func perform() async throws -> some ProvidesDialog {
+        do {
+            let presetToApply: OverridePreset
+            if let preset = preset {
+                presetToApply = preset
+            } else {
+                presetToApply = try await $preset.requestDisambiguation(
+                    among: await intentRequest.fetchAndProcessOverrides(),
+                    dialog: IntentDialog(LocalizedStringResource("Select override", table: "ShortcutsDetail"))
+                )
+            }
+
+            let displayName: String = presetToApply.name
+            if confirmBeforeApplying {
+                try await requestConfirmation(
+                    result: .result(
+                        dialog: IntentDialog(LocalizedStringResource(
+                            "Confirm to apply override '\(displayName)'",
+                            table: "ShortcutsDetail"
+                        ))
+                    )
+                )
+            }
+
+            if await intentRequest.enactOverride(presetToApply) {
+                return .result(
+                    dialog: IntentDialog(
+                        LocalizedStringResource(
+                            "Override '\(presetToApply.name)' applied",
+                            table: "ShortcutsDetail"
+                        )
+                    )
+                )
+            } else {
+                return .result(
+                    dialog: IntentDialog(
+                        LocalizedStringResource(
+                            "Override '\(presetToApply.name)' failed",
+                            table: "ShortcutsDetail"
+                        )
+                    )
+                )
+            }
+
+        } catch {
+            throw error
+        }
+    }
+}

+ 25 - 0
FreeAPS/Sources/Shortcuts/Override/CancelOverrideIntent.swift

@@ -0,0 +1,25 @@
+import AppIntents
+import Foundation
+
+@available(iOS 16.0, *) struct CancelOverrideIntent: AppIntent {
+    // Title of the action in the Shortcuts app
+    static var title = LocalizedStringResource("Cancel override", table: "ShortcutsDetail")
+
+    // Description of the action in the Shortcuts app
+    static var description = IntentDescription(.init("Cancel an active override", table: "ShortcutsDetail"))
+
+    internal var intentRequest: OverridePresetsIntentRequest
+
+    init() {
+        intentRequest = OverridePresetsIntentRequest()
+    }
+
+    @MainActor func perform() async throws -> some ProvidesDialog {
+        do {
+            await intentRequest.cancelOverride()
+            return .result(
+                dialog: IntentDialog(LocalizedStringResource("Override canceled", table: "ShortcutsDetail"))
+            )
+        }
+    }
+}

+ 30 - 0
FreeAPS/Sources/Shortcuts/Override/ListOverridePresetIntent.swift

@@ -0,0 +1,30 @@
+import AppIntents
+import Foundation
+
+@available(iOS 16.0, *) struct ListOverridePresetsIntent: AppIntent {
+    // Title of the action in the Shortcuts app
+    static var title = LocalizedStringResource("List overrides", table: "ShortcutsDetail")
+
+    // Description of the action in the Shortcuts app
+    static var description = IntentDescription(
+        .init(
+            "Allow to list and choose a specific override",
+            table: "ShortcutsDetail"
+        )
+    )
+
+    @Parameter(
+        title: LocalizedStringResource("Override", table: "ShortcutsDetail"),
+        description: LocalizedStringResource("Override choice", table: "ShortcutsDetail")
+    ) var preset: OverridePreset?
+
+    static var parameterSummary: some ParameterSummary {
+        Summary("Choose the override  \(\.$preset)", table: "ShortcutsDetail")
+    }
+
+    @MainActor func perform() async throws -> some ReturnsValue<OverridePreset> {
+        .result(
+            value: preset!
+        )
+    }
+}

+ 33 - 0
FreeAPS/Sources/Shortcuts/Override/OverridePresetEntity.swift

@@ -0,0 +1,33 @@
+import AppIntents
+import Foundation
+import Intents
+import Swinject
+
+@available(iOS 16.0, *) struct OverridePreset: AppEntity, Identifiable {
+    static var defaultQuery = OverridePresetsQuery()
+
+    var id: String
+    var name: String
+
+    var displayRepresentation: DisplayRepresentation {
+        DisplayRepresentation(title: "\(name)")
+    }
+
+    static var typeDisplayRepresentation: TypeDisplayRepresentation = "Override"
+}
+
+@available(iOS 16.0, *) struct OverridePresetsQuery: EntityQuery {
+    internal var intentRequest: OverridePresetsIntentRequest
+
+    init() {
+        intentRequest = OverridePresetsIntentRequest()
+    }
+
+    func entities(for identifiers: [OverridePreset.ID]) async throws -> [OverridePreset] {
+        await intentRequest.fetchIDs(identifiers)
+    }
+
+    func suggestedEntities() async throws -> [OverridePreset] {
+        await intentRequest.fetchAndProcessOverrides()
+    }
+}

+ 171 - 0
FreeAPS/Sources/Shortcuts/Override/OverridePresetsIntentRequest.swift

@@ -0,0 +1,171 @@
+import CoreData
+import Foundation
+
+@available(iOS 16.0, *) final class OverridePresetsIntentRequest: BaseIntentsRequest {
+    enum overridePresetsError: Error {
+        case noTempOverrideFound
+        case noDurationDefined
+        case noActiveOverride
+    }
+
+    func fetchAndProcessOverrides() async -> [OverridePreset] {
+        // Fetch all Override Presets via OverrideStorage
+        let allOverridePresetsIDs = await overrideStorage.fetchForOverridePresets()
+
+        // Since we are fetching on a different background Thread we need to unpack the NSManagedObjectID on the correct Thread first
+        return await coredataContext.perform {
+            do {
+                let overrideObjects = try allOverridePresetsIDs.compactMap { id in
+                    try self.coredataContext.existingObject(with: id) as? OverrideStored
+                }
+
+                return overrideObjects.map { object in
+                    guard let id = object.id,
+                          let name = object.name else { return OverridePreset(id: UUID().uuidString, name: "") }
+                    return OverridePreset(id: id, name: name)
+                }
+
+            } catch {
+                debugPrint(
+                    "\(#file) \(#function) \(DebuggingIdentifiers.failed) error while fetching/ processing the overrides Array: \(error.localizedDescription)"
+                )
+                return [OverridePreset(id: UUID().uuidString, name: "")]
+            }
+        }
+    }
+
+    func fetchIDs(_ uuid: [OverridePreset.ID]) async -> [OverridePreset] {
+        await coredataContext.perform {
+            let fetchRequest: NSFetchRequest<OverrideStored> = OverrideStored.fetchRequest()
+            fetchRequest.predicate = NSPredicate(format: "id IN %@", uuid)
+
+            do {
+                let result = try self.coredataContext.fetch(fetchRequest)
+
+                if result.isEmpty {
+                    debugPrint("\(DebuggingIdentifiers.failed) \(#file) \(#function) No OverrideStored found for ids: \(uuid)")
+                    return [OverridePreset(id: UUID().uuidString, name: "")]
+                }
+
+                return result.map { overrideStored in
+                    OverridePreset(id: overrideStored.id ?? UUID().uuidString, name: overrideStored.name ?? "")
+                }
+            } catch let error as NSError {
+                debugPrint(
+                    "\(DebuggingIdentifiers.failed) \(#file) \(#function) Failed to fetch Override: \(error.localizedDescription)"
+                )
+                return [OverridePreset(id: UUID().uuidString, name: "")]
+            }
+        }
+    }
+
+    private func fetchOverrideID(_ preset: OverridePreset) async -> NSManagedObjectID? {
+        let fetchRequest: NSFetchRequest<OverrideStored> = OverrideStored.fetchRequest()
+        fetchRequest.predicate = NSPredicate(format: "id == %@", preset.id)
+        fetchRequest.fetchLimit = 1
+
+        return await coredataContext.perform {
+            do {
+                return try self.coredataContext.fetch(fetchRequest).first?.objectID
+            } catch {
+                debugPrint(
+                    "\(DebuggingIdentifiers.failed) \(#file) \(#function) Failed to fetch Override: \(error.localizedDescription)"
+                )
+                return nil
+            }
+        }
+    }
+
+    @MainActor func enactOverride(_ preset: OverridePreset) async -> Bool {
+        do {
+            guard let overrideID = await fetchOverrideID(preset),
+                  let overrideObject = try viewContext.existingObject(with: overrideID) as? OverrideStored else { return false }
+
+            overrideObject.enabled = true
+            overrideObject.date = Date()
+            overrideObject.isUploadedToNS = 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: .didUpdateOverrideConfiguration, object: nil)
+
+                return true
+            }
+        } 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)
+    }
+
+    @MainActor func disableAllActiveOverrides(
+        except overrideID: NSManagedObjectID? = nil,
+        createOverrideRunEntry _: Bool
+    ) async {
+        // Get ALL NSManagedObject IDs of ALL active Overrides to cancel every single Override
+        let ids = await overrideStorage.loadLatestOverrideConfigurations(fetchLimit: 0) // 0 = no fetch limit
+
+        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
+                }
+
+                // 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
+                    }
+                }
+
+                // 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: .didUpdateOverrideConfiguration, object: nil)
+                }
+            } catch {
+                debugPrint(
+                    "\(DebuggingIdentifiers.failed) \(#file) \(#function) Failed to disable active Overrides with error: \(error.localizedDescription)"
+                )
+            }
+        }
+    }
+}