From d8adad466b3a60f733f2cc4951f20587ba6bef05 Mon Sep 17 00:00:00 2001 From: Sylvain Bettinelli Date: Thu, 6 Aug 2026 13:51:22 +0000 Subject: [PATCH] =?UTF-8?q?fix(widget):=20le=20total=20retombait=20=C3=A0?= =?UTF-8?q?=20z=C3=A9ro=20apr=C3=A8s=20synchronisation?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Le widget affiche « snapshot + file d'attente ». La synchronisation vidait la file sans créditer le snapshot : le total repassait donc à zéro juste après que la saisie ait été enregistrée pour de bon. Le pire moment pour douter de son journal. `flush()` reporte désormais dans le snapshot ce qu'il vient d'envoyer, AVANT de vider la file. Seules les entrées du jour sont créditées — le snapshot porte des totaux quotidiens, y verser une saisie d'hier les fausserait. Le pont web repoussera un snapshot exact au prochain chargement de page ; ce report garde l'affichage juste entre-temps. Les optionnels du snapshot reçoivent `= nil` explicitement : sans valeur par défaut, l'init memberwise de Swift les aurait exigés à l'appel. Co-Authored-By: Claude Opus 5 --- ios/App/App/CoachQuickSync.swift | 29 +++++++++++++++++++++++++-- ios/App/App/CoachWidgetSnapshot.swift | 12 +++++------ 2 files changed, 33 insertions(+), 8 deletions(-) diff --git a/ios/App/App/CoachQuickSync.swift b/ios/App/App/CoachQuickSync.swift index 4a31a62..e6a3cc3 100644 --- a/ios/App/App/CoachQuickSync.swift +++ b/ios/App/App/CoachQuickSync.swift @@ -54,14 +54,39 @@ enum CoachQuickSync { } if !sent.isEmpty { + /* Le snapshot AVANT de vider la file. Le widget affiche + « snapshot + file » : vider la file sans créditer le snapshot + ferait retomber son total à zéro alors que la saisie vient + justement d'être enregistrée. C'est exactement ce qui se passait. + + Le pont web repoussera un snapshot exact au prochain chargement + de page ; en attendant, ce report garde l'affichage juste. */ + credit(queue.filter { sent.contains($0.id) }) CoachQuickLog.remove(ids: sent) - // Le widget doit cesser d'afficher la flèche d'attente, et son - // total doit repasser sur la source serveur. WidgetCenter.shared.reloadAllTimelines() } return sent.count } + /// Reporte dans le snapshot ce qui vient de partir au serveur. + /// + /// Seules les entrées du JOUR comptent : le snapshot porte des totaux + /// quotidiens, et y verser une saisie d'hier les fausserait. + private static func credit(_ entries: [CoachQuickEntry]) { + let calendar = Calendar.current + let today = entries.filter { calendar.isDateInToday($0.at) } + guard !today.isEmpty else { return } + + var snapshot = CoachWidgetStore.load() + ?? CoachWidgetSnapshot(updatedAt: Date(), today: nil, forme: nil) + let water = today.filter { $0.ref == "water" }.reduce(0) { $0 + $1.amount } + let coffees = today.filter { $0.ref == "coffee" }.count + if water > 0 { snapshot.waterMlToday = (snapshot.waterMlToday ?? 0) + water } + if coffees > 0 { snapshot.coffeeCountToday = (snapshot.coffeeCountToday ?? 0) + coffees } + snapshot.updatedAt = Date() + CoachWidgetStore.save(snapshot) + } + private static func post(_ entry: CoachQuickEntry) async -> Bool { var request = URLRequest(url: base.appendingPathComponent("api/drinks")) request.httpMethod = "POST" diff --git a/ios/App/App/CoachWidgetSnapshot.swift b/ios/App/App/CoachWidgetSnapshot.swift index 4aeb78d..2be05cf 100644 --- a/ios/App/App/CoachWidgetSnapshot.swift +++ b/ios/App/App/CoachWidgetSnapshot.swift @@ -41,8 +41,8 @@ enum CoachWidgetStore { /// le widget n'a pas d'accès réseau, tout vient de ce snapshot poussé par l'app. struct CoachWidgetSnapshot: Codable { var updatedAt: Date - var today: TodaySession? - var forme: FormeScore? + var today: TodaySession? = nil + var forme: FormeScore? = nil /// Boissons du jour DÉJÀ connues du serveur, en millilitres et en nombre de /// cafés. Le widget de saisie rapide y ajoute ce qui attend encore dans la @@ -50,14 +50,14 @@ struct CoachWidgetSnapshot: Codable { /// « 1,5 L » tant que l'app n'a pas resynchronisé, et l'utilisateur /// douterait de saisies pourtant enregistrées. /// Optionnels : un snapshot écrit par une version antérieure reste lisible. - var waterMlToday: Double? - var coffeeCountToday: Int? + var waterMlToday: Double? = nil + var coffeeCountToday: Int? = nil /// Énergie du jour et objectif, poussés par l'app depuis le journal. /// `kcalGoal` vient des objectifs nutritionnels réels (1950 au 06/08/2026) : /// il n'est pas écrit en dur ici, pour qu'un changement d'objectif suive. - var kcalToday: Double? - var kcalGoal: Double? + var kcalToday: Double? = nil + var kcalGoal: Double? = nil /// Séance planifiée du jour (nil = jour OFF / pas de séance).