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

Add unit and pump selection; add delivery limits WIP

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

+ 8 - 0
Trio.xcodeproj/project.pbxproj

@@ -550,6 +550,8 @@
 		DD32CFA22CC824E2003686D6 /* TrioRemoteControl+Helpers.swift in Sources */ = {isa = PBXBuildFile; fileRef = DD32CFA12CC824E1003686D6 /* TrioRemoteControl+Helpers.swift */; };
 		DD32CFA22CC824E2003686D6 /* TrioRemoteControl+Helpers.swift in Sources */ = {isa = PBXBuildFile; fileRef = DD32CFA12CC824E1003686D6 /* TrioRemoteControl+Helpers.swift */; };
 		DD3A3CE72D29C93F00AE478E /* Helper+Extensions.swift in Sources */ = {isa = PBXBuildFile; fileRef = DD3A3CE62D29C93F00AE478E /* Helper+Extensions.swift */; };
 		DD3A3CE72D29C93F00AE478E /* Helper+Extensions.swift in Sources */ = {isa = PBXBuildFile; fileRef = DD3A3CE62D29C93F00AE478E /* Helper+Extensions.swift */; };
 		DD3A3CE92D29C97800AE478E /* Helper+ButtonStyles.swift in Sources */ = {isa = PBXBuildFile; fileRef = DD3A3CE82D29C97800AE478E /* Helper+ButtonStyles.swift */; };
 		DD3A3CE92D29C97800AE478E /* Helper+ButtonStyles.swift in Sources */ = {isa = PBXBuildFile; fileRef = DD3A3CE82D29C97800AE478E /* Helper+ButtonStyles.swift */; };
+		DD3F1F832D9DC78800DCE7B3 /* UnitSelectionStepView.swift in Sources */ = {isa = PBXBuildFile; fileRef = DD3F1F822D9DC78300DCE7B3 /* UnitSelectionStepView.swift */; };
+		DD3F1F852D9DD84000DCE7B3 /* DeliveryLimitsStepView.swift in Sources */ = {isa = PBXBuildFile; fileRef = DD3F1F842D9DD83B00DCE7B3 /* DeliveryLimitsStepView.swift */; };
 		DD498F2B2D692BEA00AAEA30 /* Localizable.xcstrings in Resources */ = {isa = PBXBuildFile; fileRef = 8A9134292D63D9A1007F8874 /* Localizable.xcstrings */; };
 		DD498F2B2D692BEA00AAEA30 /* Localizable.xcstrings in Resources */ = {isa = PBXBuildFile; fileRef = 8A9134292D63D9A1007F8874 /* Localizable.xcstrings */; };
 		DD498F2C2D692BEA00AAEA30 /* 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 */; };
 		DD498F2D2D692BEA00AAEA30 /* Localizable.xcstrings in Resources */ = {isa = PBXBuildFile; fileRef = 8A9134292D63D9A1007F8874 /* Localizable.xcstrings */; };
@@ -1327,6 +1329,8 @@
 		DD32CFA12CC824E1003686D6 /* TrioRemoteControl+Helpers.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "TrioRemoteControl+Helpers.swift"; sourceTree = "<group>"; };
 		DD32CFA12CC824E1003686D6 /* TrioRemoteControl+Helpers.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "TrioRemoteControl+Helpers.swift"; sourceTree = "<group>"; };
 		DD3A3CE62D29C93F00AE478E /* Helper+Extensions.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "Helper+Extensions.swift"; sourceTree = "<group>"; };
 		DD3A3CE62D29C93F00AE478E /* Helper+Extensions.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "Helper+Extensions.swift"; sourceTree = "<group>"; };
 		DD3A3CE82D29C97800AE478E /* Helper+ButtonStyles.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "Helper+ButtonStyles.swift"; sourceTree = "<group>"; };
 		DD3A3CE82D29C97800AE478E /* Helper+ButtonStyles.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "Helper+ButtonStyles.swift"; sourceTree = "<group>"; };
+		DD3F1F822D9DC78300DCE7B3 /* UnitSelectionStepView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = UnitSelectionStepView.swift; sourceTree = "<group>"; };
+		DD3F1F842D9DD83B00DCE7B3 /* DeliveryLimitsStepView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = DeliveryLimitsStepView.swift; sourceTree = "<group>"; };
 		DD4C57A72D73ADEA001BFF2C /* RestartLiveActivityIntent.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = RestartLiveActivityIntent.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>"; };
 		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>"; };
 		DD4C581E2D73C43D001BFF2C /* LoopStatsView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = LoopStatsView.swift; sourceTree = "<group>"; };
@@ -2733,6 +2737,8 @@
 		BD47FDD52D8B64AE0043966B /* OnboardingSteps */ = {
 		BD47FDD52D8B64AE0043966B /* OnboardingSteps */ = {
 			isa = PBXGroup;
 			isa = PBXGroup;
 			children = (
 			children = (
+				DD3F1F842D9DD83B00DCE7B3 /* DeliveryLimitsStepView.swift */,
+				DD3F1F822D9DC78300DCE7B3 /* UnitSelectionStepView.swift */,
 				BD47FD162D88AAEF0043966B /* OnboardingStepViews.swift */,
 				BD47FD162D88AAEF0043966B /* OnboardingStepViews.swift */,
 				BD47FDDC2D8B65AD0043966B /* GlucoseTargetStepView.swift */,
 				BD47FDDC2D8B65AD0043966B /* GlucoseTargetStepView.swift */,
 				BD47FDDA2D8B65960043966B /* BasalProfileStepView.swift */,
 				BD47FDDA2D8B65960043966B /* BasalProfileStepView.swift */,
@@ -4317,6 +4323,7 @@
 				0D9A5E34A899219C5C4CDFAF /* DataTableStateModel.swift in Sources */,
 				0D9A5E34A899219C5C4CDFAF /* DataTableStateModel.swift in Sources */,
 				6BCF84DD2B16843A003AD46E /* LiveActitiyAttributes.swift in Sources */,
 				6BCF84DD2B16843A003AD46E /* LiveActitiyAttributes.swift in Sources */,
 				195D80B92AF697F700D25097 /* DynamicSettingsProvider.swift in Sources */,
 				195D80B92AF697F700D25097 /* DynamicSettingsProvider.swift in Sources */,
+				DD3F1F832D9DC78800DCE7B3 /* UnitSelectionStepView.swift in Sources */,
 				DD09D47D2C5986DA003FEA5D /* CalendarEventSettingsProvider.swift in Sources */,
 				DD09D47D2C5986DA003FEA5D /* CalendarEventSettingsProvider.swift in Sources */,
 				DD09D47B2C5986D1003FEA5D /* CalendarEventSettingsDataFlow.swift in Sources */,
 				DD09D47B2C5986D1003FEA5D /* CalendarEventSettingsDataFlow.swift in Sources */,
 				DD1745202C55523E00211FAC /* SMBSettingsDataFlow.swift in Sources */,
 				DD1745202C55523E00211FAC /* SMBSettingsDataFlow.swift in Sources */,
@@ -4359,6 +4366,7 @@
 				BDDAF9EF2D00554500B34E7A /* SelectionPopoverView.swift in Sources */,
 				BDDAF9EF2D00554500B34E7A /* SelectionPopoverView.swift in Sources */,
 				DDE1795F2C910127003CDDB7 /* PumpEventStored+CoreDataProperties.swift in Sources */,
 				DDE1795F2C910127003CDDB7 /* PumpEventStored+CoreDataProperties.swift in Sources */,
 				DDE179602C910127003CDDB7 /* StatsData+CoreDataClass.swift in Sources */,
 				DDE179602C910127003CDDB7 /* StatsData+CoreDataClass.swift in Sources */,
+				DD3F1F852D9DD84000DCE7B3 /* DeliveryLimitsStepView.swift in Sources */,
 				DDE179612C910127003CDDB7 /* StatsData+CoreDataProperties.swift in Sources */,
 				DDE179612C910127003CDDB7 /* StatsData+CoreDataProperties.swift in Sources */,
 				DDE179622C910127003CDDB7 /* Forecast+CoreDataClass.swift in Sources */,
 				DDE179622C910127003CDDB7 /* Forecast+CoreDataClass.swift in Sources */,
 				DDE179632C910127003CDDB7 /* Forecast+CoreDataProperties.swift in Sources */,
 				DDE179632C910127003CDDB7 /* Forecast+CoreDataProperties.swift in Sources */,

+ 6 - 6
Trio/Sources/Localizations/Main/Localizable.xcstrings

@@ -17113,9 +17113,6 @@
     "75th Percentile" : {
     "75th Percentile" : {
 
 
     },
     },
-    "80" : {
-
-    },
     "90%:" : {
     "90%:" : {
 
 
     },
     },
@@ -17322,9 +17319,6 @@
         }
         }
       }
       }
     },
     },
-    "120" : {
-
-    },
     "123" : {
     "123" : {
       "localizations" : {
       "localizations" : {
         "bg" : {
         "bg" : {
@@ -125911,6 +125905,9 @@
         }
         }
       }
       }
     },
     },
+    "Note: Choosing your pump model determines which increments for setting up your basal rates (0.1 or 0.05) are available. You will pair your actual pump after finishing the onboarding process through Trio's main screen, the Home View." : {
+
+    },
     "Note: If enabled, the smoothed values you see in Trio may differ from what is shown in your CGM app." : {
     "Note: If enabled, the smoothed values you see in Trio may differ from what is shown in your CGM app." : {
       "localizations" : {
       "localizations" : {
         "bg" : {
         "bg" : {
@@ -134759,6 +134756,9 @@
         }
         }
       }
       }
     },
     },
+    "Please choose from the options below." : {
+
+    },
     "Please make sure that your Libre 2 sensor is already activated and finished warming up. If you have other apps connecting to the sensor via bluetooth, these need to be shut down or uninstalled. \n\n You can only have one app communicating with the sensor via bluetooth. Then press the \"pariring and connection\" button below to start the process. Please note that the bluetooth connection might take up to a couple of minutes before it starts working." : {
     "Please make sure that your Libre 2 sensor is already activated and finished warming up. If you have other apps connecting to the sensor via bluetooth, these need to be shut down or uninstalled. \n\n You can only have one app communicating with the sensor via bluetooth. Then press the \"pariring and connection\" button below to start the process. Please note that the bluetooth connection might take up to a couple of minutes before it starts working." : {
       "extractionState" : "manual",
       "extractionState" : "manual",
       "localizations" : {
       "localizations" : {

+ 3 - 1
Trio/Sources/Models/BloodGlucose.swift

@@ -157,11 +157,13 @@ struct BloodGlucose: JSON, Identifiable, Hashable, Codable {
     }
     }
 }
 }
 
 
-enum GlucoseUnits: String, JSON, Equatable {
+enum GlucoseUnits: String, JSON, Equatable, CaseIterable, Identifiable {
     case mgdL = "mg/dL"
     case mgdL = "mg/dL"
     case mmolL = "mmol/L"
     case mmolL = "mmol/L"
 
 
     static let exchangeRate: Decimal = 0.0555
     static let exchangeRate: Decimal = 0.0555
+
+    var id: String { rawValue }
 }
 }
 
 
 extension Int {
 extension Int {

+ 50 - 1
Trio/Sources/Modules/Onboarding/OnboardingStateModel.swift

@@ -7,10 +7,12 @@ import SwiftUI
 /// Represents the different steps in the onboarding process.
 /// Represents the different steps in the onboarding process.
 enum OnboardingStep: Int, CaseIterable, Identifiable {
 enum OnboardingStep: Int, CaseIterable, Identifiable {
     case welcome
     case welcome
+    case unitSelection
     case glucoseTarget
     case glucoseTarget
     case basalProfile
     case basalProfile
     case carbRatio
     case carbRatio
     case insulinSensitivity
     case insulinSensitivity
+    case deliveryLimits
     case completed
     case completed
 
 
     var id: Int { rawValue }
     var id: Int { rawValue }
@@ -20,6 +22,8 @@ enum OnboardingStep: Int, CaseIterable, Identifiable {
         switch self {
         switch self {
         case .welcome:
         case .welcome:
             return "Welcome to Trio"
             return "Welcome to Trio"
+        case .unitSelection:
+            return "Units & Pump"
         case .glucoseTarget:
         case .glucoseTarget:
             return "Glucose Target"
             return "Glucose Target"
         case .basalProfile:
         case .basalProfile:
@@ -28,6 +32,8 @@ enum OnboardingStep: Int, CaseIterable, Identifiable {
             return "Carbohydrate Ratio"
             return "Carbohydrate Ratio"
         case .insulinSensitivity:
         case .insulinSensitivity:
             return "Insulin Sensitivity"
             return "Insulin Sensitivity"
+        case .deliveryLimits:
+            return "Delivery Limits"
         case .completed:
         case .completed:
             return "All Set!"
             return "All Set!"
         }
         }
@@ -38,6 +44,8 @@ enum OnboardingStep: Int, CaseIterable, Identifiable {
         switch self {
         switch self {
         case .welcome:
         case .welcome:
             return "Trio is a powerful app that helps you manage your diabetes. Let's get started by setting up a few important parameters that will help Trio work effectively for you."
             return "Trio is a powerful app that helps you manage your diabetes. Let's get started by setting up a few important parameters that will help Trio work effectively for you."
+        case .unitSelection:
+            return "Before you can begin with configuring your therapy settigns, Trio needs to know which units you use for your glucose and insulin measurements (based on your pump model)."
         case .glucoseTarget:
         case .glucoseTarget:
             return "Your glucose target is the blood glucose level you aim to maintain. Trio will use this to calculate insulin doses and provide recommendations."
             return "Your glucose target is the blood glucose level you aim to maintain. Trio will use this to calculate insulin doses and provide recommendations."
         case .basalProfile:
         case .basalProfile:
@@ -46,6 +54,8 @@ enum OnboardingStep: Int, CaseIterable, Identifiable {
             return "Your carb ratio tells how many grams of carbohydrates one unit of insulin will cover. This is essential for accurate meal bolus calculations."
             return "Your carb ratio tells how many grams of carbohydrates one unit of insulin will cover. This is essential for accurate meal bolus calculations."
         case .insulinSensitivity:
         case .insulinSensitivity:
             return "Your insulin sensitivity factor (ISF) indicates how much one unit of insulin will lower your blood glucose. This helps calculate correction boluses."
             return "Your insulin sensitivity factor (ISF) indicates how much one unit of insulin will lower your blood glucose. This helps calculate correction boluses."
+        case .deliveryLimits:
+            return "Trio offers various delivery limits which represent the maximum amount of insulin it can deliver at a time. This helps ensure safe and effective experience."
         case .completed:
         case .completed:
             return "Great job! You've completed the initial setup of Trio. You can always adjust these settings later in the app."
             return "Great job! You've completed the initial setup of Trio. You can always adjust these settings later in the app."
         }
         }
@@ -56,6 +66,8 @@ enum OnboardingStep: Int, CaseIterable, Identifiable {
         switch self {
         switch self {
         case .welcome:
         case .welcome:
             return "hand.wave.fill"
             return "hand.wave.fill"
+        case .unitSelection:
+            return "numbers.rectangle"
         case .glucoseTarget:
         case .glucoseTarget:
             return "target"
             return "target"
         case .basalProfile:
         case .basalProfile:
@@ -64,6 +76,8 @@ enum OnboardingStep: Int, CaseIterable, Identifiable {
             return "fork.knife"
             return "fork.knife"
         case .insulinSensitivity:
         case .insulinSensitivity:
             return "drop.fill"
             return "drop.fill"
+        case .deliveryLimits:
+            return "slider.horizontal.3"
         case .completed:
         case .completed:
             return "checkmark.circle.fill"
             return "checkmark.circle.fill"
         }
         }
@@ -90,6 +104,8 @@ enum OnboardingStep: Int, CaseIterable, Identifiable {
         switch self {
         switch self {
         case .welcome:
         case .welcome:
             return Color.blue
             return Color.blue
+        case .unitSelection:
+            return Color.blue
         case .glucoseTarget:
         case .glucoseTarget:
             return Color.green
             return Color.green
         case .basalProfile:
         case .basalProfile:
@@ -104,6 +120,28 @@ enum OnboardingStep: Int, CaseIterable, Identifiable {
     }
     }
 }
 }
 
 
+enum PumpOptionsForOnboardingUnits: String, Equatable, CaseIterable, Identifiable {
+    case minimed
+    case omnipodEros
+    case omnipodDash
+    case dana
+
+    var id: String { rawValue }
+
+    var displayName: String {
+        switch self {
+        case .minimed:
+            return "Medtronic 5xx / 7xx"
+        case .omnipodEros:
+            return "Omnipod Eros"
+        case .omnipodDash:
+            return "Omnipod Dash"
+        case .dana:
+            return "Dana (RS/-i)"
+        }
+    }
+}
+
 /// Model that holds the data collected during onboarding.
 /// Model that holds the data collected during onboarding.
 extension Onboarding {
 extension Onboarding {
     @Observable final class StateModel: BaseStateModel<Provider> {
     @Observable final class StateModel: BaseStateModel<Provider> {
@@ -120,7 +158,16 @@ extension Onboarding {
         var initialBasalProfileItems: [BasalProfileEditor.Item] = []
         var initialBasalProfileItems: [BasalProfileEditor.Item] = []
         var basalProfileItems: [BasalProfileEditor.Item] = []
         var basalProfileItems: [BasalProfileEditor.Item] = []
         let basalProfileTimeValues = stride(from: 0.0, to: 1.days.timeInterval, by: 30.minutes.timeInterval).map { $0 }
         let basalProfileTimeValues = stride(from: 0.0, to: 1.days.timeInterval, by: 30.minutes.timeInterval).map { $0 }
-        var basalProfileRateValues: [Decimal] = stride(from: 0.05, to: 3.05, by: 0.05).map { Decimal($0) }
+        var basalProfileRateValues: [Decimal] {
+            switch pumpModel {
+            case .dana,
+                 .minimed:
+                return stride(from: 0.1, to: 30.0, by: 0.1).map { Decimal($0) }
+            case .omnipodDash,
+                 .omnipodEros:
+                return stride(from: 0.05, to: 30.0, by: 0.05).map { Decimal($0) }
+            }
+        }
 
 
         // ISF related
         // ISF related
         var isfItems: [ISFEditor.Item] = []
         var isfItems: [ISFEditor.Item] = []
@@ -159,6 +206,8 @@ extension Onboarding {
         // Blood Glucose Units
         // Blood Glucose Units
         var units: GlucoseUnits = .mgdL
         var units: GlucoseUnits = .mgdL
 
 
+        var pumpModel: PumpOptionsForOnboardingUnits = .omnipodDash
+
         struct BasalRateEntry: Identifiable {
         struct BasalRateEntry: Identifiable {
             var id = UUID()
             var id = UUID()
             var startTime: Int // Minutes from midnight
             var startTime: Int // Minutes from midnight

+ 1 - 1
Trio/Sources/Modules/Onboarding/View/OnboardingSteps/BasalProfileStepView.swift

@@ -33,7 +33,7 @@ struct BasalProfileStepView: View {
     }
     }
 
 
     var body: some View {
     var body: some View {
-        ScrollView {
+        LazyVStack {
             VStack(alignment: .leading, spacing: 0) {
             VStack(alignment: .leading, spacing: 0) {
                 // Chart visualization
                 // Chart visualization
                 if !state.basalProfileItems.isEmpty {
                 if !state.basalProfileItems.isEmpty {

+ 1 - 1
Trio/Sources/Modules/Onboarding/View/OnboardingSteps/CarbRatioStepView.swift

@@ -33,7 +33,7 @@ struct CarbRatioStepView: View {
     }
     }
 
 
     var body: some View {
     var body: some View {
-        ScrollView {
+        LazyVStack {
             VStack(alignment: .leading, spacing: 0) {
             VStack(alignment: .leading, spacing: 0) {
                 // Chart visualization
                 // Chart visualization
                 if !state.carbRatioItems.isEmpty {
                 if !state.carbRatioItems.isEmpty {

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

@@ -0,0 +1,21 @@
+//
+//  DeliveryLimitsStepView.swift
+//  Trio
+//
+//  Created by Cengiz Deniz on 02.04.25.
+//
+
+import SwiftUI
+
+struct DeliveryLimitsStepView: View {
+    @Bindable var state: Onboarding.StateModel
+
+    var body: some View {
+        VStack(alignment: .leading, spacing: 8) {
+            Text("Max IOB")
+            Text("Max Bolus")
+            Text("Max Basal")
+            Text("Max COB")
+        }
+    }
+}

+ 1 - 1
Trio/Sources/Modules/Onboarding/View/OnboardingSteps/GlucoseTargetStepView.swift

@@ -34,7 +34,7 @@ struct GlucoseTargetStepView: View {
         .date(from: DateComponents(year: 2001, month: 01, day: 01, hour: 0, minute: 0, second: 0))
         .date(from: DateComponents(year: 2001, month: 01, day: 01, hour: 0, minute: 0, second: 0))
 
 
     var body: some View {
     var body: some View {
-        ScrollView {
+        LazyVStack {
             VStack(alignment: .leading, spacing: 0) {
             VStack(alignment: .leading, spacing: 0) {
                 // Chart visualization
                 // Chart visualization
                 if !state.targetItems.isEmpty {
                 if !state.targetItems.isEmpty {

+ 1 - 1
Trio/Sources/Modules/Onboarding/View/OnboardingSteps/InsulinSensitivityStepView.swift

@@ -33,7 +33,7 @@ struct InsulinSensitivityStepView: View {
     }
     }
 
 
     var body: some View {
     var body: some View {
-        ScrollView {
+        LazyVStack {
             VStack(alignment: .leading, spacing: 0) {
             VStack(alignment: .leading, spacing: 0) {
                 // Chart visualization
                 // Chart visualization
                 if !state.isfItems.isEmpty {
                 if !state.isfItems.isEmpty {

+ 47 - 0
Trio/Sources/Modules/Onboarding/View/OnboardingSteps/UnitSelectionStepView.swift

@@ -0,0 +1,47 @@
+import SwiftUI
+
+struct UnitSelectionStepView: View {
+    @Bindable var state: Onboarding.StateModel
+
+    var body: some View {
+        VStack(alignment: .leading, spacing: 8) {
+            Text("Please choose from the options below.")
+                .font(.headline)
+
+            Spacer(minLength: 20)
+
+            HStack {
+                Text("Glucose Units")
+                Spacer()
+                Picker("Glucose Units", selection: $state.units) {
+                    ForEach(GlucoseUnits.allCases, id: \.self) { unit in
+                        Text(unit.rawValue).tag(unit)
+                    }
+                }
+            }
+            .padding()
+            .background(Color.chart.opacity(0.45))
+            .cornerRadius(10)
+
+            HStack {
+                Text("Pump Model")
+                Spacer()
+                Picker("Pump Model", selection: $state.pumpModel) {
+                    ForEach(PumpOptionsForOnboardingUnits.allCases, id: \.self) { pumpModel in
+                        Text(pumpModel.displayName).tag(pumpModel)
+                    }
+                }
+            }
+            .padding()
+            .background(Color.chart.opacity(0.45))
+            .cornerRadius(10)
+
+            Text(
+                "Note: Choosing your pump model determines which increments for setting up your basal rates are available. You will pair your actual pump after finishing the onboarding process."
+            )
+            .padding(.vertical)
+            .font(.footnote)
+            .foregroundStyle(Color.secondary)
+        }
+    }
+}

+ 236 - 232
Trio/Sources/Modules/Onboarding/View/OnboardingView.swift

@@ -82,6 +82,8 @@ extension Onboarding {
                                     switch currentStep {
                                     switch currentStep {
                                     case .welcome:
                                     case .welcome:
                                         WelcomeStepView()
                                         WelcomeStepView()
+                                    case .unitSelection:
+                                        UnitSelectionStepView(state: state)
                                     case .glucoseTarget:
                                     case .glucoseTarget:
                                         GlucoseTargetStepView(state: state)
                                         GlucoseTargetStepView(state: state)
                                     case .basalProfile:
                                     case .basalProfile:
@@ -90,6 +92,8 @@ extension Onboarding {
                                         CarbRatioStepView(state: state)
                                         CarbRatioStepView(state: state)
                                     case .insulinSensitivity:
                                     case .insulinSensitivity:
                                         InsulinSensitivityStepView(state: state)
                                         InsulinSensitivityStepView(state: state)
+                                    case .deliveryLimits:
+                                        DeliveryLimitsStepView(state: state)
                                     case .completed:
                                     case .completed:
                                         CompletedStepView()
                                         CompletedStepView()
                                     }
                                     }
@@ -195,238 +199,238 @@ struct OnboardingProgressBar: View {
     }
     }
 }
 }
 
 
-/// A simple animated placeholder for each step
-struct AnimationPlaceholder: View {
-    let step: OnboardingStep
-    @State private var animationValue: Double = 0
-
-    init(for step: OnboardingStep) {
-        self.step = step
-    }
-
-    var body: some View {
-        VStack {
-            Group {
-                switch step {
-                case .welcome:
-                    welcomeAnimation
-                case .glucoseTarget:
-                    glucoseTargetAnimation
-                case .basalProfile:
-                    basalProfileAnimation
-                case .carbRatio:
-                    carbRatioAnimation
-                case .insulinSensitivity:
-                    insulinSensitivityAnimation
-                case .completed:
-                    completedAnimation
-                }
-            }
-            .frame(height: 180)
-        }
-        .onAppear {
-            withAnimation(Animation.easeInOut(duration: 2).repeatForever(autoreverses: true)) {
-                animationValue = 1.0
-            }
-        }
-    }
-
-    // Custom animated views for each step
-    var welcomeAnimation: some View {
-        ZStack {
-            ForEach(0 ..< 5) { index in
-                Image(systemName: "heart.fill")
-                    .font(.system(size: 40))
-                    .foregroundColor(step.accentColor.opacity(0.8 - Double(index) * 0.15))
-                    .offset(x: CGFloat.random(in: -100 ... 100), y: CGFloat.random(in: -60 ... 60))
-                    .scaleEffect(1.0 + animationValue * 0.3)
-                    .rotationEffect(.degrees(animationValue * Double.random(in: -30 ... 30)))
-            }
-
-            Image(systemName: "syringe.fill")
-                .font(.system(size: 80))
-                .foregroundColor(step.accentColor)
-                .scaleEffect(1.0 + animationValue * 0.2)
-                .shadow(color: step.accentColor.opacity(0.5), radius: 10 * animationValue, x: 0, y: 0)
-        }
-    }
-
-    var glucoseTargetAnimation: some View {
-        ZStack {
-            // Target rings
-            ForEach(0 ..< 3) { index in
-                Circle()
-                    .stroke(step.accentColor.opacity(Double(3 - index) * 0.3), lineWidth: 8)
-                    .frame(width: 120 + CGFloat(index * 40))
-                    .scaleEffect(1.0 + animationValue * 0.05)
-            }
-
-            // Arrow
-            Image(systemName: "arrow.down.to.line")
-                .font(.system(size: 50))
-                .foregroundColor(step.accentColor)
-                .offset(y: -10 + animationValue * 20)
-                .rotationEffect(.degrees(animationValue * 360))
-        }
-    }
-
-    var basalProfileAnimation: some View {
-        ZStack {
-            // Line graph representation
-            Path { path in
-                let width: CGFloat = 300
-                let height: CGFloat = 100
-
-                path.move(to: CGPoint(x: 0, y: height * 0.5))
-
-                for i in 0 ..< 8 {
-                    let x = width * CGFloat(i) / 7
-                    let y = height * (0.5 + (sin(Double(i) * .pi / 3) * 0.4))
-
-                    if i == 0 {
-                        path.move(to: CGPoint(x: x, y: y))
-                    } else {
-                        path.addLine(to: CGPoint(x: x, y: y))
-                    }
-                }
-            }
-            .trim(from: 0, to: animationValue)
-            .stroke(step.accentColor, style: StrokeStyle(lineWidth: 5, lineCap: .round, lineJoin: .round))
-            .frame(width: 300, height: 100)
-
-            // Clock symbols to represent time
-            HStack(spacing: 50) {
-                Image(systemName: "clock")
-                    .font(.system(size: 20))
-                    .foregroundColor(step.accentColor)
-                    .opacity(animationValue)
-
-                Image(systemName: "clock.fill")
-                    .font(.system(size: 20))
-                    .foregroundColor(step.accentColor)
-                    .opacity(animationValue)
-
-                Image(systemName: "clock")
-                    .font(.system(size: 20))
-                    .foregroundColor(step.accentColor)
-                    .opacity(animationValue)
-            }
-            .offset(y: 70)
-        }
-    }
-
-    var carbRatioAnimation: some View {
-        ZStack {
-            // Plate
-            Circle()
-                .fill(Color.gray.opacity(0.1))
-                .frame(width: 150)
-
-            // Food items
-            ForEach(0 ..< 5) { index in
-                Image(systemName: [
-                    "carrot.fill",
-                    "fork.knife",
-                    "takeoutbag.and.cup.and.straw.fill",
-                    "wallet.pass.fill",
-                    "cup.and.saucer.fill"
-                ][index % 5])
-                    .font(.system(size: 25))
-                    .foregroundColor(step.accentColor)
-                    .offset(
-                        x: cos(Double(index) * .pi * 2 / 5) * 50 * animationValue,
-                        y: sin(Double(index) * .pi * 2 / 5) * 50 * animationValue
-                    )
-                    .rotationEffect(.degrees(animationValue * 360))
-            }
-
-            // Insulin
-            Image(systemName: "drop.fill")
-                .font(.system(size: 40))
-                .foregroundColor(.blue)
-                .scaleEffect(0.8 + animationValue * 0.3)
-                .shadow(color: .blue.opacity(0.5), radius: 5, x: 0, y: 0)
-        }
-    }
-
-    var insulinSensitivityAnimation: some View {
-        ZStack {
-            // Glucose meter
-            RoundedRectangle(cornerRadius: 20)
-                .fill(Color.gray.opacity(0.1))
-                .frame(width: 120, height: 200)
-
-            // Display screen
-            RoundedRectangle(cornerRadius: 10)
-                .fill(Color.black.opacity(0.1))
-                .frame(width: 100, height: 60)
-                .offset(y: -60)
-
-            // Value on screen
-            Text("120")
-                .font(.system(size: 24, weight: .bold, design: .monospaced))
-                .foregroundColor(step.accentColor)
-                .offset(y: -60)
-                .opacity(animationValue)
-
-            // Insulin drop
-            Image(systemName: "drop.fill")
-                .font(.system(size: 30))
-                .foregroundColor(.blue)
-                .offset(y: 20)
-                .opacity(1)
-
-            // Arrow showing decrease
-            Image(systemName: "arrow.down")
-                .font(.system(size: 30))
-                .foregroundColor(step.accentColor)
-                .offset(y: 60)
-                .opacity(animationValue)
-                .scaleEffect(1.0 + animationValue * 0.5)
-
-            // Lower value
-            Text("80")
-                .font(.system(size: 24, weight: .bold, design: .monospaced))
-                .foregroundColor(step.accentColor)
-                .offset(y: 100)
-                .opacity(animationValue)
-        }
-    }
-
-    var completedAnimation: some View {
-        ZStack {
-            // Success checkmark
-            Circle()
-                .fill(step.accentColor.opacity(0.2))
-                .frame(width: 150)
-                .scaleEffect(animationValue)
-
-            Circle()
-                .stroke(step.accentColor, lineWidth: 5)
-                .frame(width: 150)
-                .scaleEffect(animationValue)
-
-            Image(systemName: "checkmark")
-                .font(.system(size: 80, weight: .bold))
-                .foregroundColor(step.accentColor)
-                .offset(y: animationValue * 5)
-                .scaleEffect(animationValue)
-
-            // Celebrate particles
-            ForEach(0 ..< 8) { index in
-                Image(systemName: "star.fill")
-                    .font(.system(size: 20))
-                    .foregroundColor(step.accentColor)
-                    .offset(
-                        x: cos(Double(index) * .pi / 4) * 100 * animationValue,
-                        y: sin(Double(index) * .pi / 4) * 100 * animationValue
-                    )
-                    .opacity(animationValue)
-                    .scaleEffect(animationValue)
-            }
-        }
-    }
-}
+///// A simple animated placeholder for each step
+// struct AnimationPlaceholder: View {
+//    let step: OnboardingStep
+//    @State private var animationValue: Double = 0
+//
+//    init(for step: OnboardingStep) {
+//        self.step = step
+//    }
+//
+//    var body: some View {
+//        VStack {
+//            Group {
+//                switch step {
+//                case .welcome:
+//                    welcomeAnimation
+//                case .glucoseTarget:
+//                    glucoseTargetAnimation
+//                case .basalProfile:
+//                    basalProfileAnimation
+//                case .carbRatio:
+//                    carbRatioAnimation
+//                case .insulinSensitivity:
+//                    insulinSensitivityAnimation
+//                case .completed:
+//                    completedAnimation
+//                }
+//            }
+//            .frame(height: 180)
+//        }
+//        .onAppear {
+//            withAnimation(Animation.easeInOut(duration: 2).repeatForever(autoreverses: true)) {
+//                animationValue = 1.0
+//            }
+//        }
+//    }
+//
+//    // Custom animated views for each step
+//    var welcomeAnimation: some View {
+//        ZStack {
+//            ForEach(0 ..< 5) { index in
+//                Image(systemName: "heart.fill")
+//                    .font(.system(size: 40))
+//                    .foregroundColor(step.accentColor.opacity(0.8 - Double(index) * 0.15))
+//                    .offset(x: CGFloat.random(in: -100 ... 100), y: CGFloat.random(in: -60 ... 60))
+//                    .scaleEffect(1.0 + animationValue * 0.3)
+//                    .rotationEffect(.degrees(animationValue * Double.random(in: -30 ... 30)))
+//            }
+//
+//            Image(systemName: "syringe.fill")
+//                .font(.system(size: 80))
+//                .foregroundColor(step.accentColor)
+//                .scaleEffect(1.0 + animationValue * 0.2)
+//                .shadow(color: step.accentColor.opacity(0.5), radius: 10 * animationValue, x: 0, y: 0)
+//        }
+//    }
+//
+//    var glucoseTargetAnimation: some View {
+//        ZStack {
+//            // Target rings
+//            ForEach(0 ..< 3) { index in
+//                Circle()
+//                    .stroke(step.accentColor.opacity(Double(3 - index) * 0.3), lineWidth: 8)
+//                    .frame(width: 120 + CGFloat(index * 40))
+//                    .scaleEffect(1.0 + animationValue * 0.05)
+//            }
+//
+//            // Arrow
+//            Image(systemName: "arrow.down.to.line")
+//                .font(.system(size: 50))
+//                .foregroundColor(step.accentColor)
+//                .offset(y: -10 + animationValue * 20)
+//                .rotationEffect(.degrees(animationValue * 360))
+//        }
+//    }
+//
+//    var basalProfileAnimation: some View {
+//        ZStack {
+//            // Line graph representation
+//            Path { path in
+//                let width: CGFloat = 300
+//                let height: CGFloat = 100
+//
+//                path.move(to: CGPoint(x: 0, y: height * 0.5))
+//
+//                for i in 0 ..< 8 {
+//                    let x = width * CGFloat(i) / 7
+//                    let y = height * (0.5 + (sin(Double(i) * .pi / 3) * 0.4))
+//
+//                    if i == 0 {
+//                        path.move(to: CGPoint(x: x, y: y))
+//                    } else {
+//                        path.addLine(to: CGPoint(x: x, y: y))
+//                    }
+//                }
+//            }
+//            .trim(from: 0, to: animationValue)
+//            .stroke(step.accentColor, style: StrokeStyle(lineWidth: 5, lineCap: .round, lineJoin: .round))
+//            .frame(width: 300, height: 100)
+//
+//            // Clock symbols to represent time
+//            HStack(spacing: 50) {
+//                Image(systemName: "clock")
+//                    .font(.system(size: 20))
+//                    .foregroundColor(step.accentColor)
+//                    .opacity(animationValue)
+//
+//                Image(systemName: "clock.fill")
+//                    .font(.system(size: 20))
+//                    .foregroundColor(step.accentColor)
+//                    .opacity(animationValue)
+//
+//                Image(systemName: "clock")
+//                    .font(.system(size: 20))
+//                    .foregroundColor(step.accentColor)
+//                    .opacity(animationValue)
+//            }
+//            .offset(y: 70)
+//        }
+//    }
+//
+//    var carbRatioAnimation: some View {
+//        ZStack {
+//            // Plate
+//            Circle()
+//                .fill(Color.gray.opacity(0.1))
+//                .frame(width: 150)
+//
+//            // Food items
+//            ForEach(0 ..< 5) { index in
+//                Image(systemName: [
+//                    "carrot.fill",
+//                    "fork.knife",
+//                    "takeoutbag.and.cup.and.straw.fill",
+//                    "wallet.pass.fill",
+//                    "cup.and.saucer.fill"
+//                ][index % 5])
+//                    .font(.system(size: 25))
+//                    .foregroundColor(step.accentColor)
+//                    .offset(
+//                        x: cos(Double(index) * .pi * 2 / 5) * 50 * animationValue,
+//                        y: sin(Double(index) * .pi * 2 / 5) * 50 * animationValue
+//                    )
+//                    .rotationEffect(.degrees(animationValue * 360))
+//            }
+//
+//            // Insulin
+//            Image(systemName: "drop.fill")
+//                .font(.system(size: 40))
+//                .foregroundColor(.blue)
+//                .scaleEffect(0.8 + animationValue * 0.3)
+//                .shadow(color: .blue.opacity(0.5), radius: 5, x: 0, y: 0)
+//        }
+//    }
+//
+//    var insulinSensitivityAnimation: some View {
+//        ZStack {
+//            // Glucose meter
+//            RoundedRectangle(cornerRadius: 20)
+//                .fill(Color.gray.opacity(0.1))
+//                .frame(width: 120, height: 200)
+//
+//            // Display screen
+//            RoundedRectangle(cornerRadius: 10)
+//                .fill(Color.black.opacity(0.1))
+//                .frame(width: 100, height: 60)
+//                .offset(y: -60)
+//
+//            // Value on screen
+//            Text("120")
+//                .font(.system(size: 24, weight: .bold, design: .monospaced))
+//                .foregroundColor(step.accentColor)
+//                .offset(y: -60)
+//                .opacity(animationValue)
+//
+//            // Insulin drop
+//            Image(systemName: "drop.fill")
+//                .font(.system(size: 30))
+//                .foregroundColor(.blue)
+//                .offset(y: 20)
+//                .opacity(1)
+//
+//            // Arrow showing decrease
+//            Image(systemName: "arrow.down")
+//                .font(.system(size: 30))
+//                .foregroundColor(step.accentColor)
+//                .offset(y: 60)
+//                .opacity(animationValue)
+//                .scaleEffect(1.0 + animationValue * 0.5)
+//
+//            // Lower value
+//            Text("80")
+//                .font(.system(size: 24, weight: .bold, design: .monospaced))
+//                .foregroundColor(step.accentColor)
+//                .offset(y: 100)
+//                .opacity(animationValue)
+//        }
+//    }
+//
+//    var completedAnimation: some View {
+//        ZStack {
+//            // Success checkmark
+//            Circle()
+//                .fill(step.accentColor.opacity(0.2))
+//                .frame(width: 150)
+//                .scaleEffect(animationValue)
+//
+//            Circle()
+//                .stroke(step.accentColor, lineWidth: 5)
+//                .frame(width: 150)
+//                .scaleEffect(animationValue)
+//
+//            Image(systemName: "checkmark")
+//                .font(.system(size: 80, weight: .bold))
+//                .foregroundColor(step.accentColor)
+//                .offset(y: animationValue * 5)
+//                .scaleEffect(animationValue)
+//
+//            // Celebrate particles
+//            ForEach(0 ..< 8) { index in
+//                Image(systemName: "star.fill")
+//                    .font(.system(size: 20))
+//                    .foregroundColor(step.accentColor)
+//                    .offset(
+//                        x: cos(Double(index) * .pi / 4) * 100 * animationValue,
+//                        y: sin(Double(index) * .pi / 4) * 100 * animationValue
+//                    )
+//                    .opacity(animationValue)
+//                    .scaleEffect(animationValue)
+//            }
+//        }
+//    }
+// }
 
 
 struct Onboarding_Preview: PreviewProvider {
 struct Onboarding_Preview: PreviewProvider {
     static var previews: some View {
     static var previews: some View {