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:
@@ -570,7 +570,23 @@ struct HomeView: View {
|
||||
DispatchQueue.main.asyncAfter(deadline: .now() + 0.15) {
|
||||
attemptCreateDish()
|
||||
}
|
||||
}
|
||||
},
|
||||
onPickSecondDish: { dish in
|
||||
guard let freshSlot = plan.slotList.first(where: { $0.id == slotId }) else {
|
||||
selectedFilledSlotId = nil
|
||||
return
|
||||
}
|
||||
viewModel.assignSecondaryDish(dish, to: freshSlot, plan: plan, settings: settings)
|
||||
selectedFilledSlotId = nil
|
||||
},
|
||||
onRemoveSecondDish: plan.slotList.first(where: { $0.id == slotId })?.secondaryDishId != nil ? {
|
||||
guard let freshSlot = plan.slotList.first(where: { $0.id == slotId }) else {
|
||||
selectedFilledSlotId = nil
|
||||
return
|
||||
}
|
||||
viewModel.removeSecondaryDish(from: freshSlot, plan: plan)
|
||||
selectedFilledSlotId = nil
|
||||
} : nil
|
||||
)
|
||||
}
|
||||
}
|
||||
@@ -673,9 +689,14 @@ struct HomeView: View {
|
||||
let onPickDish: (Dish) -> Void
|
||||
let onCreateDish: () -> Void
|
||||
var onEatingOut: (() -> Void)? = nil
|
||||
/// Set for filled slots: taps add a companion dish instead of replacing
|
||||
/// when the "second dish" mode is on.
|
||||
var onPickSecondDish: ((Dish) -> Void)? = nil
|
||||
var onRemoveSecondDish: (() -> Void)? = nil
|
||||
|
||||
@Environment(\.dismiss) private var dismiss
|
||||
@State private var searchText: String = ""
|
||||
@State private var asSecondDish: Bool = false
|
||||
|
||||
private var filteredDishes: [Dish] {
|
||||
let trimmed = searchText.trimmingCharacters(in: .whitespacesAndNewlines)
|
||||
@@ -720,9 +741,35 @@ struct HomeView: View {
|
||||
.listRowBackground(Color(hex: "#EEF8F5"))
|
||||
}
|
||||
|
||||
if onPickSecondDish != nil, searchText.isEmpty {
|
||||
Toggle(isOn: $asSecondDish) {
|
||||
Label("home_pick_second_dish", systemImage: "plus.square.on.square")
|
||||
.font(.mealMoodBody)
|
||||
.foregroundColor(.mealMoodTextPrimary)
|
||||
}
|
||||
.tint(.mealMoodCoral)
|
||||
.listRowBackground(Color.mealMoodSurface)
|
||||
|
||||
if let onRemoveSecondDish {
|
||||
Button {
|
||||
dismiss()
|
||||
onRemoveSecondDish()
|
||||
} label: {
|
||||
Label("home_remove_second_dish", systemImage: "minus.square")
|
||||
.font(.mealMoodBody)
|
||||
.foregroundColor(.mealMoodError)
|
||||
}
|
||||
.listRowBackground(Color.mealMoodSurface)
|
||||
}
|
||||
}
|
||||
|
||||
ForEach(filteredDishes, id: \.id) { dish in
|
||||
Button {
|
||||
onPickDish(dish)
|
||||
if asSecondDish, let onPickSecondDish {
|
||||
onPickSecondDish(dish)
|
||||
} else {
|
||||
onPickDish(dish)
|
||||
}
|
||||
dismiss()
|
||||
} label: {
|
||||
VStack(alignment: .leading, spacing: 6) {
|
||||
|
||||
@@ -214,6 +214,7 @@ struct WeekCalendarView: View {
|
||||
dayOfWeek: day,
|
||||
mealType: mealType.rawValue,
|
||||
dishId: preferred.dishId,
|
||||
secondaryDishId: preferred.secondaryDishId,
|
||||
isRuleOverridden: preferred.isRuleOverridden,
|
||||
isEatingOut: preferred.isEatingOut
|
||||
)
|
||||
@@ -264,8 +265,9 @@ struct WeekCalendarView: View {
|
||||
if let slot = slot, let dishId = slot.dishId,
|
||||
let dishData = dishData(for: dishId) {
|
||||
let dishTags = tags.filter { dishData.tagIds.contains($0.id) }
|
||||
let secondaryName = slot.secondaryDishId.flatMap { self.dishData(for: $0)?.name }
|
||||
FilledSlotView(
|
||||
dishName: dishData.name,
|
||||
dishName: secondaryName.map { "\(dishData.name) + \($0)" } ?? dishData.name,
|
||||
dishDescription: dishData.descriptionText,
|
||||
tags: dishTags.map { (name: $0.localizedName(language: settings.languageEnum.resolved()), color: $0.color) },
|
||||
mealType: mealType,
|
||||
@@ -321,6 +323,7 @@ struct WeekCalendarView: View {
|
||||
let dayOfWeek: Int
|
||||
let mealType: String
|
||||
let dishId: UUID?
|
||||
let secondaryDishId: UUID?
|
||||
let isRuleOverridden: Bool
|
||||
let isEatingOut: Bool
|
||||
}
|
||||
@@ -370,6 +373,7 @@ struct WeekCalendarView: View {
|
||||
dayOfWeek: preferred.dayOfWeek,
|
||||
mealType: preferred.mealType,
|
||||
dishId: preferred.dishId,
|
||||
secondaryDishId: preferred.secondaryDishId,
|
||||
isRuleOverridden: preferred.isRuleOverridden,
|
||||
isEatingOut: preferred.isEatingOut
|
||||
)
|
||||
|
||||
@@ -513,6 +513,10 @@ struct WeekPlanShareView: View {
|
||||
guard let dishId = slot.dishId, let dish = dishes.first(where: { $0.id == dishId }) else {
|
||||
return String(localized: "share_slot_empty")
|
||||
}
|
||||
if let secondaryId = slot.secondaryDishId,
|
||||
let secondary = dishes.first(where: { $0.id == secondaryId }) {
|
||||
return "\(dish.name) + \(secondary.name)"
|
||||
}
|
||||
return dish.name
|
||||
}
|
||||
|
||||
|
||||
Reference in New Issue
Block a user