Polish onboarding layout with fixed header, back nav, and consistent step framing
This commit is contained in:
@@ -5,6 +5,9 @@
|
||||
"onboarding_welcome_step_2" = "Decide whether to include weekends";
|
||||
"onboarding_welcome_step_3" = "Optionally connect your calendar";
|
||||
"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_continue" = "Continue";
|
||||
"onboarding_skip" = "Skip";
|
||||
|
||||
@@ -5,6 +5,9 @@
|
||||
"onboarding_welcome_step_2" = "Decide si incluir fines de semana";
|
||||
"onboarding_welcome_step_3" = "Conecta tu calendario de forma opcional";
|
||||
"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_continue" = "Continuar";
|
||||
"onboarding_skip" = "Omitir";
|
||||
|
||||
@@ -17,13 +17,6 @@ struct CalendarStepView: View {
|
||||
var body: some View {
|
||||
ScrollView {
|
||||
VStack(spacing: 24) {
|
||||
Text("calendar_title")
|
||||
.font(.mealMoodH2)
|
||||
.foregroundColor(.mealMoodTextPrimary)
|
||||
.frame(maxWidth: .infinity, alignment: .leading)
|
||||
.padding(.horizontal, 24)
|
||||
.padding(.top, 32)
|
||||
|
||||
VStack(spacing: 16) {
|
||||
// iCloud sync toggle
|
||||
HStack {
|
||||
@@ -122,6 +115,7 @@ struct CalendarStepView: View {
|
||||
}
|
||||
}
|
||||
.padding(.horizontal, 24)
|
||||
.padding(.top, 24)
|
||||
|
||||
Text("calendar_optional")
|
||||
.font(.mealMoodSmall)
|
||||
|
||||
@@ -34,18 +34,12 @@ struct FirstDishesStepView: View {
|
||||
var body: some View {
|
||||
ScrollView {
|
||||
VStack(spacing: 24) {
|
||||
VStack(alignment: .leading, spacing: 8) {
|
||||
Text("first_dishes_title")
|
||||
.font(.mealMoodH2)
|
||||
.foregroundColor(.mealMoodTextPrimary)
|
||||
|
||||
Text("first_dishes_subtitle")
|
||||
.font(.mealMoodSmall)
|
||||
.foregroundColor(.mealMoodTextSecondary)
|
||||
}
|
||||
.frame(maxWidth: .infinity, alignment: .leading)
|
||||
.padding(.horizontal, 24)
|
||||
.padding(.top, 32)
|
||||
Text("first_dishes_subtitle")
|
||||
.font(.mealMoodSmall)
|
||||
.foregroundColor(.mealMoodTextSecondary)
|
||||
.frame(maxWidth: .infinity, alignment: .leading)
|
||||
.padding(.horizontal, 24)
|
||||
.padding(.top, 20)
|
||||
|
||||
// New dish form
|
||||
VStack(spacing: 12) {
|
||||
|
||||
@@ -8,12 +8,6 @@ struct MealWindowsStepView: View {
|
||||
VStack(spacing: 24) {
|
||||
Spacer()
|
||||
|
||||
Text("meal_windows_title")
|
||||
.font(.mealMoodH2)
|
||||
.foregroundColor(.mealMoodTextPrimary)
|
||||
.frame(maxWidth: .infinity, alignment: .leading)
|
||||
.padding(.horizontal, 24)
|
||||
|
||||
VStack(spacing: 12) {
|
||||
ForEach(MealWindows.allCases, id: \.self) { option in
|
||||
SelectionCard(
|
||||
|
||||
@@ -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)
|
||||
}
|
||||
}
|
||||
|
||||
@@ -8,12 +8,6 @@ struct WeekendsStepView: View {
|
||||
VStack(spacing: 24) {
|
||||
Spacer()
|
||||
|
||||
Text("weekends_title")
|
||||
.font(.mealMoodH2)
|
||||
.foregroundColor(.mealMoodTextPrimary)
|
||||
.frame(maxWidth: .infinity, alignment: .leading)
|
||||
.padding(.horizontal, 24)
|
||||
|
||||
VStack(spacing: 16) {
|
||||
HStack {
|
||||
VStack(alignment: .leading, spacing: 4) {
|
||||
|
||||
@@ -10,11 +10,6 @@ struct WelcomeStepView: View {
|
||||
AppIconPlaceholder(size: 120)
|
||||
|
||||
VStack(spacing: 12) {
|
||||
Text("onboarding_welcome_title")
|
||||
.font(.mealMoodH1)
|
||||
.foregroundColor(.mealMoodTextPrimary)
|
||||
.multilineTextAlignment(.center)
|
||||
|
||||
Text("onboarding_welcome_subtitle")
|
||||
.font(.mealMoodBody)
|
||||
.foregroundColor(.mealMoodTextSecondary)
|
||||
|
||||
Reference in New Issue
Block a user