diff --git a/MealMood/Resources/Info.plist b/MealMood/Resources/Info.plist
index 1faf265..8163334 100644
--- a/MealMood/Resources/Info.plist
+++ b/MealMood/Resources/Info.plist
@@ -16,6 +16,17 @@
APPL
CFBundleShortVersionString
1.1.5
+ CFBundleURLTypes
+
+
+ CFBundleURLName
+ com.alexandrevazquez.mealmood
+ CFBundleURLSchemes
+
+ mealmood
+
+
+
CFBundleVersion
54
GADApplicationIdentifier
diff --git a/MealMood/Resources/de.lproj/Localizable.strings b/MealMood/Resources/de.lproj/Localizable.strings
index ea97000..fd411db 100644
--- a/MealMood/Resources/de.lproj/Localizable.strings
+++ b/MealMood/Resources/de.lproj/Localizable.strings
@@ -376,3 +376,7 @@
"settings_weekly_reminder" = "Wöchentliche Planungserinnerung";
"settings_notifications_denied_hint" = "Mitteilungen sind in den Systemeinstellungen deaktiviert.";
"settings_open_system_settings" = "Einstellungen öffnen";
+
+/* Widget promo (1.2.0) */
+"widget_promo_title" = "Die heutigen Mahlzeiten auf deinem Home-Bildschirm";
+"widget_promo_body" = "Halte den Home-Bildschirm gedrückt, tippe auf + und suche nach MealMood, um das Widget hinzuzufügen.";
diff --git a/MealMood/Resources/en.lproj/Localizable.strings b/MealMood/Resources/en.lproj/Localizable.strings
index 4e74bce..96b3f26 100644
--- a/MealMood/Resources/en.lproj/Localizable.strings
+++ b/MealMood/Resources/en.lproj/Localizable.strings
@@ -376,3 +376,7 @@
"settings_weekly_reminder" = "Weekly planning reminder";
"settings_notifications_denied_hint" = "Notifications are disabled in system settings.";
"settings_open_system_settings" = "Open Settings";
+
+/* Widget promo (1.2.0) */
+"widget_promo_title" = "Today's meals on your Home Screen";
+"widget_promo_body" = "Long-press your Home Screen, tap +, and search for MealMood to add the widget.";
diff --git a/MealMood/Resources/es.lproj/Localizable.strings b/MealMood/Resources/es.lproj/Localizable.strings
index 20578c6..18b8a53 100644
--- a/MealMood/Resources/es.lproj/Localizable.strings
+++ b/MealMood/Resources/es.lproj/Localizable.strings
@@ -376,3 +376,7 @@
"settings_weekly_reminder" = "Recordatorio semanal de planificación";
"settings_notifications_denied_hint" = "Las notificaciones están desactivadas en los ajustes del sistema.";
"settings_open_system_settings" = "Abrir Ajustes";
+
+/* Widget promo (1.2.0) */
+"widget_promo_title" = "Las comidas de hoy en tu pantalla de inicio";
+"widget_promo_body" = "Mantén pulsada la pantalla de inicio, toca + y busca MealMood para añadir el widget.";
diff --git a/MealMood/Resources/fr.lproj/Localizable.strings b/MealMood/Resources/fr.lproj/Localizable.strings
index 41399b2..381ca0a 100644
--- a/MealMood/Resources/fr.lproj/Localizable.strings
+++ b/MealMood/Resources/fr.lproj/Localizable.strings
@@ -376,3 +376,7 @@
"settings_weekly_reminder" = "Rappel hebdomadaire de planification";
"settings_notifications_denied_hint" = "Les notifications sont désactivées dans les réglages du système.";
"settings_open_system_settings" = "Ouvrir Réglages";
+
+/* Widget promo (1.2.0) */
+"widget_promo_title" = "Les repas du jour sur votre écran d'accueil";
+"widget_promo_body" = "Appuyez longuement sur l'écran d'accueil, touchez + et cherchez MealMood pour ajouter le widget.";
diff --git a/MealMood/Resources/it.lproj/Localizable.strings b/MealMood/Resources/it.lproj/Localizable.strings
index 99c165c..349db56 100644
--- a/MealMood/Resources/it.lproj/Localizable.strings
+++ b/MealMood/Resources/it.lproj/Localizable.strings
@@ -376,3 +376,7 @@
"settings_weekly_reminder" = "Promemoria settimanale di pianificazione";
"settings_notifications_denied_hint" = "Le notifiche sono disattivate nelle impostazioni di sistema.";
"settings_open_system_settings" = "Apri Impostazioni";
+
+/* Widget promo (1.2.0) */
+"widget_promo_title" = "I pasti di oggi sulla schermata Home";
+"widget_promo_body" = "Tieni premuta la schermata Home, tocca + e cerca MealMood per aggiungere il widget.";
diff --git a/MealMood/Resources/pt-BR.lproj/Localizable.strings b/MealMood/Resources/pt-BR.lproj/Localizable.strings
index 1e02143..1449a81 100644
--- a/MealMood/Resources/pt-BR.lproj/Localizable.strings
+++ b/MealMood/Resources/pt-BR.lproj/Localizable.strings
@@ -376,3 +376,7 @@
"settings_weekly_reminder" = "Lembrete semanal de planejamento";
"settings_notifications_denied_hint" = "As notificações estão desativadas nos ajustes do sistema.";
"settings_open_system_settings" = "Abrir Ajustes";
+
+/* Widget promo (1.2.0) */
+"widget_promo_title" = "As refeições de hoje na sua tela de início";
+"widget_promo_body" = "Pressione a tela de início, toque em + e procure MealMood para adicionar o widget.";
diff --git a/MealMood/Views/Home/HomeView.swift b/MealMood/Views/Home/HomeView.swift
index a690590..6c24314 100644
--- a/MealMood/Views/Home/HomeView.swift
+++ b/MealMood/Views/Home/HomeView.swift
@@ -28,6 +28,10 @@ struct HomeView: View {
@State private var showPostOnboardingAutoAssignPrompt: Bool = false
@State private var showPostOnboardingPremiumPrompt: Bool = false
@State private var hasEvaluatedPostOnboardingPrompts: Bool = false
+ @State private var showWidgetPromo: Bool = false
+
+ private static let widgetPromoDismissedKey = "widget_promo_dismissed"
+ private static let homeSessionCountKey = "home_session_count"
@State private var showExportStylePicker: Bool = false
@State private var pendingExportPlan: WeekPlan?
@State private var shareImageURL: URL?
@@ -228,6 +232,12 @@ struct HomeView: View {
.padding(.bottom, 8)
}
+ if showWidgetPromo {
+ widgetPromoCard
+ .padding(.horizontal, 16)
+ .padding(.bottom, 8)
+ }
+
VStack(spacing: 10) {
WeekCalendarView(
plan: plan,
@@ -524,8 +534,14 @@ struct HomeView: View {
)
wasWeekComplete = isWeekComplete(plan: plan)
evaluatePostOnboardingPromptsIfNeeded(plan: plan, settings: settings)
+ evaluateWidgetPromo()
updateWidget(settings: settings)
}
+ .onOpenURL { url in
+ // mealmood://today — widget deep-link back to the current week.
+ guard url.scheme == "mealmood", url.host == "today" else { return }
+ viewModel.jumpToWeek(startDate: Date().startOfWeek())
+ }
.onChange(of: plan.updatedAt) { _, _ in
let nowComplete = isWeekComplete(plan: plan)
if nowComplete && !wasWeekComplete {
@@ -944,6 +960,65 @@ struct HomeView: View {
return DefaultDataService.createWeekPlan(for: viewModel.currentWeekStart, settings: settings, context: context)
}
+ private var widgetPromoCard: some View {
+ HStack(spacing: 12) {
+ Image(systemName: "apps.iphone.badge.plus")
+ .font(.system(size: 24))
+ .foregroundColor(.mealMoodCoral)
+
+ VStack(alignment: .leading, spacing: 2) {
+ Text("widget_promo_title")
+ .font(.mealMoodBodyBold)
+ .foregroundColor(.mealMoodTextPrimary)
+ Text("widget_promo_body")
+ .font(.mealMoodSmall)
+ .foregroundColor(.mealMoodTextSecondary)
+ .fixedSize(horizontal: false, vertical: true)
+ }
+
+ Spacer()
+
+ Button {
+ dismissWidgetPromo()
+ } label: {
+ Image(systemName: "xmark")
+ .font(.system(size: 12, weight: .semibold))
+ .foregroundColor(.mealMoodTextSecondary)
+ .frame(width: 28, height: 28)
+ .background(Color.mealMoodBackground)
+ .clipShape(Circle())
+ }
+ .buttonStyle(.plain)
+ }
+ .padding(14)
+ .background(Color.mealMoodSurface)
+ .cornerRadius(14)
+ .overlay(
+ RoundedRectangle(cornerRadius: 14)
+ .stroke(Color(hex: "#F0F0F0"), lineWidth: 1)
+ )
+ }
+
+ private func evaluateWidgetPromo() {
+ guard !UserDefaults.standard.bool(forKey: Self.widgetPromoDismissedKey) else { return }
+ let sessions = UserDefaults.standard.integer(forKey: Self.homeSessionCountKey) + 1
+ UserDefaults.standard.set(sessions, forKey: Self.homeSessionCountKey)
+ // From the 2nd session on: the user has seen the app's value, now make it sticky.
+ guard sessions >= 2 else { return }
+ if !showWidgetPromo {
+ showWidgetPromo = true
+ AnalyticsService.logEvent("widget_promo_shown")
+ }
+ }
+
+ private func dismissWidgetPromo() {
+ UserDefaults.standard.set(true, forKey: Self.widgetPromoDismissedKey)
+ withAnimation(.easeOut(duration: 0.2)) {
+ showWidgetPromo = false
+ }
+ AnalyticsService.logEvent("widget_promo_dismissed")
+ }
+
private func evaluateReviewPrompt() {
let descriptor = FetchDescriptor()
guard let plans = try? context.fetch(descriptor) else { return }
diff --git a/MealMoodWidget/MealMoodWidget.swift b/MealMoodWidget/MealMoodWidget.swift
index bd813f3..0279d21 100644
--- a/MealMoodWidget/MealMoodWidget.swift
+++ b/MealMoodWidget/MealMoodWidget.swift
@@ -199,6 +199,51 @@ private struct MediumWidgetView: View {
}
}
+// MARK: - Lock Screen widgets
+
+private struct AccessoryRectangularView: View {
+ let entry: TodayMealEntry
+
+ var body: some View {
+ VStack(alignment: .leading, spacing: 2) {
+ HStack(spacing: 3) {
+ Image(systemName: "fork.knife")
+ .font(.system(size: 10, weight: .bold))
+ Text(entry.weekdayName.isEmpty ? "MealMood" : entry.weekdayName)
+ .font(.system(size: 12, weight: .bold))
+ .lineLimit(1)
+ }
+ .widgetAccentable()
+
+ if let lunch = entry.lunch {
+ Text("\(Image(systemName: "sun.max.fill")) \(lunch)")
+ .font(.system(size: 12))
+ .lineLimit(1)
+ }
+ if let dinner = entry.dinner {
+ Text("\(Image(systemName: "moon.stars.fill")) \(dinner)")
+ .font(.system(size: 12))
+ .lineLimit(1)
+ }
+ if entry.lunch == nil && entry.dinner == nil {
+ Text("—")
+ .font(.system(size: 12))
+ }
+ }
+ .frame(maxWidth: .infinity, alignment: .leading)
+ }
+}
+
+private struct AccessoryInlineView: View {
+ let entry: TodayMealEntry
+
+ var body: some View {
+ // Inline is a single line: show the next relevant meal.
+ let name = entry.dinner ?? entry.lunch
+ Label(name ?? "MealMood", systemImage: "fork.knife")
+ }
+}
+
// MARK: - Widget entry view
struct MealMoodWidgetView: View {
@@ -206,12 +251,24 @@ struct MealMoodWidgetView: View {
@Environment(\.widgetFamily) var family
var body: some View {
- switch family {
- case .systemMedium:
- MediumWidgetView(entry: entry)
- default:
- SmallWidgetView(entry: entry)
+ Group {
+ switch family {
+ case .systemMedium:
+ MediumWidgetView(entry: entry)
+ .containerBackground(for: .widget) { Color.white }
+ case .accessoryRectangular:
+ AccessoryRectangularView(entry: entry)
+ .containerBackground(for: .widget) { Color.clear }
+ case .accessoryInline:
+ AccessoryInlineView(entry: entry)
+ .containerBackground(for: .widget) { Color.clear }
+ default:
+ SmallWidgetView(entry: entry)
+ .containerBackground(for: .widget) { Color.white }
+ }
}
+ // Deep-link into the app's current week ("today") when tapped.
+ .widgetURL(URL(string: "mealmood://today"))
}
}
@@ -224,10 +281,9 @@ struct MealMoodWidget: Widget {
var body: some WidgetConfiguration {
StaticConfiguration(kind: kind, provider: Provider()) { entry in
MealMoodWidgetView(entry: entry)
- .containerBackground(for: .widget) { Color.white }
}
.configurationDisplayName("MealMood")
.description("Today's lunch and dinner at a glance.")
- .supportedFamilies([.systemSmall, .systemMedium])
+ .supportedFamilies([.systemSmall, .systemMedium, .accessoryRectangular, .accessoryInline])
}
}