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

Merge branch 'core-data-sync-trio' of github.com:dnzxy/Trio-dev into rework-loop-status

Deniz Cengiz 1 год назад
Родитель
Сommit
b3791cbcdc

+ 4 - 0
FreeAPS.xcodeproj/project.pbxproj

@@ -487,6 +487,7 @@
 		DD9ECB722CA9A0BA00AA7C45 /* RemoteControlConfigDataFlow.swift in Sources */ = {isa = PBXBuildFile; fileRef = DD9ECB6F2CA9A0BA00AA7C45 /* RemoteControlConfigDataFlow.swift */; };
 		DD9ECB722CA9A0BA00AA7C45 /* RemoteControlConfigDataFlow.swift in Sources */ = {isa = PBXBuildFile; fileRef = DD9ECB6F2CA9A0BA00AA7C45 /* RemoteControlConfigDataFlow.swift */; };
 		DD9ECB742CA9A0C300AA7C45 /* RemoteControlConfig.swift in Sources */ = {isa = PBXBuildFile; fileRef = DD9ECB732CA9A0C300AA7C45 /* RemoteControlConfig.swift */; };
 		DD9ECB742CA9A0C300AA7C45 /* RemoteControlConfig.swift in Sources */ = {isa = PBXBuildFile; fileRef = DD9ECB732CA9A0C300AA7C45 /* RemoteControlConfig.swift */; };
 		DDA6E2852D2361F800C2988C /* LoopStatusSheetView.swift in Sources */ = {isa = PBXBuildFile; fileRef = DDA6E2842D2361F800C2988C /* LoopStatusSheetView.swift */; };
 		DDA6E2852D2361F800C2988C /* LoopStatusSheetView.swift in Sources */ = {isa = PBXBuildFile; fileRef = DDA6E2842D2361F800C2988C /* LoopStatusSheetView.swift */; };
+		DDA6E2502D22187500C2988C /* ChartLegendView.swift in Sources */ = {isa = PBXBuildFile; fileRef = DDA6E24F2D22187500C2988C /* ChartLegendView.swift */; };
 		DDB37CC52D05048F00D99BF4 /* ContactImageStorage.swift in Sources */ = {isa = PBXBuildFile; fileRef = DDB37CC42D05048F00D99BF4 /* ContactImageStorage.swift */; };
 		DDB37CC52D05048F00D99BF4 /* ContactImageStorage.swift in Sources */ = {isa = PBXBuildFile; fileRef = DDB37CC42D05048F00D99BF4 /* ContactImageStorage.swift */; };
 		DDB37CC72D05127500D99BF4 /* FontExtensions.swift in Sources */ = {isa = PBXBuildFile; fileRef = DDB37CC62D05127500D99BF4 /* FontExtensions.swift */; };
 		DDB37CC72D05127500D99BF4 /* FontExtensions.swift in Sources */ = {isa = PBXBuildFile; fileRef = DDB37CC62D05127500D99BF4 /* FontExtensions.swift */; };
 		DDCEBF5B2CC1B76400DF4C36 /* LiveActivity+Helper.swift in Sources */ = {isa = PBXBuildFile; fileRef = DDCEBF5A2CC1B76400DF4C36 /* LiveActivity+Helper.swift */; };
 		DDCEBF5B2CC1B76400DF4C36 /* LiveActivity+Helper.swift in Sources */ = {isa = PBXBuildFile; fileRef = DDCEBF5A2CC1B76400DF4C36 /* LiveActivity+Helper.swift */; };
@@ -1193,6 +1194,7 @@
 		DD9ECB6F2CA9A0BA00AA7C45 /* RemoteControlConfigDataFlow.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = RemoteControlConfigDataFlow.swift; sourceTree = "<group>"; };
 		DD9ECB6F2CA9A0BA00AA7C45 /* RemoteControlConfigDataFlow.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = RemoteControlConfigDataFlow.swift; sourceTree = "<group>"; };
 		DD9ECB732CA9A0C300AA7C45 /* RemoteControlConfig.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = RemoteControlConfig.swift; sourceTree = "<group>"; };
 		DD9ECB732CA9A0C300AA7C45 /* RemoteControlConfig.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = RemoteControlConfig.swift; sourceTree = "<group>"; };
 		DDA6E2842D2361F800C2988C /* LoopStatusSheetView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = LoopStatusSheetView.swift; sourceTree = "<group>"; };
 		DDA6E2842D2361F800C2988C /* LoopStatusSheetView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = LoopStatusSheetView.swift; sourceTree = "<group>"; };
+		DDA6E24F2D22187500C2988C /* ChartLegendView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ChartLegendView.swift; sourceTree = "<group>"; };
 		DDB37CC22D05044D00D99BF4 /* ContactTrickEntryStored+CoreDataClass.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "ContactTrickEntryStored+CoreDataClass.swift"; sourceTree = "<group>"; };
 		DDB37CC22D05044D00D99BF4 /* ContactTrickEntryStored+CoreDataClass.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "ContactTrickEntryStored+CoreDataClass.swift"; sourceTree = "<group>"; };
 		DDB37CC32D05044D00D99BF4 /* ContactTrickEntryStored+CoreDataProperties.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "ContactTrickEntryStored+CoreDataProperties.swift"; sourceTree = "<group>"; };
 		DDB37CC32D05044D00D99BF4 /* ContactTrickEntryStored+CoreDataProperties.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "ContactTrickEntryStored+CoreDataProperties.swift"; sourceTree = "<group>"; };
 		DDB37CC42D05048F00D99BF4 /* ContactImageStorage.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ContactImageStorage.swift; sourceTree = "<group>"; };
 		DDB37CC42D05048F00D99BF4 /* ContactImageStorage.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ContactImageStorage.swift; sourceTree = "<group>"; };
@@ -1889,6 +1891,7 @@
 		3833B51E260264AC003021B3 /* Chart */ = {
 		3833B51E260264AC003021B3 /* Chart */ = {
 			isa = PBXGroup;
 			isa = PBXGroup;
 			children = (
 			children = (
+				DDA6E24F2D22187500C2988C /* ChartLegendView.swift */,
 				BD3CC0712B0B89D50013189E /* MainChartView.swift */,
 				BD3CC0712B0B89D50013189E /* MainChartView.swift */,
 				BDDAF9F12D0055CC00B34E7A /* ChartElements */,
 				BDDAF9F12D0055CC00B34E7A /* ChartElements */,
 			);
 			);
@@ -3555,6 +3558,7 @@
 				DD1745482C55C61D00211FAC /* AutosensSettingsStateModel.swift in Sources */,
 				DD1745482C55C61D00211FAC /* AutosensSettingsStateModel.swift in Sources */,
 				DD1745462C55C61500211FAC /* AutosensSettingsProvider.swift in Sources */,
 				DD1745462C55C61500211FAC /* AutosensSettingsProvider.swift in Sources */,
 				DDA6E2852D2361F800C2988C /* LoopStatusSheetView.swift in Sources */,
 				DDA6E2852D2361F800C2988C /* LoopStatusSheetView.swift in Sources */,
+				DDA6E2502D22187500C2988C /* ChartLegendView.swift in Sources */,
 				3811DEAF25C9D88300A708ED /* KeyValueStorage.swift in Sources */,
 				3811DEAF25C9D88300A708ED /* KeyValueStorage.swift in Sources */,
 				DDD6D4D32CDE90720029439A /* HbA1cDisplayUnit.swift in Sources */,
 				DDD6D4D32CDE90720029439A /* HbA1cDisplayUnit.swift in Sources */,
 				38FE826D25CC8461001FF17A /* NightscoutAPI.swift in Sources */,
 				38FE826D25CC8461001FF17A /* NightscoutAPI.swift in Sources */,

+ 3 - 1
FreeAPS/Sources/Modules/AutosensSettings/AutosensSettingsDataFlow.swift

@@ -2,4 +2,6 @@ enum AutosensSettings {
     enum Config {}
     enum Config {}
 }
 }
 
 
-protocol AutosensSettingsProvider: Provider {}
+protocol AutosensSettingsProvider: Provider {
+    var autosense: Autosens { get }
+}

+ 7 - 1
FreeAPS/Sources/Modules/AutosensSettings/AutosensSettingsProvider.swift

@@ -1,3 +1,9 @@
 extension AutosensSettings {
 extension AutosensSettings {
-    final class Provider: BaseProvider, AutosensSettingsProvider {}
+    final class Provider: BaseProvider, AutosensSettingsProvider {
+        var autosense: Autosens {
+            storage.retrieve(OpenAPS.Settings.autosense, as: Autosens.self)
+                ?? Autosens(from: OpenAPS.defaults(for: OpenAPS.Settings.autosense))
+                ?? Autosens(ratio: 1, newisf: nil, timestamp: nil)
+        }
+    }
 }
 }

+ 42 - 0
FreeAPS/Sources/Modules/AutosensSettings/AutosensSettingsStateModel.swift

@@ -1,3 +1,4 @@
+import CoreData
 import Observation
 import Observation
 import SwiftUI
 import SwiftUI
 
 
@@ -5,19 +6,60 @@ extension AutosensSettings {
     final class StateModel: BaseStateModel<Provider> {
     final class StateModel: BaseStateModel<Provider> {
         @Injected() var settings: SettingsManager!
         @Injected() var settings: SettingsManager!
         @Injected() var storage: FileStorage!
         @Injected() var storage: FileStorage!
+        @Injected() var determinationStorage: DeterminationStorage!
 
 
         var units: GlucoseUnits = .mgdL
         var units: GlucoseUnits = .mgdL
 
 
+        private(set) var autosensISF: Decimal?
+        private(set) var autosensRatio: Decimal = 0
+        var determinationsFromPersistence: [OrefDetermination] = []
+
+        let viewContext = CoreDataStack.shared.persistentContainer.viewContext
+
         @Published var autosensMax: Decimal = 1.2
         @Published var autosensMax: Decimal = 1.2
         @Published var autosensMin: Decimal = 0.7
         @Published var autosensMin: Decimal = 0.7
         @Published var rewindResetsAutosens: Bool = true
         @Published var rewindResetsAutosens: Bool = true
 
 
+        var preferences: Preferences {
+            settingsManager.preferences
+        }
+
         override func subscribe() {
         override func subscribe() {
             units = settingsManager.settings.units
             units = settingsManager.settings.units
 
 
             subscribePreferencesSetting(\.autosensMax, on: $autosensMax) { autosensMax = $0 }
             subscribePreferencesSetting(\.autosensMax, on: $autosensMax) { autosensMax = $0 }
             subscribePreferencesSetting(\.autosensMin, on: $autosensMin) { autosensMin = $0 }
             subscribePreferencesSetting(\.autosensMin, on: $autosensMin) { autosensMin = $0 }
             subscribePreferencesSetting(\.rewindResetsAutosens, on: $rewindResetsAutosens) { rewindResetsAutosens = $0 }
             subscribePreferencesSetting(\.rewindResetsAutosens, on: $rewindResetsAutosens) { rewindResetsAutosens = $0 }
+
+            if let newISF = provider.autosense.newisf {
+                autosensISF = newISF
+            }
+
+            autosensRatio = provider.autosense.ratio
+            setupDeterminationsArray()
+        }
+
+        private func setupDeterminationsArray() {
+            Task {
+                let ids = await determinationStorage.fetchLastDeterminationObjectID(
+                    predicate: NSPredicate.enactedDetermination
+                )
+                await updateDeterminationsArray(with: ids)
+            }
+        }
+
+        @MainActor private func updateDeterminationsArray(with IDs: [NSManagedObjectID]) {
+            do {
+                let objects = try IDs.compactMap { id in
+                    try viewContext.existingObject(with: id) as? OrefDetermination
+                }
+                determinationsFromPersistence = objects
+
+            } catch {
+                debugPrint(
+                    "Home State: \(#function) \(DebuggingIdentifiers.failed) error while updating the glucose array: \(error.localizedDescription)"
+                )
+            }
         }
         }
     }
     }
 }
 }

+ 108 - 0
FreeAPS/Sources/Modules/AutosensSettings/View/AutosensSettingsRootView.swift

@@ -16,8 +16,116 @@ extension AutosensSettings {
         @EnvironmentObject var appIcons: Icons
         @EnvironmentObject var appIcons: Icons
         @Environment(AppState.self) var appState
         @Environment(AppState.self) var appState
 
 
+        private var rateFormatter: NumberFormatter {
+            let formatter = NumberFormatter()
+            formatter.numberStyle = .decimal
+            formatter.maximumFractionDigits = 2
+            return formatter
+        }
+
+        var autosensVerboseHint: some View {
+            VStack(alignment: .leading, spacing: 15) {
+                Text(
+                    "Autosens automatically adjusts insulin delivery based on how sensitive or resistant you are to insulin at the time of the current loop cycle by analyzing past data to keep blood sugar levels stable."
+                )
+
+                VStack(alignment: .leading, spacing: 5) {
+                    Text("How it Works").bold()
+                    Text(
+                        "It looks at the last 8-24 hours of data, excluding meal-related changes, and adjusts insulin settings like basal rates and targets when needed to match your sensitivity or resistance to insulin."
+                    )
+                }
+
+                VStack(alignment: .leading, spacing: 5) {
+                    Text("What it Adjusts").bold()
+                    Text(
+                        "Autosens modifies Insulin Sensitivity Factor (ISF), basal rates, and target blood sugar levels. It doesn’t account for carbs but adjusts for insulin effectiveness based on patterns in your glucose data."
+                    )
+                }
+
+                VStack(alignment: .leading, spacing: 5) {
+                    Text("Key Limitations").bold()
+                    Text(
+                        "Autosens has safety limits determined by your Autosens Max and Autosens Min settings. These settings prevent over-adjusting."
+                    )
+                }
+
+                Text(
+                    "Autosens functions alongside certain settings, like Super Micro Bolus (SMB). Other settings, like Dynamic ISF, alter portions of the Autosens formula. Please review the in-app hints for the Algorithm Settings prior to enabling them to understand how they may influence it."
+                )
+            }
+        }
+
+        var AutosensView: some View {
+            Section(
+                header: !state.settingsManager.preferences
+                    .useNewFormula ? Text("Autosens") : Text("Dynamic Sensitivity")
+            ) {
+                VStack {
+                    let dynamicRatio = state.determinationsFromPersistence.first?.sensitivityRatio
+                    let dynamicISF = state.determinationsFromPersistence.first?.insulinSensitivity
+                    let newISF = state.autosensISF
+                    HStack {
+                        Text("Sensitivity Ratio")
+                        Spacer()
+                        Text(
+                            rateFormatter
+                                .string(from: (
+                                    (
+                                        !state.settingsManager.preferences.useNewFormula ? state
+                                            .autosensRatio as NSDecimalNumber : dynamicRatio
+                                    ) ?? 1
+                                ) as NSNumber) ?? "1"
+                        )
+                    }.padding(.vertical)
+                    HStack {
+                        Text("Calculated Sensitivity")
+                        Spacer()
+                        if state.units == .mgdL {
+                            Text(
+                                !state.settingsManager.preferences
+                                    .useNewFormula ? newISF!.description : (dynamicISF ?? 0).description
+                            )
+                        } else {
+                            Text((
+                                !state.settingsManager.preferences
+                                    .useNewFormula ? newISF!.formattedAsMmolL : dynamicISF?.decimalValue.formattedAsMmolL
+                            ) ?? "0")
+                        }
+                        Text(state.units.rawValue + "/U").foregroundColor(.secondary)
+                    }
+
+                    HStack(alignment: .top) {
+                        Text(
+                            "This adjusted ISF is temporary, will change with the next loop cycle, and should not be directly used as your profile ISF value."
+                        )
+                        .font(.footnote)
+                        .foregroundColor(.secondary)
+                        .lineLimit(nil)
+                        Spacer()
+                        Button(
+                            action: {
+                                hintLabel = "Autosens"
+                                selectedVerboseHint = AnyView(autosensVerboseHint)
+                                shouldDisplayHint.toggle()
+                            },
+                            label: {
+                                HStack {
+                                    Image(systemName: "questionmark.circle")
+                                }
+                            }
+                        ).buttonStyle(BorderlessButtonStyle())
+                    }.padding(.top)
+                }.padding(.bottom)
+            }.listRowBackground(Color.chart)
+        }
+
         var body: some View {
         var body: some View {
             List {
             List {
+                if state.autosensISF != nil {
+                    AutosensView
+                }
+
                 SettingInputSection(
                 SettingInputSection(
                     decimalValue: $state.autosensMax,
                     decimalValue: $state.autosensMax,
                     booleanValue: $booleanPlaceholder,
                     booleanValue: $booleanPlaceholder,

+ 0 - 1
FreeAPS/Sources/Modules/Home/HomeStateModel.swift

@@ -68,7 +68,6 @@ extension Home {
         var totalBolus: Decimal = 0
         var totalBolus: Decimal = 0
         var isLoopStatusPresented: Bool = false
         var isLoopStatusPresented: Bool = false
         var isLegendPresented: Bool = false
         var isLegendPresented: Bool = false
-        var legendSheetDetent = PresentationDetent.large
         var totalInsulinDisplayType: TotalInsulinDisplayType = .totalDailyDose
         var totalInsulinDisplayType: TotalInsulinDisplayType = .totalDailyDose
         var roundedTotalBolus: String = ""
         var roundedTotalBolus: String = ""
         var selectedTab: Int = 0
         var selectedTab: Int = 0

+ 229 - 0
FreeAPS/Sources/Modules/Home/View/Chart/ChartLegendView.swift

@@ -0,0 +1,229 @@
+import SwiftUI
+
+struct ChartLegendView: View {
+    @Environment(AppState.self) var appState
+    @Environment(\.colorScheme) var colorScheme
+
+    var state: Home.StateModel
+
+    @State var legendSheetDetent = PresentationDetent.large
+
+    var body: some View {
+        NavigationStack {
+            VStack(alignment: .leading) {
+                Text(
+                    "The main chart in Trio is made up of various elements and shapes. Find their meanings below."
+                )
+                .font(.subheadline)
+                .foregroundColor(.secondary)
+                .padding(.top, 50)
+
+                List {
+                    VStack(alignment: .leading) {
+                        Text("Forecasts").bold().padding(.bottom, 5).textCase(.uppercase)
+                        Text(
+                            "The oref algorithm determines insulin dosing based on a number of scenarios that it estimates with different types of forecasts."
+                        )
+                        .font(.subheadline)
+                        .foregroundColor(.primary)
+
+                        if state.forecastDisplayType == .lines {
+                            legendLinesView
+                        } else {
+                            legendConeOfUncertaintyView
+                        }
+                    }.listRowBackground(Color.gray.opacity(0.1))
+
+                    VStack(alignment: .leading) {
+                        Text("Other Elements & Shapes").bold().padding(.bottom, 5).textCase(.uppercase)
+
+                        DefinitionRow(
+                            term: "Scheduled Basal Rate",
+                            definition: VStack(alignment: .leading, spacing: 10) {
+                                Text("This dotted line represents the hourly insulin rate of your scheduled basal insulin.")
+                                Text("To review or change your scheduled basal rates, go to Settings > Therapy > Basal Rates.")
+                            },
+                            color: Color.insulin,
+                            iconString: "ellipsis"
+                        )
+
+                        DefinitionRow(
+                            term: "Temporary Basal Rate (TBR)",
+                            definition: Text(
+                                "Shows current or past TBRs, which can be set by the oref algorithm or manually."
+                            ),
+                            color: Color.insulin,
+                            iconString: "square"
+                        )
+
+                        DefinitionRow(
+                            term: "Pump Suspension",
+                            definition: Text("Indicates when insulin delivery was paused, i.e. pump is suspended."),
+                            color: Color.loopGray.opacity(colorScheme == .dark ? 0.3 : 0.8),
+                            iconString: "square.fill"
+                        )
+
+                        DefinitionRow(
+                            term: "CGM Glucose Value",
+                            definition: VStack(alignment: .leading, spacing: 10) {
+                                Text(
+                                    "Displays real-time glucose readings from your CGM. Depending on your user interface settings, this may be displayed in a static (red, green, orange) or dynamic (full color spectrum) coloring scheme."
+                                )
+                                Text(
+                                    "To modify how glucose readings are displayed, go to Settings > Features > User Interface > Glucose Color Scheme."
+                                )
+                            },
+                            color: Color.green,
+                            iconString: !state.settingsManager.settings.smoothGlucose ? "circle.fill" : "record.circle.fill"
+                        )
+
+                        DefinitionRow(
+                            term: "Manual Glucose Measurement",
+                            definition: Text("Manually entered blood glucose, such as a fingerstick test."),
+                            color: Color.red,
+                            iconString: "drop.fill"
+                        )
+
+                        DefinitionRow(
+                            term: "Bolus",
+                            definition: Text(
+                                "Shows an insulin dose, which can be a small automated dose (super-micro-bolus), a manually entered dose, or one given externally (e.g., a pen shot)."
+                            ),
+                            color: Color.insulin,
+                            iconString: "arrowtriangle.down.fill"
+                        )
+
+                        DefinitionRow(
+                            term: "Carb Entry",
+                            definition: Text("Tracks the carbohydrates you eat, entered to guide insulin dosing."),
+                            color: Color.orange,
+                            iconString: "arrowtriangle.down.fill",
+                            shouldRotateIcon: true
+                        )
+
+                        DefinitionRow(
+                            term: "Fat-Protein Carb Equivalent",
+                            definition: VStack(alignment: .leading, spacing: 10) {
+                                Text(
+                                    "Represents carb equivalent for fat and protein, calculated using the Warsaw Method."
+                                )
+                                Text(
+                                    "To enable or configure Warsaw Method application in Trio, go to Settings > Features > Meal Settings."
+                                )
+                            },
+                            color: Color.brown,
+                            iconString: "circle.fill"
+                        )
+
+                        DefinitionRow(
+                            term: "Override",
+                            definition: Text(
+                                "Indicates when an override is or was active, temporarily changing therapy settings (e.g., basal rate, insulin sensitivity, carb ratio, target glucose, or whether Trio can dose SMBs)."
+                            ),
+                            color: Color.purple.opacity(0.4),
+                            iconString: "button.horizontal.fill"
+                        )
+
+                        DefinitionRow(
+                            term: "Temporary Target",
+                            definition: Text(
+                                "Marks when a short-term temporary glucose target is or was active, (potentially) altering when or how much insulin is delivered."
+                            ),
+                            color: Color.green.opacity(0.4),
+                            iconString: "button.horizontal.fill"
+                        )
+
+                        DefinitionRow(
+                            term: "Past Insulin-on-Board (IOB)",
+                            definition: Text(
+                                "Shows the IOB value calculated by the algorithm at a specific time in the past. These values are snapshots and won’t change if insulin is added or removed after the fact."
+                            ),
+                            color: Color.darkerBlue.opacity(0.8),
+                            iconString: "line.diagonal"
+                        )
+
+                        DefinitionRow(
+                            term: "Past Carbs-on-Board (COB)",
+                            definition: Text(
+                                "Shows the COB value calculated by the algorithm at a specific time in the past. These values are snapshots and won’t change if carbs are added or removed after the fact."
+                            ),
+                            color: Color.orange.opacity(0.8),
+                            iconString: "line.diagonal"
+                        )
+                    }.listRowBackground(Color.gray.opacity(0.1))
+                }
+                .scrollContentBackground(.hidden)
+                .navigationBarTitle("Chart Legend", displayMode: .inline)
+                .padding(.trailing, 10)
+                .padding(.bottom, 15)
+
+                Button {
+                    state.isLegendPresented.toggle()
+                } label: {
+                    Text("Got it!")
+                        .frame(maxWidth: .infinity, alignment: .center)
+                }
+                .buttonStyle(.bordered)
+                .padding(.top)
+            }
+            .padding([.horizontal, .bottom])
+            .listSectionSpacing(10)
+            .ignoresSafeArea(edges: .top)
+            .presentationDetents(
+                [.fraction(0.9), .large],
+                selection: $legendSheetDetent
+            )
+        }
+    }
+
+    var legendLinesView: some View {
+        Group {
+            DefinitionRow(
+                term: "IOB (Insulin on Board)",
+                definition: Text(
+                    "Forecasts future glucose readings based on the amount of insulin still active in the body."
+                ),
+                color: .insulin
+            )
+
+            DefinitionRow(
+                term: "ZT (Zero-Temp)",
+                definition: Text(
+                    "Forecasts the worst-case future glucose reading scenario if no carbs are absorbed and insulin delivery is stopped until glucose starts rising."
+                ),
+                color: .zt
+            )
+
+            DefinitionRow(
+                term: "COB (Carbs on Board)",
+                definition: Text(
+                    "Forecasts future glucose reading changes by considering the amount of carbohydrates still being absorbed in the body."
+                ),
+                color: .loopYellow
+            )
+
+            DefinitionRow(
+                term: "UAM (Unannounced Meal)",
+                definition: Text(
+                    "Forecasts future glucose levels and insulin dosing needs for unexpected meals or other causes of glucose reading increases without prior notice."
+                ),
+                color: .uam
+            )
+        }
+    }
+
+    var legendConeOfUncertaintyView: some View {
+        DefinitionRow(
+            term: "Cone of Uncertainty",
+            definition: VStack(alignment: .leading, spacing: 10) {
+                Text(
+                    "For simplicity reasons, oref's various forecast curves are displayed as a \"Cone of Uncertainty\" that depicts a possible, forecasted range of future glucose fluctuation based on the current data and the algothim's result."
+                )
+                Text(
+                    "To modify how the forecast is displayed, go to Settings > Features > User Interface > Forecast Display Type."
+                )
+            },
+            color: Color.blue.opacity(0.5)
+        )
+    }
+}

+ 1 - 87
FreeAPS/Sources/Modules/Home/View/HomeRootView.swift

@@ -878,96 +878,10 @@ extension Home {
                 }
                 }
             }
             }
             .sheet(isPresented: $state.isLegendPresented) {
             .sheet(isPresented: $state.isLegendPresented) {
-                legendSheetView()
+                ChartLegendView(state: state)
             }
             }
         }
         }
 
 
-        @ViewBuilder func legendSheetView() -> some View {
-            NavigationStack {
-                VStack(alignment: .leading, spacing: 16) {
-                    Text(
-                        "The oref algorithm determines insulin dosing based on a number of scenarios that it estimates with different types of forecasts."
-                    )
-                    .font(.subheadline)
-                    .foregroundColor(.secondary)
-
-                    if state.forecastDisplayType == .lines {
-                        legendLinesView()
-                    } else {
-                        legendConeOfUncertaintyView()
-                    }
-
-                    Button {
-                        state.isLegendPresented.toggle()
-                    } label: {
-                        Text("Got it!")
-                            .frame(maxWidth: .infinity, alignment: .center)
-                    }
-                    .buttonStyle(.bordered)
-                    .padding(.top)
-                }
-                .padding()
-                .presentationDetents(
-                    [.fraction(0.9), .large],
-                    selection: $state.legendSheetDetent
-                )
-            }
-        }
-
-        @ViewBuilder func legendLinesView() -> some View {
-            List {
-                DefinitionRow(
-                    term: "IOB (Insulin on Board)",
-                    definition: Text(
-                        "Forecasts future glucose readings based on the amount of insulin still active in the body."
-                    ),
-                    color: .insulin
-                )
-                DefinitionRow(
-                    term: "ZT (Zero-Temp)",
-                    definition: Text(
-                        "Forecasts the worst-case future glucose reading scenario if no carbs are absorbed and insulin delivery is stopped until glucose starts rising."
-                    ),
-                    color: .zt
-                )
-                DefinitionRow(
-                    term: "COB (Carbs on Board)",
-                    definition: Text(
-                        "Forecasts future glucose reading changes by considering the amount of carbohydrates still being absorbed in the body."
-                    ),
-                    color: .loopYellow
-                )
-                DefinitionRow(
-                    term: "UAM (Unannounced Meal)",
-                    definition: Text(
-                        "Forecasts future glucose levels and insulin dosing needs for unexpected meals or other causes of glucose reading increases without prior notice."
-                    ),
-                    color: .uam
-                )
-            }
-            .padding(.trailing, 10)
-            .navigationBarTitle("Legend", displayMode: .inline)
-        }
-
-        @ViewBuilder func legendConeOfUncertaintyView() -> some View {
-            List {
-                DefinitionRow(
-                    term: "Cone of Uncertainty",
-                    definition: VStack {
-                        Text(
-                            "For simplicity reasons, oref's various forecast curves are displayed as a \"Cone of Uncertainty\" that depicts a possible, forecasted range of future glucose fluctuation based on the current data and the algothim's result."
-                        )
-                        Text(
-                            "Note: To modify the forecast display type, go to Trio Settings > Features > User Interface > Forecast Display Type."
-                        )
-                    },
-                    color: Color.blue.opacity(0.5)
-                )
-            }
-            .padding(.trailing, 10)
-            .navigationBarTitle("Legend", displayMode: .inline)
-        }
-
         @ViewBuilder func tabBar() -> some View {
         @ViewBuilder func tabBar() -> some View {
             ZStack(alignment: .bottom) {
             ZStack(alignment: .bottom) {
                 TabView(selection: $selectedTab) {
                 TabView(selection: $selectedTab) {

+ 0 - 1
FreeAPS/Sources/Modules/ISFEditor/ISFEditorDataFlow.swift

@@ -27,6 +27,5 @@ enum ISFEditor {
 protocol ISFEditorProvider: Provider {
 protocol ISFEditorProvider: Provider {
     var profile: InsulinSensitivities { get }
     var profile: InsulinSensitivities { get }
     func saveProfile(_ profile: InsulinSensitivities)
     func saveProfile(_ profile: InsulinSensitivities)
-    var autosense: Autosens { get }
     var autotune: Autotune? { get }
     var autotune: Autotune? { get }
 }
 }

+ 0 - 6
FreeAPS/Sources/Modules/ISFEditor/ISFEditorProvider.swift

@@ -35,12 +35,6 @@ extension ISFEditor {
             storage.save(profile, as: OpenAPS.Settings.insulinSensitivities)
             storage.save(profile, as: OpenAPS.Settings.insulinSensitivities)
         }
         }
 
 
-        var autosense: Autosens {
-            storage.retrieve(OpenAPS.Settings.autosense, as: Autosens.self)
-                ?? Autosens(from: OpenAPS.defaults(for: OpenAPS.Settings.autosense))
-                ?? Autosens(ratio: 1, newisf: nil, timestamp: nil)
-        }
-
         var autotune: Autotune? {
         var autotune: Autotune? {
             storage.retrieve(OpenAPS.Settings.autotune, as: Autotune.self)
             storage.retrieve(OpenAPS.Settings.autotune, as: Autotune.self)
         }
         }

+ 0 - 34
FreeAPS/Sources/Modules/ISFEditor/ISFEditorStateModel.swift

@@ -10,13 +10,9 @@ extension ISFEditor {
         var items: [Item] = []
         var items: [Item] = []
         var initialItems: [Item] = []
         var initialItems: [Item] = []
         var shouldDisplaySaving: Bool = false
         var shouldDisplaySaving: Bool = false
-        private(set) var autosensISF: Decimal?
-        private(set) var autosensRatio: Decimal = 0
         var autotune: Autotune?
         var autotune: Autotune?
-        var determinationsFromPersistence: [OrefDetermination] = []
 
 
         let context = CoreDataStack.shared.newTaskContext()
         let context = CoreDataStack.shared.newTaskContext()
-        let viewContext = CoreDataStack.shared.persistentContainer.viewContext
 
 
         let timeValues = stride(from: 0.0, to: 1.days.timeInterval, by: 30.minutes.timeInterval).map { $0 }
         let timeValues = stride(from: 0.0, to: 1.days.timeInterval, by: 30.minutes.timeInterval).map { $0 }
 
 
@@ -55,13 +51,6 @@ extension ISFEditor {
             initialItems = items.map { Item(rateIndex: $0.rateIndex, timeIndex: $0.timeIndex) }
             initialItems = items.map { Item(rateIndex: $0.rateIndex, timeIndex: $0.timeIndex) }
 
 
             autotune = provider.autotune
             autotune = provider.autotune
-
-            if let newISF = provider.autosense.newisf {
-                autosensISF = newISF
-            }
-
-            autosensRatio = provider.autosense.ratio
-            setupDeterminationsArray()
         }
         }
 
 
         func add() {
         func add() {
@@ -119,29 +108,6 @@ extension ISFEditor {
                 }
                 }
             }
             }
         }
         }
-
-        private func setupDeterminationsArray() {
-            Task {
-                let ids = await determinationStorage.fetchLastDeterminationObjectID(
-                    predicate: NSPredicate.enactedDetermination
-                )
-                await updateDeterminationsArray(with: ids)
-            }
-        }
-
-        @MainActor private func updateDeterminationsArray(with IDs: [NSManagedObjectID]) {
-            do {
-                let objects = try IDs.compactMap { id in
-                    try viewContext.existingObject(with: id) as? OrefDetermination
-                }
-                determinationsFromPersistence = objects
-
-            } catch {
-                debugPrint(
-                    "Home State: \(#function) \(DebuggingIdentifiers.failed) error while updating the glucose array: \(error.localizedDescription)"
-                )
-            }
-        }
     }
     }
 }
 }
 
 

+ 0 - 45
FreeAPS/Sources/Modules/ISFEditor/View/ISFEditorRootView.swift

@@ -18,13 +18,6 @@ extension ISFEditor {
             return formatter
             return formatter
         }
         }
 
 
-        private var rateFormatter: NumberFormatter {
-            let formatter = NumberFormatter()
-            formatter.numberStyle = .decimal
-            formatter.maximumFractionDigits = 2
-            return formatter
-        }
-
         var saveButton: some View {
         var saveButton: some View {
             ZStack {
             ZStack {
                 let shouldDisableButton = state.items.isEmpty || !state.hasChanges
                 let shouldDisableButton = state.items.isEmpty || !state.hasChanges
@@ -82,44 +75,6 @@ extension ISFEditor {
                         }
                         }
                     }.listRowBackground(Color.chart)
                     }.listRowBackground(Color.chart)
                 }
                 }
-                if let newISF = state.autosensISF {
-                    Section(
-                        header: !state.settingsManager.preferences
-                            .useNewFormula ? Text("Autosens") : Text("Dynamic Sensitivity")
-                    ) {
-                        let dynamicRatio = state.determinationsFromPersistence.first?.sensitivityRatio
-                        let dynamicISF = state.determinationsFromPersistence.first?.insulinSensitivity
-                        HStack {
-                            Text("Sensitivity Ratio")
-                            Spacer()
-                            Text(
-                                rateFormatter
-                                    .string(from: (
-                                        (
-                                            !state.settingsManager.preferences.useNewFormula ? state
-                                                .autosensRatio as NSDecimalNumber : dynamicRatio
-                                        ) ?? 1
-                                    ) as NSNumber) ?? "1"
-                            )
-                        }
-                        HStack {
-                            Text("Calculated Sensitivity")
-                            Spacer()
-                            if state.units == .mgdL {
-                                Text(
-                                    !state.settingsManager.preferences
-                                        .useNewFormula ? newISF.description : (dynamicISF ?? 0).description
-                                )
-                            } else {
-                                Text((
-                                    !state.settingsManager.preferences
-                                        .useNewFormula ? newISF.formattedAsMmolL : dynamicISF?.decimalValue.formattedAsMmolL
-                                ) ?? "0")
-                            }
-                            Text(state.units.rawValue + "/U").foregroundColor(.secondary)
-                        }
-                    }.listRowBackground(Color.chart)
-                }
 
 
                 if !state.canAdd {
                 if !state.canAdd {
                     Section {
                     Section {

+ 26 - 1
FreeAPS/Sources/Views/DefinitionRow.swift

@@ -6,12 +6,37 @@ struct DefinitionRow<DefinitionView: View>: View {
     var definition: DefinitionView
     var definition: DefinitionView
     var color: Color?
     var color: Color?
     var fontSize: Font?
     var fontSize: Font?
+    var iconString: String?
+    var shouldRotateIcon: Bool?
+
+    init(
+        term: String,
+        definition: DefinitionView,
+        color: Color? = nil,
+        fontSize: Font? = nil,
+        iconString: String? = nil,
+        shouldRotateIcon: Bool = false
+    ) {
+        self.term = term
+        self.definition = definition
+        self.color = color
+        self.fontSize = fontSize
+        self.iconString = iconString
+        self.shouldRotateIcon = shouldRotateIcon
+    }
 
 
     var body: some View {
     var body: some View {
         VStack(alignment: .leading) {
         VStack(alignment: .leading) {
             HStack {
             HStack {
                 if let color = color {
                 if let color = color {
-                    Image(systemName: "circle.fill").foregroundStyle(color)
+                    if let iconString = iconString {
+                        Image(systemName: iconString)
+                            .foregroundStyle(color)
+                            .rotationEffect(shouldRotateIcon == true ? .degrees(180) : .degrees(0))
+                    } else {
+                        Image(systemName: "circle.fill")
+                            .foregroundStyle(color)
+                    }
                 }
                 }
                 Text(term).font(fontSize ?? .subheadline).fontWeight(.semibold)
                 Text(term).font(fontSize ?? .subheadline).fontWeight(.semibold)
             }.padding(.bottom, 5)
             }.padding(.bottom, 5)

+ 2 - 18
FreeAPS/Sources/Views/SettingInputHintView.swift

@@ -7,24 +7,6 @@ struct SettingInputHintView<HintView: View>: View {
     var hintText: HintView
     var hintText: HintView
     var sheetTitle: String
     var sheetTitle: String
 
 
-    @Environment(\.colorScheme) private var colorScheme
-    private var color: LinearGradient {
-        colorScheme == .dark ? LinearGradient(
-            gradient: Gradient(colors: [
-                Color.bgDarkBlue,
-                Color.bgDarkerDarkBlue
-            ]),
-            startPoint: .top,
-            endPoint: .bottom
-        )
-            :
-            LinearGradient(
-                gradient: Gradient(colors: [Color.gray.opacity(0.1)]),
-                startPoint: .top,
-                endPoint: .bottom
-            )
-    }
-
     var body: some View {
     var body: some View {
         NavigationStack {
         NavigationStack {
             List {
             List {
@@ -33,7 +15,9 @@ struct SettingInputHintView<HintView: View>: View {
                     definition: hintText,
                     definition: hintText,
                     fontSize: .body
                     fontSize: .body
                 )
                 )
+                .listRowBackground(Color.gray.opacity(0.1))
             }
             }
+            .scrollContentBackground(.hidden)
             .navigationBarTitle(sheetTitle, displayMode: .inline)
             .navigationBarTitle(sheetTitle, displayMode: .inline)
 
 
             Spacer()
             Spacer()

+ 2 - 2
Trio.xcworkspace/xcshareddata/swiftpm/Package.resolved

@@ -1,5 +1,5 @@
 {
 {
-  "originHash" : "59ac7eba66375d6eb406e758cb0b9964f4b3b0ae45c5665596f00384c32262b9",
+  "originHash" : "52d77fc35af7fe71614051dee0b291e2a0d38522eac7ae4d37d2442e81c7530c",
   "pins" : [
   "pins" : [
     {
     {
       "identity" : "cryptoswift",
       "identity" : "cryptoswift",
@@ -49,7 +49,7 @@
     {
     {
       "identity" : "swiftcharts",
       "identity" : "swiftcharts",
       "kind" : "remoteSourceControl",
       "kind" : "remoteSourceControl",
-      "location" : "https://github.com/ivanschuetz/SwiftCharts",
+      "location" : "https://github.com/ivanschuetz/SwiftCharts.git",
       "state" : {
       "state" : {
         "branch" : "master",
         "branch" : "master",
         "revision" : "c354c1945bb35a1f01b665b22474f6db28cba4a2"
         "revision" : "c354c1945bb35a1f01b665b22474f6db28cba4a2"