Преглед изворни кода

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 пре 1 година
родитељ
комит
54bf6a3511

+ 4 - 4
Trio.xcodeproj/project.pbxproj

@@ -563,7 +563,7 @@
 		DD498F2C2D692BEA00AAEA30 /* 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 */; };
-		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 */; };
 		DD4C57A82D73ADEA001BFF2C /* RestartLiveActivityIntent.swift in Sources */ = {isa = PBXBuildFile; fileRef = DD4C57A72D73ADEA001BFF2C /* RestartLiveActivityIntent.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>"; };
 		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>"; };
-		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>"; };
 		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>"; };
@@ -3260,7 +3260,7 @@
 		DD4A00222DAEF5CD00AB7387 /* AlgorithmSettings */ = {
 			isa = PBXGroup;
 			children = (
-				DD4A00232DAEF5DC00AB7387 /* AlgorithmSubstepView.swift */,
+				DD4A00232DAEF5DC00AB7387 /* AlgorithmSettingsSubstepView.swift */,
 				DD4AFFF02DADB59100AB7387 /* AlgorithmSettingsStepView.swift */,
 			);
 			path = AlgorithmSettings;
@@ -4058,7 +4058,7 @@
 				38192E04261B82FA0094D973 /* ReachabilityManager.swift in Sources */,
 				38E44539274E411700EC9A94 /* Disk+UIImage.swift in Sources */,
 				3BAD36CC2D7D420E00CC298D /* CoreDataInitializationCoordinator.swift in Sources */,
-				DD4A00242DAEF5E400AB7387 /* AlgorithmSubstepView.swift in Sources */,
+				DD4A00242DAEF5E400AB7387 /* AlgorithmSettingsSubstepView.swift in Sources */,
 				DDB0E3742DB1BAC1004B826F /* LogoBurstSplash.swift in Sources */,
 				388E595C25AD948C0019842D /* TrioApp.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." : {
 
     },
@@ -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." : {
       "localizations" : {
         "bg" : {
@@ -125394,7 +125400,6 @@
       }
     },
     "Lower target glucose when Autosens Ratio is <1." : {
-      "extractionState" : "stale",
       "localizations" : {
         "bg" : {
           "stringUnit" : {
@@ -156980,7 +156985,6 @@
       }
     },
     "Raise target glucose if when Autosens Ratio is >1." : {
-      "extractionState" : "stale",
       "localizations" : {
         "bg" : {
           "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." : {
       "localizations" : {
         "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." : {
       "localizations" : {
         "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." : {
-      "extractionState" : "stale",
       "localizations" : {
         "bg" : {
           "stringUnit" : {

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

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

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

@@ -1,12 +1,12 @@
 //
-//  AlgorithmSubstepView.swift
+//  AlgorithmSettingsSubstepView.swift
 //  Trio
 //
 //  Created by Cengiz Deniz on 15.04.25.
 //
 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
     let substep: Substep
 
@@ -73,7 +73,8 @@ struct AlgorithmSubstepView<Substep: AlgorithmSubstepProtocol & RawRepresentable
                         setting: nil,
                         decimalValue: $decimalPlaceholder,
                         booleanValue: $state.enableSMBWithCOB,
-                        type: OnboardingInputSectionType.boolean
+                        type: OnboardingInputSectionType.boolean,
+                        disabled: state.enableSMBAlways
                     )
                 case .enableSMBWithTempTarget:
                     algorithmSettingsInput(
@@ -82,7 +83,8 @@ struct AlgorithmSubstepView<Substep: AlgorithmSubstepProtocol & RawRepresentable
                         setting: nil,
                         decimalValue: $decimalPlaceholder,
                         booleanValue: $state.enableSMBWithTempTarget,
-                        type: OnboardingInputSectionType.boolean
+                        type: OnboardingInputSectionType.boolean,
+                        disabled: state.enableSMBAlways
                     )
                 case .enableSMBAfterCarbs:
                     algorithmSettingsInput(
@@ -91,7 +93,8 @@ struct AlgorithmSubstepView<Substep: AlgorithmSubstepProtocol & RawRepresentable
                         setting: nil,
                         decimalValue: $decimalPlaceholder,
                         booleanValue: $state.enableSMBAfterCarbs,
-                        type: OnboardingInputSectionType.boolean
+                        type: OnboardingInputSectionType.boolean,
+                        disabled: state.enableSMBAlways
                     )
                 case .enableSMBWithHighGlucoseTarget:
                     algorithmSettingsInput(
@@ -100,7 +103,8 @@ struct AlgorithmSubstepView<Substep: AlgorithmSubstepProtocol & RawRepresentable
                         setting: nil,
                         decimalValue: $decimalPlaceholder,
                         booleanValue: $state.enableSMBWithHighGlucoseTarget,
-                        type: OnboardingInputSectionType.boolean
+                        type: OnboardingInputSectionType.boolean,
+                        disabled: state.enableSMBAlways
                     )
                     if state.enableSMBWithHighGlucoseTarget {
                         algorithmSettingsInput(
@@ -109,7 +113,8 @@ struct AlgorithmSubstepView<Substep: AlgorithmSubstepProtocol & RawRepresentable
                             setting: settingsProvider.settings.enableSMB_high_bg_target,
                             decimalValue: $state.highGlucoseTarget,
                             booleanValue: $booleanPlaceholder,
-                            type: OnboardingInputSectionType.decimal
+                            type: OnboardingInputSectionType.decimal,
+                            disabled: state.enableSMBAlways
                         )
                     }
                 case .allowSMBWithHighTempTarget:
@@ -211,7 +216,8 @@ struct AlgorithmSubstepView<Substep: AlgorithmSubstepProtocol & RawRepresentable
                 .padding(.horizontal)
                 .multilineTextAlignment(.leading)
         }
-        .onDisappear {
+        .onAppear {
+            // Ensure picker view is closed, when switching between setting steps
             shouldDisplayPicker = false
         }
     }
@@ -222,46 +228,58 @@ struct AlgorithmSubstepView<Substep: AlgorithmSubstepProtocol & RawRepresentable
         setting: PickerSetting?,
         decimalValue: Binding<Decimal>,
         booleanValue: Binding<Bool>,
-        type: OnboardingInputSectionType
+        type: OnboardingInputSectionType,
+        disabled: Bool = false /// parameter only relevant for `Enable SMB Always` dependent settings
     ) -> some View {
         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)
-                        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 {

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

@@ -206,9 +206,15 @@ enum AlgorithmSettingsSubstep: Int, CaseIterable, Identifiable {
             }
         case .enableSMBAlways:
             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(
-                    "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(
                     "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."
             )
         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:
             return String(localized: "Allow Trio to use Bluetooth to communicate with your insulin pump and CGM.")
         case .completed:

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

@@ -38,6 +38,11 @@ struct TherapySettingEditorView: View {
             .padding(.vertical, 5)
 
             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) {
                     Button {
                         selectedItemID = selectedItemID == item.id ? nil : item.id
@@ -116,6 +121,9 @@ struct TherapySettingEditorView: View {
             selectedItemID = nil
             validateTherapySettingItems()
         }
+        .onChange(of: items, { _, _ in
+            validateTherapySettingItems()
+        })
     }
 
     @ViewBuilder private func timeValuePickerRow(
@@ -185,12 +193,18 @@ struct TherapySettingEditorView: View {
 
     private func validateTherapySettingItems() {
         // 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)
         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 {
     case mmolLPerUnit
     case mgdLPerUnit