2.0: breakfast and snack meal types across the app
- MealType gains breakfast/snack (chronological order drives slot/row order). - AppSettings.activeMealTypes: single source of truth for enabled meals, backed by new optional enabledMealTypesRaw with legacy mealWindows fallback — additive migration, keeps pre-2.0 installs and iCloud snapshots working. Legacy field kept coherent by the setter. time(for:) resolves per-meal event times (breakfast 8:00 / snack 17:00 defaults on old stores). - Replaced the 5 duplicated mealWindows→[MealType] derivations (slot sync, default plan creation, week calendar, export view) with activeMealTypes. - CalendarService: per-type event names and times. - Export styles: per-type accent colors and gradients. - Widget: generic meals list (N rows) instead of hardcoded lunch/dinner; compact families fall back to main meals when >2 are active. - Settings: 4 meal toggles (min 1) replace the 3-option picker. - Onboarding: meal step is now multi-select over the 4 types. - iCloud sync: enabledMealTypesRaw synced (optional, pre-2.0 compatible). - Localized breakfast/snack in all 6 languages. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01BkanrydYtrme8wipTzWssG
This commit is contained in:
@@ -1,7 +1,7 @@
|
||||
import SwiftUI
|
||||
|
||||
struct MealWindowsStepView: View {
|
||||
@Binding var selection: MealWindows
|
||||
@Binding var selection: Set<MealType>
|
||||
var onNext: () -> Void
|
||||
|
||||
var body: some View {
|
||||
@@ -15,14 +15,21 @@ struct MealWindowsStepView: View {
|
||||
.padding(.top, 12)
|
||||
|
||||
VStack(spacing: 12) {
|
||||
ForEach(MealWindows.allCases, id: \.self) { option in
|
||||
ForEach(MealType.allCases, id: \.self) { mealType in
|
||||
SelectionCard(
|
||||
icon: option.icon,
|
||||
title: optionTitle(option),
|
||||
isSelected: selection == option
|
||||
icon: mealType.icon,
|
||||
title: optionTitle(mealType),
|
||||
isSelected: selection.contains(mealType)
|
||||
) {
|
||||
withAnimation(.spring(response: 0.3)) {
|
||||
selection = option
|
||||
if selection.contains(mealType) {
|
||||
// Keep at least one meal selected.
|
||||
if selection.count > 1 {
|
||||
selection.remove(mealType)
|
||||
}
|
||||
} else {
|
||||
selection.insert(mealType)
|
||||
}
|
||||
}
|
||||
HapticManager.shared.selection()
|
||||
}
|
||||
@@ -38,12 +45,15 @@ struct MealWindowsStepView: View {
|
||||
.frame(maxWidth: .infinity, maxHeight: .infinity, alignment: .top)
|
||||
}
|
||||
|
||||
private func optionTitle(_ option: MealWindows) -> String {
|
||||
switch option {
|
||||
case .dinnerOnly: return "🌙 \(String(localized: "meal_windows_dinner_only"))"
|
||||
case .lunchOnly: return "☀️ \(String(localized: "meal_windows_lunch_only"))"
|
||||
case .both: return "🌞🌙 \(String(localized: "meal_windows_both"))"
|
||||
private func optionTitle(_ mealType: MealType) -> String {
|
||||
let emoji: String
|
||||
switch mealType {
|
||||
case .breakfast: emoji = "☕️"
|
||||
case .lunch: emoji = "🌞"
|
||||
case .snack: emoji = "🍎"
|
||||
case .dinner: emoji = "🌙"
|
||||
}
|
||||
return "\(emoji) \(String(localized: String.LocalizationValue(mealType.localizedKey)))"
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
Reference in New Issue
Block a user