feat(watch-connectivity): add WearOS support alongside Apple Watch
This commit is contained in:
@@ -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) {
|
||||
|
||||
@@ -1,11 +1,11 @@
|
||||
{
|
||||
"workoutSentToWatch": "Workout an Watch gesendet!",
|
||||
"openWatchApp": "Öffne die SwimBuddy-App auf deiner Apple Watch, um mit der Verfolgung zu beginnen.",
|
||||
"openWatchApp": "Öffne die SwimBuddy-App auf deiner Watch, um mit der Verfolgung zu beginnen.",
|
||||
"failedToSend": "Senden fehlgeschlagen",
|
||||
"couldNotSend": "Workout konnte nicht an Watch gesendet werden. Starte ohne Watch.",
|
||||
"startWithoutWatch": "Ohne Watch starten",
|
||||
"sendToWatchAndStart": "An Watch senden & starten",
|
||||
"syncToWatch": "Workout wird mit deiner Apple Watch synchronisiert",
|
||||
"syncToWatch": "Workout wird mit deiner Watch synchronisiert",
|
||||
"trackedOnDevice": "Workout wird auf diesem Gerät verfolgt",
|
||||
"rest": "Pause",
|
||||
"betweenSets": "{{duration}}s Pause zwischen Sets",
|
||||
@@ -13,7 +13,7 @@
|
||||
"greatJob": "Gut gemacht! Du bist heute ins Wasser gegangen!",
|
||||
"loadingSummary": "Zusammenfassung laden...",
|
||||
"watchStatus": {
|
||||
"ready": "Apple Watch bereit",
|
||||
"ready": "Watch bereit",
|
||||
"pairedNotInRange": "Watch gekoppelt, aber nicht in Reichweite",
|
||||
"pairedNotInstalled": "Watch gekoppelt, App nicht installiert",
|
||||
"noWatch": "Keine Watch gekoppelt"
|
||||
|
||||
@@ -1,11 +1,11 @@
|
||||
{
|
||||
"workoutSentToWatch": "Træning sendt til Watch!",
|
||||
"openWatchApp": "Åbn SwimBuddy-appen på din Apple Watch for at begynde sporing.",
|
||||
"openWatchApp": "Åbn SwimBuddy-appen på din Watch for at begynde sporing.",
|
||||
"failedToSend": "Sendning mislykkedes",
|
||||
"couldNotSend": "Kunne ikke sende træning til Watch. Starter uden Watch.",
|
||||
"startWithoutWatch": "Start uden Watch",
|
||||
"sendToWatchAndStart": "Send til Watch & start",
|
||||
"syncToWatch": "Træningen synkroniseres med din Apple Watch",
|
||||
"syncToWatch": "Træningen synkroniseres med din Watch",
|
||||
"trackedOnDevice": "Træningen spores på denne enhed",
|
||||
"rest": "Hvile",
|
||||
"betweenSets": "{{duration}}s hvile mellem sæt",
|
||||
@@ -13,7 +13,7 @@
|
||||
"greatJob": "Godt gået! Du var i vandet i dag!",
|
||||
"loadingSummary": "Indlæser opsummering...",
|
||||
"watchStatus": {
|
||||
"ready": "Apple Watch klar",
|
||||
"ready": "Watch klar",
|
||||
"pairedNotInRange": "Watch parret, men ikke inden for rækkevidde",
|
||||
"pairedNotInstalled": "Watch parret, app ikke installeret",
|
||||
"noWatch": "Ingen Watch parret"
|
||||
|
||||
@@ -1,11 +1,11 @@
|
||||
{
|
||||
"workoutSentToWatch": "Workout sent to Watch!",
|
||||
"openWatchApp": "Open the SwimBuddy app on your Apple Watch to start tracking.",
|
||||
"openWatchApp": "Open the SwimBuddy app on your watch to start tracking.",
|
||||
"failedToSend": "Failed to send",
|
||||
"couldNotSend": "Could not send workout to Watch. Starting without Watch instead.",
|
||||
"startWithoutWatch": "Start Without Watch",
|
||||
"sendToWatchAndStart": "Send to Watch & Start",
|
||||
"syncToWatch": "Workout will be synced to your Apple Watch",
|
||||
"syncToWatch": "Workout will be synced to your watch",
|
||||
"trackedOnDevice": "Workout will be tracked on this device",
|
||||
"rest": "Rest",
|
||||
"betweenSets": "{{duration}}s between sets",
|
||||
@@ -13,7 +13,7 @@
|
||||
"greatJob": "Great job getting in the water today!",
|
||||
"loadingSummary": "Loading summary...",
|
||||
"watchStatus": {
|
||||
"ready": "Apple Watch ready",
|
||||
"ready": "Watch ready",
|
||||
"pairedNotInRange": "Watch paired but not in range",
|
||||
"pairedNotInstalled": "Watch paired, app not installed",
|
||||
"noWatch": "No Watch paired"
|
||||
|
||||
@@ -1,11 +1,11 @@
|
||||
{
|
||||
"workoutSentToWatch": "¡Entrenamiento enviado al Watch!",
|
||||
"openWatchApp": "Abre la app SwimBuddy en tu Apple Watch para empezar el seguimiento.",
|
||||
"openWatchApp": "Abre la app SwimBuddy en tu Watch para empezar el seguimiento.",
|
||||
"failedToSend": "Error al enviar",
|
||||
"couldNotSend": "No se pudo enviar el entrenamiento al Watch. Iniciando sin Watch.",
|
||||
"startWithoutWatch": "Iniciar sin Watch",
|
||||
"sendToWatchAndStart": "Enviar al Watch e iniciar",
|
||||
"syncToWatch": "El entrenamiento se sincronizará con tu Apple Watch",
|
||||
"syncToWatch": "El entrenamiento se sincronizará con tu Watch",
|
||||
"trackedOnDevice": "El entrenamiento se rastreará en este dispositivo",
|
||||
"rest": "Descanso",
|
||||
"betweenSets": "{{duration}}s de descanso entre series",
|
||||
@@ -13,7 +13,7 @@
|
||||
"greatJob": "¡Buen trabajo! ¡Hoy has estado en el agua!",
|
||||
"loadingSummary": "Cargando resumen...",
|
||||
"watchStatus": {
|
||||
"ready": "Apple Watch lista",
|
||||
"ready": "Watch lista",
|
||||
"pairedNotInRange": "Watch emparejada pero fuera de alcance",
|
||||
"pairedNotInstalled": "Watch emparejada, app no instalada",
|
||||
"noWatch": "Ninguna Watch emparejada"
|
||||
|
||||
@@ -1,11 +1,11 @@
|
||||
{
|
||||
"workoutSentToWatch": "Entraînement envoyé à la Watch !",
|
||||
"openWatchApp": "Ouvrez l'application SwimBuddy sur votre Apple Watch pour commencer le suivi.",
|
||||
"openWatchApp": "Ouvrez l'application SwimBuddy sur votre Watch pour commencer le suivi.",
|
||||
"failedToSend": "Échec de l'envoi",
|
||||
"couldNotSend": "Impossible d'envoyer l'entraînement à la Watch. Démarrage sans Watch.",
|
||||
"startWithoutWatch": "Démarrer sans Watch",
|
||||
"sendToWatchAndStart": "Envoyer à la Watch et commencer",
|
||||
"syncToWatch": "L'entraînement sera synchronisé avec votre Apple Watch",
|
||||
"syncToWatch": "L'entraînement sera synchronisé avec votre Watch",
|
||||
"trackedOnDevice": "L'entraînement sera suivi sur cet appareil",
|
||||
"rest": "Repos",
|
||||
"betweenSets": "{{duration}}s de repos entre les séries",
|
||||
@@ -13,7 +13,7 @@
|
||||
"greatJob": "Bravo ! Vous êtes allé à l'eau aujourd'hui !",
|
||||
"loadingSummary": "Chargement du résumé...",
|
||||
"watchStatus": {
|
||||
"ready": "Apple Watch prête",
|
||||
"ready": "Watch prête",
|
||||
"pairedNotInRange": "Watch appariée mais hors de portée",
|
||||
"pairedNotInstalled": "Watch appariée, application non installée",
|
||||
"noWatch": "Aucune Watch appariée"
|
||||
|
||||
@@ -1,11 +1,11 @@
|
||||
{
|
||||
"workoutSentToWatch": "Allenamento inviato alla Watch!",
|
||||
"openWatchApp": "Apri l'app SwimBuddy sulla tua Apple Watch per iniziare il monitoraggio.",
|
||||
"openWatchApp": "Apri l'app SwimBuddy sulla tua Watch per iniziare il monitoraggio.",
|
||||
"failedToSend": "Invio fallito",
|
||||
"couldNotSend": "Impossibile inviare l'allenamento alla Watch. Avvio senza Watch.",
|
||||
"startWithoutWatch": "Avvia senza Watch",
|
||||
"sendToWatchAndStart": "Invia alla Watch e avvia",
|
||||
"syncToWatch": "L'allenamento verrà sincronizzato con la tua Apple Watch",
|
||||
"syncToWatch": "L'allenamento verrà sincronizzato con la tua Watch",
|
||||
"trackedOnDevice": "L'allenamento verrà monitorato su questo dispositivo",
|
||||
"rest": "Recupero",
|
||||
"betweenSets": "{{duration}}s di recupero tra le serie",
|
||||
@@ -13,7 +13,7 @@
|
||||
"greatJob": "Ottimo lavoro! Sei entrato in acqua oggi!",
|
||||
"loadingSummary": "Caricamento riepilogo...",
|
||||
"watchStatus": {
|
||||
"ready": "Apple Watch pronta",
|
||||
"ready": "Watch pronta",
|
||||
"pairedNotInRange": "Watch associata ma fuori portata",
|
||||
"pairedNotInstalled": "Watch associata, app non installata",
|
||||
"noWatch": "Nessuna Watch associata"
|
||||
|
||||
@@ -1,11 +1,11 @@
|
||||
{
|
||||
"workoutSentToWatch": "Workout verzonden naar Watch!",
|
||||
"openWatchApp": "Open de SwimBuddy-app op je Apple Watch om te beginnen met bijhouden.",
|
||||
"openWatchApp": "Open de SwimBuddy-app op je Watch om te beginnen met bijhouden.",
|
||||
"failedToSend": "Verzenden mislukt",
|
||||
"couldNotSend": "Kon workout niet naar Watch verzenden. Start zonder Watch.",
|
||||
"startWithoutWatch": "Start zonder Watch",
|
||||
"sendToWatchAndStart": "Verzenden naar Watch & Start",
|
||||
"syncToWatch": "Workout wordt gesynchroniseerd naar je Apple Watch",
|
||||
"syncToWatch": "Workout wordt gesynchroniseerd naar je Watch",
|
||||
"trackedOnDevice": "Workout wordt op dit apparaat bijgehouden",
|
||||
"rest": "Rust",
|
||||
"betweenSets": "{{duration}}s rust tussen sets",
|
||||
@@ -13,7 +13,7 @@
|
||||
"greatJob": "Goed gedaan! Je bent vandaag het water in geweest!",
|
||||
"loadingSummary": "Samenvatting laden...",
|
||||
"watchStatus": {
|
||||
"ready": "Apple Watch gereed",
|
||||
"ready": "Watch gereed",
|
||||
"pairedNotInRange": "Watch gekoppeld maar niet in bereik",
|
||||
"pairedNotInstalled": "Watch gekoppeld, app niet geïnstalleerd",
|
||||
"noWatch": "Geen Watch gekoppeld"
|
||||
|
||||
@@ -1,11 +1,11 @@
|
||||
{
|
||||
"workoutSentToWatch": "Trening wysłany do Watch!",
|
||||
"openWatchApp": "Otwórz aplikację SwimBuddy na swojej Apple Watch, aby rozpocząć śledzenie.",
|
||||
"openWatchApp": "Otwórz aplikację SwimBuddy na swojej Watch, aby rozpocząć śledzenie.",
|
||||
"failedToSend": "Nie udało się wysłać",
|
||||
"couldNotSend": "Nie udało się wysłać treningu do Watch. Uruchamianie bez Watch.",
|
||||
"startWithoutWatch": "Uruchom bez Watch",
|
||||
"sendToWatchAndStart": "Wyślij do Watch i rozpocznij",
|
||||
"syncToWatch": "Trening zostanie zsynchronizowany z Apple Watch",
|
||||
"syncToWatch": "Trening zostanie zsynchronizowany z Watch",
|
||||
"trackedOnDevice": "Trening będzie śledzony na tym urządzeniu",
|
||||
"rest": "Odpoczynek",
|
||||
"betweenSets": "{{duration}}s odpoczynku między seriami",
|
||||
@@ -13,7 +13,7 @@
|
||||
"greatJob": "Świetna robota! Dziś byłeś w wodzie!",
|
||||
"loadingSummary": "Ładowanie podsumowania...",
|
||||
"watchStatus": {
|
||||
"ready": "Apple Watch gotowa",
|
||||
"ready": "Watch gotowa",
|
||||
"pairedNotInRange": "Watch sparowana, ale poza zasięgiem",
|
||||
"pairedNotInstalled": "Watch sparowana, aplikacja nie zainstalowana",
|
||||
"noWatch": "Brak sparowanej Watch"
|
||||
|
||||
@@ -1,11 +1,11 @@
|
||||
{
|
||||
"workoutSentToWatch": "Treino enviado para a Watch!",
|
||||
"openWatchApp": "Abra a aplicação SwimBuddy na sua Apple Watch para iniciar o registo.",
|
||||
"openWatchApp": "Abra a aplicação SwimBuddy na sua Watch para iniciar o registo.",
|
||||
"failedToSend": "Falha no envio",
|
||||
"couldNotSend": "Não foi possível enviar o treino para a Watch. Iniciando sem Watch.",
|
||||
"startWithoutWatch": "Iniciar sem Watch",
|
||||
"sendToWatchAndStart": "Enviar para a Watch e iniciar",
|
||||
"syncToWatch": "O treino será sincronizado com a sua Apple Watch",
|
||||
"syncToWatch": "O treino será sincronizado com a sua Watch",
|
||||
"trackedOnDevice": "O treino será registado neste dispositivo",
|
||||
"rest": "Descanso",
|
||||
"betweenSets": "{{duration}}s de descanso entre séries",
|
||||
@@ -13,7 +13,7 @@
|
||||
"greatJob": "Bom trabalho! Esteve na água hoje!",
|
||||
"loadingSummary": "A carregar resumo...",
|
||||
"watchStatus": {
|
||||
"ready": "Apple Watch pronta",
|
||||
"ready": "Watch pronta",
|
||||
"pairedNotInRange": "Watch associada, mas fora de alcance",
|
||||
"pairedNotInstalled": "Watch associada, aplicação não instalada",
|
||||
"noWatch": "Nenhuma Watch associada"
|
||||
|
||||
@@ -9,6 +9,7 @@ WatchConnectivity = requireOptionalNativeModule("WatchConnectivity") ?? {
|
||||
isReachable: async () => false,
|
||||
sendWorkoutPlan: async () => ({ error: "WatchConnectivity native module not available" }),
|
||||
sendWorkoutCompletion: async () => ({ error: "WatchConnectivity native module not available" }),
|
||||
pollEvents: async () => [],
|
||||
getApplicationContext: async () => null,
|
||||
updateApplicationContext: async () => {},
|
||||
};
|
||||
|
||||
@@ -0,0 +1,17 @@
|
||||
import { requireOptionalNativeModule, Platform } from "expo-modules-core";
|
||||
|
||||
const WearConnectivityNative = Platform.select({
|
||||
android: () => requireOptionalNativeModule("WearConnectivity"),
|
||||
default: () => null,
|
||||
})();
|
||||
|
||||
const WearConnectivity = WearConnectivityNative ?? {
|
||||
isSupported: async () => false,
|
||||
isPaired: async () => false,
|
||||
isReachable: async () => false,
|
||||
sendWorkoutPlan: async () => ({ error: "WearOS native module not available" }),
|
||||
sendWorkoutCompletion: async () => ({ error: "WearOS native module not available" }),
|
||||
pollEvents: async () => [],
|
||||
};
|
||||
|
||||
export default WearConnectivity;
|
||||
Reference in New Issue
Block a user