Files
FamilyMealPlanner/MealMood/Views/Onboarding/WelcomeStepView.swift
T
alexandrev-tibco 110807d239 1.0.5: Eating out slots, rule violations panel, Crashlytics, search bar fix
- Eating out: tap any empty slot → "Mark as eating out"; shows teal indicator,
  skipped by auto-assign, counts as complete, tap again to unmark
- Rule violations panel: access via wand long-press context menu when conflicts exist;
  shows all isRuleOverridden slots with Fix (clear) or Ignore (acknowledge) actions
- Firebase Crashlytics integrated: CrashlyticsService + dSYM upload build phase,
  isPremium property tracked per session
- PremiumSyncService: extracted premium state machine, StoreKit Transaction.updates
  listener, isPremium no longer synced via iCloud to avoid stale state
- StoreManager: analytics on purchase/restore, bundle ID fallback for product ID lookup
- Search bar contrast bug fixed: TextField now has explicit foreground color for dark mode
- WelcomeStepView: redesigned onboarding welcome screen with week preview
- Version bump: 1.0.5 build 22

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-04-27 09:48:20 +02:00

166 lines
6.4 KiB
Swift

import SwiftUI
struct WelcomeStepView: View {
var onNext: () -> Void
var body: some View {
VStack(spacing: 0) {
Spacer()
AppIconPlaceholder(size: 96)
.padding(.bottom, 12)
Text("onboarding_welcome_subtitle")
.font(.mealMoodBody)
.foregroundColor(.mealMoodTextSecondary)
.multilineTextAlignment(.center)
.padding(.horizontal, 32)
.padding(.bottom, 24)
weekPreview
.padding(.bottom, 24)
VStack(alignment: .leading, spacing: 14) {
BenefitRow(icon: "wand.and.stars", text: String(localized: "onboarding_welcome_step_1"))
BenefitRow(icon: "square.and.arrow.up", text: String(localized: "onboarding_welcome_step_3"))
BenefitRow(icon: "icloud", text: String(localized: "onboarding_welcome_step_4"))
}
.padding(.horizontal, 40)
Spacer()
PrimaryButton(title: String(localized: "onboarding_start"), icon: "", action: onNext)
.padding(.horizontal, 24)
.padding(.bottom, 40)
}
.frame(maxWidth: .infinity, maxHeight: .infinity, alignment: .top)
}
// MARK: - Week preview
private var weekPreview: some View {
VStack(alignment: .leading, spacing: 8) {
Text(String(localized: "onboarding_welcome_preview_label"))
.font(.mealMoodCaption)
.foregroundColor(.mealMoodTextSecondary)
.padding(.horizontal, 24)
ScrollView(.horizontal, showsIndicators: false) {
HStack(spacing: 6) {
ForEach(sampleDays, id: \.name) { day in
VStack(spacing: 4) {
Text(day.name)
.font(.system(size: 10, weight: .bold))
.foregroundColor(.mealMoodTextPrimary)
.frame(maxWidth: .infinity)
.padding(.vertical, 5)
.background(day.color)
.clipShape(RoundedRectangle(cornerRadius: 8))
Text(day.dish)
.font(.system(size: 10, weight: .medium))
.foregroundColor(.mealMoodTextPrimary)
.multilineTextAlignment(.center)
.lineLimit(2)
.minimumScaleFactor(0.8)
.frame(maxWidth: .infinity, minHeight: 38)
.padding(.horizontal, 4)
.padding(.vertical, 5)
.background(Color.white.opacity(0.92))
.clipShape(RoundedRectangle(cornerRadius: 8))
.overlay(
RoundedRectangle(cornerRadius: 8)
.stroke(Color(hex: "#EEE4DE"), lineWidth: 1)
)
}
.frame(width: 66)
}
}
.padding(.horizontal, 20)
.padding(.vertical, 8)
}
.background(
RoundedRectangle(cornerRadius: 14)
.fill(Color.mealMoodBackground)
.overlay(
RoundedRectangle(cornerRadius: 14)
.stroke(Color.mealMoodCoral.opacity(0.25), lineWidth: 1)
)
)
.padding(.horizontal, 16)
}
}
private var sampleDays: [(name: String, dish: String, color: Color)] {
let lang = Locale.current.language.languageCode?.identifier ?? "en"
switch lang {
case "es":
return [
("Lun", "Pasta al horno", Color(hex: "#FFD5D9")),
("Mar", "Pollo asado", Color(hex: "#FFE1C8")),
("Mié", "Lentejas", Color(hex: "#FFF0C8")),
("Jue", "Salmón", Color(hex: "#DFF2CC")),
("Vie", "Tortilla", Color(hex: "#D3F0E7"))
]
case "fr":
return [
("Lun", "Pâtes gratinées", Color(hex: "#FFD5D9")),
("Mar", "Poulet rôti", Color(hex: "#FFE1C8")),
("Mer", "Lentilles", Color(hex: "#FFF0C8")),
("Jeu", "Saumon", Color(hex: "#DFF2CC")),
("Ven", "Omelette", Color(hex: "#D3F0E7"))
]
case "de":
return [
("Mo", "Nudeln", Color(hex: "#FFD5D9")),
("Di", "Hähnchen", Color(hex: "#FFE1C8")),
("Mi", "Linsensuppe", Color(hex: "#FFF0C8")),
("Do", "Lachs", Color(hex: "#DFF2CC")),
("Fr", "Omelett", Color(hex: "#D3F0E7"))
]
case "it":
return [
("Lun", "Pasta al forno", Color(hex: "#FFD5D9")),
("Mar", "Pollo arrosto", Color(hex: "#FFE1C8")),
("Mer", "Lenticchie", Color(hex: "#FFF0C8")),
("Gio", "Salmone", Color(hex: "#DFF2CC")),
("Ven", "Frittata", Color(hex: "#D3F0E7"))
]
case "pt":
return [
("Seg", "Macarrão", Color(hex: "#FFD5D9")),
("Ter", "Frango assado", Color(hex: "#FFE1C8")),
("Qua", "Lentilhas", Color(hex: "#FFF0C8")),
("Qui", "Salmão", Color(hex: "#DFF2CC")),
("Sex", "Omelete", Color(hex: "#D3F0E7"))
]
default:
return [
("Mon", "Baked pasta", Color(hex: "#FFD5D9")),
("Tue", "Roast chicken", Color(hex: "#FFE1C8")),
("Wed", "Lentil soup", Color(hex: "#FFF0C8")),
("Thu", "Salmon", Color(hex: "#DFF2CC")),
("Fri", "Omelette", Color(hex: "#D3F0E7"))
]
}
}
}
private struct BenefitRow: View {
let icon: String
let text: String
var body: some View {
HStack(spacing: 12) {
Image(systemName: icon)
.font(.system(size: 18))
.foregroundColor(.mealMoodCoral)
.frame(width: 28)
Text(text)
.font(.mealMoodBody)
.foregroundColor(.mealMoodTextPrimary)
}
}
}