feat(wearos): track swimming stroke count during workouts

This commit is contained in:
2026-08-11 16:35:02 +02:00
parent 42e2f5fd47
commit 7f2fe0c300
3 changed files with 24 additions and 2 deletions
@@ -20,12 +20,14 @@ class HealthServicesManager(context: Context) {
private val hrChannel = Channel<Double>(Channel.CONFLATED)
private val calChannel = Channel<Double>(Channel.CONFLATED)
private val strokeCountChannel = Channel<Long>(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<Long> {
ensureCallbackRegistered()
return strokeCountChannel.receiveAsFlow()
}
private fun ensureCallbackRegistered() {
if (!callbackRegistered) {
exerciseClient.setUpdateCallback(combinedCallback)
@@ -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++
@@ -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(