Files
FamilyMealPlanner/MealMood/Services/WatchWeekPayload.swift
T
alexandrev-tibco 4579b9bb69 watch: enviar la semana aunque todavia no este planificada
Faltaba la otra mitad del "no se actualiza": makePayload devolvia nil cuando la
semana en curso no tenia plan creado, asi que el iPhone no mandaba nada — ni
respondia cuando el reloj pedia datos — y el reloj se quedaba con el ultimo
snapshot, el del miercoles. Ahora la semana viaja igualmente, vacia, y el reloj
dice que no hay nada planificado en vez de enseñar lo de hace dias.

Ademas el reloj muestra cuando se actualizo por ultima vez. Sin eso, un
snapshot viejo y uno recien llegado se ven igual, y no hay forma de saber si
el problema es el envio o lo que se pinta.

Refs #34, #35

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_013su1ttRiMeMYxkZJ1Y3246
2026-09-12 16:32:34 +02:00

119 lines
4.9 KiB
Swift
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
import Foundation
/// Snapshot of the week the iPhone pushes to the Watch (WatchConnectivity) and
/// the watch widget reads from the watch-side app group. Labels and day titles
/// arrive already localized the watch never formats, only renders.
/// Member of the iOS app, watch app and watch widget targets.
struct WatchWeekPayload: Codable {
struct Meal: Codable {
let type: String // MealType rawValue, for the icon
let label: String // localized ("Comida", "Cena")
let name: String? // dish (+ secondary), "Comer fuera", or nil if empty
}
struct Day: Codable {
let dayOfWeek: Int // 0=Monday 6=Sunday
let title: String // localized short title ("Lun 8")
/// `isToday` as of the moment the iPhone built the snapshot. Kept for
/// payloads written by older versions; `date` is what the watch should
/// use, because a stored snapshot outlives the day it was made in.
let isToday: Bool
/// The actual calendar date of this day. Optional: snapshots stored by
/// versions before 2.1.1 don't carry it.
var date: Date?
let meals: [Meal]
init(dayOfWeek: Int, title: String, isToday: Bool, date: Date? = nil, meals: [Meal]) {
self.dayOfWeek = dayOfWeek
self.title = title
self.isToday = isToday
self.date = date
self.meals = meals
}
/// Whether this day is today *now*, asked at render time.
func isCurrentDay(now: Date = Date(), calendar: Calendar = .current) -> Bool {
if let date {
return calendar.isDate(date, inSameDayAs: now)
}
// Pre-2.1.1 snapshot: fall back to the weekday offset, which is only
// right while the snapshot belongs to the current week.
return dayOfWeek == WatchWeekPayload.weekdayOffset(for: now, calendar: calendar)
}
/// Nothing planned at all for this day.
var isEmpty: Bool {
meals.allSatisfy { $0.name == nil }
}
}
let weekTitle: String // localized week range ("8 14 sep")
let days: [Day]
let updatedAt: Date
/// Localized texts the watch cannot build on its own its bundle carries no
/// strings. Optional so older snapshots still decode.
var emptyMealText: String?
var emptyDayText: String?
var staleText: String?
/// "Updated" shown next to `updatedAt` so it's visible at a glance when
/// the watch is holding an old snapshot.
var updatedLabel: String?
static let appGroupID = "group.com.alexandrevazquez.mealmood"
static let storageKey = "watch_week_payload_v1"
func encoded() -> Data? { try? JSONEncoder().encode(self) }
static func decode(_ data: Data) -> WatchWeekPayload? {
try? JSONDecoder().decode(WatchWeekPayload.self, from: data)
}
/// Reads the last snapshot stored on this device's app group.
static func stored() -> WatchWeekPayload? {
guard let data = UserDefaults(suiteName: appGroupID)?.data(forKey: storageKey) else { return nil }
return decode(data)
}
func store() {
UserDefaults(suiteName: Self.appGroupID)?.set(encoded(), forKey: Self.storageKey)
}
/// 0=Monday 6=Sunday, matching `MealSlot.dayOfWeek`.
static func weekdayOffset(for date: Date, calendar: Calendar = .current) -> Int {
(calendar.component(.weekday, from: date) + 5) % 7
}
/// The day to show as "today", resolved when the view renders rather than
/// when the iPhone built the snapshot. Returns nil when the snapshot is from
/// another week and simply doesn't contain today.
func currentDay(now: Date = Date(), calendar: Calendar = .current) -> Day? {
if days.contains(where: { $0.date != nil }) {
return days.first { $0.isCurrentDay(now: now, calendar: calendar) }
}
// Snapshot written before 2.1.1: no dates, so the weekday offset is the
// only handle and it's trustworthy only while the snapshot itself
// belongs to the current week. Otherwise last week's Saturday would
// pass for today.
guard calendar.isDate(updatedAt, equalTo: now, toGranularity: .weekOfYear) else { return nil }
let offset = Self.weekdayOffset(for: now, calendar: calendar)
return days.first { $0.dayOfWeek == offset }
}
/// True when the snapshot no longer covers today, so showing any of its days
/// would be showing the wrong meal.
func isStale(now: Date = Date(), calendar: Calendar = .current) -> Bool {
currentDay(now: now, calendar: calendar) == nil
}
static func icon(for mealType: String) -> String {
switch mealType {
case "breakfast": return "cup.and.saucer.fill"
case "lunch": return "sun.max.fill"
case "snack": return "carrot.fill"
case "dinner": return "moon.stars.fill"
default: return "fork.knife"
}
}
}