From 689591195fd26db373bd608a7bb99645bb68fd6e Mon Sep 17 00:00:00 2001 From: Roland Geider Date: Sun, 16 Nov 2025 14:38:50 +0100 Subject: [PATCH] Polish workout progression dialog Exercise names are not repeated anymore (again) and we use a bottom sheet now instead of a modal dialog --- lib/widgets/routines/gym_mode/navigation.dart | 4 +- .../routines/gym_mode/workout_menu.dart | 95 ++++++++++++------- 2 files changed, 63 insertions(+), 36 deletions(-) diff --git a/lib/widgets/routines/gym_mode/navigation.dart b/lib/widgets/routines/gym_mode/navigation.dart index d2572b52..d8981405 100644 --- a/lib/widgets/routines/gym_mode/navigation.dart +++ b/lib/widgets/routines/gym_mode/navigation.dart @@ -53,7 +53,7 @@ class NavigationHeader extends StatelessWidget { IconButton( icon: const Icon(Icons.menu), onPressed: () { - showDialog( + showModalBottomSheet( context: context, builder: (ctx) => WorkoutMenuDialog(_controller), ); @@ -95,7 +95,7 @@ class NavigationFooter extends ConsumerWidget { const SizedBox(width: 48), Expanded( child: GestureDetector( - onTap: () => showDialog( + onTap: () => showModalBottomSheet( context: context, builder: (ctx) => WorkoutMenuDialog(_controller, initialIndex: 1), ), diff --git a/lib/widgets/routines/gym_mode/workout_menu.dart b/lib/widgets/routines/gym_mode/workout_menu.dart index 83768da4..0b4eeb96 100644 --- a/lib/widgets/routines/gym_mode/workout_menu.dart +++ b/lib/widgets/routines/gym_mode/workout_menu.dart @@ -44,14 +44,11 @@ class WorkoutMenu extends StatelessWidget { ], ), Flexible( - child: Padding( - padding: const EdgeInsets.symmetric(vertical: 10), - child: TabBarView( - children: [ - NavigationTab(_controller), - ProgressionTab(_controller), - ], - ), + child: TabBarView( + children: [ + NavigationTab(_controller), + ProgressionTab(_controller), + ], ), ), ], @@ -121,6 +118,7 @@ class _ProgressionTabState extends ConsumerState { Widget build(BuildContext context) { final state = ref.watch(gymStateProvider); final theme = Theme.of(context); + final languageCode = Localizations.localeOf(context).languageCode; return SingleChildScrollView( child: Padding( @@ -128,19 +126,44 @@ class _ProgressionTabState extends ConsumerState { child: Column( children: [ ...state.pages.where((page) => page.type == PageType.set).map((page) { + if (page.exercises.isEmpty) { + widget._logger.warning('Page ${page.uuid} has no exercises, skipping'); + return Container(); + } + + // For supersets, prefix the exercise with A, B, C so it can be identified + // in the set list below + final isSuperset = page.exercises.length > 1; + final pageExerciseTitle = isSuperset + ? page.exercises + .asMap() + .entries + .map((entry) { + final label = String.fromCharCode(65 + entry.key); + final name = entry.value + .getTranslation(Localizations.localeOf(context).languageCode) + .name; + return '$label: $name'; + }) + .join('\n') + : page.exercises.first.getTranslation(languageCode).name; + return Column( mainAxisSize: MainAxisSize.max, crossAxisAlignment: CrossAxisAlignment.start, children: [ + Text(pageExerciseTitle, style: Theme.of(context).textTheme.bodyLarge), ...page.slotPages.where((slotPage) => slotPage.type == SlotPageType.log).map( - ( - slotPage, - ) { - final exercise = slotPage.setConfigData!.exercise - .getTranslation( - Localizations.localeOf(context).languageCode, - ) - .name; + (slotPage) { + String setPrefix = ''; + if (isSuperset) { + final exerciseIndex = page.exercises.indexWhere( + (ex) => ex.id == slotPage.setConfigData!.exercise.id, + ); + if (exerciseIndex != -1) { + setPrefix = '${String.fromCharCode(65 + exerciseIndex)}: '; + } + } // Sets that are done are marked with a strikethrough final decoration = slotPage.logDone @@ -157,22 +180,26 @@ class _ProgressionTabState extends ConsumerState { ? FontWeight.bold : null; - return Text.rich( - TextSpan( - children: [ - if (slotPage.logDone) const TextSpan(text: '✅ '), - TextSpan( - text: '$exercise - ${slotPage.setConfigData!.textReprWithType}', - style: theme.textTheme.bodyMedium!.copyWith( - decoration: decoration, - fontWeight: fontWeight, - color: color, - ), + IconData icon = Icons.circle_outlined; + if (slotPage.logDone) { + icon = Icons.check_circle_rounded; + } else if (state.currentPage == slotPage.pageIndex) { + icon = Icons.play_circle_fill; + } + + return Row( + children: [ + Icon(icon, size: 16), + const SizedBox(width: 4), + Text( + '$setPrefix${slotPage.setConfigData!.textReprWithType}', + style: theme.textTheme.bodyMedium!.copyWith( + decoration: decoration, + fontWeight: fontWeight, + color: color, ), - ], - ), - overflow: TextOverflow.ellipsis, - maxLines: 1, + ), + ], ); }, ), @@ -187,12 +214,10 @@ class _ProgressionTabState extends ConsumerState { : () { if (showSwapWidgetToPage == page.uuid) { setState(() { - widget._logger.fine('Hiding details'); showSwapWidgetToPage = null; }); } else { setState(() { - widget._logger.fine('Showing details for page ${page.uuid}'); showSwapWidgetToPage = page.uuid; showAddExerciseWidgetToPage = null; }); @@ -264,7 +289,8 @@ class _ProgressionTabState extends ConsumerState { Padding( padding: const EdgeInsets.all(8.0), child: Text( - 'Swapping an exercise only affects the current workout, no changes are saved.', + 'Swapping or adding an exercise only affects the current workout, ' + 'no changes are saved.', style: Theme.of(context).textTheme.bodySmall, textAlign: TextAlign.center, ), @@ -414,6 +440,7 @@ class WorkoutMenuDialog extends ConsumerWidget { textAlign: TextAlign.center, ), contentPadding: EdgeInsets.zero, + insetPadding: EdgeInsets.zero, content: SizedBox( height: double.maxFinite, width: double.maxFinite,