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

Cleanup and test feedback fixes:
* Disable Next button when NS connection is not established
* Ensure substep views properly scroll to top on navigating backwards and forwards
* Rename various variables for clarity
* Refactor boolean conditionals for next button disablement for clarity

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

+ 4 - 4
Trio.xcodeproj/project.pbxproj

@@ -555,7 +555,7 @@
 		DD3F1F872D9DDB1200DCE7B3 /* AnimationPlaceholder.swift in Sources */ = {isa = PBXBuildFile; fileRef = DD3F1F862D9DDB1200DCE7B3 /* AnimationPlaceholder.swift */; };
 		DD3F1F892D9E078D00DCE7B3 /* TherapySettingEditorView.swift in Sources */ = {isa = PBXBuildFile; fileRef = DD3F1F882D9E078300DCE7B3 /* TherapySettingEditorView.swift */; };
 		DD3F1F8B2D9E08B600DCE7B3 /* NightscoutLoginStepView.swift in Sources */ = {isa = PBXBuildFile; fileRef = DD3F1F8A2D9E08B200DCE7B3 /* NightscoutLoginStepView.swift */; };
-		DD3F1F8D2D9E0E0600DCE7B3 /* NightscoutStepView.swift in Sources */ = {isa = PBXBuildFile; fileRef = DD3F1F8C2D9E0E0000DCE7B3 /* NightscoutStepView.swift */; };
+		DD3F1F8D2D9E0E0600DCE7B3 /* NightscoutSetupStepView.swift in Sources */ = {isa = PBXBuildFile; fileRef = DD3F1F8C2D9E0E0000DCE7B3 /* NightscoutSetupStepView.swift */; };
 		DD3F1F902D9E153F00DCE7B3 /* NightscoutImportStepView.swift in Sources */ = {isa = PBXBuildFile; fileRef = DD3F1F8F2D9E153A00DCE7B3 /* NightscoutImportStepView.swift */; };
 		DD498F2B2D692BEA00AAEA30 /* Localizable.xcstrings in Resources */ = {isa = PBXBuildFile; fileRef = 8A9134292D63D9A1007F8874 /* Localizable.xcstrings */; };
 		DD498F2C2D692BEA00AAEA30 /* Localizable.xcstrings in Resources */ = {isa = PBXBuildFile; fileRef = 8A9134292D63D9A1007F8874 /* Localizable.xcstrings */; };
@@ -1340,7 +1340,7 @@
 		DD3F1F862D9DDB1200DCE7B3 /* AnimationPlaceholder.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = AnimationPlaceholder.swift; sourceTree = "<group>"; };
 		DD3F1F882D9E078300DCE7B3 /* TherapySettingEditorView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = TherapySettingEditorView.swift; sourceTree = "<group>"; };
 		DD3F1F8A2D9E08B200DCE7B3 /* NightscoutLoginStepView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = NightscoutLoginStepView.swift; sourceTree = "<group>"; };
-		DD3F1F8C2D9E0E0000DCE7B3 /* NightscoutStepView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = NightscoutStepView.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>"; };
 		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>"; };
@@ -3213,7 +3213,7 @@
 			isa = PBXGroup;
 			children = (
 				DD3F1F8F2D9E153A00DCE7B3 /* NightscoutImportStepView.swift */,
-				DD3F1F8C2D9E0E0000DCE7B3 /* NightscoutStepView.swift */,
+				DD3F1F8C2D9E0E0000DCE7B3 /* NightscoutSetupStepView.swift */,
 				DD3F1F8A2D9E08B200DCE7B3 /* NightscoutLoginStepView.swift */,
 			);
 			path = Nightscout;
@@ -3886,7 +3886,7 @@
 				58645BA72CA2D390008AFCE7 /* ChartAxisSetup.swift in Sources */,
 				38D0B3B625EBE24900CB6E88 /* Battery.swift in Sources */,
 				38C4D33725E9A1A300D30B77 /* DispatchQueue+Extensions.swift in Sources */,
-				DD3F1F8D2D9E0E0600DCE7B3 /* NightscoutStepView.swift in Sources */,
+				DD3F1F8D2D9E0E0600DCE7B3 /* NightscoutSetupStepView.swift in Sources */,
 				F90692CF274B999A0037068D /* HealthKitDataFlow.swift in Sources */,
 				CE7CA3552A064973004BE681 /* ListStateIntent.swift in Sources */,
 				BDF530D82B40F8AC002CAF43 /* LockScreenView.swift in Sources */,

+ 15 - 15
Trio/Sources/Modules/Onboarding/OnboardingStateModel+Nightscout.swift

@@ -6,41 +6,41 @@ import SwiftUI
 
 extension Onboarding.StateModel {
     func connectToNightscout() {
-        if let CheckURL = url.last, CheckURL == "/" {
-            let fixedURL = url.dropLast()
-            url = String(fixedURL)
+        if let CheckURL = nightscoutUrl.last, CheckURL == "/" {
+            let fixedURL = nightscoutUrl.dropLast()
+            nightscoutUrl = String(fixedURL)
         }
 
-        guard let url = URL(string: url), self.url.hasPrefix("https://") else {
-            message = "Invalid URL"
-            isValidURL = false
+        guard let nightscoutUrl = URL(string: nightscoutUrl), self.nightscoutUrl.hasPrefix("https://") else {
+            nightscoutResponseMessage = "Invalid URL"
+            isValidNightscoutURL = false
             return
         }
 
-        connecting = true
-        isValidURL = true
-        message = ""
+        isConnectingToNS = true
+        isValidNightscoutURL = true
+        nightscoutResponseMessage = ""
 
-        NightscoutAPI(url: url, secret: secret).checkConnection()
+        NightscoutAPI(url: nightscoutUrl, secret: nightscoutSecret).checkConnection()
             .receive(on: DispatchQueue.main)
             .sink { completion in
                 switch completion {
                 case .finished: break
                 case let .failure(error):
-                    self.message = "Error: \(error.localizedDescription)"
+                    self.nightscoutResponseMessage = "Error: \(error.localizedDescription)"
                 }
                 DispatchQueue.main.asyncAfter(deadline: .now() + 1) {
-                    self.connecting = false
+                    self.isConnectingToNS = false
                 }
             } receiveValue: {
-                self.keychain.setValue(self.url, forKey: NightscoutConfig.Config.urlKey)
-                self.keychain.setValue(self.secret, forKey: NightscoutConfig.Config.secretKey)
+                self.keychain.setValue(self.nightscoutUrl, forKey: NightscoutConfig.Config.urlKey)
+                self.keychain.setValue(self.nightscoutSecret, forKey: NightscoutConfig.Config.secretKey)
                 self.isConnectedToNS = true
             }
             .store(in: &lifetime)
     }
 
-    private var nightscoutAPI: NightscoutAPI? {
+    var nightscoutAPI: NightscoutAPI? {
         guard let urlString = keychain.getValue(String.self, forKey: NightscoutConfig.Config.urlKey),
               let url = URL(string: urlString),
               let secret = keychain.getValue(String.self, forKey: NightscoutConfig.Config.secretKey)

+ 13 - 10
Trio/Sources/Modules/Onboarding/OnboardingStateModel.swift

@@ -23,19 +23,19 @@ extension Onboarding {
 
         var nightscoutSetupOption: NightscoutSetupOption = .noSelection
         var nightscoutImportOption: NightscoutImportOption = .noSelection
-        var url = ""
-        var secret = ""
-        var message = ""
-        var isValidURL: Bool = false
-        var connecting: Bool = false
+        var nightscoutUrl = ""
+        var nightscoutSecret = ""
+        var nightscoutResponseMessage = ""
+        var isValidNightscoutURL: Bool = false
+        var isConnectingToNS: Bool = false
         var isConnectedToNS: Bool = false
         var nightscoutImportErrors: [String] = []
         var nightscoutImportStatus: ImportStatus = .finished
 
-        // MARK: - Units and Pump Model
+        // MARK: - Units and Pump Omboarding Option
 
         var units: GlucoseUnits = .mgdL
-        var pumpModel: PumpOptionsForOnboardingUnits = .omnipodDash
+        var pumpOptionForOnboardingUnits: PumpOptionForOnboardingUnits = .omnipodDash
 
         // MARK: - Time Values (shared)
 
@@ -52,7 +52,7 @@ extension Onboarding {
         // MARK: - Basal Profile
 
         var basalRatePickerSetting: PickerSetting {
-            switch pumpModel {
+            switch pumpOptionForOnboardingUnits {
             case .dana,
                  .minimed:
                 return PickerSetting(value: 0.1, step: 0.1, min: 0.1, max: 30, type: .insulinUnit)
@@ -96,8 +96,11 @@ extension Onboarding {
 
         override func subscribe() {
             // Keychain items are not removed, even after uninstalling the app. Attempt to read them initially.
-            url = keychain.getValue(String.self, forKey: NightscoutConfig.Config.urlKey) ?? ""
-            secret = keychain.getValue(String.self, forKey: NightscoutConfig.Config.secretKey) ?? ""
+            nightscoutUrl = keychain.getValue(String.self, forKey: NightscoutConfig.Config.urlKey) ?? ""
+            nightscoutSecret = keychain.getValue(String.self, forKey: NightscoutConfig.Config.secretKey) ?? ""
+            isConnectedToNS = false
+            isConnectingToNS = false
+            isValidNightscoutURL = false
         }
 
         // MARK: - Helpers

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

@@ -82,8 +82,8 @@ struct CarbRatioStepView: View {
                                         .carbRatioRateValues[state.carbRatioItems.first!.rateIndex] as NSNumber
                                 )
                             Text(
-                                "45g ÷ \(formatter.string(from: state.carbRatioRateValues[state.carbRatioItems.first!.rateIndex] as NSNumber) ?? "--") = \(String(format: "%.1f", insulinNeeded))" +
-                                    " " + String(localized: "U")
+                                "45 \(String(localized: "g", comment: "Gram abbreviation")) / \(formatter.string(from: state.carbRatioRateValues[state.carbRatioItems.first!.rateIndex] as NSNumber) ?? "--")  = \(String(format: "%.1f", insulinNeeded))" +
+                                    " " + String(localized: "U", comment: "Insulin unit abbreviation")
                             )
                             .font(.system(.body, design: .monospaced))
                             .foregroundColor(.orange)

+ 3 - 2
Trio/Sources/Modules/Onboarding/View/OnboardingSteps/DeliveryLimitsStepView.swift

@@ -56,6 +56,7 @@ struct DeliveryLimitsStepView: View {
                 .font(.footnote)
                 .foregroundStyle(.secondary)
                 .padding(.horizontal)
+                .multilineTextAlignment(.leading)
         }
     }
 
@@ -100,8 +101,8 @@ struct DeliveryLimitsStepView: View {
         case .maxCOB:
             return Text("\(decimalValue) \(String(localized: "g", comment: "Gram abbreviation"))")
         case .minimumSafetyThreshold:
-            let value = state.units == .mgdL ? decimalValue : decimalValue.asMmolL
-            return Text("\(decimalValue) \(state.units.rawValue)")
+            let optionallyParsedValue = state.units == .mgdL ? decimalValue : decimalValue.asMmolL
+            return Text("\(optionallyParsedValue) \(state.units.rawValue)")
         }
     }
 }

+ 2 - 0
Trio/Sources/Modules/Onboarding/View/OnboardingSteps/DiagnosticsStepView.swift

@@ -8,6 +8,7 @@ struct DiagnosticsStepView: View {
             Text("If you prefer not to share this anonymized data, you can opt-out of data sharing.")
                 .font(.headline)
                 .padding(.horizontal)
+                .multilineTextAlignment(.leading)
 
             ForEach(DiagnostisSharingOption.allCases, id: \.self) { option in
                 Button(action: {
@@ -42,6 +43,7 @@ struct DiagnosticsStepView: View {
                     "Trio diagnostic data is sent to a Google Firebase Crashlytics project, which is securely maintained and accessed only by the Trio team."
                 )
             }
+            .multilineTextAlignment(.leading)
             .padding(.horizontal)
             .font(.footnote)
             .foregroundStyle(Color.secondary)

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

@@ -91,8 +91,8 @@ struct InsulinSensitivityStepView: View {
                             .padding(.horizontal)
 
                             Text(
-                                "\(numberFormatter.string(from: aboveTarget as NSNumber) ?? "--") ÷ \(numberFormatter.string(from: isfValue as NSNumber) ?? "--") = \(String(format: "%.1f", Double(insulinNeeded)))" +
-                                    " " + String(localized: "U")
+                                "\(numberFormatter.string(from: aboveTarget as NSNumber) ?? "--") / \(numberFormatter.string(from: isfValue as NSNumber) ?? "--") = \(String(format: "%.1f", Double(insulinNeeded)))" +
+                                    " " + String(localized: "U", comment: "Insulin unit abbreviation")
                             )
                             .font(.system(.body, design: .monospaced))
                             .foregroundColor(.red)

+ 2 - 0
Trio/Sources/Modules/Onboarding/View/OnboardingSteps/Nightscout/NightscoutImportStepView.swift

@@ -27,6 +27,7 @@ struct NightscoutImportStepView: View {
                     )
                     .font(.headline)
                     .padding(.horizontal)
+                    .multilineTextAlignment(.leading)
 
                     ForEach([NightscoutImportOption.useImport, NightscoutImportOption.skipImport], id: \.self) { option in
                         Button(action: {
@@ -61,6 +62,7 @@ struct NightscoutImportStepView: View {
                     .padding(.horizontal)
                     .font(.footnote)
                     .foregroundStyle(Color.secondary)
+                    .multilineTextAlignment(.leading)
                 }
             }
         }

+ 8 - 8
Trio/Sources/Modules/Onboarding/View/OnboardingSteps/Nightscout/NightscoutLoginStepView.swift

@@ -10,12 +10,12 @@ struct NightscoutLoginStepView: View {
                 .padding(.horizontal)
 
             HStack {
-                TextField("URL", text: $state.url)
+                TextField("URL", text: $state.nightscoutUrl)
                     .disableAutocorrection(true)
                     .textContentType(.URL)
                     .autocapitalization(.none)
                     .keyboardType(.URL)
-                if state.message.isNotEmpty && !state.isValidURL {
+                if state.nightscoutResponseMessage.isNotEmpty && !state.isValidNightscoutURL {
                     Image(systemName: "exclamationmark.triangle.fill")
                         .foregroundStyle(.orange)
                 }
@@ -24,7 +24,7 @@ struct NightscoutLoginStepView: View {
                 .cornerRadius(10)
 
             HStack {
-                SecureField("API secret", text: $state.secret)
+                SecureField("API secret", text: $state.nightscoutSecret)
                     .disableAutocorrection(true)
                     .autocapitalization(.none)
                     .textContentType(.password)
@@ -41,21 +41,21 @@ struct NightscoutLoginStepView: View {
                 state.connectToNightscout()
             }) {
                 HStack {
-                    if state.connecting {
+                    if state.isConnectingToNS {
                         ProgressView().padding(.trailing, 10)
                     }
-                    Text(state.connecting ? "Connecting..." : "Connect to Nightscout")
+                    Text(state.isConnectingToNS ? "Connecting..." : "Connect to Nightscout")
                         .bold()
                 }
                 .frame(maxWidth: .infinity, alignment: .center)
                 .padding(.vertical, 8)
             }
-            .disabled(state.isConnectedToNS || state.url.isEmpty || state.secret.isEmpty)
+            .disabled(state.isConnectedToNS || state.nightscoutUrl.isEmpty || state.nightscoutSecret.isEmpty)
             .buttonStyle(.borderedProminent)
 
-            if state.message.isNotEmpty {
+            if state.nightscoutResponseMessage.isNotEmpty {
                 VStack(alignment: .center) {
-                    Text(state.message)
+                    Text(state.nightscoutResponseMessage)
                         .font(.subheadline)
                         .foregroundStyle(Color.orange)
                 }

+ 5 - 1
Trio/Sources/Modules/Onboarding/View/OnboardingSteps/Nightscout/NightscoutStepView.swift

@@ -1,6 +1,6 @@
 import SwiftUI
 
-struct NightscoutStepView: View {
+struct NightscoutSetupStepView: View {
     @Bindable var state: Onboarding.StateModel
 
     var body: some View {
@@ -8,6 +8,7 @@ struct NightscoutStepView: View {
             Text("Nightscout use is entirely optional. You can also setup Nightscout at a later time.")
                 .font(.headline)
                 .padding(.horizontal)
+                .multilineTextAlignment(.leading)
 
             ForEach([NightscoutSetupOption.setupNightscout, NightscoutSetupOption.skipNightscoutSetup], id: \.self) { option in
                 Button(action: {
@@ -30,5 +31,8 @@ struct NightscoutStepView: View {
                 .buttonStyle(.plain)
             }
         }
+        .onAppear {
+            debug(.nightscout, "CURRENT NS CONNECTION STATE: isConnectedToNS=\(state.isConnectedToNS)")
+        }
     }
 }

+ 3 - 2
Trio/Sources/Modules/Onboarding/View/OnboardingSteps/UnitSelectionStepView.swift

@@ -25,8 +25,8 @@ struct UnitSelectionStepView: View {
             HStack {
                 Text("Pump Model")
                 Spacer()
-                Picker("Pump Model", selection: $state.pumpModel) {
-                    ForEach(PumpOptionsForOnboardingUnits.allCases, id: \.self) { pumpModel in
+                Picker("Pump Model", selection: $state.pumpOptionForOnboardingUnits) {
+                    ForEach(PumpOptionForOnboardingUnits.allCases, id: \.self) { pumpModel in
                         Text(pumpModel.displayName).tag(pumpModel)
                     }
                 }
@@ -41,6 +41,7 @@ struct UnitSelectionStepView: View {
             .padding(.horizontal)
             .font(.footnote)
             .foregroundStyle(Color.secondary)
+            .multilineTextAlignment(.leading)
         }
     }
 }

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

@@ -284,7 +284,7 @@ enum DiagnostisSharingOption: String, Equatable, CaseIterable, Identifiable {
     }
 }
 
-enum PumpOptionsForOnboardingUnits: String, Equatable, CaseIterable, Identifiable {
+enum PumpOptionForOnboardingUnits: String, Equatable, CaseIterable, Identifiable {
     case minimed
     case omnipodEros
     case omnipodDash
@@ -295,7 +295,7 @@ enum PumpOptionsForOnboardingUnits: String, Equatable, CaseIterable, Identifiabl
     var displayName: String {
         switch self {
         case .minimed:
-            return "Medtronic 5xx / 7xx"
+            return "Medtronic"
         case .omnipodEros:
             return "Omnipod Eros"
         case .omnipodDash:

+ 33 - 11
Trio/Sources/Modules/Onboarding/View/OnboardingView.swift

@@ -16,17 +16,26 @@ extension Onboarding {
         @State private var animationOpacity: Double = 0
         @State private var isAnimating = false
 
+        // Conditional button states for Nightscout substeps
+        private var didSelectNightscoutSetupOption: Bool {
+            currentNightscoutSubstep == .setupSelection && state
+                .nightscoutSetupOption == .noSelection
+        }
+
+        private var hasValidNightscoutConnection: Bool {
+            currentNightscoutSubstep == .connectToNightscout && !state.isConnectedToNS
+        }
+
+        private var didSelectNightscoutImportOption: Bool {
+            currentNightscoutSubstep == .importFromNightscout && state.nightscoutImportOption == .noSelection
+        }
+
         private var shouldDisableNextButton: Bool {
-            currentStep == .nightscout &&
-                (
-                    currentNightscoutSubstep == .setupSelection && state
-                        .nightscoutSetupOption == .noSelection
-                ) ||
-                (
-                    currentNightscoutSubstep == .connectToNightscout && state.url.isEmpty && !state
-                        .isValidURL && state.secret.isEmpty
-                )
-                || (currentNightscoutSubstep == .importFromNightscout && state.nightscoutImportOption == .noSelection)
+            (currentStep == .nightscout && didSelectNightscoutSetupOption)
+                ||
+                (currentStep == .nightscout && hasValidNightscoutConnection)
+                ||
+                (currentStep == .nightscout && didSelectNightscoutImportOption)
         }
 
         var body: some View {
@@ -126,7 +135,7 @@ extension Onboarding {
                                         case .nightscout:
                                             switch currentNightscoutSubstep {
                                             case .setupSelection:
-                                                NightscoutStepView(state: state)
+                                                NightscoutSetupStepView(state: state)
                                             case .connectToNightscout:
                                                 NightscoutLoginStepView(state: state)
                                             case .importFromNightscout:
@@ -159,6 +168,16 @@ extension Onboarding {
                                     scrollProxy.scrollTo("top", anchor: .top)
                                 }
                             }
+                            .onChange(of: currentNightscoutSubstep) { _, _ in
+                                withAnimation {
+                                    scrollProxy.scrollTo("top", anchor: .top)
+                                }
+                            }
+                            .onChange(of: currentDeliverySubstep) { _, _ in
+                                withAnimation {
+                                    scrollProxy.scrollTo("top", anchor: .top)
+                                }
+                            }
                         }
 
                         Spacer()
@@ -287,6 +306,9 @@ extension Onboarding {
                 }
             }
             .onAppear(perform: configureView)
+            .onAppear {
+                debug(.nightscout, "CURRENT NS CONNECTION STATE: isConnectedToNS=\(state.isConnectedToNS)")
+            }
         }
     }
 }