From 7f2fe0c3002f4335facd845192d69a2de6a03537 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Zo=C3=AB?= Date: Tue, 11 Aug 2026 16:35:02 +0200 Subject: [PATCH] feat(wearos): track swimming stroke count during workouts --- .../nl/guidoit/swimbuddy/HealthServicesManager.kt | 8 ++++++++ .../java/nl/guidoit/swimbuddy/WorkoutViewModel.kt | 15 ++++++++++++++- .../main/java/nl/guidoit/swimbuddy/data/Models.kt | 3 ++- 3 files changed, 24 insertions(+), 2 deletions(-) diff --git a/targets/wearos/app/src/main/java/nl/guidoit/swimbuddy/HealthServicesManager.kt b/targets/wearos/app/src/main/java/nl/guidoit/swimbuddy/HealthServicesManager.kt index 68b80b9..bb53d54 100644 --- a/targets/wearos/app/src/main/java/nl/guidoit/swimbuddy/HealthServicesManager.kt +++ b/targets/wearos/app/src/main/java/nl/guidoit/swimbuddy/HealthServicesManager.kt @@ -20,12 +20,14 @@ class HealthServicesManager(context: Context) { private val hrChannel = Channel(Channel.CONFLATED) private val calChannel = Channel(Channel.CONFLATED) + private val strokeCountChannel = Channel(Channel.CONFLATED) private var callbackRegistered = false private val combinedCallback = object : androidx.health.services.client.ExerciseUpdateCallback { override fun onNewUpdate(update: ExerciseUpdate) { update.getData(DataType.HEART_RATE_BPM)?.toDouble()?.let { hrChannel.trySend(it) } update.getData(DataType.CALORIES)?.toDouble()?.let { calChannel.trySend(it) } + update.getData(DataType.SWIMMING_STROKES)?.let { strokeCountChannel.trySend(it) } } override fun onAvailabilityChanged(dataType: DataType, availability: DataPointAvailability) {} @@ -46,6 +48,7 @@ class HealthServicesManager(context: Context) { val config = ExerciseConfig.builder(ExerciseType.SWIMMING_POOL) .setDataTypes(poolCapabilities.supportedDataTypes) + .setSwimmingPoolLengthMeters(poolLengthMeters) .build() exerciseClient.startExerciseAsync(config).await() @@ -73,6 +76,11 @@ class HealthServicesManager(context: Context) { return calChannel.receiveAsFlow() } + fun strokeCountFlow(): Flow { + ensureCallbackRegistered() + return strokeCountChannel.receiveAsFlow() + } + private fun ensureCallbackRegistered() { if (!callbackRegistered) { exerciseClient.setUpdateCallback(combinedCallback) diff --git a/targets/wearos/app/src/main/java/nl/guidoit/swimbuddy/WorkoutViewModel.kt b/targets/wearos/app/src/main/java/nl/guidoit/swimbuddy/WorkoutViewModel.kt index 51f58da..5df208d 100644 --- a/targets/wearos/app/src/main/java/nl/guidoit/swimbuddy/WorkoutViewModel.kt +++ b/targets/wearos/app/src/main/java/nl/guidoit/swimbuddy/WorkoutViewModel.kt @@ -70,6 +70,7 @@ class WorkoutViewModel(application: Application) : AndroidViewModel(application) private var startTime = 0L private var lapStartTime = 0L private var lapDetector: LapDetector? = null + private var currentStrokeCount = 0L private val workoutReceiver = object : BroadcastReceiver() { override fun onReceive(context: Context?, intent: Intent?) { @@ -141,10 +142,12 @@ class WorkoutViewModel(application: Application) : AndroidViewModel(application) _currentSetIndex.value = 0 _currentRep.value = 0 _completedLaps.value = emptyList() + currentStrokeCount = 0L startElapsedTimeTracking() startHeartRateTracking() startCaloriesTracking() + startStrokeCountTracking() startLapDetection() } } @@ -186,6 +189,14 @@ class WorkoutViewModel(application: Application) : AndroidViewModel(application) } } + private fun startStrokeCountTracking() { + viewModelScope.launch { + healthManager.strokeCountFlow().collect { delta -> + currentStrokeCount += delta + } + } + } + fun completeLap() { if (!_isWorkoutActive.value || _isResting.value) return val workout = _selectedWorkout.value ?: return @@ -202,10 +213,12 @@ class WorkoutViewModel(application: Application) : AndroidViewModel(application) repNumber = _currentRep.value + 1, distance = currentSet.distancePerRep, duration = lapDuration, - heartRate = _heartRate.value + heartRate = _heartRate.value, + strokeCount = currentStrokeCount.toInt() ) _completedLaps.value = _completedLaps.value + lap + currentStrokeCount = 0L if (_currentRep.value < currentSet.repCount - 1) { _currentRep.value++ diff --git a/targets/wearos/app/src/main/java/nl/guidoit/swimbuddy/data/Models.kt b/targets/wearos/app/src/main/java/nl/guidoit/swimbuddy/data/Models.kt index 1032df8..a1d811d 100644 --- a/targets/wearos/app/src/main/java/nl/guidoit/swimbuddy/data/Models.kt +++ b/targets/wearos/app/src/main/java/nl/guidoit/swimbuddy/data/Models.kt @@ -21,7 +21,8 @@ data class CompletedLap( val repNumber: Int, val distance: Int, val duration: Float, - val heartRate: Int? + val heartRate: Int?, + val strokeCount: Int? = null ) data class WorkoutSummary(