diff --git a/MealMood/Components/FilledSlotView.swift b/MealMood/Components/FilledSlotView.swift index 29c4b85..2b4e4a7 100644 --- a/MealMood/Components/FilledSlotView.swift +++ b/MealMood/Components/FilledSlotView.swift @@ -6,6 +6,7 @@ struct FilledSlotView: View { let tags: [(name: String, color: String)] let mealType: MealType var showsRuleWarning: Bool = false + var photoData: Data? = nil var onRemove: (() -> Void)? var body: some View { @@ -52,6 +53,27 @@ struct FilledSlotView: View { } .padding(10) .frame(maxWidth: .infinity, minHeight: 80, alignment: .topLeading) - .mealCardStyle() + .background(cardBackground) + .cornerRadius(16) + .shadow(color: Color.black.opacity(0.05), radius: 8, y: 4) + .overlay( + RoundedRectangle(cornerRadius: 16) + .stroke(Color(hex: "#F0F0F0"), lineWidth: 1) + ) + } + + /// Dish photo as a subtle card background, scrimmed so text stays legible. + @ViewBuilder + private var cardBackground: some View { + if let photoData, let image = UIImage(data: photoData) { + ZStack { + Image(uiImage: image) + .resizable() + .scaledToFill() + Color.mealMoodSurface.opacity(0.75) + } + } else { + Color.mealMoodSurface + } } } diff --git a/MealMood/Models/AppSettings.swift b/MealMood/Models/AppSettings.swift index 1dc8631..ead2ea2 100644 --- a/MealMood/Models/AppSettings.swift +++ b/MealMood/Models/AppSettings.swift @@ -24,6 +24,9 @@ final class AppSettings { var reminderMinutesBefore: Int? var iCloudSyncEnabled: Bool? var weekExportStyle: String? + /// Show dish photos as planner card backgrounds (2.0). Optional for + /// CloudKit/migration compatibility; resolves to true. + var showDishPhotosInPlanner: Bool? var isPremium: Bool = false var onboardingCompleted: Bool = false @@ -74,6 +77,11 @@ final class AppSettings { set { iCloudSyncEnabled = newValue } } + var showDishPhotosInPlannerResolved: Bool { + get { showDishPhotosInPlanner ?? true } + set { showDishPhotosInPlanner = newValue } + } + var weekExportStyleEnum: WeekExportStyle { get { WeekExportStyle(rawValue: weekExportStyle ?? "") ?? .defaultStyle } set { weekExportStyle = newValue.rawValue } diff --git a/MealMood/Resources/de.lproj/Localizable.strings b/MealMood/Resources/de.lproj/Localizable.strings index 81bdd7b..92a4cc4 100644 --- a/MealMood/Resources/de.lproj/Localizable.strings +++ b/MealMood/Resources/de.lproj/Localizable.strings @@ -410,3 +410,5 @@ // Meal types (2.0) "breakfast" = "Frühstück"; "snack" = "Snack"; + +"settings_show_dish_photos" = "Gerichtfotos im Planer anzeigen"; diff --git a/MealMood/Resources/en.lproj/Localizable.strings b/MealMood/Resources/en.lproj/Localizable.strings index d1cea27..c86b2aa 100644 --- a/MealMood/Resources/en.lproj/Localizable.strings +++ b/MealMood/Resources/en.lproj/Localizable.strings @@ -410,3 +410,5 @@ // Meal types (2.0) "breakfast" = "Breakfast"; "snack" = "Snack"; + +"settings_show_dish_photos" = "Show dish photos in planner"; diff --git a/MealMood/Resources/es.lproj/Localizable.strings b/MealMood/Resources/es.lproj/Localizable.strings index ec88623..5c4a878 100644 --- a/MealMood/Resources/es.lproj/Localizable.strings +++ b/MealMood/Resources/es.lproj/Localizable.strings @@ -410,3 +410,5 @@ // Meal types (2.0) "breakfast" = "Desayuno"; "snack" = "Merienda"; + +"settings_show_dish_photos" = "Mostrar fotos de platos en el planificador"; diff --git a/MealMood/Resources/fr.lproj/Localizable.strings b/MealMood/Resources/fr.lproj/Localizable.strings index 49ab09f..439b1c7 100644 --- a/MealMood/Resources/fr.lproj/Localizable.strings +++ b/MealMood/Resources/fr.lproj/Localizable.strings @@ -410,3 +410,5 @@ // Meal types (2.0) "breakfast" = "Petit-déjeuner"; "snack" = "Goûter"; + +"settings_show_dish_photos" = "Afficher les photos des plats dans le planning"; diff --git a/MealMood/Resources/it.lproj/Localizable.strings b/MealMood/Resources/it.lproj/Localizable.strings index 99fdccb..547997f 100644 --- a/MealMood/Resources/it.lproj/Localizable.strings +++ b/MealMood/Resources/it.lproj/Localizable.strings @@ -410,3 +410,5 @@ // Meal types (2.0) "breakfast" = "Colazione"; "snack" = "Merenda"; + +"settings_show_dish_photos" = "Mostra le foto dei piatti nel planner"; diff --git a/MealMood/Resources/pt-BR.lproj/Localizable.strings b/MealMood/Resources/pt-BR.lproj/Localizable.strings index 6d3938d..ace4b8d 100644 --- a/MealMood/Resources/pt-BR.lproj/Localizable.strings +++ b/MealMood/Resources/pt-BR.lproj/Localizable.strings @@ -410,3 +410,5 @@ // Meal types (2.0) "breakfast" = "Café da manhã"; "snack" = "Lanche"; + +"settings_show_dish_photos" = "Mostrar fotos dos pratos no planejador"; diff --git a/MealMood/Views/Home/WeekCalendarView.swift b/MealMood/Views/Home/WeekCalendarView.swift index a8e5528..98e93c4 100644 --- a/MealMood/Views/Home/WeekCalendarView.swift +++ b/MealMood/Views/Home/WeekCalendarView.swift @@ -271,6 +271,7 @@ struct WeekCalendarView: View { tags: dishTags.map { (name: $0.localizedName(language: settings.languageEnum.resolved()), color: $0.color) }, mealType: mealType, showsRuleWarning: slot.isRuleOverridden, + photoData: settings.showDishPhotosInPlannerResolved ? dishData.photoData : nil, onRemove: viewModel.canEditCurrentWeek ? { guard let live = liveSlot(for: slot) else { return } viewModel.removeDish(from: live, plan: plan, settings: settings) @@ -313,6 +314,7 @@ struct WeekCalendarView: View { let name: String let descriptionText: String? let tagIds: [UUID] + let photoData: Data? } private struct DisplaySlot { @@ -326,7 +328,7 @@ struct WeekCalendarView: View { private var dishesSnapshotKey: String { dishes - .map { "\($0.id.uuidString)|\($0.name)|\($0.tagIds.count)|\($0.descriptionText ?? "")" } + .map { "\($0.id.uuidString)|\($0.name)|\($0.tagIds.count)|\($0.descriptionText ?? "")|\($0.photoData?.count ?? 0)" } .joined(separator: ";") } @@ -335,7 +337,8 @@ struct WeekCalendarView: View { return DishSnapshot( name: live.name, descriptionText: live.descriptionText, - tagIds: live.tagIds + tagIds: live.tagIds, + photoData: live.photoData ) } return dishSnapshotsById[dishId] @@ -348,7 +351,8 @@ struct WeekCalendarView: View { DishSnapshot( name: dish.name, descriptionText: dish.descriptionText, - tagIds: dish.tagIds + tagIds: dish.tagIds, + photoData: dish.photoData ) ) }) diff --git a/MealMood/Views/Settings/SettingsView.swift b/MealMood/Views/Settings/SettingsView.swift index 1d528fb..5a23911 100644 --- a/MealMood/Views/Settings/SettingsView.swift +++ b/MealMood/Views/Settings/SettingsView.swift @@ -89,6 +89,12 @@ struct SettingsView: View { )) .tint(.mealMoodCoral) + Toggle("settings_show_dish_photos", isOn: Binding( + get: { settings.showDishPhotosInPlannerResolved }, + set: { settings.showDishPhotosInPlannerResolved = $0 } + )) + .tint(.mealMoodCoral) + Picker("settings_export_style", selection: Binding( get: { settings.weekExportStyleEnum }, set: { settings.weekExportStyleEnum = $0 } diff --git a/MealMood/Views/Shopping/ShoppingListView.swift b/MealMood/Views/Shopping/ShoppingListView.swift index 025f92c..67799c4 100644 --- a/MealMood/Views/Shopping/ShoppingListView.swift +++ b/MealMood/Views/Shopping/ShoppingListView.swift @@ -18,6 +18,7 @@ struct ShoppingListView: View { @State private var newItemText: String = "" @State private var generatingDishId: UUID? @State private var showPaywall = false + @State private var editingDish: Dish? private var items: [ShoppingItem] { allItems @@ -131,6 +132,16 @@ struct ShoppingListView: View { .sheet(isPresented: $showPaywall) { PremiumView(settings: settings, source: "ingredient_generation") } + .sheet(item: $editingDish, onDismiss: { + // Pick up ingredients the user just added in the editor. + ShoppingListService.reconcile( + context: context, + weekStartDate: weekStartDate, + plannedDishes: plannedDishes + ) + }) { dish in + DishFormView(dish: dish) + } } } @@ -179,9 +190,23 @@ struct ShoppingListView: View { @ViewBuilder private func missingIngredientsRow(_ dish: Dish) -> some View { HStack { - Text(dish.name) - .foregroundColor(.mealMoodTextPrimary) + // Tapping the dish opens its editor — the only path to add + // ingredients on devices without on-device generation. + Button { + editingDish = dish + } label: { + HStack(spacing: 6) { + Text(dish.name) + .foregroundColor(.mealMoodTextPrimary) + Image(systemName: "square.and.pencil") + .font(.system(size: 13)) + .foregroundColor(.mealMoodTextSecondary) + } + } + .buttonStyle(.borderless) + Spacer() + if IngredientGenerator.isAvailable { if generatingDishId == dish.id { ProgressView()