Procházet zdrojové kódy

Address & fix tester feedback
* Fix navigation (delivery limits, reset to first substep when going back, then forward again)
* Fix missing translation for notifications step header
* Add conditional SMB settings behavior (disable them, display info) if 'Enable Always' is true
* Fix update of time value when first therapy item entry is deleted (always enforce first entry to begin at midnight)

Deniz Cengiz před 1 rokem
rodič
revize
54bf6a3511

+ 4 - 4
Trio.xcodeproj/project.pbxproj

@@ -563,7 +563,7 @@
 		DD498F2C2D692BEA00AAEA30 /* Localizable.xcstrings in Resources */ = {isa = PBXBuildFile; fileRef = 8A9134292D63D9A1007F8874 /* Localizable.xcstrings */; };
 		DD498F2C2D692BEA00AAEA30 /* Localizable.xcstrings in Resources */ = {isa = PBXBuildFile; fileRef = 8A9134292D63D9A1007F8874 /* Localizable.xcstrings */; };
 		DD498F2D2D692BEA00AAEA30 /* Localizable.xcstrings in Resources */ = {isa = PBXBuildFile; fileRef = 8A9134292D63D9A1007F8874 /* Localizable.xcstrings */; };
 		DD498F2D2D692BEA00AAEA30 /* Localizable.xcstrings in Resources */ = {isa = PBXBuildFile; fileRef = 8A9134292D63D9A1007F8874 /* Localizable.xcstrings */; };
 		DD4A00212DAEEED800AB7387 /* OnboardingView+AlgorithmUtil.swift in Sources */ = {isa = PBXBuildFile; fileRef = DD4A00202DAEEEC400AB7387 /* OnboardingView+AlgorithmUtil.swift */; };
 		DD4A00212DAEEED800AB7387 /* OnboardingView+AlgorithmUtil.swift in Sources */ = {isa = PBXBuildFile; fileRef = DD4A00202DAEEEC400AB7387 /* OnboardingView+AlgorithmUtil.swift */; };
-		DD4A00242DAEF5E400AB7387 /* AlgorithmSubstepView.swift in Sources */ = {isa = PBXBuildFile; fileRef = DD4A00232DAEF5DC00AB7387 /* AlgorithmSubstepView.swift */; };
+		DD4A00242DAEF5E400AB7387 /* AlgorithmSettingsSubstepView.swift in Sources */ = {isa = PBXBuildFile; fileRef = DD4A00232DAEF5DC00AB7387 /* AlgorithmSettingsSubstepView.swift */; };
 		DD4AFFF12DADB59100AB7387 /* AlgorithmSettingsStepView.swift in Sources */ = {isa = PBXBuildFile; fileRef = DD4AFFF02DADB59100AB7387 /* AlgorithmSettingsStepView.swift */; };
 		DD4AFFF12DADB59100AB7387 /* AlgorithmSettingsStepView.swift in Sources */ = {isa = PBXBuildFile; fileRef = DD4AFFF02DADB59100AB7387 /* AlgorithmSettingsStepView.swift */; };
 		DD4C57A82D73ADEA001BFF2C /* RestartLiveActivityIntent.swift in Sources */ = {isa = PBXBuildFile; fileRef = DD4C57A72D73ADEA001BFF2C /* RestartLiveActivityIntent.swift */; };
 		DD4C57A82D73ADEA001BFF2C /* RestartLiveActivityIntent.swift in Sources */ = {isa = PBXBuildFile; fileRef = DD4C57A72D73ADEA001BFF2C /* RestartLiveActivityIntent.swift */; };
 		DD4C57AA2D73B3E2001BFF2C /* RestartLiveActivityIntentRequest.swift in Sources */ = {isa = PBXBuildFile; fileRef = DD4C57A92D73B3D9001BFF2C /* RestartLiveActivityIntentRequest.swift */; };
 		DD4C57AA2D73B3E2001BFF2C /* RestartLiveActivityIntentRequest.swift in Sources */ = {isa = PBXBuildFile; fileRef = DD4C57A92D73B3D9001BFF2C /* RestartLiveActivityIntentRequest.swift */; };
@@ -1359,7 +1359,7 @@
 		DD3F1F8C2D9E0E0000DCE7B3 /* NightscoutSetupStepView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = NightscoutSetupStepView.swift; sourceTree = "<group>"; };
 		DD3F1F8C2D9E0E0000DCE7B3 /* NightscoutSetupStepView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = NightscoutSetupStepView.swift; sourceTree = "<group>"; };
 		DD3F1F8F2D9E153A00DCE7B3 /* NightscoutImportStepView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = NightscoutImportStepView.swift; sourceTree = "<group>"; };
 		DD3F1F8F2D9E153A00DCE7B3 /* NightscoutImportStepView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = NightscoutImportStepView.swift; sourceTree = "<group>"; };
 		DD4A00202DAEEEC400AB7387 /* OnboardingView+AlgorithmUtil.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "OnboardingView+AlgorithmUtil.swift"; sourceTree = "<group>"; };
 		DD4A00202DAEEEC400AB7387 /* OnboardingView+AlgorithmUtil.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "OnboardingView+AlgorithmUtil.swift"; sourceTree = "<group>"; };
-		DD4A00232DAEF5DC00AB7387 /* AlgorithmSubstepView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = AlgorithmSubstepView.swift; sourceTree = "<group>"; };
+		DD4A00232DAEF5DC00AB7387 /* AlgorithmSettingsSubstepView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = AlgorithmSettingsSubstepView.swift; sourceTree = "<group>"; };
 		DD4AFFF02DADB59100AB7387 /* AlgorithmSettingsStepView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = AlgorithmSettingsStepView.swift; sourceTree = "<group>"; };
 		DD4AFFF02DADB59100AB7387 /* AlgorithmSettingsStepView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = AlgorithmSettingsStepView.swift; sourceTree = "<group>"; };
 		DD4C57A72D73ADEA001BFF2C /* RestartLiveActivityIntent.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = RestartLiveActivityIntent.swift; sourceTree = "<group>"; };
 		DD4C57A72D73ADEA001BFF2C /* RestartLiveActivityIntent.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = RestartLiveActivityIntent.swift; sourceTree = "<group>"; };
 		DD4C57A92D73B3D9001BFF2C /* RestartLiveActivityIntentRequest.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = RestartLiveActivityIntentRequest.swift; sourceTree = "<group>"; };
 		DD4C57A92D73B3D9001BFF2C /* RestartLiveActivityIntentRequest.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = RestartLiveActivityIntentRequest.swift; sourceTree = "<group>"; };
@@ -3260,7 +3260,7 @@
 		DD4A00222DAEF5CD00AB7387 /* AlgorithmSettings */ = {
 		DD4A00222DAEF5CD00AB7387 /* AlgorithmSettings */ = {
 			isa = PBXGroup;
 			isa = PBXGroup;
 			children = (
 			children = (
-				DD4A00232DAEF5DC00AB7387 /* AlgorithmSubstepView.swift */,
+				DD4A00232DAEF5DC00AB7387 /* AlgorithmSettingsSubstepView.swift */,
 				DD4AFFF02DADB59100AB7387 /* AlgorithmSettingsStepView.swift */,
 				DD4AFFF02DADB59100AB7387 /* AlgorithmSettingsStepView.swift */,
 			);
 			);
 			path = AlgorithmSettings;
 			path = AlgorithmSettings;
@@ -4058,7 +4058,7 @@
 				38192E04261B82FA0094D973 /* ReachabilityManager.swift in Sources */,
 				38192E04261B82FA0094D973 /* ReachabilityManager.swift in Sources */,
 				38E44539274E411700EC9A94 /* Disk+UIImage.swift in Sources */,
 				38E44539274E411700EC9A94 /* Disk+UIImage.swift in Sources */,
 				3BAD36CC2D7D420E00CC298D /* CoreDataInitializationCoordinator.swift in Sources */,
 				3BAD36CC2D7D420E00CC298D /* CoreDataInitializationCoordinator.swift in Sources */,
-				DD4A00242DAEF5E400AB7387 /* AlgorithmSubstepView.swift in Sources */,
+				DD4A00242DAEF5E400AB7387 /* AlgorithmSettingsSubstepView.swift in Sources */,
 				DDB0E3742DB1BAC1004B826F /* LogoBurstSplash.swift in Sources */,
 				DDB0E3742DB1BAC1004B826F /* LogoBurstSplash.swift in Sources */,
 				388E595C25AD948C0019842D /* TrioApp.swift in Sources */,
 				388E595C25AD948C0019842D /* TrioApp.swift in Sources */,
 				38FEF3FC2737E53800574A46 /* MainStateModel.swift in Sources */,
 				38FEF3FC2737E53800574A46 /* MainStateModel.swift in Sources */,

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

@@ -1661,6 +1661,9 @@
         }
         }
       }
       }
     },
     },
+    " Allow Trio to send you Notifications. These may include alerts, sounds, and icon badges." : {
+
+    },
     " as a beginner." : {
     " as a beginner." : {
 
 
     },
     },
@@ -84410,6 +84413,9 @@
         }
         }
       }
       }
     },
     },
+    "Enabling SMB Always will disable other subsequent \"Enable SMB\" options during Onboarding." : {
+
+    },
     "Enabling the UAM (Unannounced Meals) feature allows the system to detect and respond to unexpected rises in glucose readings caused by unannounced or miscalculated carbs, meals high in fat or protein, or other factors like adrenaline." : {
     "Enabling the UAM (Unannounced Meals) feature allows the system to detect and respond to unexpected rises in glucose readings caused by unannounced or miscalculated carbs, meals high in fat or protein, or other factors like adrenaline." : {
       "localizations" : {
       "localizations" : {
         "bg" : {
         "bg" : {
@@ -125394,7 +125400,6 @@
       }
       }
     },
     },
     "Lower target glucose when Autosens Ratio is <1." : {
     "Lower target glucose when Autosens Ratio is <1." : {
-      "extractionState" : "stale",
       "localizations" : {
       "localizations" : {
         "bg" : {
         "bg" : {
           "stringUnit" : {
           "stringUnit" : {
@@ -156980,7 +156985,6 @@
       }
       }
     },
     },
     "Raise target glucose if when Autosens Ratio is >1." : {
     "Raise target glucose if when Autosens Ratio is >1." : {
-      "extractionState" : "stale",
       "localizations" : {
       "localizations" : {
         "bg" : {
         "bg" : {
           "stringUnit" : {
           "stringUnit" : {
@@ -170707,6 +170711,9 @@
         }
         }
       }
       }
     },
     },
+    "Setting cannot be changed for as long as \"Enable SMB Always\" is enabled." : {
+
+    },
     "Setting this to 100% means only the past 24 hours will be used." : {
     "Setting this to 100% means only the past 24 hours will be used." : {
       "localizations" : {
       "localizations" : {
         "bg" : {
         "bg" : {
@@ -216582,6 +216589,9 @@
         }
         }
       }
       }
     },
     },
+    "When enabled, Super Micro Boluses (SMBs) will always be allowed if dosing calculations determine insulin is needed via the SMB delivery method, except when a high Temp Target is set." : {
+
+    },
     "When enabled, Super Micro Boluses (SMBs) will always be allowed if dosing calculations determine insulin is needed via the SMB delivery method, except when a high Temp Target is set. Enabling SMB Always will remove redundant \"Enable SMB\" options when this setting is enacted." : {
     "When enabled, Super Micro Boluses (SMBs) will always be allowed if dosing calculations determine insulin is needed via the SMB delivery method, except when a high Temp Target is set. Enabling SMB Always will remove redundant \"Enable SMB\" options when this setting is enacted." : {
       "localizations" : {
       "localizations" : {
         "bg" : {
         "bg" : {
@@ -217383,7 +217393,6 @@
       }
       }
     },
     },
     "When the carb on board (COB) forecast line is active, enabling this feature allows Trio to use Super Micro Boluses (SMB) to deliver the insulin required." : {
     "When the carb on board (COB) forecast line is active, enabling this feature allows Trio to use Super Micro Boluses (SMB) to deliver the insulin required." : {
-      "extractionState" : "stale",
       "localizations" : {
       "localizations" : {
         "bg" : {
         "bg" : {
           "stringUnit" : {
           "stringUnit" : {

+ 4 - 4
Trio/Sources/Modules/Onboarding/View/OnboardingRootView.swift

@@ -304,11 +304,11 @@ struct OnboardingStepContent: View {
                         case .algorithmSettings:
                         case .algorithmSettings:
                             AlgorithmSettingsStepView(state: state)
                             AlgorithmSettingsStepView(state: state)
                         case .autosensSettings:
                         case .autosensSettings:
-                            AlgorithmSubstepView(state: state, substep: currentAutosensSubstep)
+                            AlgorithmSettingsSubstepView(state: state, substep: currentAutosensSubstep)
                         case .smbSettings:
                         case .smbSettings:
-                            AlgorithmSubstepView(state: state, substep: currentSMBSubstep)
+                            AlgorithmSettingsSubstepView(state: state, substep: currentSMBSubstep)
                         case .targetBehavior:
                         case .targetBehavior:
-                            AlgorithmSubstepView(state: state, substep: currentTargetBehaviorSubstep)
+                            AlgorithmSettingsSubstepView(state: state, substep: currentTargetBehaviorSubstep)
                         case .notifications:
                         case .notifications:
                             NotificationPermissionStepView()
                             NotificationPermissionStepView()
                         case .bluetooth:
                         case .bluetooth:
@@ -420,7 +420,7 @@ struct OnboardingNavigationButtons: View {
                 currentDeliverySubstep = previousSub
                 currentDeliverySubstep = previousSub
             } else if let previous = currentStep.previous {
             } else if let previous = currentStep.previous {
                 currentStep = previous
                 currentStep = previous
-                currentDeliverySubstep = .minimumSafetyThreshold
+                currentDeliverySubstep = .maxIOB
             }
             }
 
 
         case .algorithmSettings:
         case .algorithmSettings:

+ 55 - 37
Trio/Sources/Modules/Onboarding/View/OnboardingSteps/AlgorithmSettings/AlgorithmSubstepView.swift

@@ -1,12 +1,12 @@
 //
 //
-//  AlgorithmSubstepView.swift
+//  AlgorithmSettingsSubstepView.swift
 //  Trio
 //  Trio
 //
 //
 //  Created by Cengiz Deniz on 15.04.25.
 //  Created by Cengiz Deniz on 15.04.25.
 //
 //
 import SwiftUI
 import SwiftUI
 
 
-struct AlgorithmSubstepView<Substep: AlgorithmSubstepProtocol & RawRepresentable>: View where Substep.RawValue == Int {
+struct AlgorithmSettingsSubstepView<Substep: AlgorithmSubstepProtocol & RawRepresentable>: View where Substep.RawValue == Int {
     @Bindable var state: Onboarding.StateModel
     @Bindable var state: Onboarding.StateModel
     let substep: Substep
     let substep: Substep
 
 
@@ -73,7 +73,8 @@ struct AlgorithmSubstepView<Substep: AlgorithmSubstepProtocol & RawRepresentable
                         setting: nil,
                         setting: nil,
                         decimalValue: $decimalPlaceholder,
                         decimalValue: $decimalPlaceholder,
                         booleanValue: $state.enableSMBWithCOB,
                         booleanValue: $state.enableSMBWithCOB,
-                        type: OnboardingInputSectionType.boolean
+                        type: OnboardingInputSectionType.boolean,
+                        disabled: state.enableSMBAlways
                     )
                     )
                 case .enableSMBWithTempTarget:
                 case .enableSMBWithTempTarget:
                     algorithmSettingsInput(
                     algorithmSettingsInput(
@@ -82,7 +83,8 @@ struct AlgorithmSubstepView<Substep: AlgorithmSubstepProtocol & RawRepresentable
                         setting: nil,
                         setting: nil,
                         decimalValue: $decimalPlaceholder,
                         decimalValue: $decimalPlaceholder,
                         booleanValue: $state.enableSMBWithTempTarget,
                         booleanValue: $state.enableSMBWithTempTarget,
-                        type: OnboardingInputSectionType.boolean
+                        type: OnboardingInputSectionType.boolean,
+                        disabled: state.enableSMBAlways
                     )
                     )
                 case .enableSMBAfterCarbs:
                 case .enableSMBAfterCarbs:
                     algorithmSettingsInput(
                     algorithmSettingsInput(
@@ -91,7 +93,8 @@ struct AlgorithmSubstepView<Substep: AlgorithmSubstepProtocol & RawRepresentable
                         setting: nil,
                         setting: nil,
                         decimalValue: $decimalPlaceholder,
                         decimalValue: $decimalPlaceholder,
                         booleanValue: $state.enableSMBAfterCarbs,
                         booleanValue: $state.enableSMBAfterCarbs,
-                        type: OnboardingInputSectionType.boolean
+                        type: OnboardingInputSectionType.boolean,
+                        disabled: state.enableSMBAlways
                     )
                     )
                 case .enableSMBWithHighGlucoseTarget:
                 case .enableSMBWithHighGlucoseTarget:
                     algorithmSettingsInput(
                     algorithmSettingsInput(
@@ -100,7 +103,8 @@ struct AlgorithmSubstepView<Substep: AlgorithmSubstepProtocol & RawRepresentable
                         setting: nil,
                         setting: nil,
                         decimalValue: $decimalPlaceholder,
                         decimalValue: $decimalPlaceholder,
                         booleanValue: $state.enableSMBWithHighGlucoseTarget,
                         booleanValue: $state.enableSMBWithHighGlucoseTarget,
-                        type: OnboardingInputSectionType.boolean
+                        type: OnboardingInputSectionType.boolean,
+                        disabled: state.enableSMBAlways
                     )
                     )
                     if state.enableSMBWithHighGlucoseTarget {
                     if state.enableSMBWithHighGlucoseTarget {
                         algorithmSettingsInput(
                         algorithmSettingsInput(
@@ -109,7 +113,8 @@ struct AlgorithmSubstepView<Substep: AlgorithmSubstepProtocol & RawRepresentable
                             setting: settingsProvider.settings.enableSMB_high_bg_target,
                             setting: settingsProvider.settings.enableSMB_high_bg_target,
                             decimalValue: $state.highGlucoseTarget,
                             decimalValue: $state.highGlucoseTarget,
                             booleanValue: $booleanPlaceholder,
                             booleanValue: $booleanPlaceholder,
-                            type: OnboardingInputSectionType.decimal
+                            type: OnboardingInputSectionType.decimal,
+                            disabled: state.enableSMBAlways
                         )
                         )
                     }
                     }
                 case .allowSMBWithHighTempTarget:
                 case .allowSMBWithHighTempTarget:
@@ -211,7 +216,8 @@ struct AlgorithmSubstepView<Substep: AlgorithmSubstepProtocol & RawRepresentable
                 .padding(.horizontal)
                 .padding(.horizontal)
                 .multilineTextAlignment(.leading)
                 .multilineTextAlignment(.leading)
         }
         }
-        .onDisappear {
+        .onAppear {
+            // Ensure picker view is closed, when switching between setting steps
             shouldDisplayPicker = false
             shouldDisplayPicker = false
         }
         }
     }
     }
@@ -222,46 +228,58 @@ struct AlgorithmSubstepView<Substep: AlgorithmSubstepProtocol & RawRepresentable
         setting: PickerSetting?,
         setting: PickerSetting?,
         decimalValue: Binding<Decimal>,
         decimalValue: Binding<Decimal>,
         booleanValue: Binding<Bool>,
         booleanValue: Binding<Bool>,
-        type: OnboardingInputSectionType
+        type: OnboardingInputSectionType,
+        disabled: Bool = false /// parameter only relevant for `Enable SMB Always` dependent settings
     ) -> some View {
     ) -> some View {
         VStack {
         VStack {
-            switch type {
-            case .boolean:
-                Toggle(isOn: booleanValue) {
-                    Text(label)
-                }.tint(Color.accentColor)
-            case .decimal:
-                Group {
-                    HStack {
+            VStack {
+                switch type {
+                case .boolean:
+                    Toggle(isOn: booleanValue) {
                         Text(label)
                         Text(label)
-                        Spacer()
-                        displayText(for: substep, decimalValue: decimalValue.wrappedValue, units: state.units)
-                            .foregroundColor(!displayPicker.wrappedValue ? .primary : .accentColor)
-                            .onTapGesture {
-                                displayPicker.wrappedValue.toggle()
-                            }
-                    }
+                    }.tint(Color.accentColor)
+                        .disabled(disabled)
+                case .decimal:
+                    Group {
+                        HStack {
+                            Text(label)
+                            Spacer()
+                            displayText(for: substep, decimalValue: decimalValue.wrappedValue, units: state.units)
+                                .foregroundColor(!displayPicker.wrappedValue ? .primary : .accentColor)
+                                .onTapGesture {
+                                    displayPicker.wrappedValue.toggle()
+                                }
+                        }.disabled(disabled)
 
 
-                    if displayPicker.wrappedValue {
-                        Picker(selection: decimalValue, label: Text(label)) {
-                            if let setting = setting {
-                                ForEach(
-                                    settingsProvider.generatePickerValues(from: setting, units: state.units),
-                                    id: \.self
-                                ) { value in
-                                    displayText(for: substep, decimalValue: value, units: state.units).tag(value)
+                        if displayPicker.wrappedValue {
+                            Picker(selection: decimalValue, label: Text(label)) {
+                                if let setting = setting {
+                                    ForEach(
+                                        settingsProvider.generatePickerValues(from: setting, units: state.units),
+                                        id: \.self
+                                    ) { value in
+                                        displayText(for: substep, decimalValue: value, units: state.units).tag(value)
+                                    }
                                 }
                                 }
                             }
                             }
+                            .disabled(disabled)
+                            .pickerStyle(WheelPickerStyle())
+                            .frame(maxWidth: .infinity)
                         }
                         }
-                        .pickerStyle(WheelPickerStyle())
-                        .frame(maxWidth: .infinity)
                     }
                     }
                 }
                 }
             }
             }
+            .padding()
+            .background(Color.chart.opacity(0.65))
+            .cornerRadius(10)
+
+            if disabled {
+                Text("Setting cannot be changed for as long as \"Enable SMB Always\" is enabled.")
+                    .font(.footnote)
+                    .foregroundColor(Color.orange)
+                    .padding()
+            }
         }
         }
-        .padding()
-        .background(Color.chart.opacity(0.65))
-        .cornerRadius(10)
     }
     }
 
 
     private func displayText(for substep: Substep, decimalValue: Decimal, units: GlucoseUnits) -> Text {
     private func displayText(for substep: Substep, decimalValue: Decimal, units: GlucoseUnits) -> Text {

+ 7 - 1
Trio/Sources/Modules/Onboarding/View/OnboardingView+AlgorithmUtil.swift

@@ -206,9 +206,15 @@ enum AlgorithmSettingsSubstep: Int, CaseIterable, Identifiable {
             }
             }
         case .enableSMBAlways:
         case .enableSMBAlways:
             return VStack(alignment: .leading, spacing: 8) {
             return VStack(alignment: .leading, spacing: 8) {
+                Text(
+                    "Enabling SMB Always will disable other subsequent \"Enable SMB\" options during Onboarding."
+                )
+                .padding(.bottom)
+                .foregroundStyle(Color.orange)
+
                 Text("Default: OFF").bold().foregroundStyle(Color.primary)
                 Text("Default: OFF").bold().foregroundStyle(Color.primary)
                 Text(
                 Text(
-                    "When enabled, Super Micro Boluses (SMBs) will always be allowed if dosing calculations determine insulin is needed via the SMB delivery method, except when a high Temp Target is set. Enabling SMB Always will remove redundant \"Enable SMB\" options when this setting is enacted."
+                    "When enabled, Super Micro Boluses (SMBs) will always be allowed if dosing calculations determine insulin is needed via the SMB delivery method, except when a high Temp Target is set."
                 )
                 )
                 Text(
                 Text(
                     "Note: If you would like to allow SMBs when a high Temp Target is set, enable the \"Allow SMBs with High Temptarget\" setting."
                     "Note: If you would like to allow SMBs when a high Temp Target is set, enable the \"Allow SMBs with High Temptarget\" setting."

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

@@ -144,7 +144,7 @@ enum OnboardingStep: Int, CaseIterable, Identifiable, Equatable {
                 localized: "Target Behavior allows you to adjust how temporary targets influence ISF, basal, and auto-targeting based on sensitivity or resistance."
                 localized: "Target Behavior allows you to adjust how temporary targets influence ISF, basal, and auto-targeting based on sensitivity or resistance."
             )
             )
         case .notifications:
         case .notifications:
-            return "Allow Trio to send you Notifications. These may include alerts, sounds, and icon badges."
+            return String(localized: " Allow Trio to send you Notifications. These may include alerts, sounds, and icon badges.")
         case .bluetooth:
         case .bluetooth:
             return String(localized: "Allow Trio to use Bluetooth to communicate with your insulin pump and CGM.")
             return String(localized: "Allow Trio to use Bluetooth to communicate with your insulin pump and CGM.")
         case .completed:
         case .completed:

+ 27 - 4
Trio/Sources/Modules/Onboarding/View/TherapySettingEditorView.swift

@@ -38,6 +38,11 @@ struct TherapySettingEditorView: View {
             .padding(.vertical, 5)
             .padding(.vertical, 5)
 
 
             ForEach($items) { $item in
             ForEach($items) { $item in
+                // Determine if this is first item in list (which is locked to 00:00)
+                var isFirstItem: Bool {
+                    items.first == $item.wrappedValue
+                }
+
                 VStack(spacing: 0) {
                 VStack(spacing: 0) {
                     Button {
                     Button {
                         selectedItemID = selectedItemID == item.id ? nil : item.id
                         selectedItemID = selectedItemID == item.id ? nil : item.id
@@ -116,6 +121,9 @@ struct TherapySettingEditorView: View {
             selectedItemID = nil
             selectedItemID = nil
             validateTherapySettingItems()
             validateTherapySettingItems()
         }
         }
+        .onChange(of: items, { _, _ in
+            validateTherapySettingItems()
+        })
     }
     }
 
 
     @ViewBuilder private func timeValuePickerRow(
     @ViewBuilder private func timeValuePickerRow(
@@ -185,12 +193,18 @@ struct TherapySettingEditorView: View {
 
 
     private func validateTherapySettingItems() {
     private func validateTherapySettingItems() {
         // validates therapy items (i.e. parsed therapy settings into wrapper class)
         // validates therapy items (i.e. parsed therapy settings into wrapper class)
-        let newItems = Array(Set(items)).sorted { $0.time < $1.time }
-        if var first = newItems.first, first.time != 0 {
-            first.time = 0
-            items = newItems
+        var newItems = Array(Set(items)).sorted { $0.time < $1.time }
+        if !newItems.isEmpty {
+            var first = newItems[0]
+            if first.time != 0 {
+                first.time = 0
+            }
+            newItems[0] = first
         }
         }
 
 
+        // force ALL items to have new UUIDs (to enforce binding update)
+        items = newItems.map { TherapySettingItem(copying: $0, newID: true) }
+
         // validates underlying "raw" therapy setting (i.e. item of type basal, target, isf, carb ratio)
         // validates underlying "raw" therapy setting (i.e. item of type basal, target, isf, carb ratio)
         validateOnDelete?()
         validateOnDelete?()
     }
     }
@@ -236,6 +250,15 @@ struct TherapySettingItem: Identifiable, Equatable, Hashable {
     }
     }
 }
 }
 
 
+/// Convenience extension to ease copying of existing `TherapySettingItem`s 
+extension TherapySettingItem {
+    init(copying item: TherapySettingItem, newID: Bool = false) {
+        id = newID ? UUID() : item.id
+        time = item.time
+        value = item.value
+    }
+}
+
 enum TherapySettingUnit: String, CaseIterable {
 enum TherapySettingUnit: String, CaseIterable {
     case mmolLPerUnit
     case mmolLPerUnit
     case mgdLPerUnit
     case mgdLPerUnit