Move the workout progression to the main navigation menu

This commit is contained in:
Roland Geider
2025-11-14 23:46:00 +01:00
parent 317ae250f3
commit 03cd945bfc
4 changed files with 217 additions and 184 deletions

View File

@@ -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<void>(
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<LogPage> {
..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<LogPage> {
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}',

View File

@@ -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,

View File

@@ -0,0 +1,197 @@
// /*
// * This file is part of wger Workout Manager <https://github.com/wger-project>.
// * 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 <http://www.gnu.org/licenses/>.
// */
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),
],
);
}),
],
),
);
}
}

View File

@@ -1,108 +0,0 @@
/*
* This file is part of wger Workout Manager <https://github.com/wger-project>.
* 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 <http://www.gnu.org/licenses/>.
*/
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),
],
);
}),
],
),
);
}
}