Files
FamilyMealPlanner/MealMood/MealMoodApp.swift
T
alexandrev-tibco a049e4f74b watch: el dia de hoy se resuelve al pintar, no al generar el envio
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
2026-09-12 13:21:21 +02:00

100 lines
3.8 KiB
Swift

import SwiftUI
import SwiftData
import UIKit
import FirebaseCore
import FirebaseCrashlytics
#if canImport(GoogleMobileAds)
import GoogleMobileAds
#endif
/// Registers for remote notifications so CloudKit (the NSPersistentCloudKitContainer
/// behind SwiftData) receives its silent-push subscriptions and imports changes
/// in near-real-time across devices. Silent pushes are coalesced by the system,
/// so battery cost is negligible no polling.
final class AppDelegate: NSObject, UIApplicationDelegate {
func application(_ application: UIApplication,
didFinishLaunchingWithOptions launchOptions: [UIApplication.LaunchOptionsKey: Any]? = nil) -> Bool {
application.registerForRemoteNotifications()
return true
}
func application(_ application: UIApplication,
didReceiveRemoteNotification userInfo: [AnyHashable: Any],
fetchCompletionHandler completionHandler: @escaping (UIBackgroundFetchResult) -> Void) {
// CloudKit silent push: SwiftData's mirror imports on its own once woken;
// report new data so the system keeps delivering sync pushes.
completionHandler(.newData)
}
}
/// Whether the store is syncing through CloudKit this launch. When true, the
/// legacy iCloud KV snapshot sync must stay inert (both would fight).
enum CloudSyncRuntime {
// Written exactly once during app start (before any concurrency), then
// read-only for the rest of the launch.
nonisolated(unsafe) static var isCloudKitActive = false
}
@main
struct MealMoodApp: App {
@UIApplicationDelegateAdaptor(AppDelegate.self) private var appDelegate
private let modelContainer: ModelContainer = {
let schema = Schema([
AppSettings.self,
Tag.self,
Dish.self,
WeekPlan.self,
MealSlot.self,
ShoppingItem.self
])
// 2.0: CloudKit private-database sync (multi-device, same Apple ID).
// Falls back to the local-only store when CloudKit isn't available
// (signed-out iCloud, missing entitlement in dev builds, etc.).
let cloudConfiguration = ModelConfiguration(cloudKitDatabase: .automatic)
do {
let container = try ModelContainer(for: schema, configurations: [cloudConfiguration])
CloudSyncRuntime.isCloudKitActive = true
return container
} catch {
CrashlyticsService.record(error, context: "cloudkit_container")
print("CloudKit container unavailable, using local store: \(error)")
}
let configuration = ModelConfiguration(cloudKitDatabase: .none)
do {
return try ModelContainer(for: schema, configurations: [configuration])
} catch {
// Migration failed reset the store to avoid a crash loop
let storeURL = configuration.url
for ext in ["", "-shm", "-wal"] {
try? FileManager.default.removeItem(at: URL(fileURLWithPath: storeURL.path + ext))
}
do {
return try ModelContainer(for: schema, configurations: [configuration])
} catch {
fatalError("Cannot create ModelContainer after reset: \(error)")
}
}
}()
init() {
FirebaseApp.configure()
// The watch can ask for the week while the app is in the background, and
// answering that needs the store.
WatchSyncService.shared.configure(container: modelContainer)
AnalyticsService.applyCollectionPolicy()
#if canImport(GoogleMobileAds)
GADMobileAds.sharedInstance().start(completionHandler: nil)
#endif
}
var body: some Scene {
WindowGroup {
ContentView()
}
.modelContainer(modelContainer)
}
}