platos: segundo plato por comida (ensalada + salmon)
- MealSlot.secondaryDishId (opcional, aditivo CloudKit); se limpia al sustituir/quitar/vaciar/marcar comer fuera y viaja en swaps, copia de semana anterior, undo y snapshot KV de iCloud (retrocompatible) - Picker de hueco lleno: toggle "anadir como segundo plato" + boton para quitarlo; HomeViewModel.assignSecondaryDish/removeSecondaryDish - Se muestra "Plato + Segundo" en calendario, widget y export; la lista de la compra incluye los ingredientes del segundo; stats lo cuentan - Evento analytics secondary_dish_added; strings en 6 idiomas Co-Authored-By: Claude Fable 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_013H6bXqGX1ygwib1Dm3n3UG
This commit is contained in:
@@ -11,6 +11,7 @@ final class HomeViewModel: ObservableObject {
|
||||
|
||||
private struct SlotSnapshot {
|
||||
let dishId: UUID?
|
||||
let secondaryDishId: UUID?
|
||||
let isRuleOverridden: Bool
|
||||
let isEatingOut: Bool
|
||||
}
|
||||
@@ -94,6 +95,7 @@ final class HomeViewModel: ObservableObject {
|
||||
}
|
||||
|
||||
slot.dishId = dish.id
|
||||
slot.secondaryDishId = nil
|
||||
slot.isRuleOverridden = isOverride
|
||||
plan.updatedAt = Date()
|
||||
|
||||
@@ -104,6 +106,25 @@ final class HomeViewModel: ObservableObject {
|
||||
showToastMessage(localizedString("toast_dish_assigned", language: settings.languageEnum.resolved()))
|
||||
}
|
||||
|
||||
/// Adds a companion dish to an already-filled slot ("ensalada + salmón").
|
||||
func assignSecondaryDish(_ dish: Dish, to slot: MealSlot, plan: WeekPlan, settings: AppSettings) {
|
||||
guard slot.dishId != nil, slot.dishId != dish.id else { return }
|
||||
captureUndoSnapshot(plan: plan)
|
||||
slot.secondaryDishId = dish.id
|
||||
plan.updatedAt = Date()
|
||||
AnalyticsService.logEvent("secondary_dish_added")
|
||||
HapticManager.shared.notification(type: .success)
|
||||
showToastMessage(localizedString("toast_dish_assigned", language: settings.languageEnum.resolved()))
|
||||
}
|
||||
|
||||
func removeSecondaryDish(from slot: MealSlot, plan: WeekPlan) {
|
||||
guard slot.secondaryDishId != nil else { return }
|
||||
captureUndoSnapshot(plan: plan)
|
||||
slot.secondaryDishId = nil
|
||||
plan.updatedAt = Date()
|
||||
HapticManager.shared.notification(type: .warning)
|
||||
}
|
||||
|
||||
func removeDish(from slot: MealSlot, plan: WeekPlan, settings: AppSettings) {
|
||||
captureUndoSnapshot(plan: plan)
|
||||
|
||||
@@ -112,6 +133,7 @@ final class HomeViewModel: ObservableObject {
|
||||
slot.calendarEventId = nil
|
||||
}
|
||||
slot.dishId = nil
|
||||
slot.secondaryDishId = nil
|
||||
slot.isRuleOverridden = false
|
||||
plan.updatedAt = Date()
|
||||
applyCalendarSyncPolicy(plan: plan, settings: settings, shouldNotify: false)
|
||||
@@ -182,6 +204,7 @@ final class HomeViewModel: ObservableObject {
|
||||
slot.calendarEventId = nil
|
||||
}
|
||||
slot.dishId = nil
|
||||
slot.secondaryDishId = nil
|
||||
slot.isRuleOverridden = false
|
||||
slot.isEatingOut = false
|
||||
}
|
||||
@@ -247,8 +270,11 @@ final class HomeViewModel: ObservableObject {
|
||||
}
|
||||
|
||||
let targetDishId = targetSlot.dishId
|
||||
let targetSecondaryId = targetSlot.secondaryDishId
|
||||
sourceSlot.dishId = targetDishId
|
||||
targetSlot.dishId = sourceDishId
|
||||
targetSlot.secondaryDishId = sourceSlot.secondaryDishId
|
||||
sourceSlot.secondaryDishId = targetSecondaryId
|
||||
sourceSlot.isRuleOverridden = false
|
||||
targetSlot.isRuleOverridden = false
|
||||
|
||||
@@ -293,9 +319,12 @@ final class HomeViewModel: ObservableObject {
|
||||
let key = SlotKey(dayOfWeek: slot.dayOfWeek, mealType: slot.mealType)
|
||||
if let previousDishId = previousByKey[key]?.dishId, dishIds.contains(previousDishId) {
|
||||
slot.dishId = previousDishId
|
||||
let previousSecondary = previousByKey[key]?.secondaryDishId
|
||||
slot.secondaryDishId = previousSecondary.flatMap { dishIds.contains($0) ? $0 : nil }
|
||||
copiedCount += 1
|
||||
} else {
|
||||
slot.dishId = nil
|
||||
slot.secondaryDishId = nil
|
||||
}
|
||||
slot.isRuleOverridden = false
|
||||
updateRuleOverrideFlag(for: slot, plan: currentPlan, allTags: allTags, allDishes: allDishes)
|
||||
@@ -324,10 +353,12 @@ final class HomeViewModel: ObservableObject {
|
||||
|
||||
if let snap = lastSlotsSnapshot[slot.id] {
|
||||
slot.dishId = snap.dishId
|
||||
slot.secondaryDishId = snap.secondaryDishId
|
||||
slot.isRuleOverridden = snap.isRuleOverridden
|
||||
slot.isEatingOut = snap.isEatingOut
|
||||
} else {
|
||||
slot.dishId = nil
|
||||
slot.secondaryDishId = nil
|
||||
slot.isRuleOverridden = false
|
||||
slot.isEatingOut = false
|
||||
}
|
||||
@@ -353,7 +384,7 @@ final class HomeViewModel: ObservableObject {
|
||||
guard !isApplyingUndo else { return }
|
||||
lastWeekStartSnapshot = plan.weekStartDate
|
||||
lastSlotsSnapshot = Dictionary(plan.slotList.map { slot in
|
||||
(slot.id, SlotSnapshot(dishId: slot.dishId, isRuleOverridden: slot.isRuleOverridden, isEatingOut: slot.isEatingOut))
|
||||
(slot.id, SlotSnapshot(dishId: slot.dishId, secondaryDishId: slot.secondaryDishId, isRuleOverridden: slot.isRuleOverridden, isEatingOut: slot.isEatingOut))
|
||||
}, uniquingKeysWith: { first, _ in first })
|
||||
hasUndoSnapshot = true
|
||||
}
|
||||
@@ -463,6 +494,7 @@ final class HomeViewModel: ObservableObject {
|
||||
slot.calendarEventId = nil
|
||||
}
|
||||
slot.dishId = nil
|
||||
slot.secondaryDishId = nil
|
||||
slot.isRuleOverridden = false
|
||||
slot.isEatingOut = true
|
||||
plan.updatedAt = Date()
|
||||
|
||||
Reference in New Issue
Block a user