From ff5c6c7895d0d2b6e336200acfca6d64d7e51a51 Mon Sep 17 00:00:00 2001 From: alexandrev-tibco Date: Thu, 10 Sep 2026 21:14:49 +0200 Subject: [PATCH] home: aviso visible de incumplimientos con acceso al detalle El panel de avisos solo se alcanzaba desde el menu "..." de la barra, asi que los incumplimientos pasaban desapercibidos. Ahora, cuando la semana rompe reglas, aparece un banner bajo el selector de semana con el numero de comidas afectadas, un resumen de donde estan (Lun - Cena, Mie - Comida) y un boton Ver que abre el panel con la explicacion y el arreglo. Co-Authored-By: Claude Fable 5 Claude-Session: https://claude.ai/code/session_013H6bXqGX1ygwib1Dm3n3UG --- .../Resources/de.lproj/Localizable.strings | 2 + .../Resources/en.lproj/Localizable.strings | 2 + .../Resources/es.lproj/Localizable.strings | 2 + .../Resources/fr.lproj/Localizable.strings | 2 + .../Resources/it.lproj/Localizable.strings | 2 + .../Resources/pt-BR.lproj/Localizable.strings | 2 + MealMood/Views/Home/HomeView.swift | 62 +++++++++++++++++++ MealMoodUITests/OnboardingFlowUITests.swift | 1 + MealMoodWatchWidget/Info.plist | 2 +- MealMoodWidget/Info.plist | 2 +- 10 files changed, 77 insertions(+), 2 deletions(-) diff --git a/MealMood/Resources/de.lproj/Localizable.strings b/MealMood/Resources/de.lproj/Localizable.strings index 60eae50..47e52a9 100644 --- a/MealMood/Resources/de.lproj/Localizable.strings +++ b/MealMood/Resources/de.lproj/Localizable.strings @@ -460,3 +460,5 @@ "violations_replace" = "Gericht ändern"; "stats_tag_by_meal_title" = "Tags nach Mahlzeit"; "stats_dishes_per_tag_title" = "Gerichte pro Tag"; +"violations_banner_title" = "%d Mahlzeit(en) verletzen deine Regeln"; +"violations_banner_cta" = "Ansehen"; diff --git a/MealMood/Resources/en.lproj/Localizable.strings b/MealMood/Resources/en.lproj/Localizable.strings index ad999e0..0ddd1bb 100644 --- a/MealMood/Resources/en.lproj/Localizable.strings +++ b/MealMood/Resources/en.lproj/Localizable.strings @@ -460,3 +460,5 @@ "violations_replace" = "Change dish"; "stats_tag_by_meal_title" = "Tags by meal type"; "stats_dishes_per_tag_title" = "Dishes per tag"; +"violations_banner_title" = "%d meal(s) break your rules"; +"violations_banner_cta" = "View"; diff --git a/MealMood/Resources/es.lproj/Localizable.strings b/MealMood/Resources/es.lproj/Localizable.strings index c0567e0..2e904ac 100644 --- a/MealMood/Resources/es.lproj/Localizable.strings +++ b/MealMood/Resources/es.lproj/Localizable.strings @@ -460,3 +460,5 @@ "violations_replace" = "Cambiar plato"; "stats_tag_by_meal_title" = "Etiquetas por tipo de comida"; "stats_dishes_per_tag_title" = "Platos por etiqueta"; +"violations_banner_title" = "%d comida(s) incumplen tus reglas"; +"violations_banner_cta" = "Ver"; diff --git a/MealMood/Resources/fr.lproj/Localizable.strings b/MealMood/Resources/fr.lproj/Localizable.strings index da22f29..f30b83d 100644 --- a/MealMood/Resources/fr.lproj/Localizable.strings +++ b/MealMood/Resources/fr.lproj/Localizable.strings @@ -460,3 +460,5 @@ "violations_replace" = "Changer de plat"; "stats_tag_by_meal_title" = "Étiquettes par type de repas"; "stats_dishes_per_tag_title" = "Plats par étiquette"; +"violations_banner_title" = "%d repas ne respecte(nt) pas vos règles"; +"violations_banner_cta" = "Voir"; diff --git a/MealMood/Resources/it.lproj/Localizable.strings b/MealMood/Resources/it.lproj/Localizable.strings index a92b3ba..9008f23 100644 --- a/MealMood/Resources/it.lproj/Localizable.strings +++ b/MealMood/Resources/it.lproj/Localizable.strings @@ -460,3 +460,5 @@ "violations_replace" = "Cambia piatto"; "stats_tag_by_meal_title" = "Etichette per tipo di pasto"; "stats_dishes_per_tag_title" = "Piatti per etichetta"; +"violations_banner_title" = "%d pasto/i non rispettano le tue regole"; +"violations_banner_cta" = "Vedi"; diff --git a/MealMood/Resources/pt-BR.lproj/Localizable.strings b/MealMood/Resources/pt-BR.lproj/Localizable.strings index ba89f85..c220807 100644 --- a/MealMood/Resources/pt-BR.lproj/Localizable.strings +++ b/MealMood/Resources/pt-BR.lproj/Localizable.strings @@ -460,3 +460,5 @@ "violations_replace" = "Trocar prato"; "stats_tag_by_meal_title" = "Etiquetas por tipo de refeição"; "stats_dishes_per_tag_title" = "Pratos por etiqueta"; +"violations_banner_title" = "%d refeição(ões) quebram suas regras"; +"violations_banner_cta" = "Ver"; diff --git a/MealMood/Views/Home/HomeView.swift b/MealMood/Views/Home/HomeView.swift index 4e6a785..abb300e 100644 --- a/MealMood/Views/Home/HomeView.swift +++ b/MealMood/Views/Home/HomeView.swift @@ -317,11 +317,73 @@ struct HomeView: View { .padding(.bottom, 10) } + /// Banner shown on the home whenever the week breaks rules. The panel used + /// to be reachable only from the "···" menu, so violations went unnoticed. + @ViewBuilder + private func violationsBanner(plan: WeekPlan) -> some View { + let violations = AutocompleteEngine.findViolations(plan: plan, allDishes: dishes, allTags: tags) + if !violations.isEmpty { + Button { + showViolationsPanel = true + } label: { + HStack(spacing: 10) { + Image(systemName: "exclamationmark.triangle.fill") + .font(.system(size: 16)) + .foregroundColor(.mealMoodWarning) + + VStack(alignment: .leading, spacing: 2) { + Text(String(format: String(localized: "violations_banner_title"), violations.count)) + .font(.mealMoodSmall.weight(.semibold)) + .foregroundColor(.mealMoodTextPrimary) + Text(violationsSummary(violations)) + .font(.mealMoodCaption) + .foregroundColor(.mealMoodTextSecondary) + .lineLimit(1) + } + + Spacer() + + Text("violations_banner_cta") + .font(.mealMoodCaption.weight(.semibold)) + .foregroundColor(.white) + .padding(.horizontal, 10) + .padding(.vertical, 5) + .background(Color.mealMoodWarning) + .clipShape(Capsule()) + } + .padding(.horizontal, 12) + .padding(.vertical, 10) + .background(Color.mealMoodWarning.opacity(0.12)) + .clipShape(RoundedRectangle(cornerRadius: 12)) + .overlay( + RoundedRectangle(cornerRadius: 12) + .stroke(Color.mealMoodWarning.opacity(0.45), lineWidth: 1) + ) + } + .buttonStyle(.plain) + .padding(.horizontal, 16) + .padding(.bottom, 8) + } + } + + /// "Lun · Cena, Mié · Comida" — where the problems are, at a glance. + private func violationsSummary(_ violations: [AutocompleteEngine.RuleViolation]) -> String { + let language = settings?.languageEnum.resolved() ?? .spanish + let keys = ["day_mon","day_tue","day_wed","day_thu","day_fri","day_sat","day_sun"] + return violations.prefix(3).map { violation in + let day = violation.dayOfWeek >= 0 && violation.dayOfWeek < keys.count + ? localizedString(keys[violation.dayOfWeek], language: language) : "" + return "\(day) · \(localizedString(violation.mealType, language: language))" + }.joined(separator: ", ") + } + @ViewBuilder private func mainContent(plan: WeekPlan, settings: AppSettings) -> some View { VStack(spacing: 0) { weekSwitcher(settings: settings) + violationsBanner(plan: plan) + if showWeekLimitUpsell && !settings.isPremium { PremiumUpsellBanner( messageKey: "premium_limit_future_weeks", diff --git a/MealMoodUITests/OnboardingFlowUITests.swift b/MealMoodUITests/OnboardingFlowUITests.swift index 37e72af..1f31445 100644 --- a/MealMoodUITests/OnboardingFlowUITests.swift +++ b/MealMoodUITests/OnboardingFlowUITests.swift @@ -111,3 +111,4 @@ final class NextWeekNavigationUITests: XCTestCase { } } + diff --git a/MealMoodWatchWidget/Info.plist b/MealMoodWatchWidget/Info.plist index f8407dd..4d14ec2 100644 --- a/MealMoodWatchWidget/Info.plist +++ b/MealMoodWatchWidget/Info.plist @@ -17,7 +17,7 @@ CFBundleShortVersionString 2.1.1 CFBundleVersion - 86 + 87 NSExtension NSExtensionPointIdentifier diff --git a/MealMoodWidget/Info.plist b/MealMoodWidget/Info.plist index f8407dd..4d14ec2 100644 --- a/MealMoodWidget/Info.plist +++ b/MealMoodWidget/Info.plist @@ -17,7 +17,7 @@ CFBundleShortVersionString 2.1.1 CFBundleVersion - 86 + 87 NSExtension NSExtensionPointIdentifier