feat(watch): add lap detection via accelerometer and workout sync to phone

This commit is contained in:
2026-08-07 16:35:20 +02:00
parent 00a8220096
commit 48286041c6
8 changed files with 414 additions and 40 deletions
+50 -11
View File
@@ -3,6 +3,8 @@ import WatchConnectivity
private class SessionDelegate: NSObject, WCSessionDelegate {
var onActivationComplete: ((WCSessionActivationState) -> Void)?
var onMessageReceived: (([String: Any]) -> Void)?
var onUserInfoReceived: (([String: Any]) -> Void)?
func session(_ session: WCSession, activationDidCompleteWith activationState: WCSessionActivationState, error: Error?) {
if let error = error {
@@ -13,6 +15,22 @@ private class SessionDelegate: NSObject, WCSessionDelegate {
onActivationComplete?(activationState)
}
func session(_ session: WCSession, didReceiveMessage message: [String: Any]) {
print("[WatchConnectivity] Received message: \(message["type"] ?? "unknown")")
onMessageReceived?(message)
}
func session(_ session: WCSession, didReceiveMessage message: [String: Any], replyHandler: @escaping ([String: Any]) -> Void) {
print("[WatchConnectivity] Received message with reply: \(message["type"] ?? "unknown")")
replyHandler(["status": "received"])
onMessageReceived?(message)
}
func session(_ session: WCSession, didReceiveUserInfo userInfo: [String: Any] = [:]) {
print("[WatchConnectivity] Received userInfo: \(userInfo["type"] ?? "unknown")")
onUserInfoReceived?(userInfo)
}
func sessionDidBecomeInactive(_ session: WCSession) {}
func sessionDidDeactivate(_ session: WCSession) {
@@ -25,6 +43,7 @@ public class WatchConnectivityModule: Module {
private let delegate = SessionDelegate()
private var isActivated = false
private var pendingPromises: [(() -> Void)] = []
private var pendingEvents: [[String: Any]] = []
// WCSession only supports NSString, NSNumber, NSData, NSArray, NSDictionary
private func cleanForWCSession(_ obj: Any) -> Any {
@@ -57,6 +76,12 @@ public class WatchConnectivityModule: Module {
pending.forEach { $0() }
}
}
self.delegate.onMessageReceived = { [weak self] message in
self?.handleIncomingMessage(message)
}
self.delegate.onUserInfoReceived = { [weak self] userInfo in
self?.handleIncomingMessage(userInfo)
}
session.activate()
self.session = session
}
@@ -93,8 +118,6 @@ public class WatchConnectivityModule: Module {
print("[WatchConnectivity] sendWorkoutPlan: isReachable=\(session.isReachable), activationState=\(session.activationState.rawValue)")
// WCSession only supports NSString, NSNumber, NSData, NSArray, NSDictionary
// Remove NSNull values (from JS null/undefined)
guard let cleaned = self.cleanForWCSession(message) as? [String: Any] else {
promise.resolve(["status": "sent"])
return
@@ -107,7 +130,6 @@ public class WatchConnectivityModule: Module {
promise.resolve(["status": "sent"])
}
// Check actual session state, not our flag (which has a race condition)
if session.activationState == .activated {
doSend()
} else {
@@ -126,18 +148,20 @@ public class WatchConnectivityModule: Module {
var message = data
message["type"] = "workoutCompleted"
guard let cleaned = self.cleanForWCSession(message) as? [String: Any] else {
promise.resolve(["status": "sent"])
return
}
if session.isReachable {
session.sendMessage(message, replyHandler: { reply in
promise.resolve(reply)
}, errorHandler: { error in
print("[WatchConnectivity] sendMessage failed, falling back to transferUserInfo: \(error)")
session.transferUserInfo(message)
promise.resolve(["status": "queued"])
session.sendMessage(cleaned, replyHandler: nil, errorHandler: { error in
print("[WatchConnectivity] sendMessage failed: \(error), falling back to transferUserInfo")
session.transferUserInfo(cleaned)
})
} else {
session.transferUserInfo(message)
promise.resolve(["status": "queued"])
session.transferUserInfo(cleaned)
}
promise.resolve(["status": "sent"])
}
if self.isActivated {
@@ -154,5 +178,20 @@ public class WatchConnectivityModule: Module {
AsyncFunction("updateApplicationContext") { (context: [String: Any]) in
try? self.session?.updateApplicationContext(context)
}
AsyncFunction("pollEvents") { () -> [[String: Any]] in
let events = self.pendingEvents
self.pendingEvents = []
return events
}
}
private func handleIncomingMessage(_ message: [String: Any]) {
guard let type = message["type"] as? String else { return }
print("[WatchConnectivity] handleIncomingMessage type=\(type)")
DispatchQueue.main.async {
self.pendingEvents.append(message)
}
}
}