polscm32 1 год назад
Родитель
Сommit
6201e4245a

+ 15 - 3
Trio.xcodeproj/project.pbxproj

@@ -318,6 +318,9 @@
 		BD47FD192D88AAFE0043966B /* OnboardingView.swift in Sources */ = {isa = PBXBuildFile; fileRef = BD47FD182D88AAF90043966B /* OnboardingView.swift */; };
 		BD47FD1B2D88AB4F0043966B /* Model.swift in Sources */ = {isa = PBXBuildFile; fileRef = BD47FD1A2D88AB4A0043966B /* Model.swift */; };
 		BD47FDD72D8B64D20043966B /* CarbRatioStepView.swift in Sources */ = {isa = PBXBuildFile; fileRef = BD47FDD62D8B64CC0043966B /* CarbRatioStepView.swift */; };
+		BD47FDD92D8B657D0043966B /* InsulinSensitivityStepView.swift in Sources */ = {isa = PBXBuildFile; fileRef = BD47FDD82D8B65730043966B /* InsulinSensitivityStepView.swift */; };
+		BD47FDDB2D8B659B0043966B /* BasalProfileStepView.swift in Sources */ = {isa = PBXBuildFile; fileRef = BD47FDDA2D8B65960043966B /* BasalProfileStepView.swift */; };
+		BD47FDDD2D8B65B10043966B /* GlucoseTargetStepView.swift in Sources */ = {isa = PBXBuildFile; fileRef = BD47FDDC2D8B65AD0043966B /* GlucoseTargetStepView.swift */; };
 		BD4D738D2D15A4080052227B /* TDDStored+CoreDataClass.swift in Sources */ = {isa = PBXBuildFile; fileRef = BD4D738B2D15A4080052227B /* TDDStored+CoreDataClass.swift */; };
 		BD4D738E2D15A4080052227B /* TDDStored+CoreDataProperties.swift in Sources */ = {isa = PBXBuildFile; fileRef = BD4D738C2D15A4080052227B /* TDDStored+CoreDataProperties.swift */; };
 		BD4D73A22D15A42A0052227B /* TDDStorage.swift in Sources */ = {isa = PBXBuildFile; fileRef = BD4D73A12D15A4220052227B /* TDDStorage.swift */; };
@@ -1047,6 +1050,9 @@
 		BD47FD182D88AAF90043966B /* OnboardingView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = OnboardingView.swift; sourceTree = "<group>"; };
 		BD47FD1A2D88AB4A0043966B /* Model.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = Model.swift; sourceTree = "<group>"; };
 		BD47FDD62D8B64CC0043966B /* CarbRatioStepView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = CarbRatioStepView.swift; sourceTree = "<group>"; };
+		BD47FDD82D8B65730043966B /* InsulinSensitivityStepView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = InsulinSensitivityStepView.swift; sourceTree = "<group>"; };
+		BD47FDDA2D8B65960043966B /* BasalProfileStepView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = BasalProfileStepView.swift; sourceTree = "<group>"; };
+		BD47FDDC2D8B65AD0043966B /* GlucoseTargetStepView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = GlucoseTargetStepView.swift; sourceTree = "<group>"; };
 		BD4D738B2D15A4080052227B /* TDDStored+CoreDataClass.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "TDDStored+CoreDataClass.swift"; sourceTree = SOURCE_ROOT; };
 		BD4D738C2D15A4080052227B /* TDDStored+CoreDataProperties.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "TDDStored+CoreDataProperties.swift"; sourceTree = SOURCE_ROOT; };
 		BD4D73A12D15A4220052227B /* TDDStorage.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = TDDStorage.swift; sourceTree = "<group>"; };
@@ -1754,7 +1760,7 @@
 		3811DE1F25C9D48300A708ED /* View */ = {
 			isa = PBXGroup;
 			children = (
-				BD47FDD52D8B64AE0043966B /* Setup */,
+				BD47FDD52D8B64AE0043966B /* OnboardingSteps */,
 				BD47FD182D88AAF90043966B /* OnboardingView.swift */,
 				BD47FD162D88AAEF0043966B /* OnboardingStepViews.swift */,
 				3BAD36B12D7CDC1400CC298D /* MainLoadingView.swift */,
@@ -2599,12 +2605,15 @@
 			path = View;
 			sourceTree = "<group>";
 		};
-		BD47FDD52D8B64AE0043966B /* Setup */ = {
+		BD47FDD52D8B64AE0043966B /* OnboardingSteps */ = {
 			isa = PBXGroup;
 			children = (
+				BD47FDDC2D8B65AD0043966B /* GlucoseTargetStepView.swift */,
+				BD47FDDA2D8B65960043966B /* BasalProfileStepView.swift */,
+				BD47FDD82D8B65730043966B /* InsulinSensitivityStepView.swift */,
 				BD47FDD62D8B64CC0043966B /* CarbRatioStepView.swift */,
 			);
-			path = Setup;
+			path = OnboardingSteps;
 			sourceTree = "<group>";
 		};
 		BD793CAD2CE7660C00D669AC /* Overrides */ = {
@@ -3735,6 +3744,7 @@
 				DD9ECB6A2CA99F6C00AA7C45 /* PushMessage.swift in Sources */,
 				3811DE8F25C9D80400A708ED /* User.swift in Sources */,
 				5825A1BE2C97335C0046467E /* EditTempTargetForm.swift in Sources */,
+				BD47FDDD2D8B65B10043966B /* GlucoseTargetStepView.swift in Sources */,
 				19D466A329AA2B80004D5F33 /* MealSettingsDataFlow.swift in Sources */,
 				3811DEB225C9D88300A708ED /* KeychainItemAccessibility.swift in Sources */,
 				38AEE73D25F0200C0013F05B /* TrioSettings.swift in Sources */,
@@ -3747,6 +3757,7 @@
 				BDF530D82B40F8AC002CAF43 /* LockScreenView.swift in Sources */,
 				195D80B72AF697B800D25097 /* DynamicSettingsDataFlow.swift in Sources */,
 				DD98ACC02D71013200C0778F /* StatChartUtils.swift in Sources */,
+				BD47FDD92D8B657D0043966B /* InsulinSensitivityStepView.swift in Sources */,
 				3862CC2E2743F9F700BF832C /* CalendarManager.swift in Sources */,
 				CEA4F62329BE10F70011ADF7 /* SavitzkyGolayFilter.swift in Sources */,
 				38B4F3C325E2A20B00E76A18 /* PumpSetupView.swift in Sources */,
@@ -4114,6 +4125,7 @@
 				DD6B7CB42C7B71F700B75029 /* ForecastDisplayType.swift in Sources */,
 				BD47FD172D88AAF50043966B /* OnboardingStepViews.swift in Sources */,
 				DD5DC9F32CF3D9DD00AB8703 /* AdjustmentsStateModel+TempTargets.swift in Sources */,
+				BD47FDDB2D8B659B0043966B /* BasalProfileStepView.swift in Sources */,
 				F5F7E6C1B7F098F59EB67EC5 /* TargetsEditorDataFlow.swift in Sources */,
 				DD17453A2C55BFA600211FAC /* AlgorithmAdvancedSettingsDataFlow.swift in Sources */,
 				5075C1608E6249A51495C422 /* TargetsEditorProvider.swift in Sources */,

+ 12 - 3
Trio/Sources/Localizations/Main/Localizable.xcstrings

@@ -9167,6 +9167,9 @@
         }
       }
     },
+    "• Different times of day may require different ratios" : {
+
+    },
     "• Duration of Insulin Action" : {
       "localizations" : {
         "bg" : {
@@ -20528,6 +20531,9 @@
         }
       }
     },
+    "Add Carb Ratio" : {
+
+    },
     "Add carbs" : {
       "localizations" : {
         "bg" : {
@@ -21744,6 +21750,9 @@
         }
       }
     },
+    "Add Initial Carb Ratio" : {
+
+    },
     "Add insulin without actually bolusing" : {
       "comment" : "Bolus screen when adding insulin",
       "extractionState" : "manual",
@@ -44048,9 +44057,6 @@
         }
       }
     },
-    "Carb Ratio" : {
-
-    },
     "Carb Ratio:" : {
       "localizations" : {
         "bg" : {
@@ -48652,6 +48658,9 @@
     "Choose when this basal rate should start" : {
 
     },
+    "Choose when this carb ratio should start" : {
+
+    },
     "Choose whether or not to display one or both X- and Y-Axis grid lines." : {
       "localizations" : {
         "bg" : {

+ 0 - 477
Trio/Sources/Modules/Main/View/OnboardingStepViews.swift

@@ -30,483 +30,6 @@ struct WelcomeStepView: View {
     }
 }
 
-/// Glucose target step view for setting target glucose range.
-struct GlucoseTargetStepView: View {
-    @State var onboardingData: OnboardingData
-    @State private var showUnitPicker = false
-
-    // Formatter for glucose values
-    private var numberFormatter: NumberFormatter {
-        let formatter = NumberFormatter()
-        formatter.numberStyle = .decimal
-        formatter.maximumFractionDigits = onboardingData.units == .mmolL ? 1 : 0
-        return formatter
-    }
-
-    var body: some View {
-        VStack(alignment: .leading, spacing: 20) {
-            // Unit selector
-            HStack {
-                Text("Blood Glucose Units")
-                    .font(.headline)
-
-                Spacer()
-
-                Button(action: {
-                    showUnitPicker.toggle()
-                }) {
-                    HStack {
-                        Text(onboardingData.units == .mgdL ? "mg/dL" : "mmol/L")
-                        Image(systemName: "chevron.down")
-                    }
-                    .padding(.horizontal, 12)
-                    .padding(.vertical, 8)
-                    .background(Color.blue.opacity(0.1))
-                    .cornerRadius(8)
-                }
-                .actionSheet(isPresented: $showUnitPicker) {
-                    ActionSheet(
-                        title: Text("Select Blood Glucose Units"),
-                        buttons: [
-                            .default(Text("mg/dL")) {
-                                onboardingData.units = .mgdL
-                                // Adjust values for unit change
-                                if onboardingData.units == .mgdL {
-                                    onboardingData.targetLow = max(70, onboardingData.targetLow * 18)
-                                    onboardingData.targetHigh = max(120, onboardingData.targetHigh * 18)
-                                    onboardingData.isf = max(30, onboardingData.isf * 18)
-                                }
-                            },
-                            .default(Text("mmol/L")) {
-                                onboardingData.units = .mmolL
-                                // Adjust values for unit change
-                                if onboardingData.units == .mmolL {
-                                    onboardingData.targetLow = max(3.9, onboardingData.targetLow / 18)
-                                    onboardingData.targetHigh = max(6.7, onboardingData.targetHigh / 18)
-                                    onboardingData.isf = max(1.7, onboardingData.isf / 18)
-                                }
-                            },
-                            .cancel()
-                        ]
-                    )
-                }
-            }
-
-            Divider()
-
-            // Target glucose range
-            VStack(alignment: .leading, spacing: 12) {
-                Text("Target Glucose Range")
-                    .font(.headline)
-
-                Text("This range defines your ideal blood glucose values. Trio uses this to calculate insulin doses.")
-                    .font(.subheadline)
-                    .foregroundColor(.secondary)
-
-                // Low target
-                VStack(alignment: .leading) {
-                    Text("Low Target")
-                        .font(.subheadline)
-
-                    HStack {
-                        Slider(
-                            value: Binding(
-                                get: { Double(truncating: onboardingData.targetLow as NSNumber) },
-                                set: { onboardingData.targetLow = Decimal($0) }
-                            ),
-                            in: onboardingData.units == .mgdL ? 70 ... 120 : 3.9 ... 6.7,
-                            step: onboardingData.units == .mgdL ? 1 : 0.1
-                        )
-                        .accentColor(.green)
-
-                        Text(
-                            "\(numberFormatter.string(from: onboardingData.targetLow as NSNumber) ?? "--") \(onboardingData.units == .mgdL ? "mg/dL" : "mmol/L")"
-                        )
-                        .frame(width: 80, alignment: .trailing)
-                    }
-                }
-                .padding(.vertical, 4)
-
-                // High target
-                VStack(alignment: .leading) {
-                    Text("High Target")
-                        .font(.subheadline)
-
-                    HStack {
-                        Slider(
-                            value: Binding(
-                                get: { Double(truncating: onboardingData.targetHigh as NSNumber) },
-                                set: { onboardingData.targetHigh = Decimal($0) }
-                            ),
-                            in: onboardingData.units == .mgdL ?
-                                Double(truncating: onboardingData.targetLow as NSNumber) + 10 ... 200 :
-                                Double(truncating: onboardingData.targetLow as NSNumber) + 0.6 ... 11.1,
-                            step: onboardingData.units == .mgdL ? 1 : 0.1
-                        )
-                        .accentColor(.green)
-
-                        Text(
-                            "\(numberFormatter.string(from: onboardingData.targetHigh as NSNumber) ?? "--") \(onboardingData.units == .mgdL ? "mg/dL" : "mmol/L")"
-                        )
-                        .frame(width: 80, alignment: .trailing)
-                    }
-                }
-                .padding(.vertical, 4)
-            }
-
-            Divider()
-
-            // Target range visualization
-            VStack(alignment: .leading, spacing: 8) {
-                Text("Your Target Range")
-                    .font(.headline)
-
-                HStack(spacing: 0) {
-                    // Below range
-                    Rectangle()
-                        .fill(Color.red.opacity(0.3))
-                        .frame(width: 50, height: 30)
-                        .overlay(
-                            Text("Low")
-                                .font(.caption)
-                                .foregroundColor(.red)
-                        )
-
-                    // Target range
-                    Rectangle()
-                        .fill(Color.green.opacity(0.3))
-                        .frame(width: 100, height: 30)
-                        .overlay(
-                            Text("Target")
-                                .font(.caption)
-                                .foregroundColor(.green)
-                        )
-
-                    // Above range
-                    Rectangle()
-                        .fill(Color.yellow.opacity(0.3))
-                        .frame(width: 50, height: 30)
-                        .overlay(
-                            Text("High")
-                                .font(.caption)
-                                .foregroundColor(.orange)
-                        )
-                }
-                .cornerRadius(8)
-
-                // Range values
-                HStack(spacing: 0) {
-                    Text("\(numberFormatter.string(from: onboardingData.targetLow as NSNumber) ?? "--")")
-                        .font(.caption)
-                        .frame(width: 50, alignment: .center)
-
-                    Spacer()
-                        .frame(width: 100)
-
-                    Text("\(numberFormatter.string(from: onboardingData.targetHigh as NSNumber) ?? "--")")
-                        .font(.caption)
-                        .frame(width: 50, alignment: .center)
-                }
-
-                Text("These values reflect your personal target range and can be adjusted at any time in the Settings.")
-                    .font(.caption)
-                    .foregroundColor(.secondary)
-                    .padding(.top, 8)
-            }
-        }
-        .padding()
-    }
-}
-
-/// Basal profile step view for setting basal insulin rates.
-struct BasalProfileStepView: View {
-    @State var onboardingData: OnboardingData
-    @State private var showTimeSelector = false
-    @State private var selectedBasalIndex: Int?
-    @State private var newStartTime: Int = 0
-
-    var body: some View {
-        VStack(alignment: .leading, spacing: 20) {
-            Text("Your basal insulin profile determines how much background insulin you receive throughout the day.")
-                .font(.subheadline)
-                .foregroundColor(.secondary)
-
-            // Basal rates list
-            VStack(alignment: .leading, spacing: 10) {
-                Text("Basal Rates")
-                    .font(.headline)
-
-                ForEach(Array(onboardingData.basalRates.enumerated()), id: \.element.id) { index, basalRate in
-                    HStack {
-                        Text(basalRate.timeFormatted)
-                            .frame(width: 80, alignment: .leading)
-
-                        Slider(
-                            value: Binding(
-                                get: { Double(truncating: onboardingData.basalRates[index].rate as NSNumber) },
-                                set: { onboardingData.basalRates[index].rate = Decimal($0) }
-                            ),
-                            in: 0 ... 5,
-                            step: 0.05
-                        )
-                        .accentColor(.purple)
-
-                        Text("\(String(format: "%.2f", Double(truncating: basalRate.rate as NSNumber))) U/h")
-                            .frame(width: 70, alignment: .trailing)
-
-                        // Delete button (not for the first entry at 00:00)
-                        if index > 0 {
-                            Button(action: {
-                                onboardingData.basalRates.remove(at: index)
-                            }) {
-                                Image(systemName: "trash")
-                                    .foregroundColor(.red)
-                            }
-                        }
-                    }
-                    .padding(.vertical, 8)
-                    .background(Color.purple.opacity(0.05))
-                    .cornerRadius(8)
-                }
-            }
-
-            // Add new basal rate button
-            if onboardingData.basalRates.count < 24 {
-                Button(action: {
-                    showTimeSelector = true
-                }) {
-                    HStack {
-                        Image(systemName: "plus.circle.fill")
-                        Text("Add Basal Rate")
-                    }
-                    .foregroundColor(.purple)
-                    .padding(.vertical, 8)
-                }
-            }
-
-            Divider()
-
-            // Basal profile visualization
-            VStack(alignment: .leading, spacing: 8) {
-                Text("Your Basal Profile")
-                    .font(.headline)
-
-                // Simple chart representation
-                HStack(alignment: .bottom, spacing: 2) {
-                    ForEach(0 ..< 24) { hour in
-                        let rate = basalRateAt(hour: hour)
-                        let height = min(120, CGFloat(Double(rate) * 30))
-
-                        VStack {
-                            Rectangle()
-                                .fill(Color.purple.opacity(0.7))
-                                .frame(width: 10, height: height)
-
-                            if hour % 6 == 0 {
-                                Text("\(hour):00")
-                                    .font(.system(size: 8))
-                                    .frame(width: 20)
-                                    .rotationEffect(.degrees(-45))
-                                    .offset(y: 10)
-                            }
-                        }
-                    }
-                }
-                .frame(height: 150)
-                .padding(.top)
-
-                Text("This chart shows your basal insulin delivery throughout a 24-hour day.")
-                    .font(.caption)
-                    .foregroundColor(.secondary)
-            }
-        }
-        .padding()
-        .actionSheet(isPresented: $showTimeSelector) {
-            var buttons: [ActionSheet.Button] = []
-
-            // Find available time slots in 1-hour increments
-            for hour in 1 ..< 24 {
-                let hourInMinutes = hour * 60
-                // Check if this hour is already in the profile
-                if !onboardingData.basalRates.contains(where: { $0.startTime == hourInMinutes }) {
-                    buttons.append(.default(Text("\(String(format: "%02d:00", hour))")) {
-                        // Get the current basal rate active at this time
-                        let rate = basalRateAt(hour: hour)
-                        // Add new basal rate with the same value
-                        onboardingData.basalRates.append(
-                            OnboardingData.BasalRateEntry(startTime: hourInMinutes, rate: rate)
-                        )
-                        // Sort basal rates by time
-                        onboardingData.basalRates.sort(by: { $0.startTime < $1.startTime })
-                    })
-                }
-            }
-
-            buttons.append(.cancel())
-
-            return ActionSheet(
-                title: Text("Select Start Time"),
-                message: Text("Choose when this basal rate should start"),
-                buttons: buttons
-            )
-        }
-    }
-
-    /// Calculates the basal rate at a specific hour based on the profile.
-    private func basalRateAt(hour: Int) -> Decimal {
-        let minutes = hour * 60
-        // Find the most recent basal rate entry that starts before or at the given hour
-        let applicableRate = onboardingData.basalRates
-            .filter { $0.startTime <= minutes }
-            .sorted(by: { $0.startTime > $1.startTime })
-            .first
-
-        return applicableRate?.rate ?? Decimal(1.0)
-    }
-}
-
-/// Insulin sensitivity step view for setting insulin sensitivity factor.
-struct InsulinSensitivityStepView: View {
-    @State var onboardingData: OnboardingData
-
-    private var numberFormatter: NumberFormatter {
-        let formatter = NumberFormatter()
-        formatter.numberStyle = .decimal
-        formatter.maximumFractionDigits = onboardingData.units == .mmolL ? 1 : 0
-        return formatter
-    }
-
-    private var ispRange: ClosedRange<Double> {
-        if onboardingData.units == .mgdL {
-            return 10 ... 100
-        } else {
-            return 0.5 ... 5.5
-        }
-    }
-
-    private var ispStep: Double {
-        onboardingData.units == .mgdL ? 1 : 0.1
-    }
-
-    var body: some View {
-        VStack(alignment: .leading, spacing: 20) {
-            Text("Your insulin sensitivity factor (ISF) indicates how much one unit of insulin will lower your blood glucose.")
-                .font(.subheadline)
-                .foregroundColor(.secondary)
-
-            VStack(alignment: .leading, spacing: 12) {
-                Text("Insulin Sensitivity Factor")
-                    .font(.headline)
-
-                HStack {
-                    Slider(
-                        value: Binding(
-                            get: { Double(truncating: onboardingData.isf as NSNumber) },
-                            set: { onboardingData.isf = Decimal($0) }
-                        ),
-                        in: ispRange,
-                        step: ispStep
-                    )
-                    .accentColor(.red)
-
-                    // Display the current value
-                    Text(
-                        "\(numberFormatter.string(from: onboardingData.isf as NSNumber) ?? "--") \(onboardingData.units == .mgdL ? "mg/dL" : "mmol/L")"
-                    )
-                    .frame(width: 80, alignment: .trailing)
-                }
-
-                // Example calculation
-                VStack(alignment: .leading, spacing: 8) {
-                    Text("Example Calculation")
-                        .font(.headline)
-                        .padding(.top)
-
-                    VStack(alignment: .leading, spacing: 4) {
-                        // Current glucose is 40 mg/dL or 2.2 mmol/L above target
-                        let aboveTarget = onboardingData.units == .mgdL ? 40.0 : 2.2
-                        let insulinNeeded = aboveTarget / Double(truncating: onboardingData.isf as NSNumber)
-
-                        Text(
-                            "If you are \(numberFormatter.string(from: NSNumber(value: aboveTarget)) ?? "--") \(onboardingData.units == .mgdL ? "mg/dL" : "mmol/L") above target:"
-                        )
-                        .font(.subheadline)
-
-                        Text(
-                            "\(numberFormatter.string(from: NSNumber(value: aboveTarget)) ?? "--") ÷ \(numberFormatter.string(from: onboardingData.isf as NSNumber) ?? "--") = \(String(format: "%.1f", insulinNeeded)) units of insulin"
-                        )
-                        .font(.system(.body, design: .monospaced))
-                        .foregroundColor(.red)
-                        .padding(.vertical, 8)
-                        .padding(.horizontal, 12)
-                        .background(Color.red.opacity(0.1))
-                        .cornerRadius(8)
-                    }
-                    .padding(.vertical, 4)
-
-                    // Information about ISF
-                    VStack(alignment: .leading, spacing: 8) {
-                        Text("What This Means")
-                            .font(.headline)
-                            .padding(.top, 8)
-
-                        VStack(alignment: .leading, spacing: 4) {
-                            if onboardingData.units == .mgdL {
-                                Text("• An ISF of 50 mg/dL means 1 unit of insulin lowers your BG by 50 mg/dL")
-                                Text("• A lower number means you're more sensitive to insulin")
-                                Text("• A higher number means you're less sensitive to insulin")
-                            } else {
-                                Text("• An ISF of 2.8 mmol/L means 1 unit of insulin lowers your BG by 2.8 mmol/L")
-                                Text("• A lower number means you're more sensitive to insulin")
-                                Text("• A higher number means you're less sensitive to insulin")
-                            }
-                        }
-                        .font(.caption)
-                        .foregroundColor(.secondary)
-                    }
-                }
-            }
-
-            // Visualization of ISF
-            VStack(alignment: .leading, spacing: 8) {
-                Text("Visual Reference")
-                    .font(.headline)
-                    .padding(.top)
-
-                HStack(spacing: 20) {
-                    VStack {
-                        Image(systemName: "drop.fill")
-                            .font(.system(size: 40))
-                            .foregroundColor(.blue)
-                        Text("1U")
-                            .font(.headline)
-                        Text("Insulin")
-                            .font(.caption)
-                    }
-
-                    Text("⟹")
-                        .font(.title)
-
-                    VStack {
-                        Image(systemName: "arrow.down.circle.fill")
-                            .font(.system(size: 40))
-                            .foregroundColor(.red)
-                        Text("\(numberFormatter.string(from: onboardingData.isf as NSNumber) ?? "--")")
-                            .font(.headline)
-                        Text(onboardingData.units == .mgdL ? "mg/dL" : "mmol/L")
-                            .font(.caption)
-                    }
-                }
-                .frame(maxWidth: .infinity)
-                .padding()
-                .background(Color.red.opacity(0.1))
-                .cornerRadius(12)
-            }
-        }
-        .padding()
-    }
-}
-
 /// Completed step view shown at the end of onboarding.
 struct CompletedStepView: View {
     var body: some View {

+ 154 - 0
Trio/Sources/Modules/Main/View/OnboardingSteps/BasalProfileStepView.swift

@@ -0,0 +1,154 @@
+//
+//  BasalProfileStepView.swift
+//  Trio
+//
+//  Created by Marvin Polscheit on 19.03.25.
+//
+import SwiftUI
+
+/// Basal profile step view for setting basal insulin rates.
+struct BasalProfileStepView: View {
+    @State var onboardingData: OnboardingData
+    @State private var showTimeSelector = false
+    @State private var selectedBasalIndex: Int?
+    @State private var newStartTime: Int = 0
+
+    var body: some View {
+        VStack(alignment: .leading, spacing: 20) {
+            Text("Your basal insulin profile determines how much background insulin you receive throughout the day.")
+                .font(.subheadline)
+                .foregroundColor(.secondary)
+
+            // Basal rates list
+            VStack(alignment: .leading, spacing: 10) {
+                Text("Basal Rates")
+                    .font(.headline)
+
+                ForEach(Array(onboardingData.basalRates.enumerated()), id: \.element.id) { index, basalRate in
+                    HStack {
+                        Text(basalRate.timeFormatted)
+                            .frame(width: 80, alignment: .leading)
+
+                        Slider(
+                            value: Binding(
+                                get: { Double(truncating: onboardingData.basalRates[index].rate as NSNumber) },
+                                set: { onboardingData.basalRates[index].rate = Decimal($0) }
+                            ),
+                            in: 0 ... 5,
+                            step: 0.05
+                        )
+                        .accentColor(.purple)
+
+                        Text("\(String(format: "%.2f", Double(truncating: basalRate.rate as NSNumber))) U/h")
+                            .frame(width: 70, alignment: .trailing)
+
+                        // Delete button (not for the first entry at 00:00)
+                        if index > 0 {
+                            Button(action: {
+                                onboardingData.basalRates.remove(at: index)
+                            }) {
+                                Image(systemName: "trash")
+                                    .foregroundColor(.red)
+                            }
+                        }
+                    }
+                    .padding(.vertical, 8)
+                    .background(Color.purple.opacity(0.05))
+                    .cornerRadius(8)
+                }
+            }
+
+            // Add new basal rate button
+            if onboardingData.basalRates.count < 24 {
+                Button(action: {
+                    showTimeSelector = true
+                }) {
+                    HStack {
+                        Image(systemName: "plus.circle.fill")
+                        Text("Add Basal Rate")
+                    }
+                    .foregroundColor(.purple)
+                    .padding(.vertical, 8)
+                }
+            }
+
+            Divider()
+
+            // Basal profile visualization
+            VStack(alignment: .leading, spacing: 8) {
+                Text("Your Basal Profile")
+                    .font(.headline)
+
+                // Simple chart representation
+                HStack(alignment: .bottom, spacing: 2) {
+                    ForEach(0 ..< 24) { hour in
+                        let rate = basalRateAt(hour: hour)
+                        let height = min(120, CGFloat(Double(rate) * 30))
+
+                        VStack {
+                            Rectangle()
+                                .fill(Color.purple.opacity(0.7))
+                                .frame(width: 10, height: height)
+
+                            if hour % 6 == 0 {
+                                Text("\(hour):00")
+                                    .font(.system(size: 8))
+                                    .frame(width: 20)
+                                    .rotationEffect(.degrees(-45))
+                                    .offset(y: 10)
+                            }
+                        }
+                    }
+                }
+                .frame(height: 150)
+                .padding(.top)
+
+                Text("This chart shows your basal insulin delivery throughout a 24-hour day.")
+                    .font(.caption)
+                    .foregroundColor(.secondary)
+            }
+        }
+        .padding()
+        .actionSheet(isPresented: $showTimeSelector) {
+            var buttons: [ActionSheet.Button] = []
+
+            // Find available time slots in 1-hour increments
+            for hour in 1 ..< 24 {
+                let hourInMinutes = hour * 60
+                // Check if this hour is already in the profile
+                if !onboardingData.basalRates.contains(where: { $0.startTime == hourInMinutes }) {
+                    buttons.append(.default(Text("\(String(format: "%02d:00", hour))")) {
+                        // Get the current basal rate active at this time
+                        let rate = basalRateAt(hour: hour)
+                        // Add new basal rate with the same value
+                        onboardingData.basalRates.append(
+                            OnboardingData.BasalRateEntry(startTime: hourInMinutes, rate: rate)
+                        )
+                        // Sort basal rates by time
+                        onboardingData.basalRates.sort(by: { $0.startTime < $1.startTime })
+                    })
+                }
+            }
+
+            buttons.append(.cancel())
+
+            return ActionSheet(
+                title: Text("Select Start Time"),
+                message: Text("Choose when this basal rate should start"),
+                buttons: buttons
+            )
+        }
+    }
+
+    /// Calculates the basal rate at a specific hour based on the profile.
+    private func basalRateAt(hour: Int) -> Decimal {
+        let minutes = hour * 60
+        // Find the most recent basal rate entry that starts before or at the given hour
+        let applicableRate = onboardingData.basalRates
+            .filter { $0.startTime <= minutes }
+            .sorted(by: { $0.startTime > $1.startTime })
+            .first
+
+        return applicableRate?.rate ?? Decimal(1.0)
+    }
+}

Trio/Sources/Modules/Main/View/Setup/CarbRatioStepView.swift → Trio/Sources/Modules/Main/View/OnboardingSteps/CarbRatioStepView.swift


+ 195 - 0
Trio/Sources/Modules/Main/View/OnboardingSteps/GlucoseTargetStepView.swift

@@ -0,0 +1,195 @@
+//
+//  GlucoseTargetStepView.swift
+//  Trio
+//
+//  Created by Marvin Polscheit on 19.03.25.
+//
+import SwiftUI
+
+/// Glucose target step view for setting target glucose range.
+struct GlucoseTargetStepView: View {
+    @State var onboardingData: OnboardingData
+    @State private var showUnitPicker = false
+
+    // Formatter for glucose values
+    private var numberFormatter: NumberFormatter {
+        let formatter = NumberFormatter()
+        formatter.numberStyle = .decimal
+        formatter.maximumFractionDigits = onboardingData.units == .mmolL ? 1 : 0
+        return formatter
+    }
+
+    var body: some View {
+        VStack(alignment: .leading, spacing: 20) {
+            // Unit selector
+            HStack {
+                Text("Blood Glucose Units")
+                    .font(.headline)
+
+                Spacer()
+
+                Button(action: {
+                    showUnitPicker.toggle()
+                }) {
+                    HStack {
+                        Text(onboardingData.units == .mgdL ? "mg/dL" : "mmol/L")
+                        Image(systemName: "chevron.down")
+                    }
+                    .padding(.horizontal, 12)
+                    .padding(.vertical, 8)
+                    .background(Color.blue.opacity(0.1))
+                    .cornerRadius(8)
+                }
+                .actionSheet(isPresented: $showUnitPicker) {
+                    ActionSheet(
+                        title: Text("Select Blood Glucose Units"),
+                        buttons: [
+                            .default(Text("mg/dL")) {
+                                onboardingData.units = .mgdL
+                                // Adjust values for unit change
+                                if onboardingData.units == .mgdL {
+                                    onboardingData.targetLow = max(70, onboardingData.targetLow * 18)
+                                    onboardingData.targetHigh = max(120, onboardingData.targetHigh * 18)
+                                    onboardingData.isf = max(30, onboardingData.isf * 18)
+                                }
+                            },
+                            .default(Text("mmol/L")) {
+                                onboardingData.units = .mmolL
+                                // Adjust values for unit change
+                                if onboardingData.units == .mmolL {
+                                    onboardingData.targetLow = max(3.9, onboardingData.targetLow / 18)
+                                    onboardingData.targetHigh = max(6.7, onboardingData.targetHigh / 18)
+                                    onboardingData.isf = max(1.7, onboardingData.isf / 18)
+                                }
+                            },
+                            .cancel()
+                        ]
+                    )
+                }
+            }
+
+            Divider()
+
+            // Target glucose range
+            VStack(alignment: .leading, spacing: 12) {
+                Text("Target Glucose Range")
+                    .font(.headline)
+
+                Text("This range defines your ideal blood glucose values. Trio uses this to calculate insulin doses.")
+                    .font(.subheadline)
+                    .foregroundColor(.secondary)
+
+                // Low target
+                VStack(alignment: .leading) {
+                    Text("Low Target")
+                        .font(.subheadline)
+
+                    HStack {
+                        Slider(
+                            value: Binding(
+                                get: { Double(truncating: onboardingData.targetLow as NSNumber) },
+                                set: { onboardingData.targetLow = Decimal($0) }
+                            ),
+                            in: onboardingData.units == .mgdL ? 70 ... 120 : 3.9 ... 6.7,
+                            step: onboardingData.units == .mgdL ? 1 : 0.1
+                        )
+                        .accentColor(.green)
+
+                        Text(
+                            "\(numberFormatter.string(from: onboardingData.targetLow as NSNumber) ?? "--") \(onboardingData.units == .mgdL ? "mg/dL" : "mmol/L")"
+                        )
+                        .frame(width: 80, alignment: .trailing)
+                    }
+                }
+                .padding(.vertical, 4)
+
+                // High target
+                VStack(alignment: .leading) {
+                    Text("High Target")
+                        .font(.subheadline)
+
+                    HStack {
+                        Slider(
+                            value: Binding(
+                                get: { Double(truncating: onboardingData.targetHigh as NSNumber) },
+                                set: { onboardingData.targetHigh = Decimal($0) }
+                            ),
+                            in: onboardingData.units == .mgdL ?
+                                Double(truncating: onboardingData.targetLow as NSNumber) + 10 ... 200 :
+                                Double(truncating: onboardingData.targetLow as NSNumber) + 0.6 ... 11.1,
+                            step: onboardingData.units == .mgdL ? 1 : 0.1
+                        )
+                        .accentColor(.green)
+
+                        Text(
+                            "\(numberFormatter.string(from: onboardingData.targetHigh as NSNumber) ?? "--") \(onboardingData.units == .mgdL ? "mg/dL" : "mmol/L")"
+                        )
+                        .frame(width: 80, alignment: .trailing)
+                    }
+                }
+                .padding(.vertical, 4)
+            }
+
+            Divider()
+
+            // Target range visualization
+            VStack(alignment: .leading, spacing: 8) {
+                Text("Your Target Range")
+                    .font(.headline)
+
+                HStack(spacing: 0) {
+                    // Below range
+                    Rectangle()
+                        .fill(Color.red.opacity(0.3))
+                        .frame(width: 50, height: 30)
+                        .overlay(
+                            Text("Low")
+                                .font(.caption)
+                                .foregroundColor(.red)
+                        )
+
+                    // Target range
+                    Rectangle()
+                        .fill(Color.green.opacity(0.3))
+                        .frame(width: 100, height: 30)
+                        .overlay(
+                            Text("Target")
+                                .font(.caption)
+                                .foregroundColor(.green)
+                        )
+
+                    // Above range
+                    Rectangle()
+                        .fill(Color.yellow.opacity(0.3))
+                        .frame(width: 50, height: 30)
+                        .overlay(
+                            Text("High")
+                                .font(.caption)
+                                .foregroundColor(.orange)
+                        )
+                }
+                .cornerRadius(8)
+
+                // Range values
+                HStack(spacing: 0) {
+                    Text("\(numberFormatter.string(from: onboardingData.targetLow as NSNumber) ?? "--")")
+                        .font(.caption)
+                        .frame(width: 50, alignment: .center)
+
+                    Spacer()
+                        .frame(width: 100)
+
+                    Text("\(numberFormatter.string(from: onboardingData.targetHigh as NSNumber) ?? "--")")
+                        .font(.caption)
+                        .frame(width: 50, alignment: .center)
+                }
+
+                Text("These values reflect your personal target range and can be adjusted at any time in the Settings.")
+                    .font(.caption)
+                    .foregroundColor(.secondary)
+                    .padding(.top, 8)
+            }
+        }
+        .padding()
+    }
+}

+ 149 - 0
Trio/Sources/Modules/Main/View/OnboardingSteps/InsulinSensitivityStepView.swift

@@ -0,0 +1,149 @@
+//
+//  InsulinSensitivityStepView.swift
+//  Trio
+//
+//  Created by Marvin Polscheit on 19.03.25.
+//
+import SwiftUI
+
+/// Insulin sensitivity step view for setting insulin sensitivity factor.
+struct InsulinSensitivityStepView: View {
+    @State var onboardingData: OnboardingData
+
+    private var numberFormatter: NumberFormatter {
+        let formatter = NumberFormatter()
+        formatter.numberStyle = .decimal
+        formatter.maximumFractionDigits = onboardingData.units == .mmolL ? 1 : 0
+        return formatter
+    }
+
+    private var ispRange: ClosedRange<Double> {
+        if onboardingData.units == .mgdL {
+            return 10 ... 100
+        } else {
+            return 0.5 ... 5.5
+        }
+    }
+
+    private var ispStep: Double {
+        onboardingData.units == .mgdL ? 1 : 0.1
+    }
+
+    var body: some View {
+        VStack(alignment: .leading, spacing: 20) {
+            Text("Your insulin sensitivity factor (ISF) indicates how much one unit of insulin will lower your blood glucose.")
+                .font(.subheadline)
+                .foregroundColor(.secondary)
+
+            VStack(alignment: .leading, spacing: 12) {
+                Text("Insulin Sensitivity Factor")
+                    .font(.headline)
+
+                HStack {
+                    Slider(
+                        value: Binding(
+                            get: { Double(truncating: onboardingData.isf as NSNumber) },
+                            set: { onboardingData.isf = Decimal($0) }
+                        ),
+                        in: ispRange,
+                        step: ispStep
+                    )
+                    .accentColor(.red)
+
+                    // Display the current value
+                    Text(
+                        "\(numberFormatter.string(from: onboardingData.isf as NSNumber) ?? "--") \(onboardingData.units == .mgdL ? "mg/dL" : "mmol/L")"
+                    )
+                    .frame(width: 80, alignment: .trailing)
+                }
+
+                // Example calculation
+                VStack(alignment: .leading, spacing: 8) {
+                    Text("Example Calculation")
+                        .font(.headline)
+                        .padding(.top)
+
+                    VStack(alignment: .leading, spacing: 4) {
+                        // Current glucose is 40 mg/dL or 2.2 mmol/L above target
+                        let aboveTarget = onboardingData.units == .mgdL ? 40.0 : 2.2
+                        let insulinNeeded = aboveTarget / Double(truncating: onboardingData.isf as NSNumber)
+
+                        Text(
+                            "If you are \(numberFormatter.string(from: NSNumber(value: aboveTarget)) ?? "--") \(onboardingData.units == .mgdL ? "mg/dL" : "mmol/L") above target:"
+                        )
+                        .font(.subheadline)
+
+                        Text(
+                            "\(numberFormatter.string(from: NSNumber(value: aboveTarget)) ?? "--") ÷ \(numberFormatter.string(from: onboardingData.isf as NSNumber) ?? "--") = \(String(format: "%.1f", insulinNeeded)) units of insulin"
+                        )
+                        .font(.system(.body, design: .monospaced))
+                        .foregroundColor(.red)
+                        .padding(.vertical, 8)
+                        .padding(.horizontal, 12)
+                        .background(Color.red.opacity(0.1))
+                        .cornerRadius(8)
+                    }
+                    .padding(.vertical, 4)
+
+                    // Information about ISF
+                    VStack(alignment: .leading, spacing: 8) {
+                        Text("What This Means")
+                            .font(.headline)
+                            .padding(.top, 8)
+
+                        VStack(alignment: .leading, spacing: 4) {
+                            if onboardingData.units == .mgdL {
+                                Text("• An ISF of 50 mg/dL means 1 unit of insulin lowers your BG by 50 mg/dL")
+                                Text("• A lower number means you're more sensitive to insulin")
+                                Text("• A higher number means you're less sensitive to insulin")
+                            } else {
+                                Text("• An ISF of 2.8 mmol/L means 1 unit of insulin lowers your BG by 2.8 mmol/L")
+                                Text("• A lower number means you're more sensitive to insulin")
+                                Text("• A higher number means you're less sensitive to insulin")
+                            }
+                        }
+                        .font(.caption)
+                        .foregroundColor(.secondary)
+                    }
+                }
+            }
+
+            // Visualization of ISF
+            VStack(alignment: .leading, spacing: 8) {
+                Text("Visual Reference")
+                    .font(.headline)
+                    .padding(.top)
+
+                HStack(spacing: 20) {
+                    VStack {
+                        Image(systemName: "drop.fill")
+                            .font(.system(size: 40))
+                            .foregroundColor(.blue)
+                        Text("1U")
+                            .font(.headline)
+                        Text("Insulin")
+                            .font(.caption)
+                    }
+
+                    Text("⟹")
+                        .font(.title)
+
+                    VStack {
+                        Image(systemName: "arrow.down.circle.fill")
+                            .font(.system(size: 40))
+                            .foregroundColor(.red)
+                        Text("\(numberFormatter.string(from: onboardingData.isf as NSNumber) ?? "--")")
+                            .font(.headline)
+                        Text(onboardingData.units == .mgdL ? "mg/dL" : "mmol/L")
+                            .font(.caption)
+                    }
+                }
+                .frame(maxWidth: .infinity)
+                .padding()
+                .background(Color.red.opacity(0.1))
+                .cornerRadius(12)
+            }
+        }
+        .padding()
+    }
+}