Polish onboarding layout with fixed header, back nav, and consistent step framing

This commit is contained in:
alexandrev-tibco
2026-02-26 10:53:46 +01:00
parent 3937abeb6e
commit 3d72d53889
8 changed files with 78 additions and 46 deletions
@@ -5,6 +5,9 @@
"onboarding_welcome_step_2" = "Decide whether to include weekends"; "onboarding_welcome_step_2" = "Decide whether to include weekends";
"onboarding_welcome_step_3" = "Optionally connect your calendar"; "onboarding_welcome_step_3" = "Optionally connect your calendar";
"onboarding_welcome_step_4" = "Add your usual dishes to start fast"; "onboarding_welcome_step_4" = "Add your usual dishes to start fast";
"onboarding_nav_intro" = "Quick setup";
"onboarding_nav_setup" = "Setup";
"onboarding_nav_step_counter" = "Step %d/%d";
"onboarding_start" = "Get Started"; "onboarding_start" = "Get Started";
"onboarding_continue" = "Continue"; "onboarding_continue" = "Continue";
"onboarding_skip" = "Skip"; "onboarding_skip" = "Skip";
@@ -5,6 +5,9 @@
"onboarding_welcome_step_2" = "Decide si incluir fines de semana"; "onboarding_welcome_step_2" = "Decide si incluir fines de semana";
"onboarding_welcome_step_3" = "Conecta tu calendario de forma opcional"; "onboarding_welcome_step_3" = "Conecta tu calendario de forma opcional";
"onboarding_welcome_step_4" = "Añade tus platos habituales para empezar rápido"; "onboarding_welcome_step_4" = "Añade tus platos habituales para empezar rápido";
"onboarding_nav_intro" = "Configuración rápida";
"onboarding_nav_setup" = "Config";
"onboarding_nav_step_counter" = "Paso %d/%d";
"onboarding_start" = "Comenzar"; "onboarding_start" = "Comenzar";
"onboarding_continue" = "Continuar"; "onboarding_continue" = "Continuar";
"onboarding_skip" = "Omitir"; "onboarding_skip" = "Omitir";
@@ -17,13 +17,6 @@ struct CalendarStepView: View {
var body: some View { var body: some View {
ScrollView { ScrollView {
VStack(spacing: 24) { VStack(spacing: 24) {
Text("calendar_title")
.font(.mealMoodH2)
.foregroundColor(.mealMoodTextPrimary)
.frame(maxWidth: .infinity, alignment: .leading)
.padding(.horizontal, 24)
.padding(.top, 32)
VStack(spacing: 16) { VStack(spacing: 16) {
// iCloud sync toggle // iCloud sync toggle
HStack { HStack {
@@ -122,6 +115,7 @@ struct CalendarStepView: View {
} }
} }
.padding(.horizontal, 24) .padding(.horizontal, 24)
.padding(.top, 24)
Text("calendar_optional") Text("calendar_optional")
.font(.mealMoodSmall) .font(.mealMoodSmall)
@@ -34,18 +34,12 @@ struct FirstDishesStepView: View {
var body: some View { var body: some View {
ScrollView { ScrollView {
VStack(spacing: 24) { VStack(spacing: 24) {
VStack(alignment: .leading, spacing: 8) {
Text("first_dishes_title")
.font(.mealMoodH2)
.foregroundColor(.mealMoodTextPrimary)
Text("first_dishes_subtitle") Text("first_dishes_subtitle")
.font(.mealMoodSmall) .font(.mealMoodSmall)
.foregroundColor(.mealMoodTextSecondary) .foregroundColor(.mealMoodTextSecondary)
}
.frame(maxWidth: .infinity, alignment: .leading) .frame(maxWidth: .infinity, alignment: .leading)
.padding(.horizontal, 24) .padding(.horizontal, 24)
.padding(.top, 32) .padding(.top, 20)
// New dish form // New dish form
VStack(spacing: 12) { VStack(spacing: 12) {
@@ -8,12 +8,6 @@ struct MealWindowsStepView: View {
VStack(spacing: 24) { VStack(spacing: 24) {
Spacer() Spacer()
Text("meal_windows_title")
.font(.mealMoodH2)
.foregroundColor(.mealMoodTextPrimary)
.frame(maxWidth: .infinity, alignment: .leading)
.padding(.horizontal, 24)
VStack(spacing: 12) { VStack(spacing: 12) {
ForEach(MealWindows.allCases, id: \.self) { option in ForEach(MealWindows.allCases, id: \.self) { option in
SelectionCard( SelectionCard(
+59 -4
View File
@@ -12,8 +12,8 @@ struct OnboardingView: View {
Color.mealMoodBackground.ignoresSafeArea() Color.mealMoodBackground.ignoresSafeArea()
VStack(spacing: 0) { VStack(spacing: 0) {
// Progress indicator onboardingHeader
if viewModel.currentStep > 0 {
HStack(spacing: 8) { HStack(spacing: 8) {
ForEach(1..<viewModel.totalSteps, id: \.self) { step in ForEach(1..<viewModel.totalSteps, id: \.self) { step in
Capsule() Capsule()
@@ -22,8 +22,8 @@ struct OnboardingView: View {
} }
} }
.padding(.horizontal, 24) .padding(.horizontal, 24)
.padding(.top, 16) .padding(.top, 8)
} .padding(.bottom, 12)
TabView(selection: $viewModel.currentStep) { TabView(selection: $viewModel.currentStep) {
WelcomeStepView(onNext: { viewModel.nextStep() }) WelcomeStepView(onNext: { viewModel.nextStep() })
@@ -68,6 +68,7 @@ struct OnboardingView: View {
} }
.tabViewStyle(.page(indexDisplayMode: .never)) .tabViewStyle(.page(indexDisplayMode: .never))
.animation(.easeInOut(duration: 0.3), value: viewModel.currentStep) .animation(.easeInOut(duration: 0.3), value: viewModel.currentStep)
.frame(maxHeight: .infinity, alignment: .center)
} }
} }
.onAppear { .onAppear {
@@ -106,4 +107,58 @@ struct OnboardingView: View {
let plans = (try? context.fetch(FetchDescriptor<WeekPlan>())) ?? [] let plans = (try? context.fetch(FetchDescriptor<WeekPlan>())) ?? []
return !plans.isEmpty 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)
}
} }
@@ -8,12 +8,6 @@ struct WeekendsStepView: View {
VStack(spacing: 24) { VStack(spacing: 24) {
Spacer() Spacer()
Text("weekends_title")
.font(.mealMoodH2)
.foregroundColor(.mealMoodTextPrimary)
.frame(maxWidth: .infinity, alignment: .leading)
.padding(.horizontal, 24)
VStack(spacing: 16) { VStack(spacing: 16) {
HStack { HStack {
VStack(alignment: .leading, spacing: 4) { VStack(alignment: .leading, spacing: 4) {
@@ -10,11 +10,6 @@ struct WelcomeStepView: View {
AppIconPlaceholder(size: 120) AppIconPlaceholder(size: 120)
VStack(spacing: 12) { VStack(spacing: 12) {
Text("onboarding_welcome_title")
.font(.mealMoodH1)
.foregroundColor(.mealMoodTextPrimary)
.multilineTextAlignment(.center)
Text("onboarding_welcome_subtitle") Text("onboarding_welcome_subtitle")
.font(.mealMoodBody) .font(.mealMoodBody)
.foregroundColor(.mealMoodTextSecondary) .foregroundColor(.mealMoodTextSecondary)