diff --git a/stronglift_replacement/workout_app/lib/screens/settings_screen.dart b/stronglift_replacement/workout_app/lib/screens/settings_screen.dart index c14e70c..bfcc2b4 100644 --- a/stronglift_replacement/workout_app/lib/screens/settings_screen.dart +++ b/stronglift_replacement/workout_app/lib/screens/settings_screen.dart @@ -22,6 +22,9 @@ import 'package:workout_app/services/progression_sync_service.dart'; import 'package:workout_app/services/storage_service.dart'; import 'package:workout_app/ui/theme.dart'; +part 'settings_screen_rows.dart'; +part 'settings_screen_thresholds.dart'; + /// Screen for editing per-exercise thresholds and manual weight overrides. /// /// No [BackupSlot] is wired into the shared Sync settings screen: unlike the @@ -498,274 +501,3 @@ class _SettingsScreenState extends State { ); } } - -class _SectionHeader extends StatelessWidget { - const _SectionHeader(this.text); - - final String text; - - @override - Widget build(BuildContext context) { - return Text( - text, - style: TextStyle( - color: Theme.of(context).colorScheme.onSurfaceVariant, - fontSize: AppTextSize.caption, - letterSpacing: 1.4, - ), - ); - } -} - -class _RepsRow extends StatelessWidget { - const _RepsRow({ - required this.name, - required this.reps, - required this.onChanged, - }); - - final String name; - final int reps; - final ValueChanged onChanged; - - @override - Widget build(BuildContext context) { - final colorScheme = Theme.of(context).colorScheme; - return Padding( - padding: const EdgeInsets.only(bottom: 10), - child: Row( - children: [ - Expanded( - child: Text( - name, - style: TextStyle( - color: colorScheme.onSurfaceVariant, - fontSize: AppTextSize.label, - ), - ), - ), - _StepperButton( - icon: Icons.remove, - onTap: () => onChanged((reps - 1).clamp(1, 999)), - ), - SizedBox( - width: 72, - child: Text( - '$reps reps', - textAlign: TextAlign.center, - style: TextStyle( - color: colorScheme.onSurface, - fontSize: AppTextSize.label, - fontWeight: FontWeight.bold, - ), - ), - ), - _StepperButton( - icon: Icons.add, - onTap: () => onChanged((reps + 1).clamp(1, 999)), - ), - ], - ), - ); - } -} - -class _WeightRow extends StatelessWidget { - const _WeightRow({ - required this.name, - required this.weight, - required this.onChanged, - }); - - final String name; - final double weight; - final ValueChanged onChanged; - - @override - Widget build(BuildContext context) { - final colorScheme = Theme.of(context).colorScheme; - return Padding( - padding: const EdgeInsets.only(bottom: 10), - child: Row( - children: [ - Expanded( - child: Text( - name, - style: TextStyle( - color: colorScheme.onSurfaceVariant, - fontSize: AppTextSize.label, - ), - ), - ), - _StepperButton( - icon: Icons.remove, - onTap: () => onChanged( - (weight - kWeightIncrement).clamp(0.0, 999.0), - ), - ), - // Fixed-width container supports up to "999.9kg" (7 chars). - SizedBox( - width: 72, - child: Text( - '${weight}kg', - textAlign: TextAlign.center, - style: TextStyle( - color: colorScheme.onSurface, - fontSize: AppTextSize.label, - fontWeight: FontWeight.bold, - ), - ), - ), - _StepperButton( - icon: Icons.add, - onTap: () => onChanged(weight + kWeightIncrement), - ), - ], - ), - ); - } -} - -class _StepperButton extends StatelessWidget { - const _StepperButton({required this.icon, required this.onTap}); - - final IconData icon; - final VoidCallback onTap; - - @override - Widget build(BuildContext context) { - final colorScheme = Theme.of(context).colorScheme; - return GestureDetector( - onTap: onTap, - child: Container( - width: 36, - height: 36, - decoration: BoxDecoration( - color: colorScheme.surfaceContainerHighest, - borderRadius: BorderRadius.circular(6), - ), - alignment: Alignment.center, - child: Icon(icon, color: colorScheme.onSurface, size: 18), - ), - ); - } -} - -class _ExerciseThresholdCard extends StatelessWidget { - const _ExerciseThresholdCard({ - required this.name, - required this.successThreshold, - required this.failThreshold, - required this.onSuccessChanged, - required this.onFailChanged, - }); - - final String name; - final int successThreshold; - final int failThreshold; - final ValueChanged onSuccessChanged; - final ValueChanged onFailChanged; - - @override - Widget build(BuildContext context) { - final colorScheme = Theme.of(context).colorScheme; - final status = Theme.of(context).extension()!; - return Container( - margin: const EdgeInsets.only(bottom: 12), - padding: const EdgeInsets.all(14), - decoration: BoxDecoration( - color: colorScheme.surfaceContainerHigh, - borderRadius: BorderRadius.circular(AppRadius.sm), - ), - child: Column( - crossAxisAlignment: CrossAxisAlignment.start, - children: [ - Text( - name, - style: TextStyle( - color: colorScheme.onSurface, - fontWeight: FontWeight.bold, - fontSize: AppTextSize.label, - ), - ), - const SizedBox(height: 10), - _ThresholdRow( - label: '↑ Increase after N successes', - value: successThreshold, - color: status.success, - onChanged: onSuccessChanged, - ), - const SizedBox(height: 6), - _ThresholdRow( - label: '↓ Decrease after N failures', - value: failThreshold, - color: colorScheme.error, - onChanged: onFailChanged, - ), - ], - ), - ); - } -} - -class _ThresholdRow extends StatelessWidget { - const _ThresholdRow({ - required this.label, - required this.value, - required this.color, - required this.onChanged, - }); - - final String label; - final int value; - final Color color; - final ValueChanged onChanged; - - @override - Widget build(BuildContext context) { - final colorScheme = Theme.of(context).colorScheme; - return Row( - children: [ - Expanded( - child: Text( - label, - style: TextStyle( - color: colorScheme.onSurfaceVariant, - fontSize: AppTextSize.caption, - ), - ), - ), - const SizedBox(width: 8), - for (int i = 1; i <= 5; i++) - Padding( - padding: const EdgeInsets.only(left: 4), - child: GestureDetector( - onTap: () => onChanged(i), - child: Container( - width: 32, - height: 32, - decoration: BoxDecoration( - shape: BoxShape.circle, - color: i == value - ? color - : colorScheme.surfaceContainerHighest, - ), - alignment: Alignment.center, - child: Text( - '$i', - style: TextStyle( - // on-fill on the selected (filled) circle. - color: i == value - ? colorScheme.onPrimary - : colorScheme.onSurface, - fontWeight: FontWeight.bold, - fontSize: AppTextSize.label, - ), - ), - ), - ), - ), - ], - ); - } -} diff --git a/stronglift_replacement/workout_app/lib/screens/settings_screen_rows.dart b/stronglift_replacement/workout_app/lib/screens/settings_screen_rows.dart new file mode 100644 index 0000000..d73c44d --- /dev/null +++ b/stronglift_replacement/workout_app/lib/screens/settings_screen_rows.dart @@ -0,0 +1,157 @@ +// Section header and the reps/weight adjustment rows. +// +// `part` files: these widgets are library-private and stay that way. +// See history_screen_charts.dart for the full reasoning. +part of 'settings_screen.dart'; + +class _SectionHeader extends StatelessWidget { + const _SectionHeader(this.text); + + final String text; + + @override + Widget build(BuildContext context) { + return Text( + text, + style: TextStyle( + color: Theme.of(context).colorScheme.onSurfaceVariant, + fontSize: AppTextSize.caption, + letterSpacing: 1.4, + ), + ); + } +} + +class _RepsRow extends StatelessWidget { + const _RepsRow({ + required this.name, + required this.reps, + required this.onChanged, + }); + + final String name; + final int reps; + final ValueChanged onChanged; + + @override + Widget build(BuildContext context) { + final colorScheme = Theme.of(context).colorScheme; + return Padding( + padding: const EdgeInsets.only(bottom: 10), + child: Row( + children: [ + Expanded( + child: Text( + name, + style: TextStyle( + color: colorScheme.onSurfaceVariant, + fontSize: AppTextSize.label, + ), + ), + ), + _StepperButton( + icon: Icons.remove, + onTap: () => onChanged((reps - 1).clamp(1, 999)), + ), + SizedBox( + width: 72, + child: Text( + '$reps reps', + textAlign: TextAlign.center, + style: TextStyle( + color: colorScheme.onSurface, + fontSize: AppTextSize.label, + fontWeight: FontWeight.bold, + ), + ), + ), + _StepperButton( + icon: Icons.add, + onTap: () => onChanged((reps + 1).clamp(1, 999)), + ), + ], + ), + ); + } +} + +class _WeightRow extends StatelessWidget { + const _WeightRow({ + required this.name, + required this.weight, + required this.onChanged, + }); + + final String name; + final double weight; + final ValueChanged onChanged; + + @override + Widget build(BuildContext context) { + final colorScheme = Theme.of(context).colorScheme; + return Padding( + padding: const EdgeInsets.only(bottom: 10), + child: Row( + children: [ + Expanded( + child: Text( + name, + style: TextStyle( + color: colorScheme.onSurfaceVariant, + fontSize: AppTextSize.label, + ), + ), + ), + _StepperButton( + icon: Icons.remove, + onTap: () => onChanged( + (weight - kWeightIncrement).clamp(0.0, 999.0), + ), + ), + // Fixed-width container supports up to "999.9kg" (7 chars). + SizedBox( + width: 72, + child: Text( + '${weight}kg', + textAlign: TextAlign.center, + style: TextStyle( + color: colorScheme.onSurface, + fontSize: AppTextSize.label, + fontWeight: FontWeight.bold, + ), + ), + ), + _StepperButton( + icon: Icons.add, + onTap: () => onChanged(weight + kWeightIncrement), + ), + ], + ), + ); + } +} + +class _StepperButton extends StatelessWidget { + const _StepperButton({required this.icon, required this.onTap}); + + final IconData icon; + final VoidCallback onTap; + + @override + Widget build(BuildContext context) { + final colorScheme = Theme.of(context).colorScheme; + return GestureDetector( + onTap: onTap, + child: Container( + width: 36, + height: 36, + decoration: BoxDecoration( + color: colorScheme.surfaceContainerHighest, + borderRadius: BorderRadius.circular(6), + ), + alignment: Alignment.center, + child: Icon(icon, color: colorScheme.onSurface, size: 18), + ), + ); + } +} diff --git a/stronglift_replacement/workout_app/lib/screens/settings_screen_thresholds.dart b/stronglift_replacement/workout_app/lib/screens/settings_screen_thresholds.dart new file mode 100644 index 0000000..7f5acc8 --- /dev/null +++ b/stronglift_replacement/workout_app/lib/screens/settings_screen_thresholds.dart @@ -0,0 +1,123 @@ +// The stepper button and the per-exercise threshold card. +// +// See settings_screen_rows.dart for why these are `part` files. +part of 'settings_screen.dart'; + +class _ExerciseThresholdCard extends StatelessWidget { + const _ExerciseThresholdCard({ + required this.name, + required this.successThreshold, + required this.failThreshold, + required this.onSuccessChanged, + required this.onFailChanged, + }); + + final String name; + final int successThreshold; + final int failThreshold; + final ValueChanged onSuccessChanged; + final ValueChanged onFailChanged; + + @override + Widget build(BuildContext context) { + final colorScheme = Theme.of(context).colorScheme; + final status = Theme.of(context).extension()!; + return Container( + margin: const EdgeInsets.only(bottom: 12), + padding: const EdgeInsets.all(14), + decoration: BoxDecoration( + color: colorScheme.surfaceContainerHigh, + borderRadius: BorderRadius.circular(AppRadius.sm), + ), + child: Column( + crossAxisAlignment: CrossAxisAlignment.start, + children: [ + Text( + name, + style: TextStyle( + color: colorScheme.onSurface, + fontWeight: FontWeight.bold, + fontSize: AppTextSize.label, + ), + ), + const SizedBox(height: 10), + _ThresholdRow( + label: '↑ Increase after N successes', + value: successThreshold, + color: status.success, + onChanged: onSuccessChanged, + ), + const SizedBox(height: 6), + _ThresholdRow( + label: '↓ Decrease after N failures', + value: failThreshold, + color: colorScheme.error, + onChanged: onFailChanged, + ), + ], + ), + ); + } +} + +class _ThresholdRow extends StatelessWidget { + const _ThresholdRow({ + required this.label, + required this.value, + required this.color, + required this.onChanged, + }); + + final String label; + final int value; + final Color color; + final ValueChanged onChanged; + + @override + Widget build(BuildContext context) { + final colorScheme = Theme.of(context).colorScheme; + return Row( + children: [ + Expanded( + child: Text( + label, + style: TextStyle( + color: colorScheme.onSurfaceVariant, + fontSize: AppTextSize.caption, + ), + ), + ), + const SizedBox(width: 8), + for (int i = 1; i <= 5; i++) + Padding( + padding: const EdgeInsets.only(left: 4), + child: GestureDetector( + onTap: () => onChanged(i), + child: Container( + width: 32, + height: 32, + decoration: BoxDecoration( + shape: BoxShape.circle, + color: i == value + ? color + : colorScheme.surfaceContainerHighest, + ), + alignment: Alignment.center, + child: Text( + '$i', + style: TextStyle( + // on-fill on the selected (filled) circle. + color: i == value + ? colorScheme.onPrimary + : colorScheme.onSurface, + fontWeight: FontWeight.bold, + fontSize: AppTextSize.label, + ), + ), + ), + ), + ), + ], + ); + } +}