import SwiftUI import WatchConnectivity import WidgetKit @main struct MealMoodWatchApp: App { @StateObject private var store = WatchWeekStore.shared @Environment(\.scenePhase) private var scenePhase var body: some Scene { WindowGroup { TabView { TodayView() WeekView() } .tabViewStyle(.verticalPage) .environmentObject(store) // Opening the app is exactly when a snapshot from another day would // be on screen, so ask the phone for a fresh one. .onChange(of: scenePhase) { _, phase in if phase == .active { store.refresh() } } } } } /// Shared palette for the watch UI — same family as the iOS app. enum WatchTheme { static let coral = Color(red: 1.0, green: 0.45, blue: 0.35) static func accent(for mealType: String) -> Color { switch mealType { case "breakfast": return Color(red: 0.95, green: 0.68, blue: 0.25) case "lunch": return Color(red: 0.98, green: 0.55, blue: 0.30) case "snack": return Color(red: 0.62, green: 0.50, blue: 0.83) case "dinner": return Color(red: 0.28, green: 0.62, blue: 0.55) default: return coral } } } /// Receives the week snapshot from the iPhone and persists it in the watch's /// app group so the complication can read it too. On launch it also *asks* /// the iPhone (which wakes in background) so a fresh install shows data /// immediately instead of waiting for the next push. final class WatchWeekStore: NSObject, ObservableObject, WCSessionDelegate { static let shared = WatchWeekStore() @Published var payload: WatchWeekPayload? private override init() { super.init() payload = WatchWeekPayload.stored() guard WCSession.isSupported() else { return } WCSession.default.delegate = self WCSession.default.activate() } private func apply(_ data: Data) { guard let received = WatchWeekPayload.decode(data) else { return } DispatchQueue.main.async { self.payload = received received.store() WidgetCenter.shared.reloadAllTimelines() } } /// Asks the phone for an up-to-date snapshot. The phone rebuilds it from its /// store, so this also fixes a snapshot left over from a previous week. func refresh() { guard WCSession.isSupported() else { return } requestFromPhone(WCSession.default) } private func requestFromPhone(_ session: WCSession) { guard session.isReachable else { return } session.sendMessage(["request": "week"], replyHandler: { [weak self] reply in if let data = reply[WatchWeekPayload.storageKey] as? Data { self?.apply(data) } }, errorHandler: nil) } func session(_ session: WCSession, activationDidCompleteWith activationState: WCSessionActivationState, error: Error?) { // Pick up a context delivered while the app wasn't running… if let data = session.receivedApplicationContext[WatchWeekPayload.storageKey] as? Data { apply(data) } // …and pull fresh data from the phone if it's around. requestFromPhone(session) } func sessionReachabilityDidChange(_ session: WCSession) { // Refresh when there's nothing yet, and also when what we have no longer // covers today — otherwise the watch keeps showing an old day. if payload == nil || payload?.isStale() == true { requestFromPhone(session) } } func session(_ session: WCSession, didReceiveApplicationContext applicationContext: [String: Any]) { if let data = applicationContext[WatchWeekPayload.storageKey] as? Data { apply(data) } } }