Przeglądaj źródła

Add algorithm settings to Onboarding WIP

Deniz Cengiz 1 rok temu
rodzic
commit
a77c38df89

+ 4 - 0
Trio.xcodeproj/project.pbxproj

@@ -562,6 +562,7 @@
 		DD498F2B2D692BEA00AAEA30 /* Localizable.xcstrings in Resources */ = {isa = PBXBuildFile; fileRef = 8A9134292D63D9A1007F8874 /* Localizable.xcstrings */; };
 		DD498F2C2D692BEA00AAEA30 /* Localizable.xcstrings in Resources */ = {isa = PBXBuildFile; fileRef = 8A9134292D63D9A1007F8874 /* Localizable.xcstrings */; };
 		DD498F2D2D692BEA00AAEA30 /* Localizable.xcstrings in Resources */ = {isa = PBXBuildFile; fileRef = 8A9134292D63D9A1007F8874 /* Localizable.xcstrings */; };
+		DD4AFFF12DADB59100AB7387 /* AlgorithmSettingsStepView.swift in Sources */ = {isa = PBXBuildFile; fileRef = DD4AFFF02DADB59100AB7387 /* AlgorithmSettingsStepView.swift */; };
 		DD4C57A82D73ADEA001BFF2C /* RestartLiveActivityIntent.swift in Sources */ = {isa = PBXBuildFile; fileRef = DD4C57A72D73ADEA001BFF2C /* RestartLiveActivityIntent.swift */; };
 		DD4C57AA2D73B3E2001BFF2C /* RestartLiveActivityIntentRequest.swift in Sources */ = {isa = PBXBuildFile; fileRef = DD4C57A92D73B3D9001BFF2C /* RestartLiveActivityIntentRequest.swift */; };
 		DD4C581F2D73C43D001BFF2C /* LoopStatsView.swift in Sources */ = {isa = PBXBuildFile; fileRef = DD4C581E2D73C43D001BFF2C /* LoopStatsView.swift */; };
@@ -1351,6 +1352,7 @@
 		DD3F1F8A2D9E08B200DCE7B3 /* NightscoutLoginStepView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = NightscoutLoginStepView.swift; sourceTree = "<group>"; };
 		DD3F1F8C2D9E0E0000DCE7B3 /* NightscoutSetupStepView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = NightscoutSetupStepView.swift; sourceTree = "<group>"; };
 		DD3F1F8F2D9E153A00DCE7B3 /* NightscoutImportStepView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = NightscoutImportStepView.swift; sourceTree = "<group>"; };
+		DD4AFFF02DADB59100AB7387 /* AlgorithmSettingsStepView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = AlgorithmSettingsStepView.swift; sourceTree = "<group>"; };
 		DD4C57A72D73ADEA001BFF2C /* RestartLiveActivityIntent.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = RestartLiveActivityIntent.swift; sourceTree = "<group>"; };
 		DD4C57A92D73B3D9001BFF2C /* RestartLiveActivityIntentRequest.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = RestartLiveActivityIntentRequest.swift; sourceTree = "<group>"; };
 		DD4C581E2D73C43D001BFF2C /* LoopStatsView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = LoopStatsView.swift; sourceTree = "<group>"; };
@@ -2768,6 +2770,7 @@
 		BD47FDD52D8B64AE0043966B /* OnboardingSteps */ = {
 			isa = PBXGroup;
 			children = (
+				DD4AFFF02DADB59100AB7387 /* AlgorithmSettingsStepView.swift */,
 				DDBD53FB2DAA903100F940A6 /* OverviewStepView.swift */,
 				BD10516C2DA986DC007C6D89 /* LogoAnimation.swift */,
 				DDF691362DA30332008BF16C /* StartupGuideStepView.swift */,
@@ -4334,6 +4337,7 @@
 				58A3D5442C96DE11003F90FC /* TempTargetStored+Helper.swift in Sources */,
 				DD6B7CB42C7B71F700B75029 /* ForecastDisplayType.swift in Sources */,
 				BD47FD172D88AAF50043966B /* CompletedStepView.swift in Sources */,
+				DD4AFFF12DADB59100AB7387 /* AlgorithmSettingsStepView.swift in Sources */,
 				DDEBB05C2D89E9050032305D /* TimeInRangeType.swift in Sources */,
 				DD5DC9F32CF3D9DD00AB8703 /* AdjustmentsStateModel+TempTargets.swift in Sources */,
 				BD47FDDB2D8B659B0043966B /* BasalProfileStepView.swift in Sources */,

+ 24 - 0
Trio/Sources/Localizations/Main/Localizable.xcstrings

@@ -31982,6 +31982,9 @@
         }
       }
     },
+    "Allow SMB when glucose is above the High Glucose Target value." : {
+
+    },
     "Allow SMB With High Temporary Target" : {
       "extractionState" : "stale",
       "localizations" : {
@@ -39011,6 +39014,9 @@
         }
       }
     },
+    "Auto-sensitivity (Autosens) adjusts insulin delivery based on observed sensitivity or resistance." : {
+
+    },
     "Automate boluses using the iOS Shortcuts App." : {
       "localizations" : {
         "bg" : {
@@ -83175,6 +83181,9 @@
         }
       }
     },
+    "Enable SMB With High Glucose" : {
+      "comment" : "Enable SMB With High Glucose"
+    },
     "Enable SMB With Temporary Target" : {
       "extractionState" : "stale",
       "localizations" : {
@@ -85007,6 +85016,9 @@
         }
       }
     },
+    "Enabling this feature allows Trio to deliver insulin required using Super Micro Boluses (SMB) when glucose reading is above the value set as High Glucose Target." : {
+
+    },
     "Enabling this feature allows you to create and save preset meals." : {
       "localizations" : {
         "bg" : {
@@ -105105,6 +105117,9 @@
         }
       }
     },
+    "High Glucose Target" : {
+
+    },
     "High Limit" : {
       "localizations" : {
         "bg" : {
@@ -175458,6 +175473,9 @@
         }
       }
     },
+    "SMB is an oref algorithm feature that delivers small frequent boluses instead of temporary basals for faster glucose control." : {
+
+    },
     "SMB Minutes" : {
       "extractionState" : "manual",
       "localizations" : {
@@ -184076,6 +184094,9 @@
         }
       }
     },
+    "Target Behavior allows you to adjust how temporary targets influence ISF, basal, and auto-targeting based on sensitivity or resistance." : {
+
+    },
     "Target Glucose" : {
       "extractionState" : "manual",
       "localizations" : {
@@ -204611,6 +204632,9 @@
         }
       }
     },
+    "Trio includes several algorithm settings that allow you to customize the oref algorithm behavior to suit your specific needs." : {
+
+    },
     "Trio includes several safety limits for insulin delivery and carbohydrate entry, helping ensure a safe and effective experience." : {
 
     },

+ 22 - 0
Trio/Sources/Modules/Onboarding/OnboardingStateModel.swift

@@ -95,6 +95,28 @@ extension Onboarding {
         var maxCOB: Decimal = 120
         var minimumSafetyThreshold: Decimal = 60
 
+        // MARK: - Algorithm Settings Defaults
+
+        var autosensMin: Decimal = 0.7
+        var autosensMax: Decimal = 1.2
+        var rewindResetsAutosens: Bool = false
+        var enableSMBAlways: Bool = false
+        var enableSMBWithCOB: Bool = false
+        var enableSMBWithTempTarget: Bool = false
+        var enableSMBAfterCarbs: Bool = false
+        var enableSMBWithHighGlucoseTarget: Bool = false
+        var highGlucoseTarget: Decimal = 110
+        var allowSMBWithHighTempTarget: Bool = false
+        var enableUAM: Bool = false
+        var maxSMBMinutes: Decimal = 30
+        var maxUAMMinutes: Decimal = 30
+        var maxDeltaGlucoseThreshold: Decimal = 0.2
+        var highTempTargetRaisesSensitivity: Bool = false
+        var lowTempTargetLowersSensitivity: Bool = false
+        var sensitivityRaisesTarget: Bool = false
+        var resistanceLowersTarget: Bool = false
+        var halfBasalTarget: Decimal = 160
+
         // MARK: - Subscribe
 
         override func subscribe() {

+ 35 - 6
Trio/Sources/Modules/Onboarding/View/OnboardingRootView.swift

@@ -8,9 +8,12 @@ extension Onboarding {
         @State var state = StateModel()
         @State private var navigationDirection: OnboardingNavigationDirection = .forward
         let onboardingManager: OnboardingManager
+
+        // Step management
         @State private var currentStep: OnboardingStep = .welcome
-        @State private var currentDeliverySubstep: DeliveryLimitSubstep = .maxIOB
         @State private var currentNightscoutSubstep: NightscoutSubstep = .setupSelection
+        @State private var currentDeliverySubstep: DeliveryLimitSubstep = .maxIOB
+        @State private var currentAlgorithmSubstep: AlgorithmSettingsSubstep = .autosensMin
 
         // Animation states
         @State private var animationScale: CGFloat = 1.0
@@ -59,12 +62,14 @@ extension Onboarding {
                                     switch currentStep {
                                     case .deliveryLimits: return currentDeliverySubstep.rawValue
                                     case .nightscout: return currentNightscoutSubstep.rawValue
+                                    case .algorithmSettings: return currentAlgorithmSubstep.rawValue
                                     default: return nil
                                     }
                                 }(),
                                 stepsWithSubsteps: [
                                     .nightscout: NightscoutSubstep.allCases.count,
-                                    .deliveryLimits: DeliveryLimitSubstep.allCases.count
+                                    .deliveryLimits: DeliveryLimitSubstep.allCases.count,
+                                    .algorithmSettings: AlgorithmSettingsSubstep.allCases.count
                                 ],
                                 nightscoutSetupOption: state.nightscoutSetupOption
                             )
@@ -145,6 +150,8 @@ extension Onboarding {
                                             InsulinSensitivityStepView(state: state)
                                         case .deliveryLimits:
                                             DeliveryLimitsStepView(state: state, substep: currentDeliverySubstep)
+                                        case .algorithmSettings:
+                                            AlgorithmSettingsStepView(state: state, substep: currentAlgorithmSubstep)
                                         case .completed:
                                             CompletedStepView()
                                         }
@@ -180,9 +187,9 @@ extension Onboarding {
                                     navigationDirection = .backward
                                     withAnimation {
                                         if currentStep == .completed {
-                                            currentStep = .deliveryLimits
-                                            currentDeliverySubstep =
-                                                .minimumSafetyThreshold // ensure we land on the last substep visually
+                                            currentStep = .algorithmSettings
+                                            currentAlgorithmSubstep =
+                                                .halfBasalTarget // ensure we land on the last substep visually
                                         } else if currentStep == .nightscout {
                                             if currentNightscoutSubstep == .setupSelection {
                                                 // First substep: go to previous main step
@@ -205,7 +212,19 @@ extension Onboarding {
                                                 currentDeliverySubstep = previousSub
                                             } else if let previousMainStep = currentStep.previous {
                                                 currentStep = previousMainStep
-                                                currentDeliverySubstep = .maxIOB // reset to first substep for later return
+                                                currentDeliverySubstep =
+                                                    .maxIOB // reset to first substep for later return
+                                            }
+                                        } else if currentStep == .algorithmSettings {
+                                            if let previousSub = AlgorithmSettingsSubstep(
+                                                rawValue: currentAlgorithmSubstep
+                                                    .rawValue - 1
+                                            ) {
+                                                currentAlgorithmSubstep = previousSub
+                                            } else if let previousMainStep = currentStep.previous {
+                                                currentStep = previousMainStep
+                                                currentAlgorithmSubstep = .autosensMin // reset to first substep for later return
+                                                currentDeliverySubstep = .minimumSafetyThreshold // set delivery limits to last
                                             }
                                         } else if let previous = currentStep.previous {
                                             currentStep = previous
@@ -262,6 +281,16 @@ extension Onboarding {
                                             currentStep = next
                                             currentDeliverySubstep = .maxIOB
                                         }
+                                    } else if currentStep == .algorithmSettings {
+                                        if let nextSub = AlgorithmSettingsSubstep(
+                                            rawValue: currentAlgorithmSubstep
+                                                .rawValue + 1
+                                        ) {
+                                            currentAlgorithmSubstep = nextSub
+                                        } else if let next = currentStep.next {
+                                            currentStep = next
+                                            currentAlgorithmSubstep = .autosensMin
+                                        }
                                     } else if let next = currentStep.next {
                                         currentStep = next
                                     }

+ 327 - 0
Trio/Sources/Modules/Onboarding/View/OnboardingSteps/AlgorithmSettingsStepView.swift

@@ -0,0 +1,327 @@
+//
+//  AlgorithmSettingsStepView.swift
+//  Trio
+//
+//  Created by Cengiz Deniz on 14.04.25
+//
+import SwiftUI
+
+struct AlgorithmSettingsStepView: View {
+    @Bindable var state: Onboarding.StateModel
+    let substep: AlgorithmSettingsSubstep
+
+    @State private var shouldDisplayPicker: Bool = false
+    @State private var decimalPlaceholder: Decimal = 0.0
+    @State private var booleanPlaceholder: Bool = false
+
+    private let settingsProvider = PickerSettingsProvider.shared
+
+    var body: some View {
+        VStack(alignment: .leading, spacing: 16) {
+            switch substep {
+            case .autosensMax,
+                 .autosensMin,
+                 .rewindResetsAutosens:
+                Text("Autosens")
+                    .padding(.horizontal)
+                    .font(.title3)
+                    .bold()
+                Text("Auto-sensitivity (Autosens) adjusts insulin delivery based on observed sensitivity or resistance.")
+                    .font(.subheadline)
+                    .multilineTextAlignment(.leading)
+                    .padding(.horizontal)
+                    .foregroundStyle(Color.secondary)
+            case .allowSMBWithHighTempTarget,
+                 .enableSMBAfterCarbs,
+                 .enableSMBAlways,
+                 .enableSMBWithCOB,
+                 .enableSMBWithHighGlucoseTarget,
+                 .enableSMBWithTempTarget,
+                 .enableUAM,
+                 .maxDeltaGlucoseThreshold,
+                 .maxSMBMinutes,
+                 .maxUAMMinutes:
+                Text("Super Micro Bolus (SMB)")
+                    .padding(.horizontal)
+                    .font(.title3)
+                    .bold()
+                Text(
+                    "SMB is an oref algorithm feature that delivers small frequent boluses instead of temporary basals for faster glucose control."
+                ).font(.subheadline)
+                    .multilineTextAlignment(.leading)
+                    .padding(.horizontal)
+                    .foregroundStyle(Color.secondary)
+            case .halfBasalTarget,
+                 .highTempTargetRaisesSensitivity,
+                 .lowTempTargetLowersSensitivity,
+                 .resistanceLowersTarget,
+                 .sensitivityRaisesTarget:
+                Text("Target Behavior")
+                    .padding(.horizontal)
+                    .font(.title3)
+                    .bold()
+                Text(
+                    "Target Behavior allows you to adjust how temporary targets influence ISF, basal, and auto-targeting based on sensitivity or resistance."
+                ).font(.subheadline)
+                    .multilineTextAlignment(.leading)
+                    .padding(.horizontal)
+                    .foregroundStyle(Color.secondary)
+            }
+
+            switch substep {
+            case .autosensMin:
+                algorithmSettingsInput(
+                    label: substep.title,
+                    displayPicker: $shouldDisplayPicker,
+                    setting: settingsProvider.settings.autosensMin,
+                    decimalValue: $state.autosensMin,
+                    booleanValue: $booleanPlaceholder,
+                    type: .decimal
+                )
+            case .autosensMax:
+                algorithmSettingsInput(
+                    label: substep.title,
+                    displayPicker: $shouldDisplayPicker,
+                    setting: settingsProvider.settings.autosensMax,
+                    decimalValue: $state.autosensMax,
+                    booleanValue: $booleanPlaceholder,
+                    type: .decimal
+                )
+            case .rewindResetsAutosens:
+                algorithmSettingsInput(
+                    label: substep.title,
+                    displayPicker: $shouldDisplayPicker,
+                    setting: nil,
+                    decimalValue: $decimalPlaceholder,
+                    booleanValue: $state.rewindResetsAutosens,
+                    type: .boolean
+                )
+            case .enableSMBAlways:
+                algorithmSettingsInput(
+                    label: substep.title,
+                    displayPicker: $shouldDisplayPicker,
+                    setting: nil,
+                    decimalValue: $decimalPlaceholder,
+                    booleanValue: $state.enableSMBAlways,
+                    type: .boolean
+                )
+            case .enableSMBWithCOB:
+                algorithmSettingsInput(
+                    label: substep.title,
+                    displayPicker: $shouldDisplayPicker,
+                    setting: nil,
+                    decimalValue: $decimalPlaceholder,
+                    booleanValue: $state.enableSMBWithCOB,
+                    type: .boolean
+                )
+            case .enableSMBWithTempTarget:
+                algorithmSettingsInput(
+                    label: substep.title,
+                    displayPicker: $shouldDisplayPicker,
+                    setting: nil,
+                    decimalValue: $decimalPlaceholder,
+                    booleanValue: $state.enableSMBWithTempTarget,
+                    type: .boolean
+                )
+            case .enableSMBAfterCarbs:
+                algorithmSettingsInput(
+                    label: substep.title,
+                    displayPicker: $shouldDisplayPicker,
+                    setting: nil,
+                    decimalValue: $decimalPlaceholder,
+                    booleanValue: $state.enableSMBAfterCarbs,
+                    type: .boolean
+                )
+            case .enableSMBWithHighGlucoseTarget:
+                algorithmSettingsInput(
+                    label: substep.title,
+                    displayPicker: $shouldDisplayPicker,
+                    setting: nil,
+                    decimalValue: $decimalPlaceholder,
+                    booleanValue: $state.enableSMBWithHighGlucoseTarget,
+                    type: .boolean
+                )
+                if state.enableSMBWithHighGlucoseTarget {
+                    algorithmSettingsInput(
+                        label: String(localized: "High Glucose Target"),
+                        displayPicker: $shouldDisplayPicker,
+                        setting: settingsProvider.settings.enableSMB_high_bg_target,
+                        decimalValue: $state.highGlucoseTarget,
+                        booleanValue: $booleanPlaceholder,
+                        type: .decimal
+                    )
+                }
+            case .allowSMBWithHighTempTarget:
+                algorithmSettingsInput(
+                    label: substep.title,
+                    displayPicker: $shouldDisplayPicker,
+                    setting: nil,
+                    decimalValue: $decimalPlaceholder,
+                    booleanValue: $state.allowSMBWithHighTempTarget,
+                    type: .boolean
+                )
+            case .enableUAM:
+                algorithmSettingsInput(
+                    label: substep.title,
+                    displayPicker: $shouldDisplayPicker,
+                    setting: nil,
+                    decimalValue: $decimalPlaceholder,
+                    booleanValue: $state.enableUAM,
+                    type: .boolean
+                )
+            case .maxSMBMinutes:
+                algorithmSettingsInput(
+                    label: substep.title,
+                    displayPicker: $shouldDisplayPicker,
+                    setting: settingsProvider.settings.maxSMBBasalMinutes,
+                    decimalValue: $state.maxSMBMinutes,
+                    booleanValue: $booleanPlaceholder,
+                    type: .decimal
+                )
+            case .maxUAMMinutes:
+                algorithmSettingsInput(
+                    label: substep.title,
+                    displayPicker: $shouldDisplayPicker,
+                    setting: settingsProvider.settings.maxUAMSMBBasalMinutes,
+                    decimalValue: $state.maxUAMMinutes,
+                    booleanValue: $booleanPlaceholder,
+                    type: .decimal
+                )
+            case .maxDeltaGlucoseThreshold:
+                algorithmSettingsInput(
+                    label: substep.title,
+                    displayPicker: $shouldDisplayPicker,
+                    setting: settingsProvider.settings.maxDeltaBGthreshold,
+                    decimalValue: $state.maxDeltaGlucoseThreshold,
+                    booleanValue: $booleanPlaceholder,
+                    type: .decimal
+                )
+            case .highTempTargetRaisesSensitivity:
+                algorithmSettingsInput(
+                    label: substep.title,
+                    displayPicker: $shouldDisplayPicker,
+                    setting: nil,
+                    decimalValue: $decimalPlaceholder,
+                    booleanValue: $state.highTempTargetRaisesSensitivity,
+                    type: .boolean
+                )
+            case .lowTempTargetLowersSensitivity:
+                algorithmSettingsInput(
+                    label: substep.title,
+                    displayPicker: $shouldDisplayPicker,
+                    setting: nil,
+                    decimalValue: $decimalPlaceholder,
+                    booleanValue: $state.lowTempTargetLowersSensitivity,
+                    type: .boolean
+                )
+            case .sensitivityRaisesTarget:
+                algorithmSettingsInput(
+                    label: substep.title,
+                    displayPicker: $shouldDisplayPicker,
+                    setting: nil,
+                    decimalValue: $decimalPlaceholder,
+                    booleanValue: $state.sensitivityRaisesTarget,
+                    type: .boolean
+                )
+            case .resistanceLowersTarget:
+                algorithmSettingsInput(
+                    label: substep.title,
+                    displayPicker: $shouldDisplayPicker,
+                    setting: nil,
+                    decimalValue: $decimalPlaceholder,
+                    booleanValue: $state.resistanceLowersTarget,
+                    type: .boolean
+                )
+            case .halfBasalTarget:
+                algorithmSettingsInput(
+                    label: substep.title,
+                    displayPicker: $shouldDisplayPicker,
+                    setting: settingsProvider.settings.halfBasalExerciseTarget,
+                    decimalValue: $state.halfBasalTarget,
+                    booleanValue: $booleanPlaceholder,
+                    type: .decimal
+                )
+            }
+
+            Text(substep.hint(units: state.units))
+                .font(.body)
+                .padding(.horizontal)
+                .multilineTextAlignment(.leading)
+
+            AnyView(substep.description(units: state.units))
+                .font(.footnote)
+                .foregroundStyle(.secondary)
+                .padding(.horizontal)
+                .multilineTextAlignment(.leading)
+        }
+        .onDisappear {
+            shouldDisplayPicker = false
+        }
+    }
+
+    @ViewBuilder private func algorithmSettingsInput(
+        label: String,
+        displayPicker: Binding<Bool>,
+        setting: PickerSetting?,
+        decimalValue: Binding<Decimal>,
+        booleanValue: Binding<Bool>,
+        type: OnboardingInputSectionType
+    ) -> some View {
+        VStack {
+            switch type {
+            case .boolean:
+                Toggle(isOn: booleanValue) {
+                    Text(label)
+                }.tint(Color.accentColor)
+            case .decimal:
+                Group {
+                    HStack {
+                        Text(label)
+                        Spacer()
+                        displayText(for: substep, decimalValue: decimalValue.wrappedValue, units: state.units)
+                            .foregroundColor(!displayPicker.wrappedValue ? .primary : .accentColor)
+                            .onTapGesture {
+                                displayPicker.wrappedValue.toggle()
+                            }
+                    }
+
+                    if displayPicker.wrappedValue {
+                        Picker(selection: decimalValue, label: Text(label)) {
+                            if let setting = setting {
+                                ForEach(
+                                    settingsProvider.generatePickerValues(from: setting, units: state.units),
+                                    id: \.self
+                                ) { value in
+                                    displayText(for: substep, decimalValue: value, units: state.units).tag(value)
+                                }
+                            }
+                        }
+                        .pickerStyle(WheelPickerStyle())
+                        .frame(maxWidth: .infinity)
+                    }
+                }
+            }
+        }
+        .padding()
+        .background(Color.chart.opacity(0.65))
+        .cornerRadius(10)
+    }
+
+    private func displayText(for substep: AlgorithmSettingsSubstep, decimalValue: Decimal, units: GlucoseUnits) -> Text {
+        switch substep {
+        case .autosensMax,
+             .autosensMin,
+             .maxDeltaGlucoseThreshold:
+            return Text("\(decimalValue * 100) \(String(localized: "%", comment: "Percentage symbol"))")
+        case .enableSMBWithHighGlucoseTarget,
+             .halfBasalTarget:
+            let displayValue = units == .mmolL ? decimalValue.asMmolL : decimalValue
+            return Text("\(displayValue.description) \(units.rawValue)")
+        case .maxSMBMinutes,
+             .maxUAMMinutes:
+            return Text("\(decimalValue) \(String(localized: "min", comment: "Minutes abbreviation"))")
+        default:
+            return Text("") // not needed, because input type is boolean
+        }
+    }
+}

+ 3 - 0
Trio/Sources/Modules/Onboarding/View/OnboardingSteps/DeliveryLimitsStepView.swift

@@ -58,6 +58,9 @@ struct DeliveryLimitsStepView: View {
                 .padding(.horizontal)
                 .multilineTextAlignment(.leading)
         }
+        .onDisappear {
+            shouldDisplayPicker = false
+        }
     }
 
     @ViewBuilder private func deliveryLimitInputSection(

+ 386 - 2
Trio/Sources/Modules/Onboarding/View/OnboardingView+Util.swift

@@ -19,6 +19,10 @@ enum OnboardingStep: Int, CaseIterable, Identifiable, Equatable {
     case carbRatio
     case insulinSensitivity
     case deliveryLimits
+    case algorithmSettings
+//    case autosensSettings
+//    case smbSettings
+//    case targetBehavior
     case completed
 
     var id: Int { rawValue }
@@ -57,6 +61,14 @@ enum OnboardingStep: Int, CaseIterable, Identifiable, Equatable {
             return String(localized: "Insulin Sensitivities")
         case .deliveryLimits:
             return String(localized: "Delivery Limits")
+        case .algorithmSettings:
+            return String(localized: "Algorithm Settings")
+//        case .autosensSettings:
+//            return String(localized: "Autosens")
+//        case .smbSettings:
+//            return String(localized: "Super Micro Bolus (SMB)")
+//        case .targetBehavior:
+//            return String(localized: "Target Behavior")
         case .completed:
             return String(localized: "All Set!")
         }
@@ -109,6 +121,22 @@ enum OnboardingStep: Int, CaseIterable, Identifiable, Equatable {
             return String(
                 localized: "Trio includes several safety limits for insulin delivery and carbohydrate entry, helping ensure a safe and effective experience."
             )
+        case .algorithmSettings:
+            return String(
+                localized: "Trio includes several algorithm settings that allow you to customize the oref algorithm behavior to suit your specific needs."
+            )
+//        case .autosensSettings:
+//            return String(
+//                localized: "Auto-sensitivity (Autosens) adjusts insulin delivery based on observed sensitivity or resistance."
+//            )
+//        case .smbSettings:
+//            return String(
+//                localized: "SMB (Super Micro Bolus) is an oref algorithm feature that delivers small frequent boluses instead of temporary basals for faster glucose control."
+//            )
+//        case .targetBehavior:
+//            return String(
+//                localized: "Target Behavior allows you to adjust how temporary targets influence ISF, basal, and auto-targeting based on sensitivity or resistance."
+//            )
         case .completed:
             return String(
                 localized: "Great job! You've completed the initial setup of Trio. You can always adjust these settings later in the app."
@@ -123,7 +151,8 @@ enum OnboardingStep: Int, CaseIterable, Identifiable, Equatable {
             return "hand.wave.fill"
         case .startupGuide:
             return "list.bullet.clipboard.fill"
-        case .overview:
+        case .algorithmSettings,
+             .overview:
             return "checklist.unchecked"
         case .diagnostics:
             return "waveform.badge.magnifyingglass"
@@ -141,6 +170,11 @@ enum OnboardingStep: Int, CaseIterable, Identifiable, Equatable {
             return "drop.fill"
         case .deliveryLimits:
             return "slider.horizontal.3"
+//        case .autosensSettings,
+//             .deliveryLimits,
+//             .smbSettings,
+//             .targetBehavior:
+//            return "slider.horizontal.3"
         case .completed:
             return "checkmark.circle.fill"
         }
@@ -165,12 +199,16 @@ enum OnboardingStep: Int, CaseIterable, Identifiable, Equatable {
     /// The accent color to use for this step.
     var accentColor: Color {
         switch self {
-        case .completed,
+        case .algorithmSettings,
+//             .autosensSettings,
+             .completed,
              .deliveryLimits,
              .diagnostics,
              .nightscout,
              .overview,
+//             .smbSettings,
              .startupGuide,
+//             .targetBehavior,
              .unitSelection,
              .welcome:
             return Color.blue
@@ -298,6 +336,336 @@ enum DeliveryLimitSubstep: Int, CaseIterable, Identifiable {
     }
 }
 
+enum AlgorithmSettingsSubstep: Int, CaseIterable, Identifiable {
+    case autosensMin
+    case autosensMax
+    case rewindResetsAutosens
+    case enableSMBAlways
+    case enableSMBWithCOB
+    case enableSMBWithTempTarget
+    case enableSMBAfterCarbs
+    case enableSMBWithHighGlucoseTarget
+    case allowSMBWithHighTempTarget
+    case enableUAM
+    case maxSMBMinutes
+    case maxUAMMinutes
+    case maxDeltaGlucoseThreshold
+    case highTempTargetRaisesSensitivity
+    case lowTempTargetLowersSensitivity
+    case sensitivityRaisesTarget
+    case resistanceLowersTarget
+    case halfBasalTarget
+
+    var id: Int { rawValue }
+
+    var title: String {
+        switch self {
+        case .autosensMin: return String(localized: "Autosens Min", comment: "Autosens Min")
+        case .autosensMax: return String(localized: "Autosens Max", comment: "Autosens Max")
+        case .rewindResetsAutosens: return String(localized: "Rewind Resets Autosens", comment: "Rewind Resets Autosens")
+        case .enableSMBAlways: return String(localized: "Enable SMB Always", comment: "Enable SMB Always")
+        case .enableSMBWithCOB: return String(localized: "Enable SMB With COB", comment: "Enable SMB With COB")
+        case .enableSMBWithTempTarget: return String(
+                localized: "Enable SMB With Temptarget",
+                comment: "Enable SMB With Temptarget"
+            )
+        case .enableSMBAfterCarbs: return String(localized: "Enable SMB After Carbs", comment: "Enable SMB After Carbs")
+        case .enableSMBWithHighGlucoseTarget: return String(
+                localized: "Enable SMB With High BG",
+                comment: "Enable SMB With High BG"
+            )
+        case .allowSMBWithHighTempTarget: return String(
+                localized: "Allow SMB With High Temptarget",
+                comment: "Allow SMB With High Temptarget"
+            )
+        case .enableUAM: return String(localized: "Enable UAM", comment: "Enable UAM")
+        case .maxSMBMinutes: return String(localized: "Max SMB Basal Minutes", comment: "Max SMB Basal Minutes")
+        case .maxUAMMinutes: return String(localized: "Max UAM Basal Minutes", comment: "Max UAM Basal Minutes")
+        case .maxDeltaGlucoseThreshold: return String(localized: "Max Delta-BG Threshold SMB", comment: "Max Delta-BG Threshold")
+        case .highTempTargetRaisesSensitivity: return String(
+                localized: "High Temp Target Raises Sensitivity",
+                comment: "High Temp Target Raises Sensitivity"
+            )
+        case .lowTempTargetLowersSensitivity: return String(
+                localized: "High Temp Target Raises Sensitivity",
+                comment: "High Temp Target Raises Sensitivity"
+            )
+        case .sensitivityRaisesTarget: return String(localized: "Sensitivity Raises Target", comment: "Sensitivity Raises Target")
+        case .resistanceLowersTarget: return String(localized: "Resistance Lowers Target", comment: "Resistance Lowers Target")
+        case .halfBasalTarget: return String(localized: "Half Basal Exercise Target", comment: "Half Basal Exercise Target")
+        }
+    }
+
+    func hint(units: GlucoseUnits) -> String {
+        switch self {
+        case .autosensMin: return String(localized: "Lower limit of the Autosens Ratio.")
+        case .autosensMax: return String(localized: "Upper limit of the Autosens Ratio.")
+        case .rewindResetsAutosens: return String(localized: "Pump rewind initiates a reset in Autosens Ratio.")
+        case .enableSMBAlways: return String(localized: "Allow SMBs at all times except when a high Temp Target is set.")
+        case .enableSMBWithCOB: return String(localized: "Allow SMB when carbs are on board.")
+        case .enableSMBWithTempTarget: return String(
+                localized: "Allow SMB when a manual Temporary Target is set under \(units == .mgdL ? "100" : 100.formattedAsMmolL) \(units.rawValue)."
+            )
+        case .enableSMBAfterCarbs: return String(localized: "Allow SMB for 6 hrs after a carb entry.")
+        case .enableSMBWithHighGlucoseTarget: return String(localized: "Allow SMB when glucose is above the High BG Target value.")
+        case .allowSMBWithHighTempTarget: return String(
+                localized: "Allow SMB when a manual Temporary Target is set greater than \(units == .mgdL ? "100" : 100.formattedAsMmolL) \(units.rawValue)."
+            )
+        case .enableUAM: return String(localized: "Enable Unannounced Meals SMB.")
+        case .maxSMBMinutes: return String(localized: "Limits the size of a single Super Micro Bolus (SMB) dose.")
+        case .maxUAMMinutes: return String(localized: "Limits the size of a single Unannounced Meal (UAM) SMB dose.")
+        case .maxDeltaGlucoseThreshold: return String(localized: "Disables SMBs if last two glucose values differ by more than this percent.")
+        case .highTempTargetRaisesSensitivity: return String(
+                localized: "Increase sensitivity when glucose is above target if a manual Temp Target > \(units == .mgdL ? "100" : 100.formattedAsMmolL) \(units.rawValue) is set."
+            )
+        case .lowTempTargetLowersSensitivity: return String(
+                localized: "Decrease sensitivity when glucose is below target if a manual Temp Target < \(units == .mgdL ? "100" : 100.formattedAsMmolL) \(units.rawValue) is set."
+            )
+        case .sensitivityRaisesTarget: return String(localized: "Raise target glucose if when Autosens Ratio is >1.")
+        case .resistanceLowersTarget: return String(localized: "Lower target glucose when Autosens Ratio is <1.")
+        case .halfBasalTarget: return String(localized: "Scales down your basal rate to 50% at this value.")
+        }
+    }
+
+    func description(units: GlucoseUnits) -> any View {
+        switch self {
+        case .autosensMin:
+            return VStack(alignment: .leading, spacing: 8) {
+                Text("Default: 70%").bold()
+                Text(
+                    "Autosens Min sets the minimum Autosens Ratio used by Autosens, Dynamic ISF, and Sigmoid Formula."
+                )
+                Text(
+                    "The Autosens Ratio is used to calculate the amount of adjustment needed to basal rates, ISF, and CR."
+                )
+                Text(
+                    "Tip: Decreasing this value allows automatic adjustments of basal rates to be lower, ISF to be higher, and CR to be higher."
+                )
+            }
+        case .autosensMax:
+            return VStack(alignment: .leading, spacing: 8) {
+                Text("Default: 120%").bold()
+                Text(
+                    "Autosens Max sets the maximum Autosens Ratio used by Autosens, Dynamic ISF, and Sigmoid Formula."
+                )
+                Text(
+                    "The Autosens Ratio is used to calculate the amount of adjustment needed to basal rates, ISF, and CR."
+                )
+                Text(
+                    "Tip: Increasing this value allows automatic adjustments of basal rates to be higher, ISF to be lower, and CR to be lower."
+                )
+            }
+        case .rewindResetsAutosens:
+            return VStack(alignment: .leading, spacing: 8) {
+                Text(
+                    "This feature resets the Autosens Ratio to neutral when you rewind your pump on the assumption that this corresponds to a site change."
+                )
+                Text(
+                    "Autosens will begin learning sensitivity anew from the time of the rewind, which may take up to 6 hours."
+                )
+                Text(
+                    "Tip: If you usually rewind your pump independently of site changes, you may want to consider disabling this feature."
+                )
+            }
+        case .enableSMBAlways:
+            return VStack(alignment: .leading, spacing: 8) {
+                Text("Default: OFF").bold()
+                Text(
+                    "When enabled, Super Micro Boluses (SMBs) will always be allowed if dosing calculations determine insulin is needed via the SMB delivery method, except when a high Temp Target is set. Enabling SMB Always will remove redundant \"Enable SMB\" options when this setting is enacted."
+                )
+                Text(
+                    "Note: If you would like to allow SMBs when a high Temp Target is set, enable the \"Allow SMBs with High Temptarget\" setting."
+                )
+            }
+        case .enableSMBWithCOB:
+            return VStack(alignment: .leading, spacing: 8) {
+                Text("Default: OFF").bold()
+                Text(
+                    "When the carb on board (COB) forecast line is active, enabling this feature allows Trio to use Super Micro Boluses (SMB) to deliver the insulin required."
+                )
+                Text(
+                    "Note: If this is enabled and the criteria are met, SMBs could be utilized regardless of other SMB settings being enabled or not."
+                )
+            }
+        case .enableSMBWithTempTarget:
+            return VStack(alignment: .leading, spacing: 8) {
+                Text("Default: OFF").bold()
+                Text(
+                    "Enabling this feature allows Trio to deliver insulin required using Super Micro Boluses (SMB) at times when a manual Temporary Target under \(units == .mgdL ? "100" : 100.formattedAsMmolL) \(units.rawValue) is set."
+                )
+                Text(
+                    "Note: If this is enabled and the criteria are met, SMBs could be utilized regardless of other SMB settings being enabled or not."
+                )
+            }
+        case .enableSMBAfterCarbs:
+            return VStack(alignment: .leading, spacing: 8) {
+                Text("Default: OFF").bold()
+                Text(
+                    "Enabling this feature allows Trio to deliver insulin required using Super Micro Boluses (SMB) for 6 hours after a carb entry, regardless of whether there are active carbs on board (COB)."
+                )
+                Text(
+                    "Note: If this is enabled and the criteria are met, SMBs could be utilized regardless of other SMB settings being enabled or not."
+                )
+            }
+        case .enableSMBWithHighGlucoseTarget:
+            return VStack(alignment: .leading, spacing: 8) {
+                Text("Default: OFF").bold()
+                Text(
+                    "Enabling this feature allows Trio to deliver insulin required using Super Micro Boluses (SMB) when glucose reading is above the value set as High BG Target."
+                )
+                Text(
+                    "Note: If this is enabled and the criteria are met, SMBs could be utilized regardless of other SMB settings being enabled or not."
+                )
+            }
+        case .allowSMBWithHighTempTarget:
+            return VStack(alignment: .leading, spacing: 8) {
+                Text("Default: OFF").bold()
+                Text(
+                    "Enabling this feature allows Trio to deliver insulin required using Super Micro Boluses (SMB) when a manual Temporary Target above \(units == .mgdL ? "100" : 100.formattedAsMmolL) \(units.rawValue) is set."
+                )
+                Text(
+                    "Note: If this is enabled and the criteria are met, SMBs could be utilized regardless of other SMB settings being enabled or not."
+                )
+                Text(
+                    "Warning: High Temp Targets are often set when recovering from lows. If you use High Temp Targets for that purpose, this feature should remain disabled."
+                ).bold()
+            }
+        case .enableUAM:
+            return VStack(alignment: .leading, spacing: 8) {
+                Text("Default: OFF").bold()
+                Text(
+                    "Enabling the UAM (Unannounced Meals) feature allows the system to detect and respond to unexpected rises in glucose readings caused by unannounced or miscalculated carbs, meals high in fat or protein, or other factors like adrenaline."
+                )
+                Text(
+                    "It uses the SMB (Super Micro Bolus) algorithm to deliver insulin in small amounts to correct glucose spikes. UAM also works in reverse, reducing or stopping SMBs if glucose levels drop unexpectedly."
+                )
+                Text(
+                    "This feature ensures more accurate insulin adjustments when carb entries are missing or incorrect."
+                )
+            }
+        case .maxSMBMinutes:
+            return VStack(spacing: 8) {
+                VStack(alignment: .leading, spacing: 8) {
+                    VStack(alignment: .leading, spacing: 1) {
+                        Text("Default: 30 minutes").bold()
+                        Text("(50% current basal rate)").bold()
+                    }
+                    VStack(alignment: .leading, spacing: 8) {
+                        Text(
+                            "This is a limit on the size of a single SMB. One SMB can only be as large as this many minutes of your current profile basal rate."
+                        )
+                        Text(
+                            "To calculate the maximum SMB allowed based on this setting, use the following formula:"
+                        )
+                    }
+                }
+                VStack(alignment: .center, spacing: 5) {
+                    Text(
+                        "𝒳 = Max SMB Basal Minutes"
+                    )
+                    Text("(𝒳 / 60) × current basal rate")
+                }
+
+                VStack(alignment: .leading, spacing: 8) {
+                    Text(
+                        "Warning: Increasing this value above 90 minutes may impact Trio's ability to effectively zero temp and prevent lows."
+                    ).bold()
+                    Text("Note: SMBs must be enabled to use this limit.")
+                }
+            }
+        case .maxUAMMinutes:
+            return VStack(spacing: 8) {
+                VStack(alignment: .leading, spacing: 8) {
+                    VStack(alignment: .leading, spacing: 1) {
+                        Text("Default: 30 minutes").bold()
+                        Text("(50% current basal rate)").bold()
+                    }
+                    VStack(alignment: .leading, spacing: 8) {
+                        Text(
+                            "This is a limit on the size of a single UAM SMB. One UAM SMB can only be as large as this many minutes of your current profile basal rate."
+                        )
+                        Text(
+                            "To calculate the maximum UAM SMB allowed based on this setting, use the following formula:"
+                        )
+                    }
+                }
+                VStack(alignment: .center, spacing: 5) {
+                    Text(
+                        "𝒳 = Max UAM SMB Basal Minutes"
+                    )
+                    Text("(𝒳 / 60) × current basal rate")
+                }
+                VStack(alignment: .leading, spacing: 8) {
+                    Text(
+                        "Warning: Increasing this value above 60 minutes may impact Trio's ability to effectively zero temp and prevent lows."
+                    ).bold()
+                    Text("Note: UAM SMBs must be enabled to use this limit.")
+                }
+            }
+        case .maxDeltaGlucoseThreshold:
+            return VStack(alignment: .leading, spacing: 8) {
+                Text("Default: 20% increase").bold()
+                Text(
+                    "Maximum allowed positive percent change in glucose level to permit SMBs. If the difference in glucose is greater than this, Trio will disable SMBs."
+                )
+                Text("Note: This setting has a hard-coded cap of 40%")
+            }
+        case .highTempTargetRaisesSensitivity:
+            return VStack(alignment: .leading, spacing: 8) {
+                Text("Default: OFF").bold()
+                Text(
+                    "When this feature is enabled, manually setting a temporary target above \(units == .mgdL ? "100" : 100.formattedAsMmolL) \(units.rawValue) will decrease the Autosens Ratio used for ISF and basal adjustments, resulting in less insulin delivered overall. This scales with the temporary target set; the higher the temp target, the lower the Autosens Ratio used."
+                )
+                Text(
+                    "If Half Basal Exercise Target is set to \(units == .mgdL ? "160" : 160.formattedAsMmolL) \(units.rawValue), a temp target of \(units == .mgdL ? "120" : 120.formattedAsMmolL) \(units.rawValue) uses an Autosens Ratio of 0.75. A temp target of \(units == .mgdL ? "140" : 140.formattedAsMmolL) \(units.rawValue) uses an Autosens Ratio of 0.6."
+                )
+                Text("Note: The effect of this can be adjusted with the Half Basal Exercise Target")
+            }
+        case .lowTempTargetLowersSensitivity:
+            return VStack(alignment: .leading, spacing: 8) {
+                Text("Default: OFF").bold()
+                Text(
+                    "When this feature is enabled, setting a temporary target below \(units == .mgdL ? "100" : 100.formattedAsMmolL) \(units.rawValue) will increase the Autosens Ratio used for ISF and basal adjustments, resulting in more insulin delivered overall. This scales with the temporary target set; the lower the Temp Target, the higher the Autosens Ratio used. It requires Algorithm Settings > Autosens > Autosens Max to be set to > 100% to work."
+                )
+                Text(
+                    "If Half Basal Exercise Target is \(units == .mgdL ? "160" : 160.formattedAsMmolL) \(units.rawValue), a Temp Target of \(units == .mgdL ? "95" : 95.formattedAsMmolL) \(units.rawValue) uses an Autosens Ratio of 1.09. A Temp Target of \(units == .mgdL ? "85" : 85.formattedAsMmolL) \(units.rawValue) uses an Autosens Ratio of 1.33."
+                )
+                Text("Note: The effect of this can be adjusted with the Half Basal Exercise Target")
+            }
+        case .sensitivityRaisesTarget:
+            return VStack(alignment: .leading, spacing: 8) {
+                Text("Default: OFF").bold()
+                Text(
+                    "Enabling this feature causes Trio to automatically raise the targeted glucose if it detects an increase in insulin sensitivity from your baseline."
+                )
+            }
+        case .resistanceLowersTarget:
+            return VStack(alignment: .leading, spacing: 8) {
+                Text("Default: OFF").bold()
+                Text(
+                    "Enabling this feature causes Trio to automatically reduce the targeted glucose if it detects a decrease in sensitivity (resistance) from your baseline."
+                )
+            }
+        case .halfBasalTarget:
+            return VStack(alignment: .leading, spacing: 8) {
+                Text(
+                    "Default: \(units == .mgdL ? "160" : 160.formattedAsMmolL) \(units.rawValue)"
+                )
+                .bold()
+                Text(
+                    "The Half Basal Exercise Target allows you to scale down your basal insulin during exercise or scale up your basal insulin when eating soon when a temporary glucose target is set."
+                )
+                Text(
+                    "For example, at a temp target of \(units == .mgdL ? "160" : 160.formattedAsMmolL) \(units.rawValue), your basal is reduced to 50%, but this scales depending on the target (e.g., 75% at \(units == .mgdL ? "120" : 120.formattedAsMmolL) \(units.rawValue), 60% at \(units == .mgdL ? "140" : 140.formattedAsMmolL) \(units.rawValue))."
+                )
+                Text(
+                    "Note: This setting is only utilized if the settings \"Low Temp Target Lowers Sensitivity\" OR \"High Temp Target Raises Sensitivity\" are enabled."
+                )
+            }
+        }
+    }
+}
+
 enum DiagnosticsSharingOption: String, Equatable, CaseIterable, Identifiable {
     case enabled
     case disabled
@@ -406,6 +774,22 @@ enum OnboardingSettingItemType: Equatable, CaseIterable, Identifiable {
     }
 }
 
+enum OnboardingInputSectionType: Equatable {
+    case decimal
+    case boolean
+
+    static func == (lhs: OnboardingInputSectionType, rhs: OnboardingInputSectionType) -> Bool {
+        switch (lhs, rhs) {
+        case (.boolean, .boolean):
+            return true
+        case (.decimal, .decimal):
+            return true
+        default:
+            return false
+        }
+    }
+}
+
 /// A reusable view for displaying setting items in the completed step.
 struct SettingItemView: View {
     let step: OnboardingStep

+ 8 - 5
Trio/Sources/Modules/SMBSettings/View/SMBSettingsRootView.swift

@@ -137,19 +137,22 @@ extension SMBSettings {
                             get: { selectedVerboseHint },
                             set: {
                                 selectedVerboseHint = $0.map { AnyView($0) }
-                                hintLabel = String(localized: "Enable SMB With High BG", comment: "Enable SMB With High BG")
+                                hintLabel = String(
+                                    localized: "Enable SMB With High Glucose",
+                                    comment: "Enable SMB With High Glucose"
+                                )
                             }
                         ),
                         units: state.units,
                         type: .conditionalDecimal("enableSMB_high_bg_target"),
-                        label: String(localized: "Enable SMB With High BG", comment: "Enable SMB With High BG"),
-                        conditionalLabel: "High BG Target",
-                        miniHint: String(localized: "Allow SMB when glucose is above the High BG Target value."),
+                        label: String(localized: "Enable SMB With High Glucose", comment: "Enable SMB With High Glucose"),
+                        conditionalLabel: String(localized: "High Glucose Target"),
+                        miniHint: String(localized: "Allow SMB when glucose is above the High Glucose Target value."),
                         verboseHint:
                         VStack(alignment: .leading, spacing: 10) {
                             Text("Default: OFF").bold()
                             Text(
-                                "Enabling this feature allows Trio to deliver insulin required using Super Micro Boluses (SMB) when glucose reading is above the value set as High BG Target."
+                                "Enabling this feature allows Trio to deliver insulin required using Super Micro Boluses (SMB) when glucose reading is above the value set as High Glucose Target."
                             )
                             Text(
                                 "Note: If this is enabled and the criteria are met, SMBs could be utilized regardless of other SMB settings being enabled or not."