Release 1.0.0 baseline

This commit is contained in:
alexandrev-tibco
2026-02-26 10:27:09 +01:00
parent e593453abd
commit c5c7e3d2e4
24 changed files with 600 additions and 168 deletions
@@ -29,6 +29,8 @@ struct FirstDishesStepView: View {
?? availableTags.sorted(by: { $0.sortOrder < $1.sortOrder }).first?.id
}
private let maxVisibleSuggestions = 3
var body: some View {
ScrollView {
VStack(spacing: 24) {
@@ -165,12 +167,13 @@ struct FirstDishesStepView: View {
.foregroundColor(.mealMoodTextSecondary)
.padding(.horizontal, 24)
ForEach(suggestedDishes(), id: \.name) { suggestion in
let isAdded = viewModel.addedDishes.contains(where: {
$0.name.trimmingCharacters(in: .whitespacesAndNewlines).lowercased() == suggestion.name.lowercased()
})
Text("Added \(viewModel.addedDishes.count)/\(viewModel.maxOnboardingDishes)")
.font(.mealMoodCaption)
.foregroundColor(.mealMoodTextSecondary)
.padding(.horizontal, 24)
ForEach(visibleSuggestions(), id: \.name) { suggestion in
Button {
guard !isAdded else { return }
viewModel.addSuggestedDish(
name: suggestion.name,
preferredTagNames: suggestion.tagHints,
@@ -182,21 +185,28 @@ struct FirstDishesStepView: View {
Text(suggestion.name)
.font(.mealMoodBodyBold)
.foregroundColor(.mealMoodTextPrimary)
Text(isAdded ? "first_dishes_suggestion_added" : "first_dishes_suggestion_add")
Text("first_dishes_suggestion_add")
.font(.mealMoodCaption)
.foregroundColor(.mealMoodTextSecondary)
}
Spacer()
Image(systemName: isAdded ? "checkmark.circle.fill" : "plus.circle.fill")
.foregroundColor(isAdded ? .mealMoodSuccess : .mealMoodCoral)
Image(systemName: "plus.circle.fill")
.foregroundColor(.mealMoodCoral)
}
.padding(12)
.background(Color.mealMoodSurface)
.cornerRadius(12)
}
.disabled(isAdded)
.disabled(!viewModel.canAddMoreDishes)
.padding(.horizontal, 24)
}
if !viewModel.canAddMoreDishes {
Text("first_dishes_limit_reached")
.font(.mealMoodCaption)
.foregroundColor(.mealMoodTextSecondary)
.padding(.horizontal, 24)
}
}
Spacer(minLength: 40)
@@ -236,17 +246,41 @@ struct FirstDishesStepView: View {
return [
(name: "Roast chicken", tagHints: ["Meat"]),
(name: "Lentil stew", tagHints: ["Legumes"]),
(name: "Veggie pasta", tagHints: ["Pasta/Rice", "Vegetables"])
(name: "Veggie pasta", tagHints: ["Pasta/Rice", "Vegetables"]),
(name: "Chicken curry", tagHints: ["Meat"]),
(name: "Salmon with rice", tagHints: ["Fish", "Pasta/Rice"]),
(name: "Turkey meatballs", tagHints: ["Meat"]),
(name: "Chickpea salad", tagHints: ["Legumes", "Vegetables"]),
(name: "Beef tacos", tagHints: ["Meat"]),
(name: "Vegetable soup", tagHints: ["Vegetables"]),
(name: "Baked cod", tagHints: ["Fish"])
]
}
return [
(name: "Pollo asado", tagHints: ["Carne"]),
(name: "Lentejas estofadas", tagHints: ["Legumbres"]),
(name: "Pasta con verduras", tagHints: ["Pasta/Arroz", "Verduras"])
(name: "Pasta con verduras", tagHints: ["Pasta/Arroz", "Verduras"]),
(name: "Pollo al curry", tagHints: ["Carne"]),
(name: "Salmón con arroz", tagHints: ["Pescado", "Pasta/Arroz"]),
(name: "Albóndigas de pavo", tagHints: ["Carne"]),
(name: "Ensalada de garbanzos", tagHints: ["Legumbres", "Verduras"]),
(name: "Tacos de ternera", tagHints: ["Carne"]),
(name: "Crema de verduras", tagHints: ["Verduras"]),
(name: "Merluza al horno", tagHints: ["Pescado"])
]
}
private func visibleSuggestions() -> [(name: String, tagHints: [String])] {
let addedNames = Set(
viewModel.addedDishes.map {
$0.name.trimmingCharacters(in: .whitespacesAndNewlines).lowercased()
}
)
let remaining = suggestedDishes().filter { !addedNames.contains($0.name.lowercased()) }
return Array(remaining.prefix(maxVisibleSuggestions))
}
private func ensureDefaultTagsIfNeeded() {
let descriptor = FetchDescriptor<Tag>()
if (try? context.fetch(descriptor))?.isEmpty ?? true {