Polish onboarding layout with fixed header, back nav, and consistent step framing
This commit is contained in:
@@ -12,18 +12,18 @@ struct OnboardingView: View {
|
||||
Color.mealMoodBackground.ignoresSafeArea()
|
||||
|
||||
VStack(spacing: 0) {
|
||||
// Progress indicator
|
||||
if viewModel.currentStep > 0 {
|
||||
HStack(spacing: 8) {
|
||||
ForEach(1..<viewModel.totalSteps, id: \.self) { step in
|
||||
Capsule()
|
||||
.fill(step <= viewModel.currentStep ? Color.mealMoodCoral : Color(hex: "#E0E0E0"))
|
||||
.frame(height: 4)
|
||||
}
|
||||
onboardingHeader
|
||||
|
||||
HStack(spacing: 8) {
|
||||
ForEach(1..<viewModel.totalSteps, id: \.self) { step in
|
||||
Capsule()
|
||||
.fill(step <= viewModel.currentStep ? Color.mealMoodCoral : Color(hex: "#E0E0E0"))
|
||||
.frame(height: 4)
|
||||
}
|
||||
.padding(.horizontal, 24)
|
||||
.padding(.top, 16)
|
||||
}
|
||||
.padding(.horizontal, 24)
|
||||
.padding(.top, 8)
|
||||
.padding(.bottom, 12)
|
||||
|
||||
TabView(selection: $viewModel.currentStep) {
|
||||
WelcomeStepView(onNext: { viewModel.nextStep() })
|
||||
@@ -68,6 +68,7 @@ struct OnboardingView: View {
|
||||
}
|
||||
.tabViewStyle(.page(indexDisplayMode: .never))
|
||||
.animation(.easeInOut(duration: 0.3), value: viewModel.currentStep)
|
||||
.frame(maxHeight: .infinity, alignment: .center)
|
||||
}
|
||||
}
|
||||
.onAppear {
|
||||
@@ -106,4 +107,58 @@ struct OnboardingView: View {
|
||||
let plans = (try? context.fetch(FetchDescriptor<WeekPlan>())) ?? []
|
||||
return !plans.isEmpty
|
||||
}
|
||||
|
||||
private var onboardingHeader: some View {
|
||||
VStack(spacing: 6) {
|
||||
HStack {
|
||||
if viewModel.currentStep > 0 {
|
||||
Button {
|
||||
viewModel.previousStep()
|
||||
} label: {
|
||||
Image(systemName: "chevron.left")
|
||||
.font(.system(size: 16, weight: .semibold))
|
||||
.foregroundColor(.mealMoodTextPrimary)
|
||||
.frame(width: 32, height: 32)
|
||||
.background(Color.mealMoodSurface)
|
||||
.clipShape(Circle())
|
||||
}
|
||||
.buttonStyle(.plain)
|
||||
} else {
|
||||
Color.clear.frame(width: 32, height: 32)
|
||||
}
|
||||
|
||||
Spacer()
|
||||
|
||||
Text(headerTitle)
|
||||
.font(.mealMoodBodyBold)
|
||||
.foregroundColor(.mealMoodTextPrimary)
|
||||
.lineLimit(1)
|
||||
|
||||
Spacer()
|
||||
|
||||
Text(stepCounterText)
|
||||
.font(.mealMoodCaption)
|
||||
.foregroundColor(.mealMoodTextSecondary)
|
||||
.frame(width: 70, alignment: .trailing)
|
||||
}
|
||||
}
|
||||
.padding(.horizontal, 16)
|
||||
.padding(.top, 8)
|
||||
}
|
||||
|
||||
private var headerTitle: String {
|
||||
switch viewModel.currentStep {
|
||||
case 0: return String(localized: "onboarding_nav_intro")
|
||||
case 1: return String(localized: "meal_windows_title")
|
||||
case 2: return String(localized: "weekends_title")
|
||||
case 3: return String(localized: "calendar_title")
|
||||
case 4: return String(localized: "first_dishes_title")
|
||||
default: return ""
|
||||
}
|
||||
}
|
||||
|
||||
private var stepCounterText: String {
|
||||
guard viewModel.currentStep > 0 else { return String(localized: "onboarding_nav_setup") }
|
||||
return String(format: String(localized: "onboarding_nav_step_counter"), viewModel.currentStep, viewModel.totalSteps - 1)
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user