import SwiftUI /// Today page — Weather-app style: a bold colored header with the day, then /// one vivid gradient card per meal. struct TodayView: View { @EnvironmentObject private var store: WatchWeekStore /// Resolved as the view renders. The snapshot's own `isToday` is frozen at /// the moment the iPhone built it, which is how a Wednesday plan ended up /// showing on a Saturday. private var today: WatchWeekPayload.Day? { store.payload?.currentDay() } var body: some View { ScrollView { VStack(alignment: .leading, spacing: 8) { if let today { HStack(alignment: .firstTextBaseline) { Text(today.title) .font(.system(.title3, design: .rounded, weight: .bold)) .foregroundStyle(WatchTheme.coral) Spacer() Image(systemName: "fork.knife") .font(.caption) .foregroundStyle(.secondary) } .padding(.horizontal, 2) if today.isEmpty { EmptyDayView(text: store.payload?.emptyDayText) } else { ForEach(Array(today.meals.enumerated()), id: \.offset) { _, meal in MealCard(meal: meal, emptyText: store.payload?.emptyMealText) } } } else if let payload = store.payload { // There is a snapshot, but it doesn't cover today: better to // say so than to show another day's meals as if they were // today's. StaleView(text: payload.staleText) } else { EmptySyncView() } // When the watch is holding an old snapshot, this is what makes // it obvious instead of guessing why the meals look wrong. if let payload = store.payload { LastUpdatedFooter(payload: payload) } } } } } private struct LastUpdatedFooter: View { let payload: WatchWeekPayload var body: some View { HStack(spacing: 3) { Image(systemName: "arrow.clockwise") // Label comes localized from the iPhone; the date formats itself // with the watch's own locale. Text("\(payload.updatedLabel ?? "") \(payload.updatedAt.formatted(date: .abbreviated, time: .shortened))") } .font(.system(size: 9, weight: .medium, design: .rounded)) .foregroundStyle(.secondary) .frame(maxWidth: .infinity) .padding(.top, 6) } } struct MealCard: View { let meal: WatchWeekPayload.Meal /// Localized "nothing planned" coming from the iPhone — the watch bundle /// has no strings of its own. var emptyText: String? private var accent: Color { WatchTheme.accent(for: meal.type) } var body: some View { VStack(alignment: .leading, spacing: 4) { HStack(spacing: 5) { Image(systemName: WatchWeekPayload.icon(for: meal.type)) .font(.system(size: 11, weight: .bold)) Text(meal.label.uppercased()) .font(.system(size: 11, weight: .bold, design: .rounded)) .tracking(0.5) } .foregroundStyle(.white.opacity(0.85)) Text(meal.name ?? emptyText ?? "—") .font(.system(.body, design: .rounded, weight: .semibold)) .foregroundStyle(.white.opacity(meal.name == nil ? 0.7 : 1)) .lineLimit(2) .minimumScaleFactor(0.8) } .frame(maxWidth: .infinity, alignment: .leading) .padding(.horizontal, 10) .padding(.vertical, 9) .background( LinearGradient( colors: [accent, accent.opacity(0.65)], startPoint: .topLeading, endPoint: .bottomTrailing ), in: RoundedRectangle(cornerRadius: 12) ) } } /// The day exists in the snapshot but has no meals planned. struct EmptyDayView: View { var text: String? var body: some View { VStack(spacing: 6) { Image(systemName: "moon.zzz.fill") .font(.title3) .foregroundStyle(WatchTheme.coral) if let text { Text(text) .font(.system(.caption2, design: .rounded, weight: .semibold)) .multilineTextAlignment(.center) .foregroundStyle(.secondary) } } .frame(maxWidth: .infinity) .padding(.top, 18) } } /// A snapshot from another week: the watch has data, but not for today. struct StaleView: View { var text: String? var body: some View { VStack(spacing: 6) { Image(systemName: "iphone.gen3.radiowaves.left.and.right") .font(.title3) .foregroundStyle(WatchTheme.coral) if let text { Text(text) .font(.system(.caption2, design: .rounded, weight: .semibold)) .multilineTextAlignment(.center) .foregroundStyle(.secondary) } } .frame(maxWidth: .infinity) .padding(.top, 18) } } struct EmptySyncView: View { var body: some View { VStack(spacing: 8) { Image(systemName: "applewatch.radiowaves.left.and.right") .font(.title3) .foregroundStyle(WatchTheme.coral) // Payload arrives fully localized from the iPhone; the watch // bundle carries no strings of its own, so this stays neutral. Text(verbatim: "MealMood") .font(.system(.caption2, design: .rounded, weight: .semibold)) Image(systemName: "iphone.gen3") .font(.caption2) .foregroundStyle(.secondary) } .frame(maxWidth: .infinity) .padding(.top, 24) } }