diff --git a/ios/App/App/CoachRoutineBridge.swift b/ios/App/App/CoachRoutineBridge.swift index 5f0e586..de139f6 100644 --- a/ios/App/App/CoachRoutineBridge.swift +++ b/ios/App/App/CoachRoutineBridge.swift @@ -54,10 +54,14 @@ public class CoachRoutineBridgePlugin: CAPPlugin, CAPBridgedPlugin { // MARK: - JS → natif - /// `pushToWatch({ date: "2026-08-03", done: ["hanches", ...] })` + /// `pushToWatch({ date, done: [...], blocks: [{id, num, title, duration_min, moment}] })` + /// + /// Les blocs voyagent avec l'état : la montre n'a aucune liste codée en dur, + /// modifier la routine côté web ne demande donc pas de rebuild watchOS. @objc func pushToWatch(_ call: CAPPluginCall) { let date = call.getString("date") ?? Self.todayISO() let done = call.getArray("done", String.self) ?? [] + let blocks = Self.sanitizeBlocks(call.getArray("blocks") as? [[String: Any]] ?? []) guard WCSession.isSupported() else { call.resolve(["pushed": false, "reason": "WCSession non supporté"]) @@ -75,10 +79,10 @@ public class CoachRoutineBridgePlugin: CAPPlugin, CAPBridgedPlugin { do { try session.updateApplicationContext([ - "routineSnapshot": ["date": date, "done": done] + "routineSnapshot": ["date": date, "done": done, "blocks": blocks] ]) - routineLog.info("routine poussée vers la Watch (\(done.count, privacy: .public) blocs)") - call.resolve(["pushed": true, "count": done.count]) + routineLog.info("routine poussée vers la Watch : \(blocks.count, privacy: .public) blocs, \(done.count, privacy: .public) faits") + call.resolve(["pushed": true, "blocks": blocks.count, "done": done.count]) } catch { routineLog.error("updateApplicationContext a échoué : \(error.localizedDescription, privacy: .public)") call.reject("Envoi vers la Watch impossible : \(error.localizedDescription)") @@ -151,6 +155,23 @@ public class CoachRoutineBridgePlugin: CAPPlugin, CAPBridgedPlugin { }.resume() } + /// Ne garde que les clés attendues, en types property-list stricts. + /// + /// Deux raisons : `updateApplicationContext` rejette (exception ObjC) tout + /// ce qui n'est pas plist-compatible, et les entiers venus du JS arrivent en + /// `NSNumber` — un `as? Int` direct échoue quand JavaScriptCore les a passés + /// en double. Même piège que le score perdu dans `CoachWidgetBridge`. + private static func sanitizeBlocks(_ raw: [[String: Any]]) -> [[String: Any]] { + raw.compactMap { b in + guard let id = b["id"] as? String, let title = b["title"] as? String else { return nil } + var out: [String: Any] = ["id": id, "title": title] + out["num"] = (b["num"] as? NSNumber)?.intValue ?? 0 + out["duration_min"] = (b["duration_min"] as? NSNumber)?.intValue ?? 0 + out["moment"] = (b["moment"] as? String) ?? "matin" + return out + } + } + private static func todayISO() -> String { let fmt = DateFormatter() fmt.dateFormat = "yyyy-MM-dd"