2.0: dish editor gains photo picker and ingredients section
- PhotosPicker with downscaling (1024pt max, JPEG 0.8) before storing in photoData; change/remove actions. - Ingredients: editable line list (add/edit/remove) + on-device "Generate" from the dish name (hidden when Foundation Models unavailable; paywall after the free generation quota, source=ingredient_generation). - DishViewModel: drafts ingredients/photo, cleans empty lines on save. - Localized 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:
@@ -7,6 +7,9 @@ final class DishViewModel: ObservableObject {
|
||||
@Published var descriptionText: String = ""
|
||||
@Published var selectedTagIds: Set<UUID> = []
|
||||
@Published var isPriority: Bool = false
|
||||
@Published var ingredients: [String] = []
|
||||
@Published var photoData: Data?
|
||||
@Published var isGeneratingIngredients: Bool = false
|
||||
@Published var showTagSelector: Bool = false
|
||||
@Published var showDeleteAlert: Bool = false
|
||||
@Published var showAssignedDeleteAlert: Bool = false
|
||||
@@ -25,6 +28,8 @@ final class DishViewModel: ObservableObject {
|
||||
descriptionText = dish.descriptionText ?? ""
|
||||
selectedTagIds = Set(dish.tagIds)
|
||||
isPriority = dish.isPriority
|
||||
ingredients = dish.ingredients
|
||||
photoData = dish.photoData
|
||||
}
|
||||
|
||||
func reset() {
|
||||
@@ -33,23 +38,51 @@ final class DishViewModel: ObservableObject {
|
||||
descriptionText = ""
|
||||
selectedTagIds = []
|
||||
isPriority = false
|
||||
ingredients = []
|
||||
photoData = nil
|
||||
}
|
||||
|
||||
/// Generates ingredient lines on-device from the dish name and replaces
|
||||
/// the current draft list. No-op when unavailable or the name is empty.
|
||||
func generateIngredients(language: AppLanguage) {
|
||||
guard !isGeneratingIngredients, isValid else { return }
|
||||
isGeneratingIngredients = true
|
||||
let dishName = name
|
||||
Task {
|
||||
let lines = await IngredientGenerator.generate(dishName: dishName, language: language)
|
||||
await MainActor.run {
|
||||
self.isGeneratingIngredients = false
|
||||
guard !lines.isEmpty else { return }
|
||||
self.ingredients = lines
|
||||
PremiumAccess.recordIngredientGeneration()
|
||||
AnalyticsService.logIngredientsGenerated(lineCount: lines.count, source: "dish_editor")
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func save(context: ModelContext) {
|
||||
let trimmedName = name.trimmingCharacters(in: .whitespaces)
|
||||
guard !trimmedName.isEmpty else { return }
|
||||
|
||||
let cleanIngredients = ingredients
|
||||
.map { $0.trimmingCharacters(in: .whitespacesAndNewlines) }
|
||||
.filter { !$0.isEmpty }
|
||||
|
||||
if let dish = editingDish {
|
||||
dish.name = trimmedName
|
||||
dish.descriptionText = descriptionText.isEmpty ? nil : descriptionText
|
||||
dish.tagIds = Array(selectedTagIds)
|
||||
dish.isPriority = isPriority
|
||||
dish.ingredients = cleanIngredients
|
||||
dish.photoData = photoData
|
||||
} else {
|
||||
let dish = Dish(
|
||||
name: trimmedName,
|
||||
descriptionText: descriptionText.isEmpty ? nil : descriptionText,
|
||||
tagIds: Array(selectedTagIds),
|
||||
isPriority: isPriority
|
||||
isPriority: isPriority,
|
||||
ingredients: cleanIngredients,
|
||||
photoData: photoData
|
||||
)
|
||||
context.insert(dish)
|
||||
AnalyticsService.logDishAdded(tagCount: selectedTagIds.count)
|
||||
|
||||
Reference in New Issue
Block a user