-
-
Notifications
You must be signed in to change notification settings - Fork 26
feat(#646): label set types in workout UI #647
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Changes from all commits
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
| Original file line number | Diff line number | Diff line change |
|---|---|---|
|
|
@@ -700,5 +700,9 @@ | |
| <string name="cd_equipment_rack">Rack de equipamiento</string> | ||
| <string name="bodyweight_effective_load_includes">Incluye: %1$s</string> | ||
| <string name="bodyweight_effective_load_more">+%1$d más</string> | ||
| <!-- ==================== Set type labels (Issue #646) ==================== --> | ||
| <string name="set_type_calibration">Calibration Reps</string> | ||
| <string name="set_type_warmup">Warm-up Set %1$d</string> | ||
| <string name="set_type_working">Working Set</string> | ||
|
Comment on lines
+703
to
+706
Contributor
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. In Android and Compose Multiplatform, string resources automatically fall back to the default |
||
|
|
||
| </resources> | ||
| Original file line number | Diff line number | Diff line change |
|---|---|---|
|
|
@@ -700,5 +700,9 @@ | |
| <string name="cd_equipment_rack">Rack d\'équipement</string> | ||
| <string name="bodyweight_effective_load_includes">Inclus : %1$s</string> | ||
| <string name="bodyweight_effective_load_more">+%1$d de plus</string> | ||
| <!-- ==================== Set type labels (Issue #646) ==================== --> | ||
| <string name="set_type_calibration">Calibration Reps</string> | ||
| <string name="set_type_warmup">Warm-up Set %1$d</string> | ||
| <string name="set_type_working">Working Set</string> | ||
|
Comment on lines
+703
to
+706
Contributor
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. In Android and Compose Multiplatform, string resources automatically fall back to the default |
||
|
|
||
| </resources> | ||
| Original file line number | Diff line number | Diff line change |
|---|---|---|
|
|
@@ -67,5 +67,9 @@ | |
| <string name="settings_ble_compat_on">Attivato</string> | ||
| <string name="settings_ble_compat_off">Disattivato</string> | ||
| <string name="settings_ble_compat_reconnect_hint">Ha effetto alla prossima connessione. Riconnettiti al trainer dopo la modifica.</string> | ||
| <!-- ==================== Set type labels (Issue #646) ==================== --> | ||
| <string name="set_type_calibration">Calibration Reps</string> | ||
| <string name="set_type_warmup">Warm-up Set %1$d</string> | ||
| <string name="set_type_working">Working Set</string> | ||
|
Comment on lines
+70
to
+73
Contributor
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. In Android and Compose Multiplatform, string resources automatically fall back to the default |
||
|
|
||
| </resources> | ||
| Original file line number | Diff line number | Diff line change |
|---|---|---|
|
|
@@ -679,5 +679,9 @@ | |
| <string name="cd_equipment_rack">Equipment rack</string> | ||
| <string name="bodyweight_effective_load_includes">Inclusief: %1$s</string> | ||
| <string name="bodyweight_effective_load_more">+%1$d meer</string> | ||
| <!-- ==================== Set type labels (Issue #646) ==================== --> | ||
| <string name="set_type_calibration">Calibration Reps</string> | ||
| <string name="set_type_warmup">Warm-up Set %1$d</string> | ||
| <string name="set_type_working">Working Set</string> | ||
|
Comment on lines
+682
to
+685
Contributor
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. In Android and Compose Multiplatform, string resources automatically fall back to the default |
||
|
|
||
| </resources> | ||
| Original file line number | Diff line number | Diff line change |
|---|---|---|
|
|
@@ -51,6 +51,9 @@ import androidx.compose.runtime.setValue | |
| import androidx.compose.ui.Alignment | ||
| import androidx.compose.ui.Modifier | ||
| import androidx.compose.ui.draw.clip | ||
| import com.devil.phoenixproject.domain.model.RepCount | ||
| import com.devil.phoenixproject.presentation.util.SetTypeLabel | ||
| import com.devil.phoenixproject.presentation.util.setTypeLabel | ||
| import com.devil.phoenixproject.ui.theme.screenBackgroundBrush | ||
| import androidx.compose.ui.platform.testTag | ||
| import androidx.compose.ui.semantics.Role | ||
|
|
@@ -106,6 +109,8 @@ import vitruvianprojectphoenix.shared.generated.resources.equipment_rack_save_ov | |
| import vitruvianprojectphoenix.shared.generated.resources.equipment_rack_save_override_title | ||
| import vitruvianprojectphoenix.shared.generated.resources.exit_routine_message | ||
| import vitruvianprojectphoenix.shared.generated.resources.exit_routine_title | ||
| import vitruvianprojectphoenix.shared.generated.resources.set_type_warmup | ||
| import vitruvianprojectphoenix.shared.generated.resources.set_type_working | ||
| import vitruvianprojectphoenix.shared.generated.resources.target_reps | ||
|
|
||
| /** | ||
|
|
@@ -124,6 +129,9 @@ fun SetReadyScreen(navController: NavController, viewModel: MainViewModel, exerc | |
| val weightRecommendation by viewModel.weightAdjustmentRecommendation.collectAsState() | ||
| val rackItems by viewModel.rackItems.collectAsState() | ||
| val activeRackItemIds by viewModel.activeRackItemIds.collectAsState() | ||
| // Issue #646: drive set-type badge in SetReady header | ||
| val currentWarmupSetIndex by viewModel.currentWarmupSetIndex.collectAsState() | ||
| val repCount by viewModel.repCount.collectAsState() | ||
|
|
||
| // Get current state | ||
| val setReadyState = routineFlowState as? RoutineFlowState.SetReady | ||
|
|
@@ -435,6 +443,21 @@ fun SetReadyScreen(navController: NavController, viewModel: MainViewModel, exerc | |
| style = MaterialTheme.typography.labelMedium, | ||
| color = MaterialTheme.colorScheme.onPrimaryContainer.copy(alpha = 0.72f), | ||
| ) | ||
| // Issue #646: set-type badge — calibration label suppressed at SetReady | ||
| // (calibration reps run during the active set, not at this screen) | ||
| val setType = setTypeLabel(repCount, currentWarmupSetIndex, showCalibrationLabel = false) | ||
| val setTypeText = when (setType) { | ||
| is SetTypeLabel.Warmup -> stringResource(Res.string.set_type_warmup, setType.setNumber) | ||
| SetTypeLabel.Working -> stringResource(Res.string.set_type_working) | ||
| SetTypeLabel.Calibration -> "" // unreachable; showCalibrationLabel=false | ||
|
Contributor
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. 🔥 The Roast: 🩹 The Fix: Split the helper. Keep 📏 Severity: suggestion Reply with |
||
| } | ||
| if (setTypeText.isNotEmpty()) { | ||
| Text( | ||
| text = setTypeText, | ||
| style = MaterialTheme.typography.labelMedium, | ||
| color = MaterialTheme.colorScheme.onPrimaryContainer.copy(alpha = 0.72f), | ||
| ) | ||
| } | ||
| } | ||
| } | ||
| } | ||
|
|
||
| Original file line number | Diff line number | Diff line change |
|---|---|---|
|
|
@@ -33,8 +33,10 @@ import com.devil.phoenixproject.presentation.components.ForceCurveMiniGraph | |
| import com.devil.phoenixproject.presentation.components.StableRepProgress | ||
| import com.devil.phoenixproject.presentation.util.LocalWindowSizeClass | ||
| import com.devil.phoenixproject.presentation.util.ResponsiveDimensions | ||
| import com.devil.phoenixproject.presentation.util.SetTypeLabel | ||
| import com.devil.phoenixproject.presentation.util.WindowWidthSizeClass | ||
| import com.devil.phoenixproject.presentation.util.isCompactAccessibilityLayout | ||
| import com.devil.phoenixproject.presentation.util.setTypeLabel | ||
| import com.devil.phoenixproject.ui.theme.AccessibilityTheme | ||
| import com.devil.phoenixproject.ui.theme.velocityZoneColor | ||
| import com.devil.phoenixproject.ui.theme.velocityZoneLabel | ||
|
|
@@ -82,6 +84,7 @@ fun WorkoutHud( | |
| onResetExerciseTimer: () -> Unit = {}, | ||
| velocityLossThresholdPercent: Int = 20, | ||
| rackLoadAdjustment: RackLoadAdjustment = RackLoadAdjustment(), | ||
| currentWarmupSetIndex: Int = -1, // Issue #646: -1 = no warm-up phase | ||
| modifier: Modifier = Modifier, | ||
| ) { | ||
| // Determine if we're in Echo mode | ||
|
|
@@ -172,6 +175,7 @@ fun WorkoutHud( | |
| onPauseExerciseTimer = onPauseExerciseTimer, | ||
| onResumeExerciseTimer = onResumeExerciseTimer, | ||
| onResetExerciseTimer = onResetExerciseTimer, | ||
| currentWarmupSetIndex = currentWarmupSetIndex, | ||
| ) | ||
| } | ||
|
|
||
|
|
@@ -471,6 +475,7 @@ private fun ExecutionPage( | |
| onPauseExerciseTimer: () -> Unit = {}, | ||
| onResumeExerciseTimer: () -> Unit = {}, | ||
| onResetExerciseTimer: () -> Unit = {}, | ||
| currentWarmupSetIndex: Int = -1, // Issue #646: -1 = no warm-up phase | ||
| ) { | ||
| // Issue #192: Check if this is a timed exercise | ||
| val isTimedExercise = timedExerciseRemainingSeconds != null | ||
|
|
@@ -506,6 +511,20 @@ private fun ExecutionPage( | |
| ) | ||
| } | ||
|
|
||
| // Issue #646: set-type badge (Calibration Reps / Warm-up Set N / Working Set) | ||
| val setType = setTypeLabel(repCount, currentWarmupSetIndex, showCalibrationLabel = true) | ||
|
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more.
When a routine reaches a bodyweight exercise while connected, Useful? React with 👍 / 👎. |
||
| val setTypeText = when (setType) { | ||
| SetTypeLabel.Calibration -> stringResource(Res.string.set_type_calibration) | ||
| is SetTypeLabel.Warmup -> stringResource(Res.string.set_type_warmup, setType.setNumber) | ||
| SetTypeLabel.Working -> stringResource(Res.string.set_type_working) | ||
| } | ||
| Text( | ||
| text = setTypeText, | ||
| style = MaterialTheme.typography.labelLarge, | ||
| fontWeight = FontWeight.Medium, | ||
| color = MaterialTheme.colorScheme.onSurfaceVariant.copy(alpha = 0.85f), | ||
| ) | ||
|
|
||
| Spacer(modifier = Modifier.height(24.dp)) | ||
| } | ||
|
|
||
|
|
||
| Original file line number | Diff line number | Diff line change | ||||||||||||||||||||||||||||||||
|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|
| @@ -0,0 +1,39 @@ | ||||||||||||||||||||||||||||||||||
| package com.devil.phoenixproject.presentation.util | ||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||
| import com.devil.phoenixproject.domain.model.RepCount | ||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||
| /** | ||||||||||||||||||||||||||||||||||
| * Issue #646: pure-Kotlin helper for the workout set-type badge. | ||||||||||||||||||||||||||||||||||
| * | ||||||||||||||||||||||||||||||||||
| * Decides whether the active workout header should read | ||||||||||||||||||||||||||||||||||
| * "Calibration Reps" / "Warm-up Set N" / "Working Set". | ||||||||||||||||||||||||||||||||||
| * | ||||||||||||||||||||||||||||||||||
| * Kept free of `@Composable` and string resources so it stays trivially | ||||||||||||||||||||||||||||||||||
| * testable as plain JVM code. Call sites resolve the sealed label to a | ||||||||||||||||||||||||||||||||||
| * locale string via [com.devil.phoenixproject.presentation.R] mappings. | ||||||||||||||||||||||||||||||||||
| * | ||||||||||||||||||||||||||||||||||
| * `showCalibrationLabel` is `false` at Set Ready because the firmware | ||||||||||||||||||||||||||||||||||
| * calibration buffer runs during the active set, not at Set Ready. | ||||||||||||||||||||||||||||||||||
| */ | ||||||||||||||||||||||||||||||||||
| sealed class SetTypeLabel { | ||||||||||||||||||||||||||||||||||
| data object Calibration : SetTypeLabel() | ||||||||||||||||||||||||||||||||||
| data class Warmup(val setNumber: Int) : SetTypeLabel() | ||||||||||||||||||||||||||||||||||
| data object Working : SetTypeLabel() | ||||||||||||||||||||||||||||||||||
| } | ||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||
| // ponytail: 3 mirrors Constants.DEFAULT_WARMUP_REPS; revisit if firmware | ||||||||||||||||||||||||||||||||||
| // calibration buffer size ever changes. | ||||||||||||||||||||||||||||||||||
| private const val CALIBRATION_BUFFER_REPS = 3 | ||||||||||||||||||||||||||||||||||
|
Contributor
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. 🔥 The Roast: You wrote 🩹 The Fix: Delete 📏 Severity: warning Reply with |
||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||
| fun setTypeLabel( | ||||||||||||||||||||||||||||||||||
| repCount: RepCount, | ||||||||||||||||||||||||||||||||||
| currentWarmupSetIndex: Int, | ||||||||||||||||||||||||||||||||||
| showCalibrationLabel: Boolean, | ||||||||||||||||||||||||||||||||||
| ): SetTypeLabel = when { | ||||||||||||||||||||||||||||||||||
| showCalibrationLabel && repCount.warmupReps < CALIBRATION_BUFFER_REPS && !repCount.isWarmupComplete -> | ||||||||||||||||||||||||||||||||||
| SetTypeLabel.Calibration | ||||||||||||||||||||||||||||||||||
| currentWarmupSetIndex >= 0 -> | ||||||||||||||||||||||||||||||||||
| SetTypeLabel.Warmup(currentWarmupSetIndex + 1) | ||||||||||||||||||||||||||||||||||
| else -> | ||||||||||||||||||||||||||||||||||
| SetTypeLabel.Working | ||||||||||||||||||||||||||||||||||
| } | ||||||||||||||||||||||||||||||||||
|
Comment on lines
+32
to
+39
Contributor
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. During the active set of a warm-up set, the first 3 reps are unweighted calibration reps. However, labeling them as Calibration Reps instead of Warm-up Set N defeats the core purpose of this feature (which is to remind the user that they are in a warm-up set). If a warm-up set is configured with 3 or fewer reps, the user will never see the warm-up label during the active set. By reordering the conditions so that
Suggested change
|
||||||||||||||||||||||||||||||||||
| Original file line number | Diff line number | Diff line change | ||||||||||||||||||||||||||||||||||||||||||
|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|
| @@ -0,0 +1,42 @@ | ||||||||||||||||||||||||||||||||||||||||||||
| package com.devil.phoenixproject.presentation | ||||||||||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||||||||||
| import com.devil.phoenixproject.domain.model.RepCount | ||||||||||||||||||||||||||||||||||||||||||||
| import com.devil.phoenixproject.presentation.util.SetTypeLabel | ||||||||||||||||||||||||||||||||||||||||||||
| import com.devil.phoenixproject.presentation.util.setTypeLabel | ||||||||||||||||||||||||||||||||||||||||||||
| import kotlin.test.Test | ||||||||||||||||||||||||||||||||||||||||||||
| import kotlin.test.assertEquals | ||||||||||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||||||||||
| /** | ||||||||||||||||||||||||||||||||||||||||||||
| * Issue #646: pin the helper that drives the active-workout set-type badge. | ||||||||||||||||||||||||||||||||||||||||||||
| * Pure Kotlin, no Compose runtime. | ||||||||||||||||||||||||||||||||||||||||||||
| */ | ||||||||||||||||||||||||||||||||||||||||||||
| class SetTypeLabelTest { | ||||||||||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||||||||||
| @Test | ||||||||||||||||||||||||||||||||||||||||||||
| fun calibration_when_buffer_incomplete() { | ||||||||||||||||||||||||||||||||||||||||||||
| val rc = RepCount(warmupReps = 0, isWarmupComplete = false) | ||||||||||||||||||||||||||||||||||||||||||||
| val label = setTypeLabel(rc, currentWarmupSetIndex = -1, showCalibrationLabel = true) | ||||||||||||||||||||||||||||||||||||||||||||
| assertEquals(SetTypeLabel.Calibration, label) | ||||||||||||||||||||||||||||||||||||||||||||
| } | ||||||||||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||||||||||
| @Test | ||||||||||||||||||||||||||||||||||||||||||||
| fun working_when_buffer_complete() { | ||||||||||||||||||||||||||||||||||||||||||||
| val rc = RepCount(warmupReps = 3, isWarmupComplete = true) | ||||||||||||||||||||||||||||||||||||||||||||
| val label = setTypeLabel(rc, currentWarmupSetIndex = -1, showCalibrationLabel = true) | ||||||||||||||||||||||||||||||||||||||||||||
| assertEquals(SetTypeLabel.Working, label) | ||||||||||||||||||||||||||||||||||||||||||||
| } | ||||||||||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||||||||||
| @Test | ||||||||||||||||||||||||||||||||||||||||||||
| fun warmup_during_variable_warmup_set_1() { | ||||||||||||||||||||||||||||||||||||||||||||
| val rc = RepCount(warmupReps = 0, isWarmupComplete = false) | ||||||||||||||||||||||||||||||||||||||||||||
| val label = setTypeLabel(rc, currentWarmupSetIndex = 0, showCalibrationLabel = false) | ||||||||||||||||||||||||||||||||||||||||||||
| assertEquals(SetTypeLabel.Warmup(setNumber = 1), label) | ||||||||||||||||||||||||||||||||||||||||||||
| } | ||||||||||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||||||||||
| @Test | ||||||||||||||||||||||||||||||||||||||||||||
| fun working_when_no_warmup_index_at_setready() { | ||||||||||||||||||||||||||||||||||||||||||||
| val rc = RepCount(warmupReps = 0, isWarmupComplete = false) | ||||||||||||||||||||||||||||||||||||||||||||
| val label = setTypeLabel(rc, currentWarmupSetIndex = -1, showCalibrationLabel = false) | ||||||||||||||||||||||||||||||||||||||||||||
| assertEquals(SetTypeLabel.Working, label) | ||||||||||||||||||||||||||||||||||||||||||||
| } | ||||||||||||||||||||||||||||||||||||||||||||
|
Comment on lines
+35
to
+41
Contributor
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Add a unit test to verify and pin the precedence behavior where warm-up sets take precedence over calibration reps during the active set.
Suggested change
|
||||||||||||||||||||||||||||||||||||||||||||
| } | ||||||||||||||||||||||||||||||||||||||||||||
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
In Android and Compose Multiplatform, string resources automatically fall back to the default
values/strings.xmlif they are missing from locale-specific files. Explicitly adding untranslated English strings to non-English locale files is redundant, pollutes the translation files, and makes it harder for translation tools/translators to identify untranslated keys. These should be removed and only added when actual German translations are available.