-
-
Notifications
You must be signed in to change notification settings - Fork 26
feat(#674): Add eccentric load option for Old School mode #683
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
f4fa9ed
7a8381a
398f056
1ec4ae0
d403059
964c6f7
c59424a
269dc13
1bcdc6f
2883acc
18177ca
5d9c2de
e787444
692a40e
4145174
0911004
974a1cc
5fa60f9
78b58f3
7b23c5b
2ec92a4
97fde7f
d8d04db
bc2e7c1
ed890a7
920fc15
9634100
b8577ee
aea975a
139e14a
2081143
c91f4f8
63bef4c
9672738
1be5b46
fb5c0eb
92ea819
f200f61
298a957
ca4861e
ee94b97
316c9e4
cab4dda
ceafc9d
365502d
43d9233
f86e103
9ef4c45
3e3bb1a
6fa1a98
25ab6a4
a78e526
de92936
28355e4
ad17b6d
b5097ad
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 |
|---|---|---|
|
|
@@ -234,6 +234,7 @@ fun ExerciseEditBottomSheet( | |
| val isTutMode = showCableOnlyExerciseControls && | ||
| (selectedMode is WorkoutMode.TUT || selectedMode is WorkoutMode.TUTBeast) | ||
| val isEchoMode = showCableOnlyExerciseControls && selectedMode is WorkoutMode.Echo | ||
| val isOldSchool = showCableOnlyExerciseControls && selectedMode is WorkoutMode.OldSchool | ||
|
|
||
| LaunchedEffect(rackItems, defaultRackItemIds) { | ||
| val enabledRackIds = rackItems.filter { it.enabled }.map { it.id }.toSet() | ||
|
|
@@ -414,12 +415,14 @@ fun ExerciseEditBottomSheet( | |
| } | ||
| } | ||
|
|
||
| // Echo Mode options | ||
| if (isEchoMode) { | ||
| // Echo Mode options — EccentricLoad visible for Echo + Old School; EchoLevel Echo-only | ||
| if (isEchoMode || isOldSchool) { | ||
| EccentricLoadSelector( | ||
| eccentricLoad = eccentricLoad, | ||
| onLoadChange = viewModel::onEccentricLoadChange, | ||
| ) | ||
|
Comment on lines
+419
to
423
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.
Old School routines can now save a selected overload locally, but Useful? React with 👍 / 👎.
Owner
Author
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. Fixed in commit 1a02b59: |
||
| } | ||
| if (isEchoMode) { | ||
| EchoLevelSelector( | ||
| level = echoLevel, | ||
| onLevelChange = viewModel::onEchoLevelChange, | ||
|
|
||
| Original file line number | Diff line number | Diff line change |
|---|---|---|
|
|
@@ -211,10 +211,8 @@ fun JustLiftScreen(navController: NavController, viewModel: MainViewModel, theme | |
| } | ||
|
|
||
| LaunchedEffect(workoutParameters.programMode) { | ||
| if (workoutParameters.isEchoMode) { | ||
| eccentricLoad = workoutParameters.eccentricLoad | ||
| echoLevel = workoutParameters.echoLevel | ||
| } | ||
| eccentricLoad = workoutParameters.eccentricLoad | ||
| echoLevel = workoutParameters.echoLevel | ||
| } | ||
|
|
||
| // Navigate to ActiveWorkout when workout becomes active | ||
|
|
@@ -582,7 +580,10 @@ fun JustLiftScreen(navController: NavController, viewModel: MainViewModel, theme | |
| } | ||
|
|
||
| val isEchoMode = selectedMode is WorkoutMode.Echo | ||
| if (isEchoMode) { | ||
| val isOldSchool = selectedMode is WorkoutMode.OldSchool | ||
| val showEccentricLoad = isEchoMode || isOldSchool | ||
|
|
||
| if (showEccentricLoad) { | ||
|
Comment on lines
+583
to
+586
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 Old School is selected, this exposes the existing dropdown containing 0%, 50%, and 75% as well as overload values; the routine editor does the same, and the SetReady/overview sliders expose the full 0–150% range. However, Useful? React with 👍 / 👎. |
||
| Card( | ||
| modifier = flexibleBodyModifier, | ||
| colors = CardDefaults.cardColors( | ||
|
|
@@ -645,48 +646,51 @@ fun JustLiftScreen(navController: NavController, viewModel: MainViewModel, theme | |
| overflow = TextOverflow.Ellipsis, | ||
| ) | ||
|
|
||
| HorizontalDivider(color = MaterialTheme.colorScheme.outlineVariant.copy(alpha = 0.35f)) | ||
| // EchoLevel selector — only for Echo mode | ||
| if (isEchoMode) { | ||
| HorizontalDivider(color = MaterialTheme.colorScheme.outlineVariant.copy(alpha = 0.35f)) | ||
|
|
||
| Text( | ||
| stringResource(Res.string.echo_level), | ||
| style = MaterialTheme.typography.labelLarge, | ||
| fontWeight = FontWeight.Bold, | ||
| color = MaterialTheme.colorScheme.onSurface, | ||
| ) | ||
| Text( | ||
| stringResource(Res.string.echo_level), | ||
| style = MaterialTheme.typography.labelLarge, | ||
| fontWeight = FontWeight.Bold, | ||
| color = MaterialTheme.colorScheme.onSurface, | ||
| ) | ||
|
|
||
| if (useCompactAccessibility) { | ||
| FlowRow( | ||
| modifier = Modifier.fillMaxWidth(), | ||
| horizontalArrangement = Arrangement.spacedBy(8.dp), | ||
| verticalArrangement = Arrangement.spacedBy(4.dp), | ||
| ) { | ||
| EchoLevel.entries.forEach { level -> | ||
| FilterChip( | ||
| selected = echoLevel == level, | ||
| onClick = { | ||
| echoLevel = level | ||
| selectedMode = WorkoutMode.Echo(level) | ||
| }, | ||
| label = { Text(echoLevelLabel(level)) }, | ||
| ) | ||
| if (useCompactAccessibility) { | ||
| FlowRow( | ||
| modifier = Modifier.fillMaxWidth(), | ||
| horizontalArrangement = Arrangement.spacedBy(8.dp), | ||
| verticalArrangement = Arrangement.spacedBy(4.dp), | ||
| ) { | ||
| EchoLevel.entries.forEach { level -> | ||
| FilterChip( | ||
| selected = echoLevel == level, | ||
| onClick = { | ||
| echoLevel = level | ||
| selectedMode = WorkoutMode.Echo(level) | ||
| }, | ||
| label = { Text(echoLevelLabel(level)) }, | ||
| ) | ||
| } | ||
| } | ||
| } | ||
| } else { | ||
| SingleChoiceSegmentedButtonRow( | ||
| modifier = Modifier | ||
| .fillMaxWidth() | ||
| .padding(horizontal = 4.dp), | ||
| ) { | ||
| EchoLevel.entries.forEachIndexed { index, level -> | ||
| SegmentedButton( | ||
| shape = SegmentedButtonDefaults.itemShape(index = index, count = EchoLevel.entries.size), | ||
| onClick = { | ||
| echoLevel = level | ||
| selectedMode = WorkoutMode.Echo(level) | ||
| }, | ||
| selected = echoLevel == level, | ||
| ) { | ||
| Text(echoLevelLabel(level), maxLines = 1) | ||
| } else { | ||
| SingleChoiceSegmentedButtonRow( | ||
| modifier = Modifier | ||
| .fillMaxWidth() | ||
| .padding(horizontal = 4.dp), | ||
| ) { | ||
| EchoLevel.entries.forEachIndexed { index, level -> | ||
| SegmentedButton( | ||
| shape = SegmentedButtonDefaults.itemShape(index = index, count = EchoLevel.entries.size), | ||
| onClick = { | ||
| echoLevel = level | ||
| selectedMode = WorkoutMode.Echo(level) | ||
| }, | ||
| selected = echoLevel == level, | ||
| ) { | ||
| Text(echoLevelLabel(level), maxLines = 1) | ||
| } | ||
| } | ||
| } | ||
| } | ||
|
|
||
| Original file line number | Diff line number | Diff line change |
|---|---|---|
|
|
@@ -333,6 +333,7 @@ fun RoutineOverviewScreen(navController: NavController, viewModel: MainViewModel | |
| adjustedReps = adjustments.reps, | ||
| isAMRAP = exercise.isAMRAP, | ||
| isEchoMode = exercise.programMode is ProgramMode.Echo, | ||
| isOldSchool = exercise.programMode is ProgramMode.OldSchool, | ||
| echoLevel = adjustments.echoLevel, | ||
| eccentricLoadPercent = adjustments.eccentricLoadPercent, | ||
| sizing = overviewSizing, | ||
|
|
@@ -552,6 +553,7 @@ private fun ExerciseOverviewCard( | |
| adjustedReps: Int, | ||
| isAMRAP: Boolean, | ||
| isEchoMode: Boolean, | ||
| isOldSchool: Boolean = false, | ||
| echoLevel: EchoLevel, | ||
| eccentricLoadPercent: Int, | ||
| sizing: RoutineOverviewSizing, | ||
|
|
@@ -667,18 +669,22 @@ private fun ExerciseOverviewCard( | |
| ) | ||
|
|
||
| if (isEchoMode) { | ||
| // Echo mode: Show Echo Level + Eccentric Load + Reps | ||
| EchoLevelPillSelector( | ||
| selectedLevel = echoLevel, | ||
| onLevelChange = onEchoLevelChange, | ||
| ) | ||
| } | ||
|
|
||
| // Eccentric Load slider — visible for Echo and Old School modes | ||
| if (isEchoMode || isOldSchool) { | ||
| OverviewEccentricLoadSlider( | ||
| percent = eccentricLoadPercent, | ||
| onPercentChange = onEccentricLoadChange, | ||
|
Comment on lines
+679
to
682
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.
For an Old School routine, changing this newly visible slider only updates Useful? React with 👍 / 👎. |
||
| ) | ||
| } | ||
|
|
||
| // Reps for Echo mode | ||
| // Reps adjuster for Echo mode only | ||
| if (isEchoMode) { | ||
| if (!isAMRAP) { | ||
| SliderWithButtons( | ||
| value = adjustedReps.toFloat(), | ||
|
|
||
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.
When an Old School set uses more than 100% eccentric load, this branch replaces the 96-byte program frame with
createEchoControl(). That 32-byte frame serializes reps, Echo timing/velocity, and eccentric percentage, but neitherweightPerCableKg, progression, nor the Old School phase profile, so a freshly connected machine cannot receive the user's selected concentric weight and is configured as Echo rather than Old School. The overload path needs a protocol sequence or packet that retains the Old School force configuration instead of sending only the Echo frame.Useful? React with 👍 / 👎.