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