Files
FamilyMealPlanner/MealMood/ViewModels/DishViewModel.swift
T
alexandrev-tibco 6c7e12b41f 1.0.4: Smarter auto-assign, quick dish entry, calendar fix, frictionless onboarding
- AutocompleteEngine: no-repeat rule now applies to all dishes (not just tagless ones); fallback still allows repeats when no alternative exists
- CalendarService: update existing events instead of delete+create to prevent duplicates; fix weekStartDate using plan date instead of currentWeekStart
- DishFormView: form stays open after saving in create mode for quick multi-dish entry; Done button to close; saved feedback banner
- OnboardingViewModel/FirstDishesStepView: dishes are now optional during onboarding; Finish button always enabled
- AnalyticsService: new events — onboarding_step_viewed(step) and auto_assign_used(filled, unfilled)
- Localization: dish_done and first_dishes_optional_hint added in 6 languages; release notes updated in all 6 languages
- Version bump: 1.0.3 → 1.0.4 (build 19)

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-04-19 11:35:50 +02:00

92 lines
2.7 KiB
Swift

import SwiftUI
import SwiftData
@MainActor
final class DishViewModel: ObservableObject {
@Published var name: String = ""
@Published var descriptionText: String = ""
@Published var selectedTagIds: Set<UUID> = []
@Published var showTagSelector: Bool = false
@Published var showDeleteAlert: Bool = false
@Published var showAssignedDeleteAlert: Bool = false
var editingDish: Dish?
var isEditing: Bool { editingDish != nil }
var isValid: Bool {
!name.trimmingCharacters(in: .whitespaces).isEmpty
}
func loadDish(_ dish: Dish) {
editingDish = dish
name = dish.name
descriptionText = dish.descriptionText ?? ""
selectedTagIds = Set(dish.tagIds)
}
func reset() {
editingDish = nil
name = ""
descriptionText = ""
selectedTagIds = []
}
func save(context: ModelContext) {
let trimmedName = name.trimmingCharacters(in: .whitespaces)
guard !trimmedName.isEmpty else { return }
if let dish = editingDish {
dish.name = trimmedName
dish.descriptionText = descriptionText.isEmpty ? nil : descriptionText
dish.tagIds = Array(selectedTagIds)
} else {
let dish = Dish(
name: trimmedName,
descriptionText: descriptionText.isEmpty ? nil : descriptionText,
tagIds: Array(selectedTagIds)
)
context.insert(dish)
AnalyticsService.logDishAdded(tagCount: selectedTagIds.count)
}
try? context.save()
reset()
}
func deleteDish(context: ModelContext) -> Bool {
guard let dish = editingDish else { return false }
if isDishAssignedInCurrentWeek(dishId: dish.id, context: context) {
showAssignedDeleteAlert = true
return false
}
context.delete(dish)
try? context.save()
AnalyticsService.logDishDeleted()
reset()
return true
}
func canDelete(currentPlan: WeekPlan?) -> Bool {
guard let dish = editingDish, let plan = currentPlan else { return true }
return !plan.slots.contains { $0.dishId == dish.id }
}
private func isDishAssignedInCurrentWeek(dishId: UUID, context: ModelContext) -> Bool {
let currentWeekStart = Date().startOfWeek()
let descriptor = FetchDescriptor<WeekPlan>(
predicate: #Predicate<WeekPlan> { plan in
plan.weekStartDate == currentWeekStart
}
)
guard let currentPlan = try? context.fetch(descriptor).first else {
return false
}
return currentPlan.slots.contains { $0.dishId == dishId }
}
}