feat(watch): add lap detection via accelerometer and workout sync to phone
This commit is contained in:
@@ -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)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user