bd8090c845
- El primer push se descartaba: updateApplicationContext se llamaba antes de que WCSession terminara de activarse (activacion asincrona). Ahora la sesion se activa al arrancar la app (ContentView), el payload pendiente se envia al completarse la activacion, y ademas el reloj PIDE los datos al abrirse (sendMessage despierta al iPhone en segundo plano y responde con el snapshot del app group) - Rediseno: Hoy con cabecera del dia en coral y tarjetas degradadas por comida (estilo Weather); Semana con circulo de dia a la izquierda y hoy relleno en coral (estilo Calendar); complicacion con iconos tintados por comida y widgetAccentable - Verificado con capturas en simulador de Watch sembrando el app group Co-Authored-By: Claude Fable 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_013H6bXqGX1ygwib1Dm3n3UG
90 lines
3.0 KiB
Swift
90 lines
3.0 KiB
Swift
import SwiftUI
|
|
import WatchConnectivity
|
|
import WidgetKit
|
|
|
|
@main
|
|
struct MealMoodWatchApp: App {
|
|
@StateObject private var store = WatchWeekStore.shared
|
|
|
|
var body: some Scene {
|
|
WindowGroup {
|
|
TabView {
|
|
TodayView()
|
|
WeekView()
|
|
}
|
|
.tabViewStyle(.verticalPage)
|
|
.environmentObject(store)
|
|
}
|
|
}
|
|
}
|
|
|
|
/// 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()
|
|
}
|
|
}
|
|
|
|
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) {
|
|
if payload == nil { requestFromPhone(session) }
|
|
}
|
|
|
|
func session(_ session: WCSession, didReceiveApplicationContext applicationContext: [String: Any]) {
|
|
if let data = applicationContext[WatchWeekPayload.storageKey] as? Data {
|
|
apply(data)
|
|
}
|
|
}
|
|
}
|