Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
Expand Up @@ -228,16 +228,49 @@ class BleConnectionManagerTest {
}
}

private class FakeWorkoutStateProvider(var active: Boolean, var midSet: Boolean = false) : WorkoutStateProvider {
private class FakeWorkoutStateProvider(
var active: Boolean,
var midSet: Boolean = false,
var allBodyweight: Boolean = false, // Issue #693
) : WorkoutStateProvider {
var connectionLostCallbacks = 0

override val isWorkoutActiveForConnectionAlert: Boolean
get() = active
get() = active && !allBodyweight
override val isWorkoutMidSet: Boolean
get() = midSet

override fun onWorkoutConnectionLost() {
connectionLostCallbacks++
}
}

@Test
fun `disconnect during bodyweight-only workout does not set connection lost alert`() = runTest {
val managerScope = CoroutineScope(coroutineContext + SupervisorJob())
try {
val workoutStateProvider = FakeWorkoutStateProvider(active = true, allBodyweight = true)
val settingsManager =
SettingsManager(fakePreferencesManager, fakeProfileRepository, managerScope)
val manager =
BleConnectionManager(
fakeBleRepository,
settingsManager,
workoutStateProvider,
MutableSharedFlow(),
managerScope,
)
advanceUntilIdle()

fakeBleRepository.simulateConnect("Vee_Test")
advanceUntilIdle()
fakeBleRepository.simulateDisconnect()
advanceUntilIdle()

assertFalse(manager.connectionLostDuringWorkout.value)
assertEquals(0, workoutStateProvider.connectionLostCallbacks)
} finally {
managerScope.cancel()
}
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -601,6 +601,44 @@ class MainViewModelTest {
assertEquals(2, viewModel.repCount.value.workingReps)
}

@Test
fun `disconnect during bodyweight-only workout does not show connection lost alert`() = runTest(testCoroutineRule.dispatcher) {
fakeBleRepository.simulateConnect("Vee_Test", "AA:BB:CC:DD:EE:FF")
advanceUntilIdle()

val bodyweightRoutine = Routine(
id = "routine-bodyweight-test",
name = "Bodyweight Test",
exercises = listOf(
RoutineExercise(
id = "routine-ex-bw-1",
exercise = Exercise(
id = "pullup",
name = "Pull-Up",
muscleGroup = "Back",
equipment = "",
isBodyweightOverride = true,
),
orderIndex = 0,
setReps = listOf(10),
weightPerCableKg = 0f,
warmupSets = emptyList(),
),
),
)
viewModel.loadRoutine(bodyweightRoutine)
advanceUntilIdle()
viewModel.enterSetReady(0, 0)
advanceUntilIdle()
viewModel.startWorkout(skipCountdown = true)
advanceUntilIdle()

fakeBleRepository.simulateDisconnect()
advanceUntilIdle()

assertFalse(viewModel.connectionLostDuringWorkout.value)
}

@Test
fun `disconnect during workout sets connection lost flag`() = runTest(testCoroutineRule.dispatcher) {
fakeBleRepository.simulateConnect("Vee_Test", "AA:BB:CC:DD:EE:FF")
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -472,9 +472,24 @@ class DefaultWorkoutSessionManager(
// ===== WorkoutStateProvider Implementation =====

override val isWorkoutActiveForConnectionAlert: Boolean
get() = when (coordinator._workoutState.value) {
is WorkoutState.Active, is WorkoutState.Countdown, is WorkoutState.Resting -> true
else -> false
get() {

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🔥 The Roast: The original isWorkoutActiveForConnectionAlert was a tidy 5-line when expression. This PR turned it into a 22-line novella with an early-return if, two stacked comments, and a when-shaped scar at the bottom. It works — but so does wearing a tuxedo to the gym.

🩹 The Fix: Collapse the state check into a single boolean and let the body be one expression. Roughly:

override val isWorkoutActiveForConnectionAlert: Boolean
    get() {
        val active = coordinator._workoutState.value.let {
            it is WorkoutState.Active || it is WorkoutState.Countdown || it is WorkoutState.Resting
        }
        if (!active) return false
        // Issue #693: trainer not needed for bodyweight-only routines.
        val routine = coordinator._loadedRoutine.value
        return routine == null ||
            routine.exercises.isEmpty() ||
            !routine.exercises.all { it.exercise.isBodyweight }
    }

📏 Severity: suggestion

val state = coordinator._workoutState.value
if (state !is WorkoutState.Active &&
state !is WorkoutState.Countdown &&
state !is WorkoutState.Resting
) {
return false
}
// Issue #693: Suppress connection alerts for bodyweight-only workouts.
// When all exercises in the loaded routine are bodyweight, the Vitruvian
// trainer is not needed and its auto-power-off should not interrupt the user.
val routine = coordinator._loadedRoutine.value
if (routine != null && routine.exercises.isNotEmpty() &&
routine.exercises.all { it.exercise.isBodyweight }
) {
return false
}
return true
}

override val isWorkoutMidSet: Boolean
Expand Down
Loading