Browse Source

Refactor therapy settings input; start with BG targets WIP

Deniz Cengiz 1 year ago
parent
commit
7e5de2138b

+ 4 - 0
Trio.xcodeproj/project.pbxproj

@@ -591,6 +591,7 @@
 		DDAA29852D2D1D9E006546A1 /* AdjustmentsRootView+TempTargets.swift in Sources */ = {isa = PBXBuildFile; fileRef = DDAA29842D2D1D98006546A1 /* AdjustmentsRootView+TempTargets.swift */; };
 		DDAA29852D2D1D9E006546A1 /* AdjustmentsRootView+TempTargets.swift in Sources */ = {isa = PBXBuildFile; fileRef = DDAA29842D2D1D98006546A1 /* AdjustmentsRootView+TempTargets.swift */; };
 		DDB37CC52D05048F00D99BF4 /* ContactImageStorage.swift in Sources */ = {isa = PBXBuildFile; fileRef = DDB37CC42D05048F00D99BF4 /* ContactImageStorage.swift */; };
 		DDB37CC52D05048F00D99BF4 /* ContactImageStorage.swift in Sources */ = {isa = PBXBuildFile; fileRef = DDB37CC42D05048F00D99BF4 /* ContactImageStorage.swift */; };
 		DDB37CC72D05127500D99BF4 /* FontExtensions.swift in Sources */ = {isa = PBXBuildFile; fileRef = DDB37CC62D05127500D99BF4 /* FontExtensions.swift */; };
 		DDB37CC72D05127500D99BF4 /* FontExtensions.swift in Sources */ = {isa = PBXBuildFile; fileRef = DDB37CC62D05127500D99BF4 /* FontExtensions.swift */; };
+		DDC38E102D9B377800ADCB46 /* OnboardingView+Util.swift in Sources */ = {isa = PBXBuildFile; fileRef = DDC38E0F2D9B376900ADCB46 /* OnboardingView+Util.swift */; };
 		DDCAE8332D78D4A800B1BB51 /* TherapySettingsUtil.swift in Sources */ = {isa = PBXBuildFile; fileRef = DDCAE8322D78D49C00B1BB51 /* TherapySettingsUtil.swift */; };
 		DDCAE8332D78D4A800B1BB51 /* TherapySettingsUtil.swift in Sources */ = {isa = PBXBuildFile; fileRef = DDCAE8322D78D49C00B1BB51 /* TherapySettingsUtil.swift */; };
 		DDCE790F2D6F97FC000A4D7A /* SubmodulesView.swift in Sources */ = {isa = PBXBuildFile; fileRef = DDCE790E2D6F97F7000A4D7A /* SubmodulesView.swift */; };
 		DDCE790F2D6F97FC000A4D7A /* SubmodulesView.swift in Sources */ = {isa = PBXBuildFile; fileRef = DDCE790E2D6F97F7000A4D7A /* SubmodulesView.swift */; };
 		DDCEBF5B2CC1B76400DF4C36 /* LiveActivity+Helper.swift in Sources */ = {isa = PBXBuildFile; fileRef = DDCEBF5A2CC1B76400DF4C36 /* LiveActivity+Helper.swift */; };
 		DDCEBF5B2CC1B76400DF4C36 /* LiveActivity+Helper.swift in Sources */ = {isa = PBXBuildFile; fileRef = DDCEBF5A2CC1B76400DF4C36 /* LiveActivity+Helper.swift */; };
@@ -1367,6 +1368,7 @@
 		DDB37CC32D05044D00D99BF4 /* ContactTrickEntryStored+CoreDataProperties.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "ContactTrickEntryStored+CoreDataProperties.swift"; sourceTree = "<group>"; };
 		DDB37CC32D05044D00D99BF4 /* ContactTrickEntryStored+CoreDataProperties.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "ContactTrickEntryStored+CoreDataProperties.swift"; sourceTree = "<group>"; };
 		DDB37CC42D05048F00D99BF4 /* ContactImageStorage.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ContactImageStorage.swift; sourceTree = "<group>"; };
 		DDB37CC42D05048F00D99BF4 /* ContactImageStorage.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ContactImageStorage.swift; sourceTree = "<group>"; };
 		DDB37CC62D05127500D99BF4 /* FontExtensions.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = FontExtensions.swift; sourceTree = "<group>"; };
 		DDB37CC62D05127500D99BF4 /* FontExtensions.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = FontExtensions.swift; sourceTree = "<group>"; };
+		DDC38E0F2D9B376900ADCB46 /* OnboardingView+Util.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "OnboardingView+Util.swift"; sourceTree = "<group>"; };
 		DDCAE8322D78D49C00B1BB51 /* TherapySettingsUtil.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = TherapySettingsUtil.swift; sourceTree = "<group>"; };
 		DDCAE8322D78D49C00B1BB51 /* TherapySettingsUtil.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = TherapySettingsUtil.swift; sourceTree = "<group>"; };
 		DDCE790E2D6F97F7000A4D7A /* SubmodulesView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SubmodulesView.swift; sourceTree = "<group>"; };
 		DDCE790E2D6F97F7000A4D7A /* SubmodulesView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SubmodulesView.swift; sourceTree = "<group>"; };
 		DDCEBF5A2CC1B76400DF4C36 /* LiveActivity+Helper.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "LiveActivity+Helper.swift"; sourceTree = "<group>"; };
 		DDCEBF5A2CC1B76400DF4C36 /* LiveActivity+Helper.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "LiveActivity+Helper.swift"; sourceTree = "<group>"; };
@@ -2721,6 +2723,7 @@
 		BD47FD152D88AAD80043966B /* View */ = {
 		BD47FD152D88AAD80043966B /* View */ = {
 			isa = PBXGroup;
 			isa = PBXGroup;
 			children = (
 			children = (
+				DDC38E0F2D9B376900ADCB46 /* OnboardingView+Util.swift */,
 				BD47FD182D88AAF90043966B /* OnboardingView.swift */,
 				BD47FD182D88AAF90043966B /* OnboardingView.swift */,
 				BD47FDD52D8B64AE0043966B /* OnboardingSteps */,
 				BD47FDD52D8B64AE0043966B /* OnboardingSteps */,
 			);
 			);
@@ -4200,6 +4203,7 @@
 				3B2F77882D7E5387005ED9FA /* CurrentTDDSetup.swift in Sources */,
 				3B2F77882D7E5387005ED9FA /* CurrentTDDSetup.swift in Sources */,
 				DBA5254DBB2586C98F61220C /* ISFEditorProvider.swift in Sources */,
 				DBA5254DBB2586C98F61220C /* ISFEditorProvider.swift in Sources */,
 				BDF34EBE2C0A31D100D51995 /* CustomNotification.swift in Sources */,
 				BDF34EBE2C0A31D100D51995 /* CustomNotification.swift in Sources */,
+				DDC38E102D9B377800ADCB46 /* OnboardingView+Util.swift in Sources */,
 				BDC2EA472C3045AD00E5BBD0 /* Override.swift in Sources */,
 				BDC2EA472C3045AD00E5BBD0 /* Override.swift in Sources */,
 				1BBB001DAD60F3B8CEA4B1C7 /* ISFEditorStateModel.swift in Sources */,
 				1BBB001DAD60F3B8CEA4B1C7 /* ISFEditorStateModel.swift in Sources */,
 				DDB37CC72D05127500D99BF4 /* FontExtensions.swift in Sources */,
 				DDB37CC72D05127500D99BF4 /* FontExtensions.swift in Sources */,

+ 11 - 15
Trio/Sources/Application/TrioApp.swift

@@ -138,8 +138,6 @@ extension Notification.Name {
                     cleanupOldData()
                     cleanupOldData()
 
 
                     self.initState.complete = true
                     self.initState.complete = true
-                    debug(.default, "showonbaording: \(onboardingManager.shouldShowOnboarding)")
-                    self.showOnboardingView = onboardingManager.shouldShowOnboarding
                     Foundation.NotificationCenter.default.post(name: .initializationCompleted, object: nil)
                     Foundation.NotificationCenter.default.post(name: .initializationCompleted, object: nil)
                     UIApplication.shared.registerForRemoteNotifications()
                     UIApplication.shared.registerForRemoteNotifications()
                     do {
                     do {
@@ -196,20 +194,18 @@ extension Notification.Name {
                     .onReceive(Foundation.NotificationCenter.default.publisher(for: .initializationError)) { _ in
                     .onReceive(Foundation.NotificationCenter.default.publisher(for: .initializationError)) { _ in
                         self.showLoadingError = true
                         self.showLoadingError = true
                     }
                     }
+            } else if onboardingManager.shouldShowOnboarding {
+                // Show onboarding if needed
+                Onboarding.RootView(resolver: resolver, onboardingManager: onboardingManager)
+                    .preferredColorScheme(colorScheme(for: .dark) ?? nil)
+                    .transition(.opacity)
             } else {
             } else {
-                if showOnboardingView {
-                    // Show onboarding if needed
-                    Onboarding.RootView(resolver: resolver, onboardingManager: onboardingManager)
-                        .preferredColorScheme(colorScheme(for: .dark) ?? nil)
-                        .transition(.opacity)
-                } else {
-                    Main.RootView(resolver: resolver)
-                        .preferredColorScheme(colorScheme(for: colorSchemePreference) ?? nil)
-                        .environment(\.managedObjectContext, coreDataStack.persistentContainer.viewContext)
-                        .environment(appState)
-                        .environmentObject(Icons())
-                        .onOpenURL(perform: handleURL)
-                }
+                Main.RootView(resolver: resolver)
+                    .preferredColorScheme(colorScheme(for: colorSchemePreference) ?? nil)
+                    .environment(\.managedObjectContext, coreDataStack.persistentContainer.viewContext)
+                    .environment(appState)
+                    .environmentObject(Icons())
+                    .onOpenURL(perform: handleURL)
             }
             }
         }
         }
         .onChange(of: scenePhase) { _, newScenePhase in
         .onChange(of: scenePhase) { _, newScenePhase in

+ 20 - 28
Trio/Sources/Localizations/Main/Localizable.xcstrings

@@ -5381,6 +5381,19 @@
         }
         }
       }
       }
     },
     },
+    "%.1f" : {
+
+    },
+    "%.1f %@" : {
+      "localizations" : {
+        "en" : {
+          "stringUnit" : {
+            "state" : "new",
+            "value" : "%1$.1f %2$@"
+          }
+        }
+      }
+    },
     "%.2f U of %.2f U" : {
     "%.2f U of %.2f U" : {
       "comment" : "Format for showing delivered and active bolus amounts, 'x U of y U' on watch",
       "comment" : "Format for showing delivered and active bolus amounts, 'x U of y U' on watch",
       "localizations" : {
       "localizations" : {
@@ -39411,9 +39424,6 @@
         }
         }
       }
       }
     },
     },
-    "Blood Glucose Units" : {
-
-    },
     "Bluetooth Power Off" : {
     "Bluetooth Power Off" : {
       "comment" : "Bluetooth Power Off",
       "comment" : "Bluetooth Power Off",
       "extractionState" : "manual",
       "extractionState" : "manual",
@@ -48697,9 +48707,6 @@
     "Choose when this sensitivity factor should start" : {
     "Choose when this sensitivity factor should start" : {
 
 
     },
     },
-    "Choose when this target should start" : {
-
-    },
     "Choose whether or not to display one or both X- and Y-Axis grid lines." : {
     "Choose whether or not to display one or both X- and Y-Axis grid lines." : {
       "localizations" : {
       "localizations" : {
         "bg" : {
         "bg" : {
@@ -78630,6 +78637,9 @@
         }
         }
       }
       }
     },
     },
+    "Entries" : {
+
+    },
     "Error" : {
     "Error" : {
       "comment" : "Error title",
       "comment" : "Error title",
       "localizations" : {
       "localizations" : {
@@ -93159,6 +93169,9 @@
         }
         }
       }
       }
     },
     },
+    "Hi there!" : {
+
+    },
     "Hidden" : {
     "Hidden" : {
       "localizations" : {
       "localizations" : {
         "bg" : {
         "bg" : {
@@ -94309,9 +94322,6 @@
         }
         }
       }
       }
     },
     },
-    "High Target" : {
-
-    },
     "High Temp Target Raises Sensitivity" : {
     "High Temp Target Raises Sensitivity" : {
       "comment" : "High Temp Target Raises Sensitivity",
       "comment" : "High Temp Target Raises Sensitivity",
       "localizations" : {
       "localizations" : {
@@ -110762,9 +110772,6 @@
         }
         }
       }
       }
     },
     },
-    "Low Target" : {
-
-    },
     "Low Temp Target Lowers Sensitivity" : {
     "Low Temp Target Lowers Sensitivity" : {
       "comment" : "Low Temp Target Lowers Sensitivity",
       "comment" : "Low Temp Target Lowers Sensitivity",
       "localizations" : {
       "localizations" : {
@@ -147033,9 +147040,6 @@
         }
         }
       }
       }
     },
     },
-    "Select Blood Glucose Units" : {
-
-    },
     "Select CGM Model" : {
     "Select CGM Model" : {
       "localizations" : {
       "localizations" : {
         "bg" : {
         "bg" : {
@@ -164031,9 +164035,6 @@
     "Target glucose is out of range (%@)." : {
     "Target glucose is out of range (%@)." : {
 
 
     },
     },
-    "Target Glucose Range" : {
-
-    },
     "Target presets" : {
     "Target presets" : {
       "comment" : "Debug option view Target presets",
       "comment" : "Debug option view Target presets",
       "extractionState" : "manual",
       "extractionState" : "manual",
@@ -170445,9 +170446,6 @@
         }
         }
       }
       }
     },
     },
-    "These values reflect your personal target range and can be adjusted at any time in the Settings." : {
-
-    },
     "This adjusted ISF is temporary, will change with the next loop cycle, and should not be directly used as your profile ISF value." : {
     "This adjusted ISF is temporary, will change with the next loop cycle, and should not be directly used as your profile ISF value." : {
       "localizations" : {
       "localizations" : {
         "bg" : {
         "bg" : {
@@ -174862,9 +174860,6 @@
         }
         }
       }
       }
     },
     },
-    "This range defines your ideal blood glucose values. Trio uses this to calculate insulin doses." : {
-
-    },
     "This range is for display and statistical purposes only and does not influence insulin dosing." : {
     "This range is for display and statistical purposes only and does not influence insulin dosing." : {
       "localizations" : {
       "localizations" : {
         "bg" : {
         "bg" : {
@@ -191822,7 +191817,7 @@
         }
         }
       }
       }
     },
     },
-    "Welcome to Trio!" : {
+    "Welcome to Trio - an automated insulin delivery system for iOS based on the OpenAPS algorithm with adaptations." : {
 
 
     },
     },
     "What is the numeric value of the carb to add" : {
     "What is the numeric value of the carb to add" : {
@@ -196339,9 +196334,6 @@
         }
         }
       }
       }
     },
     },
-    "Your Target Range" : {
-
-    },
     "ZT" : {
     "ZT" : {
       "localizations" : {
       "localizations" : {
         "bg" : {
         "bg" : {

+ 21 - 8
Trio/Sources/Modules/Onboarding/OnboardingStateModel.swift

@@ -147,10 +147,6 @@ extension Onboarding {
             return settingsProvider.generatePickerValues(from: glucoseSetting, units: units)
             return settingsProvider.generatePickerValues(from: glucoseSetting, units: units)
         }
         }
 
 
-        // Glucose Target
-        var targetLow: Decimal = 70
-        var targetHigh: Decimal = 180
-
         // Basal Profile
         // Basal Profile
         var basalRates: [BasalRateEntry] = [BasalRateEntry(startTime: 0, rate: 1.0)]
         var basalRates: [BasalRateEntry] = [BasalRateEntry(startTime: 0, rate: 1.0)]
 
 
@@ -184,10 +180,6 @@ extension Onboarding {
             // Make a copy of the current settings that we can mutate
             // Make a copy of the current settings that we can mutate
             var settingsCopy = settingsManager.settings
             var settingsCopy = settingsManager.settings
 
 
-            // Apply glucose target - We'll use lowGlucose and highGlucose properties
-            settingsCopy.lowGlucose = targetLow
-            settingsCopy.highGlucose = targetHigh
-
             // Apply glucose units
             // Apply glucose units
             settingsCopy.units = units
             settingsCopy.units = units
 
 
@@ -208,6 +200,27 @@ extension Onboarding {
             // we'll directly set the settings property which will trigger the didSet observer
             // we'll directly set the settings property which will trigger the didSet observer
             settingsManager.settings = settingsCopy
             settingsManager.settings = settingsCopy
         }
         }
+
+        func getTherapyItems(from targets: [TargetsEditor.Item]) -> [TherapySettingItem] {
+            targets.map {
+                TherapySettingItem(
+                    id: UUID(),
+                    time: targetTimeValues[$0.timeIndex],
+                    value: Double(targetRateValues[$0.lowIndex])
+                )
+            }
+        }
+
+        func updateTargets(from therapyItems: [TherapySettingItem]) {
+            targetItems = therapyItems.map { item in
+                let timeIndex = targetTimeValues.firstIndex(where: { $0 == item.time }) ?? 0
+                let closestRate = targetRateValues.enumerated().min(by: {
+                    abs(Double($0.element) - item.value) < abs(Double($1.element) - item.value)
+                })?.offset ?? 0
+
+                return TargetsEditor.Item(lowIndex: closestRate, highIndex: closestRate, timeIndex: timeIndex)
+            }
+        }
     }
     }
 }
 }
 
 

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

@@ -11,12 +11,12 @@ import UIKit
 /// Glucose target step view for setting target glucose range.
 /// Glucose target step view for setting target glucose range.
 struct GlucoseTargetStepView: View {
 struct GlucoseTargetStepView: View {
     @Bindable var state: Onboarding.StateModel
     @Bindable var state: Onboarding.StateModel
-    @State private var showUnitPicker = false
     @State private var showTimeSelector = false
     @State private var showTimeSelector = false
     @State private var selectedTargetIndex: Int?
     @State private var selectedTargetIndex: Int?
     @State private var showAlert = false
     @State private var showAlert = false
     @State private var errorMessage = ""
     @State private var errorMessage = ""
     @State private var refreshUI = UUID() // to update chart when slider value changes
     @State private var refreshUI = UUID() // to update chart when slider value changes
+    @State private var therapyItems: [TherapySettingItem] = []
 
 
     // Formatter for glucose values
     // Formatter for glucose values
     private var numberFormatter: NumberFormatter {
     private var numberFormatter: NumberFormatter {
@@ -40,125 +40,6 @@ struct GlucoseTargetStepView: View {
     var body: some View {
     var body: some View {
         ScrollView {
         ScrollView {
             VStack(alignment: .leading, spacing: 20) {
             VStack(alignment: .leading, spacing: 20) {
-                // Unit selector
-                HStack {
-                    Text("Blood Glucose Units")
-                        .font(.headline)
-
-                    Spacer()
-
-                    Button(action: {
-                        showUnitPicker.toggle()
-                    }) {
-                        HStack {
-                            Text(state.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) {
-                        let mgdlAction = ActionSheet.Button.default(Text("mg/dL")) {
-                            // Store current unit
-                            let oldUnit = state.units
-                            // Change to new unit
-                            state.units = .mgdL
-                            // Adjust values for unit change, only if unit actually changed
-                            if oldUnit != .mgdL {
-                                state.targetLow = max(70, state.targetLow * 18)
-                                state.targetHigh = max(120, state.targetHigh * 18)
-                                state.isf = max(30, state.isf * 18)
-                            }
-                        }
-
-                        let mmolAction = ActionSheet.Button.default(Text("mmol/L")) {
-                            // Store current unit
-                            let oldUnit = state.units
-                            // Change to new unit
-                            state.units = .mmolL
-                            // Adjust values for unit change, only if unit actually changed
-                            if oldUnit != .mmolL {
-                                state.targetLow = max(3.9, state.targetLow / 18)
-                                state.targetHigh = max(6.7, state.targetHigh / 18)
-                                state.isf = max(1.7, state.isf / 18)
-                            }
-                        }
-
-                        let cancelAction = ActionSheet.Button.cancel()
-
-                        return ActionSheet(
-                            title: Text("Select Blood Glucose Units"),
-                            buttons: [mgdlAction, mmolAction, cancelAction]
-                        )
-                    }
-                }
-
-                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: state.targetLow as NSNumber) },
-                                    set: { state.targetLow = Decimal($0) }
-                                ),
-                                in: state.units == .mgdL ? 70 ... 120 : 3.9 ... 6.7,
-                                step: state.units == .mgdL ? 1 : 0.1
-                            )
-                            .accentColor(.green)
-
-                            Text(
-                                "\(numberFormatter.string(from: state.targetLow as NSNumber) ?? "--") \(state.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: state.targetHigh as NSNumber) },
-                                    set: { state.targetHigh = Decimal($0) }
-                                ),
-                                in: state.units == .mgdL ?
-                                    Double(truncating: state.targetLow as NSNumber) + 10 ... 200 :
-                                    Double(truncating: state.targetLow as NSNumber) + 0.6 ... 11.1,
-                                step: state.units == .mgdL ? 1 : 0.1
-                            )
-                            .accentColor(.green)
-
-                            Text(
-                                "\(numberFormatter.string(from: state.targetHigh as NSNumber) ?? "--") \(state.units == .mgdL ? "mg/dL" : "mmol/L")"
-                            )
-                            .frame(width: 80, alignment: .trailing)
-                        }
-                    }
-                    .padding(.vertical, 4)
-                }
-
-                Divider()
-
                 // Chart visualization
                 // Chart visualization
                 if !state.targetItems.isEmpty {
                 if !state.targetItems.isEmpty {
                     VStack(alignment: .leading) {
                     VStack(alignment: .leading) {
@@ -176,210 +57,27 @@ struct GlucoseTargetStepView: View {
                 }
                 }
 
 
                 // Glucose target list
                 // Glucose target list
-                VStack(alignment: .leading, spacing: 10) {
-                    HStack {
-                        Text("Glucose Targets")
-                            .font(.headline)
-
-                        Spacer()
-
-                        // Add new target button
-                        if state.targetItems.count < 24 {
-                            Button(action: {
-                                showTimeSelector = true
-                            }) {
-                                HStack {
-                                    Image(systemName: "plus.circle.fill")
-                                    Text("Add Target")
-                                }
-                                .foregroundColor(.blue)
-                            }
-                            .disabled(!canAddTarget)
-                        }
-                    }
-                    .padding(.horizontal)
-
-                    // List of targets
-                    VStack(spacing: 2) {
-                        ForEach(state.targetItems.indices, id: \.self) { index in
-                            let item = state.targetItems[index]
-                            HStack {
-                                // Time display
-                                Text(
-                                    dateFormatter
-                                        .string(from: Date(
-                                            timeIntervalSince1970: state
-                                                .targetTimeValues[item.timeIndex]
-                                        ))
-                                )
-                                .frame(width: 80, alignment: .leading)
-                                .padding(.leading)
-
-                                // Low target slider
-                                Slider(
-                                    value: Binding(
-                                        get: {
-                                            Double(
-                                                truncating: state
-                                                    .targetRateValues[item.lowIndex] as NSNumber
-                                            ) },
-                                        set: { newValue in
-                                            // Find closest match in rateValues array
-                                            let newIndex = state.targetRateValues
-                                                .firstIndex { abs(Double($0) - newValue) < 0.05 } ?? item.lowIndex
-                                            state.targetItems[index].lowIndex = newIndex
-
-                                            // Ensure high target is at least as high as low target
-                                            if state.targetItems[index].highIndex < newIndex {
-                                                state.targetItems[index].highIndex = newIndex
-                                            }
-
-                                            // Force refresh when slider changes
-                                            refreshUI = UUID()
-                                        }
-                                    ),
-                                    in: Double(truncating: state.targetRateValues.first! as NSNumber) ...
-                                        Double(truncating: state.targetRateValues.last! as NSNumber),
-                                    step: state.units == .mgdL ? 1 : 0.1
-                                )
-                                .accentColor(.blue)
-                                .padding(.horizontal, 5)
-                                .onChange(of: state.targetItems[index].lowIndex) { _, _ in
-                                    let impact = UIImpactFeedbackGenerator(style: .light)
-                                    impact.impactOccurred()
-                                }
-
-                                // Display the current value
-                                Text(
-                                    "\(numberFormatter.string(from: state.targetRateValues[item.lowIndex] as NSNumber) ?? "--") \(state.units == .mgdL ? "mg/dL" : "mmol/L")"
-                                )
-                                .frame(width: 80, alignment: .trailing)
-                                .lineLimit(1)
-                                .minimumScaleFactor(0.8)
-
-                                // Delete button (not for the first entry at 00:00)
-                                if index > 0 {
-                                    Button(action: {
-                                        state.targetItems.remove(at: index)
-                                    }) {
-                                        Image(systemName: "trash")
-                                            .foregroundColor(.red)
-                                            .padding(.horizontal, 5)
-                                    }
-                                } else {
-                                    // Spacer to maintain alignment
-                                    Spacer()
-                                        .frame(width: 30)
-                                }
-                            }
-                            .padding(.vertical, 12)
-                            .background(index % 2 == 0 ? Color.blue.opacity(0.05) : Color.clear)
-                            .cornerRadius(8)
-                        }
-                    }
-                    .background(Color.blue.opacity(0.05))
-                    .cornerRadius(10)
-                    .padding(.horizontal)
-                    .onAppear {
-                        if state.targetItems.isEmpty {
-                            state.addTarget()
-                        }
-                    }
-                }
-
-                // 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: state.targetLow as NSNumber) ?? "--")")
-                            .font(.caption)
-                            .frame(width: 50, alignment: .center)
-
-                        Spacer()
-                            .frame(width: 100)
-
-                        Text("\(numberFormatter.string(from: state.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)
+                VStack(alignment: .leading) {
+                    Text("Glucose Targets")
+                        .font(.title2)
+                        .padding(.horizontal)
+
+                    TimeValueEditorView(
+                        items: $therapyItems,
+                        unit: state.units.rawValue,
+                        valueOptions: state.targetRateValues
+                    )
                 }
                 }
             }
             }
-            .padding()
-        }
-        .actionSheet(isPresented: $showTimeSelector) {
-            var buttons: [ActionSheet.Button] = []
-
-            // Find available time slots in 1-hour increments
-            for hour in 0 ..< 24 {
-                let hourInMinutes = hour * 60
-                // Calculate timeIndex for this hour
-                let timeIndex = state.targetTimeValues.firstIndex { abs($0 - Double(hourInMinutes * 60)) < 10 } ?? 0
-
-                // Check if this hour is already in the profile
-                if !state.targetItems.contains(where: { $0.timeIndex == timeIndex }) {
-                    buttons.append(.default(Text("\(String(format: "%02d:00", hour))")) {
-                        // Get the current low and high values from the last item
-                        let lowIndex = state.targetItems.last?.lowIndex ?? 0
-                        let highIndex = state.targetItems.last?.highIndex ?? lowIndex
-
-                        // Create new item with the specified time
-                        let newItem = TargetsEditor.Item(lowIndex: lowIndex, highIndex: highIndex, timeIndex: timeIndex)
-
-                        // Add the new item and sort the list by timeIndex
-                        state.targetItems.append(newItem)
-                        state.targetItems.sort(by: { $0.timeIndex < $1.timeIndex })
-                    })
-                }
+        }.onAppear {
+            if state.targetItems.isEmpty {
+                state.addTarget()
             }
             }
-
-            buttons.append(.cancel())
-
-            return ActionSheet(
-                title: Text("Select Start Time"),
-                message: Text("Choose when this target should start"),
-                buttons: buttons
-            )
+            therapyItems = state.getTherapyItems(from: state.targetItems)
+            debug(.default, "THERAPY ITEMS: \(therapyItems)")
+        }.onChange(of: therapyItems) { _, newItems in
+            state.updateTargets(from: newItems)
+            refreshUI = UUID()
         }
         }
     }
     }
 
 

+ 15 - 8
Trio/Sources/Modules/Onboarding/View/OnboardingSteps/OnboardingStepViews.swift

@@ -4,12 +4,24 @@ import SwiftUI
 struct WelcomeStepView: View {
 struct WelcomeStepView: View {
     var body: some View {
     var body: some View {
         VStack(alignment: .center, spacing: 20) {
         VStack(alignment: .center, spacing: 20) {
-            Text("Welcome to Trio!")
+            Image("trioCircledNoBackground")
+                .resizable()
+                .scaledToFit()
+                .frame(height: 100)
+                .padding()
+
+            Text("Hi there!")
                 .font(.title2)
                 .font(.title2)
                 .fontWeight(.bold)
                 .fontWeight(.bold)
                 .multilineTextAlignment(.center)
                 .multilineTextAlignment(.center)
 
 
             Text(
             Text(
+                "Welcome to Trio - an automated insulin delivery system for iOS based on the OpenAPS algorithm with adaptations."
+            )
+            .multilineTextAlignment(.center)
+            .foregroundColor(.secondary)
+
+            Text(
                 "Trio is designed to help manage your diabetes efficiently. To get the most out of the app, we'll guide you through setting up some essential parameters."
                 "Trio is designed to help manage your diabetes efficiently. To get the most out of the app, we'll guide you through setting up some essential parameters."
             )
             )
             .multilineTextAlignment(.center)
             .multilineTextAlignment(.center)
@@ -17,13 +29,8 @@ struct WelcomeStepView: View {
 
 
             Text("Let's go through a few quick steps to ensure Trio works optimally for you.")
             Text("Let's go through a few quick steps to ensure Trio works optimally for you.")
                 .multilineTextAlignment(.center)
                 .multilineTextAlignment(.center)
-                .foregroundColor(.secondary)
-
-            Image("trioCircledNoBackground")
-                .resizable()
-                .scaledToFit()
-                .frame(height: 100)
-                .padding()
+                .foregroundColor(.primary)
+                .bold()
         }
         }
         .padding()
         .padding()
         .frame(maxWidth: .infinity)
         .frame(maxWidth: .infinity)

+ 141 - 0
Trio/Sources/Modules/Onboarding/View/OnboardingView+Util.swift

@@ -0,0 +1,141 @@
+import SwiftUI
+
+struct TherapySettingItem: Identifiable, Equatable {
+    var id = UUID()
+    var time: TimeInterval // seconds since start of day
+    var value: Double
+}
+
+struct TimeValuePickerRow: View {
+    @Binding var item: TherapySettingItem
+    var valueOptions: [Decimal]
+    var unit: String
+
+    var body: some View {
+        VStack(spacing: 8) {
+            HStack {
+                Picker("Time", selection: Binding(
+                    get: { item.time },
+                    set: { item.time = $0 }
+                )) {
+                    ForEach(0 ..< 48) { i in
+                        let seconds = Double(i * 30 * 60)
+                        Text(timeFormatter.string(from: Date(timeIntervalSince1970: seconds)))
+                            .tag(seconds)
+                    }
+                }
+                .frame(maxWidth: .infinity)
+                .clipped()
+
+                Picker("Value", selection: Binding(
+                    get: { item.value },
+                    set: { item.value = $0 }
+                )) {
+                    ForEach(valueOptions, id: \.self) { value in
+                        Text("\(Double(value), specifier: "%.1f") \(unit)").tag(Double(value))
+                    }
+                }
+                .frame(maxWidth: .infinity)
+                .clipped()
+            }
+            .pickerStyle(.wheel)
+        }
+        .padding(.vertical, 8)
+    }
+
+    private var timeFormatter: DateFormatter {
+        let formatter = DateFormatter()
+        formatter.dateFormat = "HH:mm"
+        return formatter
+    }
+}
+
+struct TimeValueEditorView: View {
+    @Binding var items: [TherapySettingItem]
+    var unit: String
+    var valueOptions: [Decimal]
+
+    @State private var selectedItemID: UUID?
+
+    var body: some View {
+        List {
+            HStack {
+                Text("Entries").bold()
+                Spacer()
+                Button {
+                    let lastTime = items.last?.time ?? 0
+                    let newTime = min(lastTime + 1800, 23 * 3600 + 1800)
+                    let newValue = items.last?.value ?? 1.0
+                    items.append(TherapySettingItem(time: newTime, value: newValue))
+                } label: {
+                    HStack {
+                        Image(systemName: "plus")
+                        Text("Add")
+                            .foregroundColor(.accentColor)
+                    }
+                }
+                .disabled(items.count >= 48)
+            }
+
+            ForEach(Array(items.enumerated()), id: \.element.id) { index, item in
+                VStack(spacing: 0) {
+                    Button {
+                        selectedItemID = selectedItemID == item.id ? nil : item.id
+                    } label: {
+                        HStack {
+                            Text(timeFormatter.string(from: Date(timeIntervalSince1970: item.time)))
+                                .foregroundStyle(selectedItemID == item.id ? Color.accentColor : Color.primary)
+                            Spacer()
+                            HStack {
+                                Text("\(item.value, specifier: "%.1f")")
+                                    .foregroundStyle(selectedItemID == item.id ? Color.accentColor : Color.primary)
+                                Text(unit.description)
+                                    .foregroundStyle(Color.secondary)
+                            }
+                        }.contentShape(Rectangle())
+                    }
+                    .buttonStyle(.plain)
+                    .padding(.vertical, 8)
+
+                    if selectedItemID == item.id {
+                        TimeValuePickerRow(
+                            item: $items[index],
+                            valueOptions: valueOptions,
+                            unit: unit
+                        )
+                        .transition(.slide)
+                    }
+                }
+                .swipeActions(edge: .trailing, allowsFullSwipe: true) {
+                    if index > 0 {
+                        Button(role: .destructive) {
+                            items.remove(at: index)
+                            selectedItemID = nil
+                        } label: {
+                            Label("Delete", systemImage: "trash")
+                        }
+                    }
+                }
+            }
+        }
+    }
+
+    private var timeFormatter: DateFormatter {
+        let formatter = DateFormatter()
+        formatter.dateFormat = "HH:mm"
+        return formatter
+    }
+}
+
+#Preview {
+    @Previewable @State var previewItems = [
+        TherapySettingItem(time: 0, value: 1.0),
+        TherapySettingItem(time: 1800, value: 1.2)
+    ]
+
+    TimeValueEditorView(
+        items: $previewItems,
+        unit: "U/h",
+        valueOptions: stride(from: 0.0, through: 10.0, by: 0.05).map { Decimal(round(100 * $0) / 100) }
+    )
+}

+ 35 - 34
Trio/Sources/Modules/Onboarding/View/OnboardingView.swift

@@ -37,44 +37,45 @@ extension Onboarding {
                         ScrollView {
                         ScrollView {
                             VStack(alignment: .leading, spacing: 20) {
                             VStack(alignment: .leading, spacing: 20) {
                                 // Header
                                 // Header
-                                HStack {
-                                    Image(systemName: currentStep.iconName)
-                                        .font(.system(size: 40))
-                                        .foregroundColor(currentStep.accentColor)
-                                        .frame(width: 60, height: 60)
-                                        .background(
-                                            Circle()
-                                                .fill(currentStep.accentColor.opacity(0.2))
-                                        )
-
-                                    VStack(alignment: .leading) {
-                                        Text(currentStep.title)
-                                            .font(.largeTitle)
-                                            .fontWeight(.bold)
-                                            .foregroundColor(.primary)
-
-                                        Text(currentStep.description)
-                                            .font(.subheadline)
-                                            .foregroundColor(.secondary)
-                                            .fixedSize(horizontal: false, vertical: true)
+                                if currentStep != .welcome {
+                                    HStack {
+                                        Image(systemName: currentStep.iconName)
+                                            .font(.system(size: 40))
+                                            .foregroundColor(currentStep.accentColor)
+                                            .frame(width: 60, height: 60)
+                                            .background(
+                                                Circle()
+                                                    .fill(currentStep.accentColor.opacity(0.2))
+                                            )
+
+                                        VStack(alignment: .leading) {
+                                            Text(currentStep.title)
+                                                .font(.largeTitle)
+                                                .fontWeight(.bold)
+                                                .foregroundColor(.primary)
+
+                                            Text(currentStep.description)
+                                                .font(.subheadline)
+                                                .foregroundColor(.secondary)
+                                                .fixedSize(horizontal: false, vertical: true)
+                                        }
                                     }
                                     }
+                                    .padding([.horizontal, .top])
                                 }
                                 }
-                                .padding(.horizontal)
-                                .padding(.top)
 
 
                                 // Animation container (for steps that include animations)
                                 // Animation container (for steps that include animations)
-                                AnimationPlaceholder(for: currentStep)
-                                    .padding()
-                                    .scaleEffect(animationScale)
-                                    .opacity(animationOpacity)
-                                    .onAppear {
-                                        withAnimation(.easeInOut(duration: 0.7)) {
-                                            animationOpacity = 1
-                                            animationScale = 1.0
-                                        }
-                                        // Start pulse animation
-                                        isAnimating = true
-                                    }
+//                                AnimationPlaceholder(for: currentStep)
+//                                    .padding()
+//                                    .scaleEffect(animationScale)
+//                                    .opacity(animationOpacity)
+//                                    .onAppear {
+//                                        withAnimation(.easeInOut(duration: 0.7)) {
+//                                            animationOpacity = 1
+//                                            animationScale = 1.0
+//                                        }
+//                                        // Start pulse animation
+//                                        isAnimating = true
+//                                    }
 
 
                                 // Step-specific content
                                 // Step-specific content
                                 Group {
                                 Group {