feat(watchos): ConnectivityManager (WCSession) — envoi des samples vers l'iPhone
sendMessage si iPhone joignable, sinon buffer + transferUserInfo (livraison différée). Délégué WCSession hors-main, hop vers main pour @Published isReachable, buffer sérialisé sur file dédiée. Build watchsimulator OK. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
This commit is contained in:
@@ -20,6 +20,7 @@
|
|||||||
50B271D11FEDC1A000F3C39B /* public in Resources */ = {isa = PBXBuildFile; fileRef = 50B271D01FEDC1A000F3C39B /* public */; };
|
50B271D11FEDC1A000F3C39B /* public in Resources */ = {isa = PBXBuildFile; fileRef = 50B271D01FEDC1A000F3C39B /* public */; };
|
||||||
543A7005429A43F54C0EC61B /* CoachWatchApp.swift in Sources */ = {isa = PBXBuildFile; fileRef = F6BEAEF6F6274D61CAF22636 /* CoachWatchApp.swift */; };
|
543A7005429A43F54C0EC61B /* CoachWatchApp.swift in Sources */ = {isa = PBXBuildFile; fileRef = F6BEAEF6F6274D61CAF22636 /* CoachWatchApp.swift */; };
|
||||||
88F28EB12FBB330E00E8306E /* CoachWorkoutObserver.swift in Sources */ = {isa = PBXBuildFile; fileRef = 88F28EB02FBB330E00E8306E /* CoachWorkoutObserver.swift */; };
|
88F28EB12FBB330E00E8306E /* CoachWorkoutObserver.swift in Sources */ = {isa = PBXBuildFile; fileRef = 88F28EB02FBB330E00E8306E /* CoachWorkoutObserver.swift */; };
|
||||||
|
B06AB24B350B221F38D99E17 /* ConnectivityManager.swift in Sources */ = {isa = PBXBuildFile; fileRef = A420239FC6D065C8A599B61C /* ConnectivityManager.swift */; };
|
||||||
C0AC4A07811751FB78AA0001 /* CoachAuth.swift in Sources */ = {isa = PBXBuildFile; fileRef = C0AC4A07811751FB78AA0002 /* CoachAuth.swift */; };
|
C0AC4A07811751FB78AA0001 /* CoachAuth.swift in Sources */ = {isa = PBXBuildFile; fileRef = C0AC4A07811751FB78AA0002 /* CoachAuth.swift */; };
|
||||||
D1BD5B1990CF3F2B9C5D0001 /* CoachWorkoutKit.swift in Sources */ = {isa = PBXBuildFile; fileRef = D1BD5B1990CF3F2B9C5D0002 /* CoachWorkoutKit.swift */; };
|
D1BD5B1990CF3F2B9C5D0001 /* CoachWorkoutKit.swift in Sources */ = {isa = PBXBuildFile; fileRef = D1BD5B1990CF3F2B9C5D0002 /* CoachWorkoutKit.swift */; };
|
||||||
E2CE6C2AA1DF4F3CAD6E0001 /* MainViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = E2CE6C2AA1DF4F3CAD6E0002 /* MainViewController.swift */; };
|
E2CE6C2AA1DF4F3CAD6E0001 /* MainViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = E2CE6C2AA1DF4F3CAD6E0002 /* MainViewController.swift */; };
|
||||||
@@ -67,6 +68,7 @@
|
|||||||
88F28EB02FBB330E00E8306E /* CoachWorkoutObserver.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = CoachWorkoutObserver.swift; sourceTree = "<group>"; };
|
88F28EB02FBB330E00E8306E /* CoachWorkoutObserver.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = CoachWorkoutObserver.swift; sourceTree = "<group>"; };
|
||||||
8A06CDFF5B4F7852B6329862 /* ContentView.swift */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = sourcecode.swift; path = ContentView.swift; sourceTree = "<group>"; };
|
8A06CDFF5B4F7852B6329862 /* ContentView.swift */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = sourcecode.swift; path = ContentView.swift; sourceTree = "<group>"; };
|
||||||
958DCC722DB07C7200EA8C5F /* debug.xcconfig */ = {isa = PBXFileReference; lastKnownFileType = text.xcconfig; name = debug.xcconfig; path = ../debug.xcconfig; sourceTree = SOURCE_ROOT; };
|
958DCC722DB07C7200EA8C5F /* debug.xcconfig */ = {isa = PBXFileReference; lastKnownFileType = text.xcconfig; name = debug.xcconfig; path = ../debug.xcconfig; sourceTree = SOURCE_ROOT; };
|
||||||
|
A420239FC6D065C8A599B61C /* ConnectivityManager.swift */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = sourcecode.swift; path = ConnectivityManager.swift; sourceTree = "<group>"; };
|
||||||
C0AC4A07811751FB78AA0002 /* CoachAuth.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = CoachAuth.swift; sourceTree = "<group>"; };
|
C0AC4A07811751FB78AA0002 /* CoachAuth.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = CoachAuth.swift; sourceTree = "<group>"; };
|
||||||
D09B589FA572B2C14E8CD835 /* CoachWatch.entitlements */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.plist.entitlements; path = CoachWatch.entitlements; sourceTree = "<group>"; };
|
D09B589FA572B2C14E8CD835 /* CoachWatch.entitlements */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.plist.entitlements; path = CoachWatch.entitlements; sourceTree = "<group>"; };
|
||||||
D1BD5B1990CF3F2B9C5D0002 /* CoachWorkoutKit.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = CoachWorkoutKit.swift; sourceTree = "<group>"; };
|
D1BD5B1990CF3F2B9C5D0002 /* CoachWorkoutKit.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = CoachWorkoutKit.swift; sourceTree = "<group>"; };
|
||||||
@@ -151,6 +153,7 @@
|
|||||||
8A06CDFF5B4F7852B6329862 /* ContentView.swift */,
|
8A06CDFF5B4F7852B6329862 /* ContentView.swift */,
|
||||||
82122ACF7E5A608AD1D55D24 /* Info.plist */,
|
82122ACF7E5A608AD1D55D24 /* Info.plist */,
|
||||||
D09B589FA572B2C14E8CD835 /* CoachWatch.entitlements */,
|
D09B589FA572B2C14E8CD835 /* CoachWatch.entitlements */,
|
||||||
|
A420239FC6D065C8A599B61C /* ConnectivityManager.swift */,
|
||||||
);
|
);
|
||||||
name = CoachWatch;
|
name = CoachWatch;
|
||||||
path = CoachWatch;
|
path = CoachWatch;
|
||||||
@@ -287,6 +290,7 @@
|
|||||||
files = (
|
files = (
|
||||||
543A7005429A43F54C0EC61B /* CoachWatchApp.swift in Sources */,
|
543A7005429A43F54C0EC61B /* CoachWatchApp.swift in Sources */,
|
||||||
290D0F17978B7EF3DDB3A792 /* ContentView.swift in Sources */,
|
290D0F17978B7EF3DDB3A792 /* ContentView.swift in Sources */,
|
||||||
|
B06AB24B350B221F38D99E17 /* ConnectivityManager.swift in Sources */,
|
||||||
);
|
);
|
||||||
runOnlyForDeploymentPostprocessing = 0;
|
runOnlyForDeploymentPostprocessing = 0;
|
||||||
};
|
};
|
||||||
|
|||||||
76
ios/App/CoachWatch/ConnectivityManager.swift
Normal file
76
ios/App/CoachWatch/ConnectivityManager.swift
Normal file
@@ -0,0 +1,76 @@
|
|||||||
|
import Foundation
|
||||||
|
import WatchConnectivity
|
||||||
|
|
||||||
|
/// Envoie les samples de séance vers l'iPhone.
|
||||||
|
/// - `sendMessage` quand l'iPhone est joignable (temps réel)
|
||||||
|
/// - sinon buffer + `transferUserInfo` (livraison différée garantie)
|
||||||
|
final class ConnectivityManager: NSObject, ObservableObject {
|
||||||
|
static let shared = ConnectivityManager()
|
||||||
|
|
||||||
|
@Published private(set) var isReachable = false
|
||||||
|
|
||||||
|
private let queue = DispatchQueue(label: "ch.hypnotruck.coach.connectivity")
|
||||||
|
private var buffer: [[String: Any]] = []
|
||||||
|
|
||||||
|
private override init() {
|
||||||
|
super.init()
|
||||||
|
guard WCSession.isSupported() else { return }
|
||||||
|
let session = WCSession.default
|
||||||
|
session.delegate = self
|
||||||
|
session.activate()
|
||||||
|
}
|
||||||
|
|
||||||
|
func sendSample(_ payload: [String: Any]) {
|
||||||
|
queue.async {
|
||||||
|
let session = WCSession.default
|
||||||
|
guard session.activationState == .activated else {
|
||||||
|
self.buffer.append(payload)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
if session.isReachable {
|
||||||
|
session.sendMessage(payload, replyHandler: nil) { [weak self] _ in
|
||||||
|
// échec d'envoi temps réel -> bascule en livraison différée
|
||||||
|
self?.queue.async {
|
||||||
|
self?.buffer.append(payload)
|
||||||
|
self?.flushLocked()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
self.buffer.append(payload)
|
||||||
|
self.flushLocked()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// À appeler sur `queue` uniquement.
|
||||||
|
private func flushLocked() {
|
||||||
|
guard !buffer.isEmpty else { return }
|
||||||
|
let session = WCSession.default
|
||||||
|
guard session.activationState == .activated else { return }
|
||||||
|
session.transferUserInfo(["samples": buffer])
|
||||||
|
buffer.removeAll()
|
||||||
|
}
|
||||||
|
|
||||||
|
private func updateReachable(_ value: Bool) {
|
||||||
|
DispatchQueue.main.async { [weak self] in self?.isReachable = value }
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
extension ConnectivityManager: WCSessionDelegate {
|
||||||
|
func session(_ session: WCSession,
|
||||||
|
activationDidCompleteWith activationState: WCSessionActivationState,
|
||||||
|
error: Error?) {
|
||||||
|
updateReachable(session.isReachable)
|
||||||
|
if activationState == .activated {
|
||||||
|
queue.async { [weak self] in self?.flushLocked() }
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func sessionReachabilityDidChange(_ session: WCSession) {
|
||||||
|
let reachable = session.isReachable
|
||||||
|
updateReachable(reachable)
|
||||||
|
if reachable {
|
||||||
|
queue.async { [weak self] in self?.flushLocked() }
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
Reference in New Issue
Block a user