Procházet zdrojové kódy

Various additions and fixes
* Add conditional 'Got it' check boxes that are required to proceed to next step for long hints
* Add privacy policy check for App Diagnostics (policy with placeholder)
* Fix scroll behavior for long content views with no present progerss bar scrolling 'under' the devive status bar
* Extend ToggleStyles by optional tint

Deniz Cengiz před 1 rokem
rodič
revize
812e1e9abe

+ 4 - 4
Trio.xcodeproj/project.pbxproj

@@ -352,7 +352,7 @@
 		BD249DA12D42FD1200412DEB /* TDDSetup.swift in Sources */ = {isa = PBXBuildFile; fileRef = BD249DA02D42FD1000412DEB /* TDDSetup.swift */; };
 		BD249DA72D42FE4600412DEB /* Calendar+GlucoseStatsChart.swift in Sources */ = {isa = PBXBuildFile; fileRef = BD249DA62D42FE3800412DEB /* Calendar+GlucoseStatsChart.swift */; };
 		BD2B464E0745FBE7B79913F4 /* NightscoutConfigProvider.swift in Sources */ = {isa = PBXBuildFile; fileRef = 3BF768BD6264FF7D71D66767 /* NightscoutConfigProvider.swift */; };
-		BD2FF1A02AE29D43005D1C5D /* CheckboxToggleStyle.swift in Sources */ = {isa = PBXBuildFile; fileRef = BD2FF19F2AE29D43005D1C5D /* CheckboxToggleStyle.swift */; };
+		BD2FF1A02AE29D43005D1C5D /* ToggleStyles.swift in Sources */ = {isa = PBXBuildFile; fileRef = BD2FF19F2AE29D43005D1C5D /* ToggleStyles.swift */; };
 		BD3CC0722B0B89D50013189E /* MainChartView.swift in Sources */ = {isa = PBXBuildFile; fileRef = BD3CC0712B0B89D50013189E /* MainChartView.swift */; };
 		BD4064D12C4ED26900582F43 /* CoreDataObserver.swift in Sources */ = {isa = PBXBuildFile; fileRef = BD4064D02C4ED26900582F43 /* CoreDataObserver.swift */; };
 		BD432CA12D2F4E3600D1EB79 /* WatchMessageKeys.swift in Sources */ = {isa = PBXBuildFile; fileRef = BD432CA02D2F4E3300D1EB79 /* WatchMessageKeys.swift */; };
@@ -1143,7 +1143,7 @@
 		BD249D9E2D42FD0200412DEB /* StackedChartSetup.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = StackedChartSetup.swift; sourceTree = "<group>"; };
 		BD249DA02D42FD1000412DEB /* TDDSetup.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = TDDSetup.swift; sourceTree = "<group>"; };
 		BD249DA62D42FE3800412DEB /* Calendar+GlucoseStatsChart.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "Calendar+GlucoseStatsChart.swift"; sourceTree = "<group>"; };
-		BD2FF19F2AE29D43005D1C5D /* CheckboxToggleStyle.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = CheckboxToggleStyle.swift; sourceTree = "<group>"; };
+		BD2FF19F2AE29D43005D1C5D /* ToggleStyles.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ToggleStyles.swift; sourceTree = "<group>"; };
 		BD3CC0712B0B89D50013189E /* MainChartView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = MainChartView.swift; sourceTree = "<group>"; };
 		BD4064D02C4ED26900582F43 /* CoreDataObserver.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = CoreDataObserver.swift; sourceTree = "<group>"; };
 		BD432CA02D2F4E3300D1EB79 /* WatchMessageKeys.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = WatchMessageKeys.swift; sourceTree = "<group>"; };
@@ -2370,7 +2370,7 @@
 				FE66D16A291F74F8005D6F77 /* Bundle+Extensions.swift */,
 				FEFFA7A12929FE49007B8193 /* UIDevice+Extensions.swift */,
 				CEA4F62229BE10F70011ADF7 /* SavitzkyGolayFilter.swift */,
-				BD2FF19F2AE29D43005D1C5D /* CheckboxToggleStyle.swift */,
+				BD2FF19F2AE29D43005D1C5D /* ToggleStyles.swift */,
 				BD1661302B82ADAB00256551 /* CustomProgressView.swift */,
 				581516A32BCED84A00BF67D7 /* DebuggingIdentifiers.swift */,
 				DD1DB7CB2BECCA1F0048B367 /* BuildDetails.swift */,
@@ -4384,7 +4384,7 @@
 				CE82E02728E869DF00473A9C /* AlertEntry.swift in Sources */,
 				DD30786A2D42F94000DE0490 /* GarminDevice.swift in Sources */,
 				38E4451E274DB04600EC9A94 /* AppDelegate.swift in Sources */,
-				BD2FF1A02AE29D43005D1C5D /* CheckboxToggleStyle.swift in Sources */,
+				BD2FF1A02AE29D43005D1C5D /* ToggleStyles.swift in Sources */,
 				DDD163162C4C690300CD525A /* AdjustmentsDataFlow.swift in Sources */,
 				BDF34F932C10D0E100D51995 /* LiveActivityAttributes+Helper.swift in Sources */,
 				E0D4F80527513ECF00BDF1FE /* HealthKitSample.swift in Sources */,

+ 15 - 5
Trio/Sources/Helpers/CheckboxToggleStyle.swift

@@ -1,6 +1,6 @@
 import SwiftUI
 
-struct CheckboxToggleStyle: ToggleStyle {
+struct RadioButtonToggleStyle: ToggleStyle {
     func makeBody(configuration: Self.Configuration) -> some View {
         HStack {
             Circle()
@@ -22,22 +22,32 @@ struct CheckboxToggleStyle: ToggleStyle {
     }
 }
 
-struct Checkbox: ToggleStyle {
-    func makeBody(configuration: Self.Configuration) -> some View {
+struct CheckboxToggleStyle: ToggleStyle {
+    var tint = Color.primary
+
+    func makeBody(configuration: Configuration) -> some View {
         HStack {
             RoundedRectangle(cornerRadius: 5)
                 .stroke(lineWidth: 2)
-                .foregroundColor(.secondary)
+                .foregroundColor(Color.secondary)
                 .frame(width: 20, height: 20)
                 .overlay {
                     if configuration.isOn {
-                        Image(systemName: "checkmark").font(.body).fontWeight(.bold)
+                        Image(systemName: "checkmark")
+                            .font(.body)
+                            .fontWeight(.bold)
+                            .foregroundColor(tint)
                     }
                 }
                 .onTapGesture {
                     configuration.isOn.toggle()
                 }
+
             configuration.label
         }
+        .contentShape(Rectangle()) // make entire HStack tappable
+        .onTapGesture {
+            configuration.isOn.toggle()
+        }
     }
 }

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

@@ -102153,6 +102153,9 @@
         }
       }
     },
+    "Got it! I'm ready to continue." : {
+
+    },
     "Gradient Purple = As readings approach and exceed above target, they become more purple." : {
       "localizations" : {
         "bg" : {
@@ -107307,6 +107310,9 @@
     "https://triodocs.org/startup-guide" : {
 
     },
+    "I have read and accept the" : {
+
+    },
     "If \"Display IOB and COB\" is also enabled, \"IOB\" and \"COB\" will be replaced with the following emojis:" : {
       "localizations" : {
         "bg" : {
@@ -151595,6 +151601,9 @@
         }
       }
     },
+    "Placeholder until policy is final." : {
+
+    },
     "Play Alarm Sound" : {
       "extractionState" : "stale",
       "localizations" : {
@@ -153462,6 +153471,9 @@
         }
       }
     },
+    "Privacy Policy" : {
+
+    },
     "Proceed" : {
       "comment" : "Button Label to Proceed to Bolus on Watch",
       "localizations" : {

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

@@ -19,6 +19,11 @@ extension Onboarding {
         // MARK: - App Diagnostics
 
         var diagnosticsSharingOption: DiagnosticsSharingOption = .enabled
+        var hasAcceptedPrivacyPolicy: Bool = false
+
+        // MARK: - Important Startup Notes
+
+        var hasReadImportantStartupNotes: Bool = false
 
         // MARK: - Nightscout Setup
 
@@ -95,7 +100,9 @@ extension Onboarding {
         var maxCOB: Decimal = 120
         var minimumSafetyThreshold: Decimal = 60
 
-        // MARK: - Algorithm Settings Defaults
+        // MARK: - Algorithm Settings Defaults & State
+
+        var hasReadAlgorithmSetupInformation: Bool = false
 
         var autosensMin: Decimal = 0.7
         var autosensMax: Decimal = 1.2

+ 23 - 7
Trio/Sources/Modules/Onboarding/View/OnboardingRootView.swift

@@ -36,12 +36,19 @@ extension Onboarding {
             currentNightscoutSubstep == .importFromNightscout && state.nightscoutImportOption == .noSelection
         }
 
+        // Next button conditional
         private var shouldDisableNextButton: Bool {
-            (currentStep == .nightscout && didSelectNightscoutSetupOption)
+            (currentStep == .startupGuide && !state.hasReadImportantStartupNotes)
+                ||
+                (currentStep == .diagnostics && state.diagnosticsSharingOption == .enabled && !state.hasAcceptedPrivacyPolicy)
+                ||
+                (currentStep == .nightscout && didSelectNightscoutSetupOption)
                 ||
                 (currentStep == .nightscout && hasValidNightscoutConnection)
                 ||
                 (currentStep == .nightscout && didSelectNightscoutImportOption)
+                ||
+                (currentStep == .algorithmSettings && !state.hasReadAlgorithmSetupInformation)
         }
 
         var body: some View {
@@ -80,6 +87,9 @@ extension Onboarding {
                                 nightscoutSetupOption: state.nightscoutSetupOption
                             )
                             .padding(.top)
+                        } else {
+                            // avoid letting content scroll beneath the status bar / dynamic island for content views with no progress bar (which adds top spacing)
+                            Color.clear.frame(height: 1)
                         }
 
                         OnboardingStepContent(
@@ -256,7 +266,7 @@ struct OnboardingStepContent: View {
                         case .welcome:
                             WelcomeStepView()
                         case .startupGuide:
-                            StartupGuideStepView()
+                            StartupGuideStepView(state: state)
                         case .overview:
                             OverviewStepView()
                         case .diagnostics:
@@ -307,6 +317,12 @@ struct OnboardingStepContent: View {
             .onChange(of: currentStep) { _, _ in scrollProxy.scrollTo("top", anchor: .top) }
             .onChange(of: currentNightscoutSubstep) { _, _ in scrollProxy.scrollTo("top", anchor: .top) }
             .onChange(of: currentDeliverySubstep) { _, _ in scrollProxy.scrollTo("top", anchor: .top) }
+            .safeAreaInset(edge: .top) {
+                // avoid letting content scroll beneath the status bar / dynamic island for content views with not progress bar (which adds top spacing)
+                if currentStep == .startupGuide || currentStep == .completed {
+                    Color.clear.frame(height: 0)
+                }
+            }
         }
     }
 }
@@ -432,11 +448,6 @@ struct OnboardingNavigationButtons: View {
 
     private func handleNextNavigation() {
         switch currentStep {
-        case .completed:
-            state.saveOnboardingData()
-            onboardingManager.completeOnboarding()
-            Foundation.NotificationCenter.default.post(name: .onboardingCompleted, object: nil)
-
         case .nightscout:
             if currentNightscoutSubstep != .importFromNightscout {
                 if currentNightscoutSubstep == .setupSelection,
@@ -489,6 +500,11 @@ struct OnboardingNavigationButtons: View {
                 currentTargetBehaviorSubstep = .highTempTargetRaisesSensitivity
             }
 
+        case .completed:
+            state.saveOnboardingData()
+            onboardingManager.completeOnboarding()
+            Foundation.NotificationCenter.default.post(name: .onboardingCompleted, object: nil)
+
         default:
             if let next = currentStep.next {
                 currentStep = next

+ 8 - 0
Trio/Sources/Modules/Onboarding/View/OnboardingSteps/AlgorithmSettings/AlgorithmSettingsStepView.swift

@@ -57,6 +57,14 @@ struct AlgorithmSettingsStepView: View {
                 )
             }
             .padding(.horizontal)
+
+            Divider()
+
+            Toggle(isOn: $state.hasReadAlgorithmSetupInformation) {
+                Text("Got it! I'm ready to continue.").padding(.leading, 6).bold()
+            }
+            .toggleStyle(CheckboxToggleStyle(tint: Color.blue))
+            .padding(.horizontal)
         }
     }
 }

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

@@ -3,6 +3,8 @@ import SwiftUI
 struct DiagnosticsStepView: View {
     @Bindable var state: Onboarding.StateModel
 
+    @State private var shouldPresentPrivacyPolicy: Bool = false
+
     var body: some View {
         VStack(alignment: .leading, spacing: 20) {
             Text("If you prefer not to share this anonymized data, you can opt-out of data sharing.")
@@ -31,6 +33,23 @@ struct DiagnosticsStepView: View {
                 .buttonStyle(.plain)
             }
 
+            Toggle(isOn: $state.hasAcceptedPrivacyPolicy) {
+                HStack {
+                    Text("I have read and accept the")
+                    Button("Privacy Policy") {
+                        shouldPresentPrivacyPolicy = true
+                    }
+                    .foregroundColor(.accentColor)
+                    .underline()
+                }
+                .font(.footnote)
+                .bold()
+            }
+            .toggleStyle(CheckboxToggleStyle(tint: Color.accentColor))
+            .padding(.horizontal)
+            .disabled(state.diagnosticsSharingOption == .disabled)
+            .opacity(state.diagnosticsSharingOption == .disabled ? 0.35 : 1)
+
             VStack(alignment: .leading, spacing: 8) {
                 Text("Why does Trio collect this data?").bold()
                 VStack(alignment: .leading, spacing: 4) {
@@ -54,5 +73,15 @@ struct DiagnosticsStepView: View {
             .font(.footnote)
             .foregroundStyle(Color.secondary)
         }
+        .sheet(isPresented: $shouldPresentPrivacyPolicy) {
+            PrivacyPolicyView()
+        }
+    }
+}
+
+struct PrivacyPolicyView: View {
+    var body: some View {
+        Text("Privacy Policy").font(.headline)
+        Text("Placeholder until policy is final.")
     }
 }

+ 10 - 0
Trio/Sources/Modules/Onboarding/View/OnboardingSteps/StartupGuideStepView.swift

@@ -7,6 +7,8 @@
 import SwiftUI
 
 struct StartupGuideStepView: View {
+    @Bindable var state: Onboarding.StateModel
+
     @Environment(\.openURL) var openURL
 
     var body: some View {
@@ -54,6 +56,14 @@ struct StartupGuideStepView: View {
             }
             .multilineTextAlignment(.leading)
             .padding(.horizontal)
+
+            Divider()
+
+            Toggle(isOn: $state.hasReadImportantStartupNotes) {
+                Text("Got it! I'm ready to continue.").padding(.leading, 6).bold()
+            }
+            .toggleStyle(CheckboxToggleStyle(tint: Color.blue))
+            .padding(.horizontal)
         }
     }
 }

+ 3 - 3
Trio/Sources/Modules/Treatments/View/TreatmentsRootView.swift

@@ -249,7 +249,7 @@ extension Treatments {
                                         Toggle(isOn: $state.useFattyMealCorrectionFactor) {
                                             Text("Fatty Meal")
                                         }
-                                        .toggleStyle(CheckboxToggleStyle())
+                                        .toggleStyle(RadioButtonToggleStyle())
                                         .font(.footnote)
                                         .onChange(of: state.useFattyMealCorrectionFactor) {
                                             Task {
@@ -264,7 +264,7 @@ extension Treatments {
                                         Toggle(isOn: $state.useSuperBolus) {
                                             Text("Super Bolus")
                                         }
-                                        .toggleStyle(CheckboxToggleStyle())
+                                        .toggleStyle(RadioButtonToggleStyle())
                                         .font(.footnote)
                                         .onChange(of: state.useSuperBolus) {
                                             Task {
@@ -336,7 +336,7 @@ extension Treatments {
                             HStack {
                                 Text("External Insulin")
                                 Spacer()
-                                Toggle("", isOn: $state.externalInsulin).toggleStyle(Checkbox())
+                                Toggle("", isOn: $state.externalInsulin).toggleStyle(CheckboxToggleStyle())
                             }
                         }.listRowBackground(Color.chart)