From 03cd945bfc562a76aea2498c1ad4a0ebb78d442a Mon Sep 17 00:00:00 2001 From: Roland Geider Date: Fri, 14 Nov 2025 23:46:00 +0100 Subject: [PATCH] Move the workout progression to the main navigation menu --- lib/widgets/routines/gym_mode/log_page.dart | 58 ++---- lib/widgets/routines/gym_mode/navigation.dart | 38 +--- .../routines/gym_mode/workout_menu.dart | 197 ++++++++++++++++++ .../routines/gym_mode/workout_progresion.dart | 108 ---------- 4 files changed, 217 insertions(+), 184 deletions(-) create mode 100644 lib/widgets/routines/gym_mode/workout_menu.dart delete mode 100644 lib/widgets/routines/gym_mode/workout_progresion.dart diff --git a/lib/widgets/routines/gym_mode/log_page.dart b/lib/widgets/routines/gym_mode/log_page.dart index 84845c60..a7bc7b3f 100644 --- a/lib/widgets/routines/gym_mode/log_page.dart +++ b/lib/widgets/routines/gym_mode/log_page.dart @@ -37,27 +37,8 @@ import 'package:wger/widgets/routines/forms/reps_unit.dart'; import 'package:wger/widgets/routines/forms/rir.dart'; import 'package:wger/widgets/routines/forms/weight_unit.dart'; import 'package:wger/widgets/routines/gym_mode/navigation.dart'; -import 'package:wger/widgets/routines/gym_mode/workout_progresion.dart'; import 'package:wger/widgets/routines/plate_calculator.dart'; -void _openWorkoutProgressionDialog(BuildContext context) { - showDialog( - context: context, - builder: (ctx) { - return AlertDialog( - title: Text(AppLocalizations.of(context).todaysWorkout), - content: const WorkoutProgression(), - actions: [ - TextButton( - onPressed: () => Navigator.of(ctx).pop(), - child: Text(MaterialLocalizations.of(context).closeButtonLabel), - ), - ], - ); - }, - ); -} - class LogPage extends ConsumerStatefulWidget { final _logger = Logger('LogPage'); @@ -108,15 +89,11 @@ class _LogPageState extends ConsumerState { ..routineId = state.routine.id! ..iteration = state.iteration; + // Mark done sets final decorationStyle = slotEntryPage.logDone ? TextDecoration.lineThrough : TextDecoration.none; - final style = { - 'textDecoration': decorationStyle, - 'color': Theme.of(context).colorScheme.primary, - }; - return Column( children: [ NavigationHeader( @@ -130,31 +107,26 @@ class _LogPageState extends ConsumerState { child: Center( child: Column( children: [ - GestureDetector( - onTap: () { - _openWorkoutProgressionDialog(context); - }, - child: Column( - children: [ + Column( + children: [ + Text( + setConfigData.textRepr, + textAlign: TextAlign.center, + style: theme.textTheme.headlineMedium?.copyWith( + color: Theme.of(context).colorScheme.primary, + decoration: decorationStyle, + ), + ), + if (setConfigData.type != SlotEntryType.normal) Text( - setConfigData.textRepr, + setConfigData.type.name.toUpperCase(), textAlign: TextAlign.center, - style: theme.textTheme.headlineMedium?.copyWith( + style: theme.textTheme.headlineSmall?.copyWith( color: Theme.of(context).colorScheme.primary, decoration: decorationStyle, ), ), - if (setConfigData.type != SlotEntryType.normal) - Text( - setConfigData.type.name.toUpperCase(), - textAlign: TextAlign.center, - style: theme.textTheme.headlineSmall?.copyWith( - color: Theme.of(context).colorScheme.primary, - decoration: decorationStyle, - ), - ), - ], - ), + ], ), Text( '${slotEntryPage.setIndex + 1} / ${widget._slotData.setConfigs.length}', diff --git a/lib/widgets/routines/gym_mode/navigation.dart b/lib/widgets/routines/gym_mode/navigation.dart index 54ab4d56..4f4074c7 100644 --- a/lib/widgets/routines/gym_mode/navigation.dart +++ b/lib/widgets/routines/gym_mode/navigation.dart @@ -24,6 +24,7 @@ import 'package:wger/l10n/generated/app_localizations.dart'; import 'package:wger/providers/exercises.dart'; import 'package:wger/providers/gym_state.dart'; import 'package:wger/theme/theme.dart'; +import 'package:wger/widgets/routines/gym_mode/workout_menu.dart'; class NavigationFooter extends ConsumerWidget { final PageController _controller; @@ -109,39 +110,10 @@ class NavigationHeader extends ConsumerWidget { textAlign: TextAlign.center, ), contentPadding: EdgeInsets.zero, - content: SingleChildScrollView( - child: Column( - children: [ - ...pages.where((page) => page.type == PageType.set).map((page) { - return ListTile( - leading: page.allLogsDone ? const Icon(Icons.check) : null, - title: Text( - page.exerciseIds - .map( - (id) => exercisesProvider - .findExerciseById(id) - .getTranslation(Localizations.localeOf(context).languageCode) - .name, - ) - .toList() - .join('\n'), - style: TextStyle( - decoration: page.allLogsDone ? TextDecoration.lineThrough : TextDecoration.none, - ), - ), - trailing: const Icon(Icons.chevron_right), - onTap: () { - _controller.animateToPage( - page.pageIndex, - duration: DEFAULT_ANIMATION_DURATION, - curve: DEFAULT_ANIMATION_CURVE, - ); - Navigator.of(context).pop(); - }, - ); - }), - ], - ), + content: SizedBox( + height: double.maxFinite, + width: double.maxFinite, + child: WorkoutMenu(_controller), ), actions: [ ?endWorkoutButton, diff --git a/lib/widgets/routines/gym_mode/workout_menu.dart b/lib/widgets/routines/gym_mode/workout_menu.dart new file mode 100644 index 00000000..8419e54f --- /dev/null +++ b/lib/widgets/routines/gym_mode/workout_menu.dart @@ -0,0 +1,197 @@ +// /* +// * This file is part of wger Workout Manager . +// * Copyright (C) 2020, 2025 wger Team +// * +// * wger Workout Manager is free software: you can redistribute it and/or modify +// * it under the terms of the GNU Affero General Public License as published by +// * the Free Software Foundation, either version 3 of the License, or +// * (at your option) any later version. +// * +// * wger Workout Manager is distributed in the hope that it will be useful, +// * but WITHOUT ANY WARRANTY; without even the implied warranty of +// * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// * GNU Affero General Public License for more details. +// * +// * You should have received a copy of the GNU Affero General Public License +// * along with this program. If not, see . +// */ + +import 'package:flutter/material.dart'; +import 'package:flutter_riverpod/flutter_riverpod.dart'; +import 'package:wger/helpers/consts.dart'; +import 'package:wger/providers/gym_state.dart'; + +class WorkoutMenu extends StatelessWidget { + final PageController _controller; + + const WorkoutMenu(this._controller); + + @override + Widget build(BuildContext context) { + return DefaultTabController( + length: 2, + child: Column( + children: [ + const TabBar( + tabs: [ + Tab(icon: Icon(Icons.menu_open)), + Tab(icon: Icon(Icons.stacked_bar_chart)), + ], + ), + Flexible( + child: Padding( + padding: const EdgeInsets.symmetric(vertical: 10), + child: TabBarView( + children: [ + NavigationTab(_controller), + ProgressionTab(_controller), + ], + ), + ), + ), + ], + ), + ); + } +} + +class NavigationTab extends ConsumerWidget { + final PageController _controller; + + const NavigationTab(this._controller); + + @override + Widget build(BuildContext context, WidgetRef ref) { + final state = ref.watch(gymStateProvider); + + return Column( + children: [ + ...state.pages.where((pageEntry) => pageEntry.type == PageType.set).map((page) { + return ListTile( + leading: page.allLogsDone ? const Icon(Icons.check) : null, + title: Text( + page.exercises + .map( + (exercise) => + exercise.getTranslation(Localizations.localeOf(context).languageCode).name, + ) + .toList() + .join('\n'), + style: TextStyle( + decoration: page.allLogsDone ? TextDecoration.lineThrough : TextDecoration.none, + ), + ), + trailing: const Icon(Icons.chevron_right), + onTap: () { + _controller.animateToPage( + page.pageIndex, + duration: DEFAULT_ANIMATION_DURATION, + curve: DEFAULT_ANIMATION_CURVE, + ); + Navigator.of(context).pop(); + }, + ); + }), + ], + ); + } +} + +class ProgressionTab extends ConsumerWidget { + final PageController _controller; + + const ProgressionTab(this._controller); + + @override + Widget build(BuildContext context, WidgetRef ref) { + final state = ref.watch(gymStateProvider); + final theme = Theme.of(context); + + return Padding( + padding: const EdgeInsets.symmetric(horizontal: 15), + child: Column( + children: [ + ...state.pages.where((page) => page.type == PageType.set).map((page) { + return Column( + mainAxisSize: MainAxisSize.max, + crossAxisAlignment: CrossAxisAlignment.start, + children: [ + ...page.slotPages.where((slotPage) => slotPage.type == SlotPageType.log).map( + ( + slotPage, + ) { + final exercise = slotPage.setConfigData!.exercise + .getTranslation( + Localizations.localeOf(context).languageCode, + ) + .name; + + // Sets that are done are marked with a strikethrough + final decoration = slotPage.logDone + ? TextDecoration.lineThrough + : TextDecoration.none; + + // Sets that are done have a lighter color + final color = slotPage.logDone + ? theme.colorScheme.onSurface.withValues(alpha: 0.6) + : null; + + // he row for the current page is highlighted in bold + final fontWeight = state.currentPage == slotPage.pageIndex + ? 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, + ), + ), + ], + ), + overflow: TextOverflow.ellipsis, + maxLines: 1, + ); + }, + ), + Row( + mainAxisSize: MainAxisSize.max, + //mainAxisAlignment: MainAxisAlignment.spaceBetween, + children: [ + IconButton( + onPressed: () {}, + icon: const Icon(Icons.swap_horiz, size: 18), + ), + IconButton( + onPressed: () {}, + icon: const Icon(Icons.add, size: 18), + ), + Expanded(child: Container()), + IconButton( + onPressed: () { + _controller.animateToPage( + page.pageIndex, + duration: DEFAULT_ANIMATION_DURATION, + curve: DEFAULT_ANIMATION_CURVE, + ); + Navigator.of(context).pop(); + }, + icon: const Icon(Icons.chevron_right), + ), + ], + ), + const SizedBox(height: 8), + ], + ); + }), + ], + ), + ); + } +} diff --git a/lib/widgets/routines/gym_mode/workout_progresion.dart b/lib/widgets/routines/gym_mode/workout_progresion.dart deleted file mode 100644 index 68704f46..00000000 --- a/lib/widgets/routines/gym_mode/workout_progresion.dart +++ /dev/null @@ -1,108 +0,0 @@ -/* - * This file is part of wger Workout Manager . - * Copyright (C) 2020, 2025 wger Team - * - * wger Workout Manager is free software: you can redistribute it and/or modify - * it under the terms of the GNU Affero General Public License as published by - * the Free Software Foundation, either version 3 of the License, or - * (at your option) any later version. - * - * wger Workout Manager is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - * GNU Affero General Public License for more details. - * - * You should have received a copy of the GNU Affero General Public License - * along with this program. If not, see . - */ - -import 'package:flutter/material.dart'; -import 'package:flutter_riverpod/flutter_riverpod.dart'; -import 'package:wger/providers/gym_state.dart'; - -class WorkoutProgression extends ConsumerWidget { - const WorkoutProgression(); - - @override - Widget build(BuildContext context, WidgetRef ref) { - final state = ref.watch(gymStateProvider); - final theme = Theme.of(context); - - return SingleChildScrollView( - child: Column( - children: [ - ...state.pages.where((page) => page.type == PageType.set).map((page) { - return Column( - mainAxisSize: MainAxisSize.max, - crossAxisAlignment: CrossAxisAlignment.start, - children: [ - ...page.slotPages.where((slotPage) => slotPage.type == SlotPageType.log).map(( - slotPage, - ) { - final exercise = slotPage.setConfigData!.exercise - .getTranslation(Localizations.localeOf(context).languageCode) - .name; - - // Sets that are done are marked with a strikethrough - final decoration = slotPage.logDone - ? TextDecoration.lineThrough - : TextDecoration.none; - - // Sets that are done have a lighter color - final color = slotPage.logDone - ? theme.colorScheme.onSurface.withValues(alpha: 0.6) - : null; - - // he row for the current page is highlighted in bold - final fontWeight = state.currentPage == slotPage.pageIndex - ? 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, - ), - ), - ], - ), - overflow: TextOverflow.ellipsis, - maxLines: 1, - ); - }), - Row( - mainAxisSize: MainAxisSize.max, - //mainAxisAlignment: MainAxisAlignment.spaceBetween, - children: [ - IconButton( - onPressed: () {}, - icon: const Icon(Icons.swap_horiz, size: 18), - ), - /* - IconButton( - onPressed: () {}, - icon: const Icon(Icons.add, size: 18), - ), - */ - Expanded(child: Container()), - IconButton( - onPressed: () {}, - icon: const Icon(Icons.chevron_right), - ), - ], - ), - const SizedBox(height: 8), - ], - ); - }), - ], - ), - ); - } -}