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 hrChannel = Channel<Double>(Channel.CONFLATED)
private val calChannel = Channel<Double>(Channel.CONFLATED) private val calChannel = Channel<Double>(Channel.CONFLATED)
private val strokeCountChannel = Channel<Long>(Channel.CONFLATED)
private var callbackRegistered = false private var callbackRegistered = false
private val combinedCallback = object : androidx.health.services.client.ExerciseUpdateCallback { private val combinedCallback = object : androidx.health.services.client.ExerciseUpdateCallback {
override fun onNewUpdate(update: ExerciseUpdate) { override fun onNewUpdate(update: ExerciseUpdate) {
update.getData(DataType.HEART_RATE_BPM)?.toDouble()?.let { hrChannel.trySend(it) } update.getData(DataType.HEART_RATE_BPM)?.toDouble()?.let { hrChannel.trySend(it) }
update.getData(DataType.CALORIES)?.toDouble()?.let { calChannel.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) {} override fun onAvailabilityChanged(dataType: DataType, availability: DataPointAvailability) {}
@@ -46,6 +48,7 @@ class HealthServicesManager(context: Context) {
val config = ExerciseConfig.builder(ExerciseType.SWIMMING_POOL) val config = ExerciseConfig.builder(ExerciseType.SWIMMING_POOL)
.setDataTypes(poolCapabilities.supportedDataTypes) .setDataTypes(poolCapabilities.supportedDataTypes)
.setSwimmingPoolLengthMeters(poolLengthMeters)
.build() .build()
exerciseClient.startExerciseAsync(config).await() exerciseClient.startExerciseAsync(config).await()
@@ -73,6 +76,11 @@ class HealthServicesManager(context: Context) {
return calChannel.receiveAsFlow() return calChannel.receiveAsFlow()
} }
fun strokeCountFlow(): Flow<Long> {
ensureCallbackRegistered()
return strokeCountChannel.receiveAsFlow()
}
private fun ensureCallbackRegistered() { private fun ensureCallbackRegistered() {
if (!callbackRegistered) { if (!callbackRegistered) {
exerciseClient.setUpdateCallback(combinedCallback) exerciseClient.setUpdateCallback(combinedCallback)
@@ -70,6 +70,7 @@ class WorkoutViewModel(application: Application) : AndroidViewModel(application)
private var startTime = 0L private var startTime = 0L
private var lapStartTime = 0L private var lapStartTime = 0L
private var lapDetector: LapDetector? = null private var lapDetector: LapDetector? = null
private var currentStrokeCount = 0L
private val workoutReceiver = object : BroadcastReceiver() { private val workoutReceiver = object : BroadcastReceiver() {
override fun onReceive(context: Context?, intent: Intent?) { override fun onReceive(context: Context?, intent: Intent?) {
@@ -141,10 +142,12 @@ class WorkoutViewModel(application: Application) : AndroidViewModel(application)
_currentSetIndex.value = 0 _currentSetIndex.value = 0
_currentRep.value = 0 _currentRep.value = 0
_completedLaps.value = emptyList() _completedLaps.value = emptyList()
currentStrokeCount = 0L
startElapsedTimeTracking() startElapsedTimeTracking()
startHeartRateTracking() startHeartRateTracking()
startCaloriesTracking() startCaloriesTracking()
startStrokeCountTracking()
startLapDetection() startLapDetection()
} }
} }
@@ -186,6 +189,14 @@ class WorkoutViewModel(application: Application) : AndroidViewModel(application)
} }
} }
private fun startStrokeCountTracking() {
viewModelScope.launch {
healthManager.strokeCountFlow().collect { delta ->
currentStrokeCount += delta
}
}
}
fun completeLap() { fun completeLap() {
if (!_isWorkoutActive.value || _isResting.value) return if (!_isWorkoutActive.value || _isResting.value) return
val workout = _selectedWorkout.value ?: return val workout = _selectedWorkout.value ?: return
@@ -202,10 +213,12 @@ class WorkoutViewModel(application: Application) : AndroidViewModel(application)
repNumber = _currentRep.value + 1, repNumber = _currentRep.value + 1,
distance = currentSet.distancePerRep, distance = currentSet.distancePerRep,
duration = lapDuration, duration = lapDuration,
heartRate = _heartRate.value heartRate = _heartRate.value,
strokeCount = currentStrokeCount.toInt()
) )
_completedLaps.value = _completedLaps.value + lap _completedLaps.value = _completedLaps.value + lap
currentStrokeCount = 0L
if (_currentRep.value < currentSet.repCount - 1) { if (_currentRep.value < currentSet.repCount - 1) {
_currentRep.value++ _currentRep.value++
@@ -21,7 +21,8 @@ data class CompletedLap(
val repNumber: Int, val repNumber: Int,
val distance: Int, val distance: Int,
val duration: Float, val duration: Float,
val heartRate: Int? val heartRate: Int?,
val strokeCount: Int? = null
) )
data class WorkoutSummary( data class WorkoutSummary(