La Live Activity restait affichée après la fin de séance
La montre n'annonçait pas la fin : le flux de samples s'arrêtait, et l'iPhone ne pouvait pas distinguer « séance terminée » de « montre hors de portée ». La Live Activity restait donc figée sur la dernière FC reçue — un cœur à 74 bpm en permanence dans la Dynamic Island. Trois causes cumulées, toutes corrigées : 1. Aucun message de fin. WorkoutManager envoie maintenant `liveEnded` à l'arrêt de la session HealthKit, en cas d'échec, et en mode test. 2. Le seul chemin de terminaison côté iPhone était le watchdog de LiveStore, un Timer du main runloop — qui ne tourne pas quand l'app est suspendue en arrière-plan, c'est-à-dire pendant toute la séance. `endLive()` termine désormais l'activité dès réception du message. 3. `dismissalPolicy: .default` laissait la bannière ~4 h APRÈS la fin. Passé en `.immediate`. Filet de sécurité : `adoptExisting()` au lancement reprend la main sur une activité orpheline (app tuée pendant une séance, `current` perdu) et termine celles de plus de 6 h. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
This commit is contained in:
@@ -48,6 +48,35 @@ final class ConnectivityManager: NSObject, ObservableObject {
|
||||
}
|
||||
}
|
||||
|
||||
/// Signale la FIN de séance à l'iPhone.
|
||||
///
|
||||
/// Sans ce message, l'iPhone ne peut pas distinguer « séance terminée » de
|
||||
/// « montre hors de portée » : le flux de samples s'arrête dans les deux
|
||||
/// cas. Sa Live Activity restait alors figée sur la dernière FC reçue.
|
||||
/// Les samples encore en attente partent AVANT la fin (flushLocked) pour
|
||||
/// que l'ordre d'arrivée reste cohérent.
|
||||
func sendEnded(reason: String = "user") {
|
||||
queue.async {
|
||||
let session = WCSession.default
|
||||
guard session.activationState == .activated else { return }
|
||||
self.flushLocked()
|
||||
let payload: [String: Any] = [
|
||||
"liveEnded": true,
|
||||
"reason": reason,
|
||||
"ts": Date().timeIntervalSince1970,
|
||||
]
|
||||
if session.isReachable {
|
||||
session.sendMessage(payload, replyHandler: nil) { _ in
|
||||
// Échec temps réel : livraison différée garantie.
|
||||
session.transferUserInfo(payload)
|
||||
}
|
||||
} else {
|
||||
session.transferUserInfo(payload)
|
||||
}
|
||||
connLog.info("fin de séance signalée à l'iPhone (reason=\(reason, privacy: .public))")
|
||||
}
|
||||
}
|
||||
|
||||
/// Envoie l'état COMPLET de la routine du jour vers l'iPhone, qui le POSTe
|
||||
/// au serveur. Retourne `false` si rien n'a pu partir (l'appelant affiche
|
||||
/// alors un bouton « Renvoyer »).
|
||||
|
||||
@@ -97,6 +97,9 @@ final class WorkoutManager: NSObject, ObservableObject {
|
||||
self.simTask = nil
|
||||
isRunning = false
|
||||
statusMessage = nil
|
||||
// Le mode test ne passe pas par HKWorkoutSession : c'est ici le
|
||||
// seul endroit où l'iPhone peut apprendre que le flux s'arrête.
|
||||
ConnectivityManager.shared.sendEnded(reason: "sim")
|
||||
} else {
|
||||
session?.end()
|
||||
}
|
||||
@@ -210,6 +213,9 @@ extension WorkoutManager: HKWorkoutSessionDelegate {
|
||||
self.isRunning = false
|
||||
self.session = nil
|
||||
self.builder = nil
|
||||
// Fin explicite vers l'iPhone : sans elle, sa Live Activity reste
|
||||
// affichée avec la dernière FC reçue jusqu'à expiration d'ActivityKit.
|
||||
ConnectivityManager.shared.sendEnded(reason: "session-ended")
|
||||
}
|
||||
}
|
||||
|
||||
@@ -223,6 +229,7 @@ extension WorkoutManager: HKWorkoutSessionDelegate {
|
||||
self.isRunning = false
|
||||
self.session = nil
|
||||
self.builder = nil
|
||||
ConnectivityManager.shared.sendEnded(reason: "session-failed")
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user