Browse Source

Add interim chapter completed steps

Deniz Cengiz 1 year ago
parent
commit
57d0a5e1b6

+ 150 - 112
Trio/Sources/Modules/Onboarding/View/OnboardingRootView.swift

@@ -11,6 +11,8 @@ extension Onboarding {
 
         // Step management
         @State private var currentChapter: OnboardingChapter = .prepareTrio
+        @State private var showingChapterCompletion: OnboardingChapter? = nil
+
         @State private var currentStep: OnboardingStep = .welcome
         @State private var currentStartupSubstep: StartupSubstep = .startupGuide
         @State private var currentNightscoutSubstep: NightscoutSubstep = .setupSelection
@@ -92,6 +94,7 @@ extension Onboarding {
                             // Progress bar
                             OnboardingProgressBar(
                                 currentChapter: currentChapter,
+                                shouldDisplayChapterTitle: showingChapterCompletion == nil,
                                 currentStep: currentStep,
                                 currentSubstep: {
                                     switch currentStep {
@@ -122,6 +125,7 @@ extension Onboarding {
 
                         OnboardingStepContent(
                             currentStep: $currentStep,
+                            showingChapterCompletion: $showingChapterCompletion,
                             currentStartupSubstep: $currentStartupSubstep,
                             currentNightscoutSubstep: $currentNightscoutSubstep,
                             currentDeliverySubstep: $currentDeliverySubstep,
@@ -137,6 +141,7 @@ extension Onboarding {
 
                         OnboardingNavigationButtons(
                             currentStep: $currentStep,
+                            showingChapterCompletion: $showingChapterCompletion,
                             currentStartupSubstep: $currentStartupSubstep,
                             currentNightscoutSubstep: $currentNightscoutSubstep,
                             currentDeliverySubstep: $currentDeliverySubstep,
@@ -178,6 +183,7 @@ extension Onboarding {
 /// A progress bar that shows the user's progress through the onboarding process.
 struct OnboardingProgressBar: View {
     let currentChapter: OnboardingChapter
+    let shouldDisplayChapterTitle: Bool
     let currentStep: OnboardingStep
     let currentSubstep: Int?
     let stepsWithSubsteps: [OnboardingStep: Int]
@@ -185,10 +191,14 @@ struct OnboardingProgressBar: View {
 
     private let capsuleSize = CGFloat(UIFont.preferredFont(forTextStyle: .subheadline).pointSize) * 1.3
 
+    private var shouldShowCurrentChapter: Bool {
+        shouldDisplayChapterTitle && currentStep != .overview && currentStep != .completed
+    }
+
     var body: some View {
         VStack(alignment: .leading, spacing: 10) {
-            // only show this for the actual chapters, not the overview of chapters
-            if currentStep != .overview {
+            // only show this for the actual chapters, not the overview of chapters or completed view
+            if shouldShowCurrentChapter {
                 HStack(spacing: CGFloat(UIFont.preferredFont(forTextStyle: .subheadline).pointSize)) {
                     Text("\(currentChapter.rawValue + 1)")
                         .font(.subheadline)
@@ -271,6 +281,7 @@ struct OnboardingProgressBar: View {
 
 struct OnboardingStepContent: View {
     @Binding var currentStep: OnboardingStep
+    @Binding var showingChapterCompletion: OnboardingChapter?
     @Binding var currentStartupSubstep: StartupSubstep
     @Binding var currentNightscoutSubstep: NightscoutSubstep
     @Binding var currentDeliverySubstep: DeliveryLimitSubstep
@@ -287,118 +298,84 @@ struct OnboardingStepContent: View {
                 VStack(alignment: .leading, spacing: 20) {
                     Color.clear.frame(height: 0).id("top")
 
-                    if currentStep != .welcome && currentStep != .completed {
-                        HStack {
-                            if currentStep == .nightscout {
-                                Image(currentStep.iconName)
-                                    .resizable()
-                                    .scaledToFit()
-                                    .frame(width: 60, height: 60)
-                            } else if currentStep == .bluetooth {
-                                Image(currentStep.iconName)
-                                    .font(.system(size: 40))
-                                    .foregroundColor(currentStep.accentColor)
-                                    .frame(width: 60, height: 60)
-                                    .background(
-                                        Circle()
-                                            .fill(currentStep.accentColor.opacity(0.2))
-                                    )
-                            } else {
-                                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(.title)
-                                    .fontWeight(.bold)
-                                    .foregroundColor(.primary)
-
-                                Text(currentStep.description)
-                                    .font(.subheadline)
-                                    .foregroundColor(.secondary)
-                                    .fixedSize(horizontal: false, vertical: true)
-                            }
-                        }
-                        .padding(.horizontal)
+                    if currentStep != .welcome, currentStep != .completed, showingChapterCompletion == nil {
+                        contentHeader
                     }
 
-                    Group {
-                        switch currentStep {
-                        case .welcome:
-                            WelcomeStepView()
-                        case .startupInfo:
-                            switch currentStartupSubstep {
-                            case .startupGuide:
-                                StartupGuideStepView(state: state)
-                            case .returningUser:
-                                StartupReturningUserStepView(state: state)
-                            case .forceCloseWarning:
-                                StartupForceCloseWarningStepView(state: state)
-                            }
-                        case .overview:
-                            OverviewStepView()
-                        case .diagnostics:
-                            DiagnosticsStepView(state: state)
-                        case .nightscout:
-                            switch currentNightscoutSubstep {
-                            case .setupSelection:
-                                NightscoutSetupStepView(state: state)
-                            case .connectToNightscout:
-                                NightscoutLoginStepView(state: state)
-                            case .importFromNightscout:
-                                NightscoutImportStepView(state: state)
-                            }
-                        case .unitSelection:
-                            UnitSelectionStepView(state: state)
-                        case .glucoseTarget:
-                            GlucoseTargetStepView(state: state)
-                        case .basalRates:
-                            BasalProfileStepView(state: state)
-                        case .carbRatio:
-                            CarbRatioStepView(state: state)
-                        case .insulinSensitivity:
-                            InsulinSensitivityStepView(state: state)
-                        case .deliveryLimits:
-                            DeliveryLimitsStepView(state: state, substep: currentDeliverySubstep)
-                        case .algorithmSettings:
-                            switch currentAlgorithmSettingsOverviewSubstep {
-                            case .contents:
-                                AlgorithmSettingsContentsStepView(state: state)
-                            case .importantNotes:
-                                AlgorithmSettingsImportantNotesStepView(state: state)
+                    if let chapter = showingChapterCompletion {
+                        CompletedStepView(isOnboardingCompleted: false, currentChapter: chapter)
+                    } else {
+                        Group {
+                            switch currentStep {
+                            case .welcome:
+                                WelcomeStepView()
+                            case .startupInfo:
+                                switch currentStartupSubstep {
+                                case .startupGuide:
+                                    StartupGuideStepView(state: state)
+                                case .returningUser:
+                                    StartupReturningUserStepView(state: state)
+                                case .forceCloseWarning:
+                                    StartupForceCloseWarningStepView(state: state)
+                                }
+                            case .overview:
+                                OverviewStepView()
+                            case .diagnostics:
+                                DiagnosticsStepView(state: state)
+                            case .nightscout:
+                                switch currentNightscoutSubstep {
+                                case .setupSelection:
+                                    NightscoutSetupStepView(state: state)
+                                case .connectToNightscout:
+                                    NightscoutLoginStepView(state: state)
+                                case .importFromNightscout:
+                                    NightscoutImportStepView(state: state)
+                                }
+                            case .unitSelection:
+                                UnitSelectionStepView(state: state)
+                            case .glucoseTarget:
+                                GlucoseTargetStepView(state: state)
+                            case .basalRates:
+                                BasalProfileStepView(state: state)
+                            case .carbRatio:
+                                CarbRatioStepView(state: state)
+                            case .insulinSensitivity:
+                                InsulinSensitivityStepView(state: state)
+                            case .deliveryLimits:
+                                DeliveryLimitsStepView(state: state, substep: currentDeliverySubstep)
+                            case .algorithmSettings:
+                                switch currentAlgorithmSettingsOverviewSubstep {
+                                case .contents:
+                                    AlgorithmSettingsContentsStepView(state: state)
+                                case .importantNotes:
+                                    AlgorithmSettingsImportantNotesStepView(state: state)
+                                }
+                            case .autosensSettings:
+                                AlgorithmSettingsSubstepView(state: state, substep: currentAutosensSubstep)
+                            case .smbSettings:
+                                AlgorithmSettingsSubstepView(state: state, substep: currentSMBSubstep)
+                            case .targetBehavior:
+                                AlgorithmSettingsSubstepView(state: state, substep: currentTargetBehaviorSubstep)
+                            case .notifications:
+                                NotificationPermissionStepView(state: state, currentStep: $currentStep)
+                            case .bluetooth:
+                                BluetoothPermissionStepView(
+                                    state: state,
+                                    bluetoothManager: state.bluetoothManager,
+                                    currentStep: $currentStep
+                                )
+                            case .completed:
+                                CompletedStepView(isOnboardingCompleted: true, currentChapter: nil)
                             }
-                        case .autosensSettings:
-                            AlgorithmSettingsSubstepView(state: state, substep: currentAutosensSubstep)
-                        case .smbSettings:
-                            AlgorithmSettingsSubstepView(state: state, substep: currentSMBSubstep)
-                        case .targetBehavior:
-                            AlgorithmSettingsSubstepView(state: state, substep: currentTargetBehaviorSubstep)
-                        case .notifications:
-                            NotificationPermissionStepView(state: state, currentStep: $currentStep)
-                        case .bluetooth:
-                            BluetoothPermissionStepView(
-                                state: state,
-                                bluetoothManager: state.bluetoothManager,
-                                currentStep: $currentStep
-                            )
-                        case .completed:
-                            CompletedStepView()
                         }
+                        .transition(
+                            navigationDirection == .forward
+                                ? .asymmetric(insertion: .move(edge: .trailing), removal: .move(edge: .leading))
+                                : .asymmetric(insertion: .move(edge: .leading), removal: .move(edge: .trailing))
+                        )
+                        .padding(.horizontal)
+                        .id(currentStep.id)
                     }
-                    .transition(
-                        navigationDirection == .forward
-                            ? .asymmetric(insertion: .move(edge: .trailing), removal: .move(edge: .leading))
-                            : .asymmetric(insertion: .move(edge: .leading), removal: .move(edge: .trailing))
-                    )
-                    .padding(.horizontal)
-                    .id(currentStep.id)
                 }
                 .padding(.bottom, 80)
             }
@@ -418,10 +395,53 @@ struct OnboardingStepContent: View {
             }
         }
     }
+
+    private var contentHeader: some View {
+        HStack {
+            if currentStep == .nightscout {
+                Image(currentStep.iconName)
+                    .resizable()
+                    .scaledToFit()
+                    .frame(width: 60, height: 60)
+            } else if currentStep == .bluetooth {
+                Image(currentStep.iconName)
+                    .font(.system(size: 40))
+                    .foregroundColor(currentStep.accentColor)
+                    .frame(width: 60, height: 60)
+                    .background(
+                        Circle()
+                            .fill(currentStep.accentColor.opacity(0.2))
+                    )
+            } else {
+                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(.title)
+                    .fontWeight(.bold)
+                    .foregroundColor(.primary)
+
+                Text(currentStep.description)
+                    .font(.subheadline)
+                    .foregroundColor(.secondary)
+                    .fixedSize(horizontal: false, vertical: true)
+            }
+        }
+        .padding(.horizontal)
+    }
 }
 
 struct OnboardingNavigationButtons: View {
     @Binding var currentStep: OnboardingStep
+    @Binding var showingChapterCompletion: OnboardingChapter?
     @Binding var currentStartupSubstep: StartupSubstep
     @Binding var currentNightscoutSubstep: NightscoutSubstep
     @Binding var currentDeliverySubstep: DeliveryLimitSubstep
@@ -478,6 +498,11 @@ struct OnboardingNavigationButtons: View {
     // MARK: - Navigation Logic
 
     private func handleBackNavigation() {
+        if showingChapterCompletion != nil {
+            showingChapterCompletion = nil
+            return
+        }
+
         switch currentStep {
         case .startupInfo:
             if let previousSub = StartupSubstep(rawValue: currentStartupSubstep.rawValue - 1) {
@@ -588,6 +613,19 @@ struct OnboardingNavigationButtons: View {
     }
 
     private func handleNextNavigation() {
+        if showingChapterCompletion != nil {
+            showingChapterCompletion = nil
+            if let next = currentStep.next {
+                currentStep = next
+            }
+            return
+        }
+
+        if let chapter = currentStep.chapterCompletion {
+            showingChapterCompletion = chapter
+            return
+        }
+
         switch currentStep {
         case .startupInfo:
             if let next = StartupSubstep(rawValue: currentStartupSubstep.rawValue + 1) {
@@ -620,9 +658,9 @@ struct OnboardingNavigationButtons: View {
         case .deliveryLimits:
             if let next = DeliveryLimitSubstep(rawValue: currentDeliverySubstep.rawValue + 1) {
                 currentDeliverySubstep = next
-            } else if let nextStep = currentStep.next {
-                currentStep = nextStep
-                currentDeliverySubstep = .maxIOB
+            } else {
+                currentDeliverySubstep = .minimumSafetyThreshold
+                showingChapterCompletion = .deliveryLimits
             }
 
         case .algorithmSettings:

+ 43 - 24
Trio/Sources/Modules/Onboarding/View/OnboardingSteps/CompletedStepView.swift

@@ -2,29 +2,35 @@ import SwiftUI
 
 /// Completed step view shown at the end of onboarding.
 struct CompletedStepView: View {
+    let isOnboardingCompleted: Bool
+    let currentChapter: OnboardingChapter?
+
     var body: some View {
         VStack(alignment: .center, spacing: 20) {
-            Image(systemName: "checkmark.circle.fill")
-                .font(.system(size: 60))
-                .foregroundColor(.green)
+            if isOnboardingCompleted {
+                Image(systemName: "checkmark.circle.fill")
+                    .font(.system(size: 60))
+                    .foregroundColor(.green)
 
-            Text("You're All Set!")
-                .font(.title)
-                .fontWeight(.bold)
-                .multilineTextAlignment(.center)
+                Text("You're All Set!")
+                    .font(.title)
+                    .fontWeight(.bold)
+                    .multilineTextAlignment(.center)
 
-            Text(
-                "You've successfully completed the initial setup of Trio. Tap 'Get Started' to save your settings and start using Trio."
-            )
-            .multilineTextAlignment(.center)
-            .foregroundColor(.secondary)
+                Text(
+                    "You've successfully completed the initial setup of Trio. Tap 'Get Started' to save your settings and start using Trio."
+                )
+                .multilineTextAlignment(.center)
+                .foregroundColor(.secondary)
+            }
 
             VStack(alignment: .leading, spacing: 12) {
                 ForEach(Array(OnboardingChapter.allCases.enumerated()), id: \.element.id) { index, chapter in
                     completedItemsView(
                         stepIndex: index + 1,
                         title: chapter.title,
-                        description: chapter.completedDescription
+                        description: chapter.completedDescription,
+                        isCompleted: isChapterCompleted(chapter)
                     )
 
                     if index < (OnboardingChapter.allCases.count - 1) {
@@ -36,25 +42,35 @@ struct CompletedStepView: View {
             .background(Color.green.opacity(0.1))
             .cornerRadius(12)
 
-            Text("Remember, you can adjust these settings at any time in the app settings if needed.")
-                .multilineTextAlignment(.center)
-                .foregroundColor(.primary)
-                .bold()
+            if isOnboardingCompleted {
+                Text("Remember, you can adjust these settings at any time in the app settings if needed.")
+                    .multilineTextAlignment(.center)
+                    .foregroundColor(.primary)
+                    .bold()
+            }
         }
         .padding()
         .frame(maxWidth: .infinity)
     }
 
+    /// Determines if a chapter should be marked as completed
+    private func isChapterCompleted(_ chapter: OnboardingChapter) -> Bool {
+        guard let currentChapter else { return isOnboardingCompleted }
+        if isOnboardingCompleted { return true }
+        return chapter.id <= currentChapter.id
+    }
+
     /// A reusable view for displaying setting items in the completed step.
     @ViewBuilder private func completedItemsView(
         stepIndex: Int,
         title: String,
-        description: String
+        description: String,
+        isCompleted: Bool
     ) -> some View {
         VStack(alignment: .leading, spacing: 10) {
             HStack {
                 HStack(spacing: 14) {
-                    stepCount(stepIndex)
+                    stepCount(stepIndex, isCompleted: isCompleted)
                     Text(title)
                         .font(.headline)
                         .bold()
@@ -62,8 +78,8 @@ struct CompletedStepView: View {
 
                 Spacer()
 
-                Image(systemName: "checkmark")
-                    .foregroundStyle(Color.green)
+                Image(systemName: isCompleted ? "checkmark.circle.fill" : "circle")
+                    .foregroundStyle(isCompleted ? Color.green : Color.secondary)
                     .font(.headline)
                     .bold()
             }
@@ -76,16 +92,19 @@ struct CompletedStepView: View {
         }
     }
 
-    @ViewBuilder private func stepCount(_ count: Int) -> some View {
+    @ViewBuilder private func stepCount(_ count: Int, isCompleted: Bool) -> some View {
         Text(count.description)
             .font(.subheadline.bold())
             .frame(width: 26, height: 26, alignment: .center)
-            .background(Color.green)
+            .background(isCompleted ? Color.green : Color.secondary)
             .foregroundStyle(Color.bgDarkerDarkBlue)
             .clipShape(Capsule())
     }
 }
 
 #Preview {
-    CompletedStepView()
+    CompletedStepView(
+        isOnboardingCompleted: true,
+        currentChapter: nil
+    )
 }

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

@@ -321,6 +321,23 @@ enum OnboardingStep: Int, CaseIterable, Identifiable, Equatable {
             return Color.red
         }
     }
+
+    var chapterCompletion: OnboardingChapter? {
+        switch self {
+        case .unitSelection:
+            return .prepareTrio
+        case .insulinSensitivity:
+            return .therapySettings
+        case .deliveryLimits:
+            // ❗ Delivery limits depends on the substep, not just the step.
+            // Skip here
+            return nil
+        case .targetBehavior:
+            return .algorithmSettings
+        default:
+            return nil
+        }
+    }
 }
 
 var nonInfoOnboardingSteps: [OnboardingStep] { OnboardingStep.allCases