From bb6eeee139bf28b75d747a7764f616f4265d7f39 Mon Sep 17 00:00:00 2001 From: erik-keifer <69150393+erik-keifer@users.noreply.github.com> Date: Thu, 9 Apr 2026 22:42:53 -0700 Subject: [PATCH] Update BehaviorOnboardingView.swift Allow users to go back and change answers when they are onboarding. --- .../Screens/BehaviorOnboardingView.swift | 109 ++++++++++++++---- 1 file changed, 85 insertions(+), 24 deletions(-) diff --git a/SleepFocus/Screens/BehaviorOnboardingView.swift b/SleepFocus/Screens/BehaviorOnboardingView.swift index e149783..1d7e8aa 100644 --- a/SleepFocus/Screens/BehaviorOnboardingView.swift +++ b/SleepFocus/Screens/BehaviorOnboardingView.swift @@ -2,7 +2,7 @@ import SwiftUI // MARK: - Question Model -private struct BehaviorQuestion: Identifiable { +struct BehaviorQuestion: Identifiable { let id: Int let category: String let categoryIcon: String @@ -15,7 +15,7 @@ private struct BehaviorQuestion: Identifiable { // MARK: - Questions -private let allQuestions: [BehaviorQuestion] = [ +let allQuestions: [BehaviorQuestion] = [ // BEHAVIOR BehaviorQuestion( @@ -194,9 +194,11 @@ struct BehaviorOnboardingView: View { @State private var currentIndex: Int = 0 @State private var animateIn: Bool = false + @State private var showDismissConfirmation: Bool = false private var question: BehaviorQuestion { allQuestions[currentIndex] } private var progress: Double { Double(currentIndex + 1) / Double(allQuestions.count) } + private var isFirst: Bool { currentIndex == 0 } private var isLast: Bool { currentIndex == allQuestions.count - 1 } var body: some View { @@ -205,32 +207,49 @@ struct BehaviorOnboardingView: View { VStack(spacing: 0) { - // Progress bar - VStack(alignment: .leading, spacing: 12) { - HStack { - Text("Sleep Profile Setup") - .font(.subheadline) - .foregroundColor(.secondary) - Spacer() - Text("\(currentIndex + 1) of \(allQuestions.count)") - .font(.subheadline.monospacedDigit()) + // Top bar: back button + progress counter + X button + HStack(alignment: .center) { + Button(action: goBack) { + Image(systemName: "chevron.left") + .font(.system(size: 17, weight: .semibold)) + .foregroundColor(isFirst ? .clear : .accentColor) + } + .disabled(isFirst) + + Spacer() + + Text("\(currentIndex + 1) of \(allQuestions.count)") + .font(.subheadline.monospacedDigit()) + .foregroundColor(.secondary) + + Spacer() + + Button(action: { showDismissConfirmation = true }) { + Image(systemName: "xmark") + .font(.system(size: 15, weight: .semibold)) .foregroundColor(.secondary) + .padding(8) + .background(Color.secondary.opacity(0.12), in: Circle()) } - GeometryReader { geo in - ZStack(alignment: .leading) { - RoundedRectangle(cornerRadius: 4) - .fill(Color.secondary.opacity(0.2)) - .frame(height: 6) - RoundedRectangle(cornerRadius: 4) - .fill(Color.accentColor) - .frame(width: geo.size.width * progress, height: 6) - .animation(.spring(response: 0.4), value: progress) - } + } + .padding(.horizontal, 20) + .padding(.top, 16) + + // Progress bar + GeometryReader { geo in + ZStack(alignment: .leading) { + RoundedRectangle(cornerRadius: 4) + .fill(Color.secondary.opacity(0.2)) + .frame(height: 6) + RoundedRectangle(cornerRadius: 4) + .fill(Color.accentColor) + .frame(width: geo.size.width * progress, height: 6) + .animation(.spring(response: 0.4), value: progress) } - .frame(height: 6) } + .frame(height: 6) .padding(.horizontal, 24) - .padding(.top, 20) + .padding(.top, 12) Spacer() @@ -302,6 +321,19 @@ struct BehaviorOnboardingView: View { } } .onAppear { animateQuestion() } + .confirmationDialog( + "Finish setup later?", + isPresented: $showDismissConfirmation, + titleVisibility: .visible + ) { + Button("Finish Later") { + store.save() + onComplete() + } + Button("Continue Setup", role: .cancel) {} + } message: { + Text("You can complete your Sleep Profile anytime from the Profile section in Settings.") + } } // MARK: - Helpers @@ -326,6 +358,15 @@ struct BehaviorOnboardingView: View { } } + private func goBack() { + guard !isFirst else { return } + withAnimation(.easeIn(duration: 0.15)) { animateIn = false } + DispatchQueue.main.asyncAfter(deadline: .now() + 0.15) { + currentIndex -= 1 + animateQuestion() + } + } + private func animateQuestion() { animateIn = false withAnimation(.spring(response: 0.45, dampingFraction: 0.8).delay(0.05)) { @@ -336,7 +377,7 @@ struct BehaviorOnboardingView: View { // MARK: - Answer Button -private struct AnswerButton: View { +struct AnswerButton: View { let label: String let answer: BehaviorAnswer let selected: BehaviorAnswer @@ -380,3 +421,23 @@ private struct AnswerButton: View { print("Onboarding complete") } } + + + + + + + + + + + + + + + + + + + +