feat(watch-connectivity): add WearOS support alongside Apple Watch

This commit is contained in:
2026-08-08 12:00:02 +02:00
parent 2211b8ebe4
commit 831ec6b836
52 changed files with 2278 additions and 62 deletions
@@ -1,5 +1,7 @@
import { useState } from "react";
import { Platform } from "react-native";
import WatchConnectivity from "../../../modules/watch-connectivity";
import WearConnectivity from "../../../modules/wear-connectivity";
import type { WorkoutSet } from "@types/workout";
type SyncResult = { status: string } | { error: string };
@@ -15,20 +17,6 @@ export function useSyncWorkout() {
): Promise<SyncResult> => {
setIsSyncing(true);
try {
console.log("[useSyncWorkout] Checking module availability...");
const supported = await WatchConnectivity.isSupported();
console.log("[useSyncWorkout] isSupported:", supported);
const paired = await WatchConnectivity.isPaired();
console.log("[useSyncWorkout] isPaired:", paired);
const installed = await WatchConnectivity.isWatchAppInstalled();
console.log("[useSyncWorkout] isWatchAppInstalled:", installed);
const reachable = await WatchConnectivity.isReachable();
console.log("[useSyncWorkout] isReachable:", reachable);
const plan = {
id: workoutId,
name: name ?? "Workout",
@@ -39,16 +27,37 @@ export function useSyncWorkout() {
repCount: s.repCount,
restType: s.restType,
restDuration: s.restDuration,
targetTime: s.targetTime ?? null,
...(s.targetTime != null ? { targetTime: s.targetTime } : {}),
})),
};
console.log("[useSyncWorkout] Sending plan:", JSON.stringify(plan).slice(0, 200));
const result = await WatchConnectivity.sendWorkoutPlan(plan);
console.log("[useSyncWorkout] Result:", JSON.stringify(result));
return result as SyncResult;
if (Platform.OS === "ios") {
const supported = await WatchConnectivity.isSupported();
const paired = await WatchConnectivity.isPaired();
const installed = await WatchConnectivity.isWatchAppInstalled();
const reachable = await WatchConnectivity.isReachable();
if (!supported || !paired || !installed) {
return { error: "Apple Watch not available" };
}
const result = await WatchConnectivity.sendWorkoutPlan(plan);
return result as SyncResult;
} else if (Platform.OS === "android") {
const supported = await WearConnectivity.isSupported();
const paired = await WearConnectivity.isPaired();
const reachable = await WearConnectivity.isReachable();
if (!supported || !paired) {
return { error: "WearOS watch not available" };
}
const result = await WearConnectivity.sendWorkoutPlan(plan);
return result as SyncResult;
}
return { error: "Platform not supported" };
} catch (error: any) {
console.log("[useSyncWorkout] Error:", error.message, error.code);
return { error: error.message ?? "Failed to sync" };
} finally {
setIsSyncing(false);
@@ -1,5 +1,7 @@
import { useState, useEffect } from "react";
import { Platform } from "react-native";
import WatchConnectivity from "../../../modules/watch-connectivity";
import WearConnectivity from "../../../modules/wear-connectivity";
type WatchConnectionStatus = "connected" | "disconnected" | "pending";
@@ -16,19 +18,36 @@ export function useWatchConnection() {
const checkStatus = async () => {
try {
const supported = await WatchConnectivity.isSupported();
if (!supported) {
if (Platform.OS === "ios") {
const supported = await WatchConnectivity.isSupported();
if (!supported) {
setStatus("disconnected");
return;
}
const paired = await WatchConnectivity.isPaired();
const installed = await WatchConnectivity.isWatchAppInstalled();
const reachable = await WatchConnectivity.isReachable();
setIsPaired(paired);
setIsInstalled(installed);
setStatus(reachable ? "connected" : paired ? "disconnected" : "disconnected");
} else if (Platform.OS === "android") {
const supported = await WearConnectivity.isSupported();
if (!supported) {
setStatus("disconnected");
return;
}
const paired = await WearConnectivity.isPaired();
const reachable = await WearConnectivity.isReachable();
setIsPaired(paired);
setIsInstalled(paired); // WearOS doesn't have a separate install check
setStatus(reachable ? "connected" : paired ? "disconnected" : "disconnected");
} else {
setStatus("disconnected");
return;
}
const paired = await WatchConnectivity.isPaired();
const installed = await WatchConnectivity.isWatchAppInstalled();
const reachable = await WatchConnectivity.isReachable();
setIsPaired(paired);
setIsInstalled(installed);
setStatus(reachable ? "connected" : paired ? "disconnected" : "disconnected");
} catch {
setStatus("disconnected");
}
@@ -1,5 +1,7 @@
import { useEffect, useRef } from "react";
import { Platform } from "react-native";
import WatchConnectivity from "../../../modules/watch-connectivity";
import WearConnectivity from "../../../modules/wear-connectivity";
import { saveWatchCompletedWorkout } from "@db/operations";
export function useWatchWorkoutSync() {
@@ -8,10 +10,16 @@ export function useWatchWorkoutSync() {
useEffect(() => {
pollingRef.current = setInterval(async () => {
try {
const events = (await WatchConnectivity.pollEvents()) as any[];
let events: any[] = [];
if (Platform.OS === "ios") {
events = (await WatchConnectivity.pollEvents()) as any[];
} else if (Platform.OS === "android") {
events = (await WearConnectivity.pollEvents()) as any[];
}
for (const event of events) {
if (event.type === "workoutCompleted") {
console.log("[useWatchWorkoutSync] Received workoutCompleted:", event.workoutId);
await saveWatchCompletedWorkout({
workoutId: event.workoutId,
duration: event.duration ?? 0,
@@ -19,7 +27,6 @@ export function useWatchWorkoutSync() {
calories: event.calories ?? 0,
laps: event.laps ?? [],
});
console.log("[useWatchWorkoutSync] Saved completed workout to DB");
}
}
} catch (error) {