From a17ae0dea5103847846b9e41caf4c6401f5c6553 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Zo=C3=AB?= Date: Sun, 9 Aug 2026 18:33:23 +0200 Subject: [PATCH] feat(watch): display water lock status indicator during workout --- targets/watch/Localizable.xcstrings | 527 +++++++++++--------- targets/watch/Views/ActiveWorkoutView.swift | 11 + targets/watch/WorkoutManager.swift | 22 + 3 files changed, 320 insertions(+), 240 deletions(-) diff --git a/targets/watch/Localizable.xcstrings b/targets/watch/Localizable.xcstrings index 3f44328..f668f7b 100644 --- a/targets/watch/Localizable.xcstrings +++ b/targets/watch/Localizable.xcstrings @@ -417,7 +417,63 @@ } }, "End Workout" : { - + "extractionState" : "manual", + "localizations" : { + "de" : { + "stringUnit" : { + "state" : "translated", + "value" : "Workout beenden" + } + }, + "dk" : { + "stringUnit" : { + "state" : "translated", + "value" : "Afslut træning" + } + }, + "en" : { + "stringUnit" : { + "state" : "translated", + "value" : "End Workout" + } + }, + "es" : { + "stringUnit" : { + "state" : "translated", + "value" : "Finalizar entrenamiento" + } + }, + "fr" : { + "stringUnit" : { + "state" : "translated", + "value" : "Terminer l'entraînement" + } + }, + "it" : { + "stringUnit" : { + "state" : "translated", + "value" : "Termina allenamento" + } + }, + "nl" : { + "stringUnit" : { + "state" : "translated", + "value" : "Workout beëindigen" + } + }, + "pl" : { + "stringUnit" : { + "state" : "translated", + "value" : "Zakończ trening" + } + }, + "pt" : { + "stringUnit" : { + "state" : "translated", + "value" : "Terminar treino" + } + } + } }, "kcal" : { "extractionState" : "manual", @@ -843,7 +899,63 @@ } }, "Pause" : { - + "extractionState" : "manual", + "localizations" : { + "de" : { + "stringUnit" : { + "state" : "translated", + "value" : "Pause" + } + }, + "dk" : { + "stringUnit" : { + "state" : "translated", + "value" : "Pause" + } + }, + "en" : { + "stringUnit" : { + "state" : "translated", + "value" : "Pause" + } + }, + "es" : { + "stringUnit" : { + "state" : "translated", + "value" : "Pausa" + } + }, + "fr" : { + "stringUnit" : { + "state" : "translated", + "value" : "Pause" + } + }, + "it" : { + "stringUnit" : { + "state" : "translated", + "value" : "Pausa" + } + }, + "nl" : { + "stringUnit" : { + "state" : "translated", + "value" : "Pauze" + } + }, + "pl" : { + "stringUnit" : { + "state" : "translated", + "value" : "Pauza" + } + }, + "pt" : { + "stringUnit" : { + "state" : "translated", + "value" : "Pausa" + } + } + } }, "Recent" : { "extractionState" : "manual", @@ -974,10 +1086,122 @@ } }, "Rest %@" : { - + "extractionState" : "manual", + "localizations" : { + "de" : { + "stringUnit" : { + "state" : "translated", + "value" : "Pause %@" + } + }, + "dk" : { + "stringUnit" : { + "state" : "translated", + "value" : "Hvile %@" + } + }, + "en" : { + "stringUnit" : { + "state" : "translated", + "value" : "Rest %@" + } + }, + "es" : { + "stringUnit" : { + "state" : "translated", + "value" : "Descanso %@" + } + }, + "fr" : { + "stringUnit" : { + "state" : "translated", + "value" : "Repos %@" + } + }, + "it" : { + "stringUnit" : { + "state" : "translated", + "value" : "Recupero %@" + } + }, + "nl" : { + "stringUnit" : { + "state" : "translated", + "value" : "Rust %@" + } + }, + "pl" : { + "stringUnit" : { + "state" : "translated", + "value" : "Odpoczynek %@" + } + }, + "pt" : { + "stringUnit" : { + "state" : "translated", + "value" : "Descanso %@" + } + } + } }, "Resume" : { - + "extractionState" : "manual", + "localizations" : { + "de" : { + "stringUnit" : { + "state" : "translated", + "value" : "Fortsetzen" + } + }, + "dk" : { + "stringUnit" : { + "state" : "translated", + "value" : "Forsæt" + } + }, + "en" : { + "stringUnit" : { + "state" : "translated", + "value" : "Resume" + } + }, + "es" : { + "stringUnit" : { + "state" : "translated", + "value" : "Reanudar" + } + }, + "fr" : { + "stringUnit" : { + "state" : "translated", + "value" : "Reprendre" + } + }, + "it" : { + "stringUnit" : { + "state" : "translated", + "value" : "Riprendi" + } + }, + "nl" : { + "stringUnit" : { + "state" : "translated", + "value" : "Hervatten" + } + }, + "pl" : { + "stringUnit" : { + "state" : "translated", + "value" : "Wznów" + } + }, + "pt" : { + "stringUnit" : { + "state" : "translated", + "value" : "Retomar" + } + } + } }, "Saved Workouts" : { "extractionState" : "manual", @@ -1287,6 +1511,65 @@ } } }, + "Water Lock" : { + "extractionState" : "manual", + "localizations" : { + "de" : { + "stringUnit" : { + "state" : "translated", + "value" : "Wassersperre" + } + }, + "dk" : { + "stringUnit" : { + "state" : "translated", + "value" : "Vandlås" + } + }, + "en" : { + "stringUnit" : { + "state" : "translated", + "value" : "Water Lock" + } + }, + "es" : { + "stringUnit" : { + "state" : "translated", + "value" : "Bloqueo de agua" + } + }, + "fr" : { + "stringUnit" : { + "state" : "translated", + "value" : "Verrouillage eau" + } + }, + "it" : { + "stringUnit" : { + "state" : "translated", + "value" : "Blocco acqua" + } + }, + "nl" : { + "stringUnit" : { + "state" : "translated", + "value" : "Watermodus" + } + }, + "pl" : { + "stringUnit" : { + "state" : "translated", + "value" : "Blokada wody" + } + }, + "pt" : { + "stringUnit" : { + "state" : "translated", + "value" : "Bloqueio de água" + } + } + } + }, "Workout" : { "extractionState" : "manual", "localizations" : { @@ -1346,242 +1629,6 @@ } } }, - "End Workout" : { - "extractionState" : "manual", - "localizations" : { - "de" : { - "stringUnit" : { - "state" : "translated", - "value" : "Workout beenden" - } - }, - "dk" : { - "stringUnit" : { - "state" : "translated", - "value" : "Afslut træning" - } - }, - "en" : { - "stringUnit" : { - "state" : "translated", - "value" : "End Workout" - } - }, - "es" : { - "stringUnit" : { - "state" : "translated", - "value" : "Finalizar entrenamiento" - } - }, - "fr" : { - "stringUnit" : { - "state" : "translated", - "value" : "Terminer l'entraînement" - } - }, - "it" : { - "stringUnit" : { - "state" : "translated", - "value" : "Termina allenamento" - } - }, - "nl" : { - "stringUnit" : { - "state" : "translated", - "value" : "Workout beëindigen" - } - }, - "pl" : { - "stringUnit" : { - "state" : "translated", - "value" : "Zakończ trening" - } - }, - "pt" : { - "stringUnit" : { - "state" : "translated", - "value" : "Terminar treino" - } - } - } - }, - "Pause" : { - "extractionState" : "manual", - "localizations" : { - "de" : { - "stringUnit" : { - "state" : "translated", - "value" : "Pause" - } - }, - "dk" : { - "stringUnit" : { - "state" : "translated", - "value" : "Pause" - } - }, - "en" : { - "stringUnit" : { - "state" : "translated", - "value" : "Pause" - } - }, - "es" : { - "stringUnit" : { - "state" : "translated", - "value" : "Pausa" - } - }, - "fr" : { - "stringUnit" : { - "state" : "translated", - "value" : "Pause" - } - }, - "it" : { - "stringUnit" : { - "state" : "translated", - "value" : "Pausa" - } - }, - "nl" : { - "stringUnit" : { - "state" : "translated", - "value" : "Pauze" - } - }, - "pl" : { - "stringUnit" : { - "state" : "translated", - "value" : "Pauza" - } - }, - "pt" : { - "stringUnit" : { - "state" : "translated", - "value" : "Pausa" - } - } - } - }, - "Resume" : { - "extractionState" : "manual", - "localizations" : { - "de" : { - "stringUnit" : { - "state" : "translated", - "value" : "Fortsetzen" - } - }, - "dk" : { - "stringUnit" : { - "state" : "translated", - "value" : "Forsæt" - } - }, - "en" : { - "stringUnit" : { - "state" : "translated", - "value" : "Resume" - } - }, - "es" : { - "stringUnit" : { - "state" : "translated", - "value" : "Reanudar" - } - }, - "fr" : { - "stringUnit" : { - "state" : "translated", - "value" : "Reprendre" - } - }, - "it" : { - "stringUnit" : { - "state" : "translated", - "value" : "Riprendi" - } - }, - "nl" : { - "stringUnit" : { - "state" : "translated", - "value" : "Hervatten" - } - }, - "pl" : { - "stringUnit" : { - "state" : "translated", - "value" : "Wznów" - } - }, - "pt" : { - "stringUnit" : { - "state" : "translated", - "value" : "Retomar" - } - } - } - }, - "Rest %@" : { - "extractionState" : "manual", - "localizations" : { - "de" : { - "stringUnit" : { - "state" : "translated", - "value" : "Pause %@" - } - }, - "dk" : { - "stringUnit" : { - "state" : "translated", - "value" : "Hvile %@" - } - }, - "en" : { - "stringUnit" : { - "state" : "translated", - "value" : "Rest %@" - } - }, - "es" : { - "stringUnit" : { - "state" : "translated", - "value" : "Descanso %@" - } - }, - "fr" : { - "stringUnit" : { - "state" : "translated", - "value" : "Repos %@" - } - }, - "it" : { - "stringUnit" : { - "state" : "translated", - "value" : "Recupero %@" - } - }, - "nl" : { - "stringUnit" : { - "state" : "translated", - "value" : "Rust %@" - } - }, - "pl" : { - "stringUnit" : { - "state" : "translated", - "value" : "Odpoczynek %@" - } - }, - "pt" : { - "stringUnit" : { - "state" : "translated", - "value" : "Descanso %@" - } - } - } - }, "Workout complete!" : { "extractionState" : "manual", "localizations" : { diff --git a/targets/watch/Views/ActiveWorkoutView.swift b/targets/watch/Views/ActiveWorkoutView.swift index 2df19f6..4e8204f 100644 --- a/targets/watch/Views/ActiveWorkoutView.swift +++ b/targets/watch/Views/ActiveWorkoutView.swift @@ -199,6 +199,17 @@ struct ControlsPage: View { var body: some View { VStack(spacing: 16) { + if workoutManager.isWaterLockEnabled { + HStack(spacing: 4) { + Image(systemName: "drop.fill") + .foregroundColor(.cyan) + .font(.caption) + Text("Water Lock") + .font(.caption) + .foregroundColor(.cyan) + } + } + Spacer() Button { diff --git a/targets/watch/WorkoutManager.swift b/targets/watch/WorkoutManager.swift index 59cec16..39a3c5e 100644 --- a/targets/watch/WorkoutManager.swift +++ b/targets/watch/WorkoutManager.swift @@ -41,6 +41,9 @@ class WorkoutManager: NSObject, ObservableObject, HKLiveWorkoutBuilderDelegate { @Published var lapDetected = false @Published var healthKitAuthorized = false @Published var isPaused = false + @Published var isWaterLockEnabled = false + + private var waterLockCheckTimer: Timer? override init() { super.init() @@ -163,6 +166,7 @@ class WorkoutManager: NSObject, ObservableObject, HKLiveWorkoutBuilderDelegate { DispatchQueue.main.asyncAfter(deadline: .now() + 0.1) { WKInterfaceDevice.current().enableWaterLock() } + startWaterLockCheck() WKInterfaceDevice.current().play(.start) } @@ -189,6 +193,7 @@ class WorkoutManager: NSObject, ObservableObject, HKLiveWorkoutBuilderDelegate { restTimer?.invalidate() isWorkoutActive = false + stopWaterLockCheck() stopLapDetection() stopHeartRateQuery() stopCalorieQuery() @@ -199,6 +204,23 @@ class WorkoutManager: NSObject, ObservableObject, HKLiveWorkoutBuilderDelegate { WKInterfaceDevice.current().play(.success) } + // MARK: - Water Lock Check + + private func startWaterLockCheck() { + isWaterLockEnabled = WKInterfaceDevice.current().isWaterLockEnabled + waterLockCheckTimer = Timer.scheduledTimer(withTimeInterval: 1.5, repeats: true) { [weak self] _ in + DispatchQueue.main.async { + self?.isWaterLockEnabled = WKInterfaceDevice.current().isWaterLockEnabled + } + } + } + + private func stopWaterLockCheck() { + waterLockCheckTimer?.invalidate() + waterLockCheckTimer = nil + isWaterLockEnabled = false + } + // MARK: - Workout Summary private func createWorkoutSummary() {