import WidgetKit import SwiftUI /// Watch complication: today's meals from the last snapshot the watch app /// stored in the app group. Refreshed via WidgetCenter whenever the watch app /// receives new data from the iPhone. @main struct MealMoodWatchWidgetBundle: WidgetBundle { var body: some Widget { MealMoodWatchWidget() } } struct TodayEntry: TimelineEntry { let date: Date let day: WatchWeekPayload.Day? } struct TodayProvider: TimelineProvider { func placeholder(in context: Context) -> TodayEntry { TodayEntry(date: Date(), day: sampleDay) } func getSnapshot(in context: Context, completion: @escaping (TodayEntry) -> Void) { completion(TodayEntry(date: Date(), day: currentDay ?? sampleDay)) } func getTimeline(in context: Context, completion: @escaping (Timeline) -> Void) { let entry = TodayEntry(date: Date(), day: currentDay) // Refresh after midnight so "today" rolls over even without new pushes. let nextMidnight = Calendar.current.startOfDay(for: Date()).addingTimeInterval(86_400 + 300) completion(Timeline(entries: [entry], policy: .after(nextMidnight))) } private var currentDay: WatchWeekPayload.Day? { guard let payload = WatchWeekPayload.stored() else { return nil } let todayOffset = (Calendar.current.component(.weekday, from: Date()) + 5) % 7 return payload.days.first { $0.dayOfWeek == todayOffset } ?? payload.days.first(where: \.isToday) } private var sampleDay: WatchWeekPayload.Day { WatchWeekPayload.Day( dayOfWeek: 0, title: "Lun 8", isToday: true, meals: [ .init(type: "lunch", label: "Comida", name: "Ensalada César"), .init(type: "dinner", label: "Cena", name: "Salmón al horno") ] ) } } struct MealMoodWatchWidget: Widget { var body: some WidgetConfiguration { StaticConfiguration(kind: "MealMoodWatchToday", provider: TodayProvider()) { entry in TodayComplicationView(entry: entry) .containerBackground(.clear, for: .widget) } .configurationDisplayName("MealMood") .supportedFamilies([.accessoryRectangular, .accessoryInline, .accessoryCircular]) } } struct TodayComplicationView: View { @Environment(\.widgetFamily) private var family let entry: TodayEntry var body: some View { switch family { case .accessoryInline: if let meal = nextMeal { Text("\(meal.label): \(meal.name ?? "—")") } else { Text("MealMood") } case .accessoryCircular: VStack(spacing: 0) { Image(systemName: WatchWeekPayload.icon(for: nextMeal?.type ?? "dinner")) .font(.system(size: 14, weight: .semibold)) Text(nextMeal?.name?.prefix(6) ?? "—") .font(.system(size: 9)) .lineLimit(1) } default: VStack(alignment: .leading, spacing: 2) { if let day = entry.day { ForEach(Array(day.meals.prefix(3).enumerated()), id: \.offset) { _, meal in HStack(spacing: 3) { Image(systemName: WatchWeekPayload.icon(for: meal.type)) .font(.system(size: 9)) Text(meal.name ?? "—") .font(.system(size: 12, weight: .medium)) .lineLimit(1) } } } else { Text(verbatim: "MealMood") .font(.system(size: 12, weight: .semibold)) Image(systemName: "iphone.and.arrow.forward") .font(.system(size: 10)) .foregroundStyle(.secondary) } } .frame(maxWidth: .infinity, alignment: .leading) } } /// Lunch until 16:00 local, dinner after — simple heuristic for the small families. private var nextMeal: WatchWeekPayload.Meal? { guard let meals = entry.day?.meals, !meals.isEmpty else { return nil } let hour = Calendar.current.component(.hour, from: entry.date) if hour >= 16, let dinner = meals.last(where: { $0.name != nil }) { return dinner } return meals.first(where: { $0.name != nil }) ?? meals.first } }