a049e4f74b
El iPhone marcaba isToday al construir el snapshot y el reloj se creia esa marca para siempre. Como el snapshot sobrevive en el app group, un sabado seguia enseñando la cena del miercoles: el dia que era cuando se genero. Ahora cada dia viaja con su fecha real y el reloj busca hoy cuando dibuja. Si el snapshot es de otra semana no hay "hoy" que enseñar, asi que en vez de colar la comida de otro dia lo dice y pide abrir el iPhone. Para snapshots guardados por versiones anteriores, que no traen fecha, solo se confia en el indice del dia mientras el propio snapshot sea de esta semana. Ademas el reloj pide datos al abrirse y el iPhone, en vez de responder con lo ultimo que cacheo, reconstruye la semana desde el store — que es la otra mitad del "no se actualiza": el cache podia ser de hace dias. Y cuando no hay nada planificado se dice, que antes era un guion indistinguible de una comida marcada como no planificada: "Hoy no hay nada planificado" para el dia entero y "Sin planificar" por comida, en los 6 idiomas (el bundle del reloj no tiene cadenas propias, viajan en el envio). Refs #34, #35 Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_013su1ttRiMeMYxkZJ1Y3246
107 lines
3.8 KiB
Swift
107 lines
3.8 KiB
Swift
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)
|
|
}
|
|
}
|
|
}
|