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 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_013H6bXqGX1ygwib1Dm3n3UG
This commit is contained in:
alexandrev-tibco
2026-09-10 21:14:49 +02:00
parent 1c62182f61
commit ff5c6c7895
10 changed files with 77 additions and 2 deletions
@@ -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";
@@ -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";
@@ -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";
@@ -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";
@@ -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";
@@ -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";
+62
View File
@@ -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",
@@ -111,3 +111,4 @@ final class NextWeekNavigationUITests: XCTestCase {
}
}
+1 -1
View File
@@ -17,7 +17,7 @@
<key>CFBundleShortVersionString</key>
<string>2.1.1</string>
<key>CFBundleVersion</key>
<string>86</string>
<string>87</string>
<key>NSExtension</key>
<dict>
<key>NSExtensionPointIdentifier</key>
+1 -1
View File
@@ -17,7 +17,7 @@
<key>CFBundleShortVersionString</key>
<string>2.1.1</string>
<key>CFBundleVersion</key>
<string>86</string>
<string>87</string>
<key>NSExtension</key>
<dict>
<key>NSExtensionPointIdentifier</key>