Przeglądaj źródła

Address PR feedback by @kingst

Deniz Cengiz 1 rok temu
rodzic
commit
023f254957

+ 4 - 4
Trio.xcodeproj/project.pbxproj

@@ -356,7 +356,7 @@
 		BD432CA22D2F4E4000D1EB79 /* WatchMessageKeys.swift in Sources */ = {isa = PBXBuildFile; fileRef = BD432CA02D2F4E3300D1EB79 /* WatchMessageKeys.swift */; };
 		BD432CA22D2F4E4000D1EB79 /* WatchMessageKeys.swift in Sources */ = {isa = PBXBuildFile; fileRef = BD432CA02D2F4E3300D1EB79 /* WatchMessageKeys.swift */; };
 		BD47FD132D88AA700043966B /* OnboardingManager.swift in Sources */ = {isa = PBXBuildFile; fileRef = BD47FD122D88AA6B0043966B /* OnboardingManager.swift */; };
 		BD47FD132D88AA700043966B /* OnboardingManager.swift in Sources */ = {isa = PBXBuildFile; fileRef = BD47FD122D88AA6B0043966B /* OnboardingManager.swift */; };
 		BD47FD172D88AAF50043966B /* CompletedStepView.swift in Sources */ = {isa = PBXBuildFile; fileRef = BD47FD162D88AAEF0043966B /* CompletedStepView.swift */; };
 		BD47FD172D88AAF50043966B /* CompletedStepView.swift in Sources */ = {isa = PBXBuildFile; fileRef = BD47FD162D88AAEF0043966B /* CompletedStepView.swift */; };
-		BD47FD192D88AAFE0043966B /* OnboardingView.swift in Sources */ = {isa = PBXBuildFile; fileRef = BD47FD182D88AAF90043966B /* OnboardingView.swift */; };
+		BD47FD192D88AAFE0043966B /* OnboardingRootView.swift in Sources */ = {isa = PBXBuildFile; fileRef = BD47FD182D88AAF90043966B /* OnboardingRootView.swift */; };
 		BD47FD1B2D88AB4F0043966B /* OnboardingStateModel.swift in Sources */ = {isa = PBXBuildFile; fileRef = BD47FD1A2D88AB4A0043966B /* OnboardingStateModel.swift */; };
 		BD47FD1B2D88AB4F0043966B /* OnboardingStateModel.swift in Sources */ = {isa = PBXBuildFile; fileRef = BD47FD1A2D88AB4A0043966B /* OnboardingStateModel.swift */; };
 		BD47FDD72D8B64D20043966B /* CarbRatioStepView.swift in Sources */ = {isa = PBXBuildFile; fileRef = BD47FDD62D8B64CC0043966B /* CarbRatioStepView.swift */; };
 		BD47FDD72D8B64D20043966B /* CarbRatioStepView.swift in Sources */ = {isa = PBXBuildFile; fileRef = BD47FDD62D8B64CC0043966B /* CarbRatioStepView.swift */; };
 		BD47FDD92D8B657D0043966B /* InsulinSensitivityStepView.swift in Sources */ = {isa = PBXBuildFile; fileRef = BD47FDD82D8B65730043966B /* InsulinSensitivityStepView.swift */; };
 		BD47FDD92D8B657D0043966B /* InsulinSensitivityStepView.swift in Sources */ = {isa = PBXBuildFile; fileRef = BD47FDD82D8B65730043966B /* InsulinSensitivityStepView.swift */; };
@@ -1141,7 +1141,7 @@
 		BD432CA02D2F4E3300D1EB79 /* WatchMessageKeys.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = WatchMessageKeys.swift; sourceTree = "<group>"; };
 		BD432CA02D2F4E3300D1EB79 /* WatchMessageKeys.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = WatchMessageKeys.swift; sourceTree = "<group>"; };
 		BD47FD122D88AA6B0043966B /* OnboardingManager.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = OnboardingManager.swift; sourceTree = "<group>"; };
 		BD47FD122D88AA6B0043966B /* OnboardingManager.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = OnboardingManager.swift; sourceTree = "<group>"; };
 		BD47FD162D88AAEF0043966B /* CompletedStepView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = CompletedStepView.swift; sourceTree = "<group>"; };
 		BD47FD162D88AAEF0043966B /* CompletedStepView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = CompletedStepView.swift; sourceTree = "<group>"; };
-		BD47FD182D88AAF90043966B /* OnboardingView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = OnboardingView.swift; sourceTree = "<group>"; };
+		BD47FD182D88AAF90043966B /* OnboardingRootView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = OnboardingRootView.swift; sourceTree = "<group>"; };
 		BD47FD1A2D88AB4A0043966B /* OnboardingStateModel.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = OnboardingStateModel.swift; sourceTree = "<group>"; };
 		BD47FD1A2D88AB4A0043966B /* OnboardingStateModel.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = OnboardingStateModel.swift; sourceTree = "<group>"; };
 		BD47FDD62D8B64CC0043966B /* CarbRatioStepView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = CarbRatioStepView.swift; sourceTree = "<group>"; };
 		BD47FDD62D8B64CC0043966B /* CarbRatioStepView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = CarbRatioStepView.swift; sourceTree = "<group>"; };
 		BD47FDD82D8B65730043966B /* InsulinSensitivityStepView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = InsulinSensitivityStepView.swift; sourceTree = "<group>"; };
 		BD47FDD82D8B65730043966B /* InsulinSensitivityStepView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = InsulinSensitivityStepView.swift; sourceTree = "<group>"; };
@@ -2753,7 +2753,7 @@
 				DD3F1F882D9E078300DCE7B3 /* TherapySettingEditorView.swift */,
 				DD3F1F882D9E078300DCE7B3 /* TherapySettingEditorView.swift */,
 				DD3F1F862D9DDB1200DCE7B3 /* AnimationPlaceholder.swift */,
 				DD3F1F862D9DDB1200DCE7B3 /* AnimationPlaceholder.swift */,
 				DDC38E0F2D9B376900ADCB46 /* OnboardingView+Util.swift */,
 				DDC38E0F2D9B376900ADCB46 /* OnboardingView+Util.swift */,
-				BD47FD182D88AAF90043966B /* OnboardingView.swift */,
+				BD47FD182D88AAF90043966B /* OnboardingRootView.swift */,
 				BD47FDD52D8B64AE0043966B /* OnboardingSteps */,
 				BD47FDD52D8B64AE0043966B /* OnboardingSteps */,
 			);
 			);
 			path = View;
 			path = View;
@@ -3991,7 +3991,7 @@
 				5887527C2BD986E1008B081D /* OpenAPSBattery.swift in Sources */,
 				5887527C2BD986E1008B081D /* OpenAPSBattery.swift in Sources */,
 				38569348270B5DFB0002C50D /* GlucoseSource.swift in Sources */,
 				38569348270B5DFB0002C50D /* GlucoseSource.swift in Sources */,
 				CEE9A6582BBB418300EB5194 /* CalibrationsStateModel.swift in Sources */,
 				CEE9A6582BBB418300EB5194 /* CalibrationsStateModel.swift in Sources */,
-				BD47FD192D88AAFE0043966B /* OnboardingView.swift in Sources */,
+				BD47FD192D88AAFE0043966B /* OnboardingRootView.swift in Sources */,
 				CEB434E328B8F9DB00B70274 /* BluetoothStateManager.swift in Sources */,
 				CEB434E328B8F9DB00B70274 /* BluetoothStateManager.swift in Sources */,
 				BDC2EA452C3043B000E5BBD0 /* OverrideStorage.swift in Sources */,
 				BDC2EA452C3043B000E5BBD0 /* OverrideStorage.swift in Sources */,
 				3811DE4225C9D4A100A708ED /* SettingsDataFlow.swift in Sources */,
 				3811DE4225C9D4A100A708ED /* SettingsDataFlow.swift in Sources */,

+ 0 - 1
Trio/Sources/Modules/NightscoutConfig/NightscoutConfigStateModel.swift

@@ -80,7 +80,6 @@ extension NightscoutConfig {
             if let CheckURL = url.last, CheckURL == "/" {
             if let CheckURL = url.last, CheckURL == "/" {
                 let fixedURL = url.dropLast()
                 let fixedURL = url.dropLast()
                 url = String(fixedURL)
                 url = String(fixedURL)
-                url = String(fixedURL)
             }
             }
 
 
             guard let url = URL(string: url), self.url.hasPrefix("https://") else {
             guard let url = URL(string: url), self.url.hasPrefix("https://") else {

+ 400 - 0
Trio/Sources/Modules/Onboarding/View/OnboardingRootView.swift

@@ -0,0 +1,400 @@
+import SwiftUI
+import Swinject
+
+/// The main onboarding view that manages navigation between onboarding steps.
+extension Onboarding {
+    struct RootView: BaseView {
+        let resolver: Resolver
+        @State var state = StateModel()
+        @State private var navigationDirection: OnboardingNavigationDirection = .forward
+        let onboardingManager: OnboardingManager
+        @State private var currentStep: OnboardingStep = .welcome
+        @State private var currentDeliverySubstep: DeliveryLimitSubstep = .maxIOB
+        @State private var currentNightscoutSubstep: NightscoutSubstep = .setupSelection
+
+        // Animation states
+        @State private var animationScale: CGFloat = 1.0
+        @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 && didSelectNightscoutSetupOption)
+                ||
+                (currentStep == .nightscout && hasValidNightscoutConnection)
+                ||
+                (currentStep == .nightscout && didSelectNightscoutImportOption)
+        }
+
+        var body: some View {
+            NavigationView {
+                ZStack {
+                    // Background gradient
+                    LinearGradient(
+                        gradient: Gradient(colors: [Color.bgDarkBlue, Color.bgDarkerDarkBlue]),
+                        startPoint: .top,
+                        endPoint: .bottom
+                    )
+                    .ignoresSafeArea()
+
+                    VStack(spacing: 0) {
+                        // Progress bar
+                        OnboardingProgressBar(
+                            currentStep: currentStep,
+                            currentSubstep: {
+                                switch currentStep {
+                                case .deliveryLimits: return currentDeliverySubstep.rawValue
+                                case .nightscout: return currentNightscoutSubstep.rawValue
+                                default: return nil
+                                }
+                            }(),
+                            stepsWithSubsteps: [
+                                .nightscout: NightscoutSubstep.allCases.count,
+                                .deliveryLimits: DeliveryLimitSubstep.allCases.count
+                            ],
+                            nightscoutSetupOption: state.nightscoutSetupOption
+                        )
+
+                        .padding(.top)
+
+                        // Step content
+                        ScrollViewReader { scrollProxy in
+                            ScrollView {
+                                VStack(alignment: .leading, spacing: 20) {
+                                    // Scroll position marker at top
+                                    Color.clear.frame(height: 0).id("top")
+
+                                    // Header
+                                    if currentStep != .welcome && currentStep != .completed {
+                                        HStack {
+                                            if currentStep == .nightscout {
+                                                Image(currentStep.iconName)
+                                                    .resizable()
+                                                    .scaledToFit()
+                                                    .frame(width: 60, height: 60)
+
+                                            } 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(.largeTitle)
+                                                    .fontWeight(.bold)
+                                                    .foregroundColor(.primary)
+
+                                                Text(currentStep.description)
+                                                    .font(.subheadline)
+                                                    .foregroundColor(.secondary)
+                                                    .fixedSize(horizontal: false, vertical: true)
+                                            }
+                                        }
+                                        .padding([.horizontal, .top])
+                                    }
+
+                                    // Animation container (for steps that include animations)
+                                    //                                AnimationPlaceholder(for: currentStep)
+                                    //                                    .padding()
+                                    //                                    .scaleEffect(animationScale)
+                                    //                                    .opacity(animationOpacity)
+                                    //                                    .onAppear {
+                                    //                                        withAnimation(.easeInOut(duration: 0.7)) {
+                                    //                                            animationOpacity = 1
+                                    //                                            animationScale = 1.0
+                                    //                                        }
+                                    //                                        // Start pulse animation
+                                    //                                        isAnimating = true
+                                    //                                    }
+
+                                    // Step-specific content
+                                    Group {
+                                        switch currentStep {
+                                        case .welcome:
+                                            WelcomeStepView()
+                                        case .startupGuide:
+                                            StartupGuideStepView()
+                                        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 .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) // Force view recreation when step changes
+                                }
+                                .padding(.bottom, 80) // Make room for buttons at bottom
+                            }
+                            .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)
+                            }
+                        }
+
+                        Spacer()
+
+                        // Navigation buttons
+                        HStack {
+                            // Back button
+                            if currentStep != .welcome {
+                                Button(action: {
+                                    navigationDirection = .backward
+                                    withAnimation {
+                                        if currentStep == .completed {
+                                            currentStep = .deliveryLimits
+                                            currentDeliverySubstep =
+                                                .minimumSafetyThreshold // ensure we land on the last substep visually
+                                        } else if currentStep == .nightscout {
+                                            if currentNightscoutSubstep == .setupSelection {
+                                                // First substep: go to previous main step
+                                                if let previousMainStep = currentStep.previous {
+                                                    currentStep = previousMainStep
+                                                    currentNightscoutSubstep = .setupSelection // reset substep
+                                                }
+                                            } else {
+                                                // Go back one substep
+                                                currentNightscoutSubstep = NightscoutSubstep(
+                                                    rawValue: currentNightscoutSubstep
+                                                        .rawValue - 1
+                                                )!
+                                            }
+                                        } else if currentStep == .deliveryLimits {
+                                            if let previousSub = DeliveryLimitSubstep(
+                                                rawValue: currentDeliverySubstep
+                                                    .rawValue - 1
+                                            ) {
+                                                currentDeliverySubstep = previousSub
+                                            } else if let previousMainStep = currentStep.previous {
+                                                currentStep = previousMainStep
+                                                currentDeliverySubstep = .maxIOB // reset to first substep for later return
+                                            }
+                                        } else if let previous = currentStep.previous {
+                                            currentStep = previous
+                                        }
+                                    }
+                                }) {
+                                    HStack {
+                                        Image(systemName: "chevron.left")
+                                        Text("Back")
+                                    }
+                                    .padding()
+                                    .foregroundColor(.primary)
+                                }
+                            }
+
+                            Spacer()
+
+                            // Next/Finish button
+                            Button(action: {
+                                navigationDirection = .forward
+                                withAnimation {
+                                    if currentStep == .completed {
+                                        state.saveOnboardingData()
+                                        onboardingManager.completeOnboarding()
+                                        Foundation.NotificationCenter.default.post(name: .onboardingCompleted, object: nil)
+                                    } else if currentStep == .nightscout {
+                                        if currentNightscoutSubstep != .importFromNightscout {
+                                            // Handle conditional skip
+                                            if currentNightscoutSubstep == .setupSelection,
+                                               state.nightscoutSetupOption == .skipNightscoutSetup,
+                                               let next = currentStep.next
+                                            {
+                                                currentStep = next
+                                            } else {
+                                                currentNightscoutSubstep = NightscoutSubstep(
+                                                    rawValue: currentNightscoutSubstep
+                                                        .rawValue + 1
+                                                )!
+                                            }
+                                        } else if currentNightscoutSubstep == .importFromNightscout,
+                                                  state.nightscoutImportOption == .useImport
+                                        {
+                                            // TODO: trigger import, show animation, then proceed to next step
+                                            Task {
+                                                await state.importSettingsFromNightscout(currentStep: $currentStep)
+                                            }
+                                        } else if let next = currentStep.next {
+                                            currentStep = next
+                                        }
+                                    } else if currentStep == .deliveryLimits {
+                                        if let nextSub = DeliveryLimitSubstep(rawValue: currentDeliverySubstep.rawValue + 1) {
+                                            currentDeliverySubstep = nextSub
+                                        } else if let next = currentStep.next {
+                                            currentStep = next
+                                            currentDeliverySubstep = .maxIOB
+                                        }
+                                    } else if let next = currentStep.next {
+                                        currentStep = next
+                                    }
+                                }
+                            }) {
+                                HStack {
+                                    Text(currentStep == .completed ? "Get Started" : "Next")
+                                    Image(systemName: "chevron.right")
+                                }
+                                .padding()
+                                .foregroundColor(.white)
+                                .background(Capsule().fill(!shouldDisableNextButton ? Color.blue : Color(.systemGray)))
+                            }.disabled(shouldDisableNextButton)
+                        }
+                        .padding(.horizontal)
+                        .padding(.bottom)
+                    }
+                }
+                .navigationBarHidden(true)
+            }
+            .onChange(of: currentStep) { _, _ in
+                // Reset animation when step changes
+                animationScale = 0.9
+                animationOpacity = 0
+                isAnimating = false
+
+                // Start new animation
+                DispatchQueue.main.asyncAfter(deadline: .now() + 0.1) {
+                    withAnimation(.easeInOut(duration: 0.7)) {
+                        animationOpacity = 1
+                        animationScale = 1.0
+                    }
+                    isAnimating = true
+                }
+            }
+            .onAppear(perform: configureView)
+        }
+    }
+}
+
+/// A progress bar that shows the user's progress through the onboarding process.
+struct OnboardingProgressBar: View {
+    let currentStep: OnboardingStep
+    let currentSubstep: Int?
+    let stepsWithSubsteps: [OnboardingStep: Int]
+    let nightscoutSetupOption: NightscoutSetupOption
+
+    var body: some View {
+        HStack(spacing: 4) {
+            ForEach(renderedSteps, id: \.id) { step in
+                ZStack(alignment: .leading) {
+                    Rectangle()
+                        .fill(Color.gray.opacity(0.3))
+                        .frame(height: 4)
+                        .cornerRadius(2)
+
+                    GeometryReader { geo in
+                        Rectangle()
+                            .fill(Color.blue)
+                            .frame(
+                                width: geo.size.width * fillFraction(for: step.step, totalSubsteps: step.substeps),
+                                height: 4
+                            )
+                            .cornerRadius(2)
+                    }
+                }
+                .frame(height: 4)
+            }
+        }
+        .padding(.horizontal)
+    }
+
+    private var visibleSteps: [OnboardingStep] {
+        OnboardingStep.allCases.filter { $0 != .welcome && $0 != .completed }
+    }
+
+    private var renderedSteps: [(id: String, step: OnboardingStep, substeps: Int?)] {
+        visibleSteps.map {
+            (id: "\($0.rawValue)", step: $0, substeps: stepsWithSubsteps[$0])
+        }
+    }
+
+    private func fillFraction(for step: OnboardingStep, totalSubsteps: Int?) -> CGFloat {
+        // If currentStep is .completed, fill everything
+        if currentStep == .completed { return 1.0 }
+
+        if let currentIndex = visibleSteps.firstIndex(of: currentStep),
+           let stepIndex = visibleSteps.firstIndex(of: step),
+           stepIndex < currentIndex
+        {
+            return 1.0
+        }
+
+        if step == currentStep {
+            if let total = totalSubsteps, let current = currentSubstep {
+                return CGFloat(current + 1) / CGFloat(total)
+            } else {
+                return 1.0
+            }
+        }
+
+        // Handle special case: Nightscout was skipped
+        if step == .nightscout,
+           nightscoutSetupOption == .skipNightscoutSetup,
+           let currentIndex = visibleSteps.firstIndex(of: currentStep),
+           let nightscoutIndex = visibleSteps.firstIndex(of: .nightscout),
+           currentIndex > nightscoutIndex
+        {
+            return 1.0
+        }
+
+        return 0.0
+    }
+}
+
+struct Onboarding_Preview: PreviewProvider {
+    static var previews: some View {
+        Group {
+            let resolver = TrioApp.resolver
+            let onboardingManager = OnboardingManager()
+            Onboarding.RootView(resolver: resolver, onboardingManager: onboardingManager)
+                .previewDisplayName("Onboarding Flow")
+        }
+    }
+}

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

@@ -1,400 +0,0 @@
-import SwiftUI
-import Swinject
-
-/// The main onboarding view that manages navigation between onboarding steps.
-extension Onboarding {
-    struct RootView: BaseView {
-        let resolver: Resolver
-        @State var state = StateModel()
-        @State private var navigationDirection: OnboardingNavigationDirection = .forward
-        let onboardingManager: OnboardingManager
-        @State private var currentStep: OnboardingStep = .welcome
-        @State private var currentDeliverySubstep: DeliveryLimitSubstep = .maxIOB
-        @State private var currentNightscoutSubstep: NightscoutSubstep = .setupSelection
-
-        // Animation states
-        @State private var animationScale: CGFloat = 1.0
-        @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 && didSelectNightscoutSetupOption)
-                ||
-                (currentStep == .nightscout && hasValidNightscoutConnection)
-                ||
-                (currentStep == .nightscout && didSelectNightscoutImportOption)
-        }
-
-        var body: some View {
-            NavigationView {
-                ZStack {
-                    // Background gradient
-                    LinearGradient(
-                        gradient: Gradient(colors: [Color.bgDarkBlue, Color.bgDarkerDarkBlue]),
-                        startPoint: .top,
-                        endPoint: .bottom
-                    )
-                    .ignoresSafeArea()
-
-                    VStack(spacing: 0) {
-                        // Progress bar
-                        OnboardingProgressBar(
-                            currentStep: currentStep,
-                            currentSubstep: {
-                                switch currentStep {
-                                case .deliveryLimits: return currentDeliverySubstep.rawValue
-                                case .nightscout: return currentNightscoutSubstep.rawValue
-                                default: return nil
-                                }
-                            }(),
-                            stepsWithSubsteps: [
-                                .nightscout: NightscoutSubstep.allCases.count,
-                                .deliveryLimits: DeliveryLimitSubstep.allCases.count
-                            ],
-                            nightscoutSetupOption: state.nightscoutSetupOption
-                        )
-
-                        .padding(.top)
-
-                        // Step content
-                        ScrollViewReader { scrollProxy in
-                            ScrollView {
-                                VStack(alignment: .leading, spacing: 20) {
-                                    // Scroll position marker at top
-                                    Color.clear.frame(height: 0).id("top")
-
-                                    // Header
-                                    if currentStep != .welcome && currentStep != .completed {
-                                        HStack {
-                                            if currentStep == .nightscout {
-                                                Image(currentStep.iconName)
-                                                    .resizable()
-                                                    .scaledToFit()
-                                                    .frame(width: 60, height: 60)
-
-                                            } 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(.largeTitle)
-                                                    .fontWeight(.bold)
-                                                    .foregroundColor(.primary)
-
-                                                Text(currentStep.description)
-                                                    .font(.subheadline)
-                                                    .foregroundColor(.secondary)
-                                                    .fixedSize(horizontal: false, vertical: true)
-                                            }
-                                        }
-                                        .padding([.horizontal, .top])
-                                    }
-
-                                    // Animation container (for steps that include animations)
-                                    //                                AnimationPlaceholder(for: currentStep)
-                                    //                                    .padding()
-                                    //                                    .scaleEffect(animationScale)
-                                    //                                    .opacity(animationOpacity)
-                                    //                                    .onAppear {
-                                    //                                        withAnimation(.easeInOut(duration: 0.7)) {
-                                    //                                            animationOpacity = 1
-                                    //                                            animationScale = 1.0
-                                    //                                        }
-                                    //                                        // Start pulse animation
-                                    //                                        isAnimating = true
-                                    //                                    }
-
-                                    // Step-specific content
-                                    Group {
-                                        switch currentStep {
-                                        case .welcome:
-                                            WelcomeStepView()
-                                        case .startupGuide:
-                                            StartupGuideStepView()
-                                        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 .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) // Force view recreation when step changes
-                                }
-                                .padding(.bottom, 80) // Make room for buttons at bottom
-                            }
-                            .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)
-                            }
-                        }
-
-                        Spacer()
-
-                        // Navigation buttons
-                        HStack {
-                            // Back button
-                            if currentStep != .welcome {
-                                Button(action: {
-                                    navigationDirection = .backward
-                                    withAnimation {
-                                        if currentStep == .completed {
-                                            currentStep = .deliveryLimits
-                                            currentDeliverySubstep =
-                                                .minimumSafetyThreshold // ensure we land on the last substep visually
-                                        } else if currentStep == .nightscout {
-                                            if currentNightscoutSubstep == .setupSelection {
-                                                // First substep: go to previous main step
-                                                if let previousMainStep = currentStep.previous {
-                                                    currentStep = previousMainStep
-                                                    currentNightscoutSubstep = .setupSelection // reset substep
-                                                }
-                                            } else {
-                                                // Go back one substep
-                                                currentNightscoutSubstep = NightscoutSubstep(
-                                                    rawValue: currentNightscoutSubstep
-                                                        .rawValue - 1
-                                                )!
-                                            }
-                                        } else if currentStep == .deliveryLimits {
-                                            if let previousSub = DeliveryLimitSubstep(
-                                                rawValue: currentDeliverySubstep
-                                                    .rawValue - 1
-                                            ) {
-                                                currentDeliverySubstep = previousSub
-                                            } else if let previousMainStep = currentStep.previous {
-                                                currentStep = previousMainStep
-                                                currentDeliverySubstep = .maxIOB // reset to first substep for later return
-                                            }
-                                        } else if let previous = currentStep.previous {
-                                            currentStep = previous
-                                        }
-                                    }
-                                }) {
-                                    HStack {
-                                        Image(systemName: "chevron.left")
-                                        Text("Back")
-                                    }
-                                    .padding()
-                                    .foregroundColor(.primary)
-                                }
-                            }
-
-                            Spacer()
-
-                            // Next/Finish button
-                            Button(action: {
-                                navigationDirection = .forward
-                                withAnimation {
-                                    if currentStep == .completed {
-                                        state.saveOnboardingData()
-                                        onboardingManager.completeOnboarding()
-                                        Foundation.NotificationCenter.default.post(name: .onboardingCompleted, object: nil)
-                                    } else if currentStep == .nightscout {
-                                        if currentNightscoutSubstep != .importFromNightscout {
-                                            // Handle conditional skip
-                                            if currentNightscoutSubstep == .setupSelection,
-                                               state.nightscoutSetupOption == .skipNightscoutSetup,
-                                               let next = currentStep.next
-                                            {
-                                                currentStep = next
-                                            } else {
-                                                currentNightscoutSubstep = NightscoutSubstep(
-                                                    rawValue: currentNightscoutSubstep
-                                                        .rawValue + 1
-                                                )!
-                                            }
-                                        } else if currentNightscoutSubstep == .importFromNightscout,
-                                                  state.nightscoutImportOption == .useImport
-                                        {
-                                            // TODO: trigger import, show animation, then proceed to next step
-                                            Task {
-                                                await state.importSettingsFromNightscout(currentStep: $currentStep)
-                                            }
-                                        } else if let next = currentStep.next {
-                                            currentStep = next
-                                        }
-                                    } else if currentStep == .deliveryLimits {
-                                        if let nextSub = DeliveryLimitSubstep(rawValue: currentDeliverySubstep.rawValue + 1) {
-                                            currentDeliverySubstep = nextSub
-                                        } else if let next = currentStep.next {
-                                            currentStep = next
-                                            currentDeliverySubstep = .maxIOB
-                                        }
-                                    } else if let next = currentStep.next {
-                                        currentStep = next
-                                    }
-                                }
-                            }) {
-                                HStack {
-                                    Text(currentStep == .completed ? "Get Started" : "Next")
-                                    Image(systemName: "chevron.right")
-                                }
-                                .padding()
-                                .foregroundColor(.white)
-                                .background(Capsule().fill(!shouldDisableNextButton ? Color.blue : Color(.systemGray)))
-                            }.disabled(shouldDisableNextButton)
-                        }
-                        .padding(.horizontal)
-                        .padding(.bottom)
-                    }
-                }
-                .navigationBarHidden(true)
-            }
-            .onChange(of: currentStep) { _, _ in
-                // Reset animation when step changes
-                animationScale = 0.9
-                animationOpacity = 0
-                isAnimating = false
-
-                // Start new animation
-                DispatchQueue.main.asyncAfter(deadline: .now() + 0.1) {
-                    withAnimation(.easeInOut(duration: 0.7)) {
-                        animationOpacity = 1
-                        animationScale = 1.0
-                    }
-                    isAnimating = true
-                }
-            }
-            .onAppear(perform: configureView)
-        }
-    }
-}
-
-/// A progress bar that shows the user's progress through the onboarding process.
-struct OnboardingProgressBar: View {
-    let currentStep: OnboardingStep
-    let currentSubstep: Int?
-    let stepsWithSubsteps: [OnboardingStep: Int]
-    let nightscoutSetupOption: NightscoutSetupOption
-
-    var body: some View {
-        HStack(spacing: 4) {
-            ForEach(renderedSteps, id: \.id) { step in
-                ZStack(alignment: .leading) {
-                    Rectangle()
-                        .fill(Color.gray.opacity(0.3))
-                        .frame(height: 4)
-                        .cornerRadius(2)
-
-                    GeometryReader { geo in
-                        Rectangle()
-                            .fill(Color.blue)
-                            .frame(
-                                width: geo.size.width * fillFraction(for: step.step, totalSubsteps: step.substeps),
-                                height: 4
-                            )
-                            .cornerRadius(2)
-                    }
-                }
-                .frame(height: 4)
-            }
-        }
-        .padding(.horizontal)
-    }
-
-    private var visibleSteps: [OnboardingStep] {
-        OnboardingStep.allCases.filter { $0 != .welcome && $0 != .completed }
-    }
-
-    private var renderedSteps: [(id: String, step: OnboardingStep, substeps: Int?)] {
-        visibleSteps.map {
-            (id: "\($0.rawValue)", step: $0, substeps: stepsWithSubsteps[$0])
-        }
-    }
-
-    private func fillFraction(for step: OnboardingStep, totalSubsteps: Int?) -> CGFloat {
-        // If currentStep is .completed, fill everything
-        if currentStep == .completed { return 1.0 }
-
-        if let currentIndex = visibleSteps.firstIndex(of: currentStep),
-           let stepIndex = visibleSteps.firstIndex(of: step),
-           stepIndex < currentIndex
-        {
-            return 1.0
-        }
-
-        if step == currentStep {
-            if let total = totalSubsteps, let current = currentSubstep {
-                return CGFloat(current + 1) / CGFloat(total)
-            } else {
-                return 1.0
-            }
-        }
-
-        // Handle special case: Nightscout was skipped
-        if step == .nightscout,
-           nightscoutSetupOption == .skipNightscoutSetup,
-           let currentIndex = visibleSteps.firstIndex(of: currentStep),
-           let nightscoutIndex = visibleSteps.firstIndex(of: .nightscout),
-           currentIndex > nightscoutIndex
-        {
-            return 1.0
-        }
-
-        return 0.0
-    }
-}
-
-struct Onboarding_Preview: PreviewProvider {
-    static var previews: some View {
-        Group {
-            let resolver = TrioApp.resolver
-            let onboardingManager = OnboardingManager()
-            Onboarding.RootView(resolver: resolver, onboardingManager: onboardingManager)
-                .previewDisplayName("Onboarding Flow")
-        }
-    }
-}