diff --git a/lib/l10n/app_en.arb b/lib/l10n/app_en.arb index 9b031fd2..bab70e0b 100644 --- a/lib/l10n/app_en.arb +++ b/lib/l10n/app_en.arb @@ -156,6 +156,10 @@ "@reps": { "description": "Shorthand for repetitions, used when space constraints are tighter" }, + "sets": "Sets", + "@sets": { + "description": "The number of sets to be done for one exercise" + }, "rir": "RiR", "@rir": { "description": "Shorthand for Repetitions In Reserve" @@ -201,6 +205,11 @@ "@workoutSession": { "description": "A (logged) workout session" }, + "restDay": "Rest day", + "isRestDay": "Is rest day", + "isRestDayHelp": "Please consider that all sets are removed from rest days when saved", + "routineDays": "Days in routine", + "resultingRoutine": "Resulting routine", "newDay": "New day", "@newDay": {}, "newSet": "New set", @@ -697,7 +706,11 @@ "images": "Images", "language": "Language", "addExercise": "Add exercise", + "fitInWeek": "Fit in week", + "fitInWeekHelp": "Select if you want to fit the workout days into a week, you can add e.g. three days and mark this checkbox.", "addSuperset": "Add superset", + "setHasProgression": "Set has progression", + "setHasProgressionWarning": "Please note that at the moment it is not possible to edit all settings for a set on the mobile application or configure the progression. ", "setHasNoExercises": "This set has no exercises yet!", "contributeExercise": "Contribute an exercise", "translation": "Translation", diff --git a/lib/providers/routines.dart b/lib/providers/routines.dart index 59e9f8ef..9df751ee 100644 --- a/lib/providers/routines.dart +++ b/lib/providers/routines.dart @@ -419,7 +419,7 @@ class RoutinesProvider with ChangeNotifier { day = Day.fromJson(data); day.slots = []; final routine = findById(day.routineId); - routine.days.insert(0, day); + routine.days.add(day); if (refresh) { fetchAndSetRoutineFull(day.routineId); } diff --git a/lib/widgets/core/settings.dart b/lib/widgets/core/settings.dart index 78c8dfa9..9dde8f8b 100644 --- a/lib/widgets/core/settings.dart +++ b/lib/widgets/core/settings.dart @@ -30,6 +30,7 @@ class SettingsPage extends StatelessWidget { @override Widget build(BuildContext context) { + final i18n = AppLocalizations.of(context); final exerciseProvider = Provider.of(context, listen: false); final nutritionProvider = Provider.of(context, listen: false); @@ -40,7 +41,12 @@ class SettingsPage extends StatelessWidget { body: ListView( children: [ ListTile( - title: Text(AppLocalizations.of(context).settingsExerciseCacheDescription), + title: Text( + i18n.settingsCacheTitle, + style: Theme.of(context).textTheme.headlineSmall, + )), + ListTile( + title: Text(i18n.settingsExerciseCacheDescription), trailing: IconButton( key: const ValueKey('cacheIconExercises'), icon: const Icon(Icons.delete), @@ -49,7 +55,7 @@ class SettingsPage extends StatelessWidget { if (context.mounted) { final snackBar = SnackBar( - content: Text(AppLocalizations.of(context).settingsCacheDeletedSnackbar), + content: Text(i18n.settingsCacheDeletedSnackbar), ); ScaffoldMessenger.of(context).showSnackBar(snackBar); diff --git a/lib/widgets/dashboard/widgets.dart b/lib/widgets/dashboard/widgets.dart index 10c9ffb9..c2abad70 100644 --- a/lib/widgets/dashboard/widgets.dart +++ b/lib/widgets/dashboard/widgets.dart @@ -394,7 +394,9 @@ class _DashboardWorkoutWidgetState extends State { children: [ Expanded( child: Text( - dayData.day == null || dayData.day!.isRest ? 'REST DAY' : dayData.day!.name, + dayData.day == null || dayData.day!.isRest + ? AppLocalizations.of(context).restDay + : dayData.day!.name, style: const TextStyle(fontWeight: FontWeight.bold), overflow: TextOverflow.ellipsis, ), diff --git a/lib/widgets/routines/day.dart b/lib/widgets/routines/day.dart index 3c74faa1..46d2644d 100644 --- a/lib/widgets/routines/day.dart +++ b/lib/widgets/routines/day.dart @@ -17,6 +17,7 @@ */ import 'package:flutter/material.dart'; +import 'package:flutter_gen/gen_l10n/app_localizations.dart'; import 'package:wger/models/workouts/day_data.dart'; import 'package:wger/models/workouts/set_config_data.dart'; import 'package:wger/models/workouts/slot_data.dart'; @@ -135,13 +136,15 @@ class DayHeader extends StatelessWidget { @override Widget build(BuildContext context) { + final i18n = AppLocalizations.of(context); + if (_dayData.day == null || _dayData.day!.isRest) { return ListTile( // tileColor: Colors.amber, tileColor: Theme.of(context).focusColor, contentPadding: const EdgeInsets.symmetric(horizontal: 16, vertical: 8), title: Text( - 'REST DAY', + i18n.restDay, style: Theme.of(context).textTheme.headlineSmall, overflow: TextOverflow.ellipsis, ), diff --git a/lib/widgets/routines/forms/day.dart b/lib/widgets/routines/forms/day.dart index db547feb..1dcd3cfc 100644 --- a/lib/widgets/routines/forms/day.dart +++ b/lib/widgets/routines/forms/day.dart @@ -25,8 +25,8 @@ class ReorderableDaysList extends StatefulWidget { context: context, builder: (BuildContext context) { return AlertDialog( - title: const Text('Confirm Delete'), - content: const Text('Are you sure you want to delete this day?'), + title: Text(AppLocalizations.of(context).delete), + content: Text(AppLocalizations.of(context).confirmDelete(day.name)), actions: [ TextButton( onPressed: () { @@ -55,6 +55,9 @@ class ReorderableDaysList extends StatefulWidget { class _ReorderableDaysListState extends State { @override Widget build(BuildContext context) { + final i18n = AppLocalizations.of(context); + final provider = context.read(); + return Column( children: [ ReorderableListView.builder( @@ -72,13 +75,13 @@ class _ReorderableDaysListState extends State { //selected: day.id == widget.selectedDayId, tileColor: isDaySelected ? Theme.of(context).highlightColor : null, key: ValueKey(day), - title: Text(day.isRest ? 'REST DAY!' : day.name), + title: Text(day.isRest ? i18n.restDay : day.name), leading: ReorderableDragStartListener( index: index, child: const Icon(Icons.drag_handle), ), subtitle: Text( - day.isRest ? '' : day.description, + day.description, style: const TextStyle(overflow: TextOverflow.ellipsis), ), trailing: Row( @@ -117,7 +120,7 @@ class _ReorderableDaysListState extends State { } }); - Provider.of(context, listen: false).editDays(widget.days); + provider.editDays(widget.days); }, ), ListTile( @@ -129,11 +132,17 @@ class _ReorderableDaysListState extends State { style: Theme.of(context).textTheme.titleMedium, ), onTap: () async { - final newDay = Day.empty(); - newDay.routineId = widget.routineId; - final result = await Provider.of(context, listen: false) - .addDay(newDay, refresh: true); - widget.onDaySelected(result.id!); + final day = Day.empty(); + day.name = i18n.newDay; + day.routineId = widget.routineId; + final newDay = await provider.addDay(day); + + // final newSlot = await provider.addSlot(Slot.withData( + // day: newDay.id, + // order: 1, + // )); + + widget.onDaySelected(newDay.id!); }, ), ], @@ -177,15 +186,32 @@ class _DayFormWidgetState extends State { @override Widget build(BuildContext context) { + final i18n = AppLocalizations.of(context); + return Form( key: _form, child: Column( children: [ Text( - widget.day.isRest ? 'REST DAY' : widget.day.name, + widget.day.isRest ? i18n.restDay : widget.day.name, style: Theme.of(context).textTheme.titleLarge, ), + SwitchListTile( + title: Text(i18n.isRestDay), + subtitle: Text(i18n.isRestDayHelp), + value: isRestDay, + contentPadding: const EdgeInsets.all(4), + onChanged: (value) { + setState(() { + isRestDay = value; + nameController.clear(); + descriptionController.clear(); + }); + widget.day.isRest = value; + }, + ), TextFormField( + enabled: !widget.day.isRest, key: const Key('field-name'), decoration: InputDecoration( labelText: AppLocalizations.of(context).name, @@ -195,6 +221,10 @@ class _DayFormWidgetState extends State { widget.day.name = value!; }, validator: (value) { + if (widget.day.isRest) { + return null; + } + if (value!.isEmpty || value.length < Day.MIN_LENGTH_NAME || value.length > Day.MAX_LENGTH_NAME) { @@ -207,19 +237,9 @@ class _DayFormWidgetState extends State { return null; }, ), - SwitchListTile( - title: const Text('is rest day'), - value: isRestDay, - contentPadding: const EdgeInsets.all(4), - onChanged: (value) { - setState(() { - isRestDay = value; - }); - widget.day.isRest = value; - }, - ), TextFormField( key: const Key('field-description'), + enabled: !widget.day.isRest, decoration: InputDecoration( labelText: AppLocalizations.of(context).description, helperText: AppLocalizations.of(context).dayDescriptionHelp, @@ -232,6 +252,10 @@ class _DayFormWidgetState extends State { minLines: 2, maxLines: 10, validator: (value) { + if (widget.day.isRest) { + return null; + } + if (value != null && value.length > Day.MAX_LENGTH_DESCRIPTION) { return AppLocalizations.of(context).enterCharacters(0, Day.MAX_LENGTH_DESCRIPTION); } @@ -239,6 +263,7 @@ class _DayFormWidgetState extends State { return null; }, ), + const SizedBox(height: 5), ElevatedButton( key: const Key(SUBMIT_BUTTON_KEY_NAME), child: Text(AppLocalizations.of(context).save), @@ -271,7 +296,8 @@ class _DayFormWidgetState extends State { } }, ), - ReorderableSlotList(widget.day.slots, widget.day.id!), + const SizedBox(height: 5), + ReorderableSlotList(widget.day.slots, widget.day), ], ), ); diff --git a/lib/widgets/routines/forms/routine.dart b/lib/widgets/routines/forms/routine.dart index 9e83fa23..8ec84966 100644 --- a/lib/widgets/routines/forms/routine.dart +++ b/lib/widgets/routines/forms/routine.dart @@ -43,19 +43,21 @@ class _RoutineFormState extends State { @override Widget build(BuildContext context) { + final i18n = AppLocalizations.of(context); + return Form( key: _form, child: Column( children: [ TextFormField( key: const Key('field-name'), - decoration: InputDecoration(labelText: AppLocalizations.of(context).name), + decoration: InputDecoration(labelText: i18n.name), controller: workoutNameController, validator: (value) { if (value!.isEmpty || value.length < Routine.MIN_LENGTH_NAME || value.length > Routine.MAX_LENGTH_NAME) { - return AppLocalizations.of(context).enterCharacters( + return i18n.enterCharacters( Routine.MIN_LENGTH_NAME, Routine.MAX_LENGTH_NAME, ); @@ -68,13 +70,13 @@ class _RoutineFormState extends State { ), TextFormField( key: const Key('field-description'), - decoration: InputDecoration(labelText: AppLocalizations.of(context).description), + decoration: InputDecoration(labelText: i18n.description), minLines: 3, maxLines: 10, controller: workoutDescriptionController, validator: (value) { if (value!.length > Routine.MAX_LENGTH_DESCRIPTION) { - return AppLocalizations.of(context).enterCharacters( + return i18n.enterCharacters( Routine.MIN_LENGTH_DESCRIPTION, Routine.MAX_LENGTH_DESCRIPTION, ); @@ -171,8 +173,11 @@ class _RoutineFormState extends State { } }, ), + const SizedBox(height: 5), SwitchListTile( - title: const Text('Fit in week'), + title: Text(i18n.fitInWeek), + subtitle: Text(i18n.fitInWeekHelp), + isThreeLine: true, value: widget._routine.fitInWeek, contentPadding: const EdgeInsets.all(4), onChanged: (bool? value) { @@ -188,6 +193,7 @@ class _RoutineFormState extends State { } }, ), + const SizedBox(height: 5), ElevatedButton( key: const Key(SUBMIT_BUTTON_KEY_NAME), child: Text(AppLocalizations.of(context).save), diff --git a/lib/widgets/routines/forms/slot.dart b/lib/widgets/routines/forms/slot.dart index b6f1073d..d635a5d1 100644 --- a/lib/widgets/routines/forms/slot.dart +++ b/lib/widgets/routines/forms/slot.dart @@ -20,6 +20,7 @@ import 'package:flutter/material.dart'; import 'package:flutter_gen/gen_l10n/app_localizations.dart'; import 'package:provider/provider.dart'; import 'package:wger/helpers/consts.dart'; +import 'package:wger/models/workouts/day.dart'; import 'package:wger/models/workouts/slot.dart'; import 'package:wger/models/workouts/slot_entry.dart'; import 'package:wger/providers/routines.dart'; @@ -35,6 +36,8 @@ class SlotEntryForm extends StatefulWidget { } class _SlotEntryFormState extends State { + final iconSize = 18.0; + final setsController = TextEditingController(); final weightController = TextEditingController(); final repsController = TextEditingController(); @@ -89,10 +92,12 @@ class _SlotEntryFormState extends State { _edit = !_edit; }); }, - icon: _edit ? const Icon(Icons.edit_off) : const Icon(Icons.edit), + icon: _edit + ? Icon(Icons.edit_off, size: iconSize) + : Icon(Icons.edit, size: iconSize), ), IconButton( - icon: const Icon(Icons.delete), + icon: Icon(Icons.delete, size: iconSize), onPressed: () { context.read().deleteSlotEntry(widget.entry.id!); }, @@ -111,7 +116,7 @@ class _SlotEntryFormState extends State { TextFormField( controller: setsController, keyboardType: TextInputType.number, - decoration: InputDecoration(labelText: 'Sets'), + decoration: InputDecoration(labelText: i18n.sets), ), TextFormField( controller: weightController, @@ -197,9 +202,9 @@ class _SlotDetailWidgetState extends State { class ReorderableSlotList extends StatefulWidget { final List slots; - final int dayId; + final Day day; - const ReorderableSlotList(this.slots, this.dayId); + const ReorderableSlotList(this.slots, this.day); @override _SlotFormWidgetStateNg createState() => _SlotFormWidgetStateNg(); @@ -217,23 +222,19 @@ class _SlotFormWidgetStateNg extends State { final languageCode = Localizations.localeOf(context).languageCode; - Slot? selectedSlot; - if (selectedSlotId != null) { - selectedSlot = widget.slots.firstWhere((slot) => slot.id == selectedSlotId); - } - return Column( children: [ - SwitchListTile( - value: simpleMode, - title: const Text('simple mode'), - contentPadding: const EdgeInsets.all(4), - onChanged: (value) { - setState(() { - simpleMode = value; - }); - }, - ), + if (!widget.day.isRest) + SwitchListTile( + value: simpleMode, + title: const Text('Simple edit mode'), + contentPadding: const EdgeInsets.all(4), + onChanged: (value) { + setState(() { + simpleMode = value; + }); + }, + ), ReorderableListView.builder( buildDefaultDragHandles: false, shrinkWrap: true, @@ -249,7 +250,8 @@ class _SlotFormWidgetStateNg extends State { child: Column( children: [ ListTile( - title: Text(i18n.setNr(index + 1)), + title: Text(slot.id.toString()), + // title: Text(i18n.setNr(index + 1)), tileColor: isCurrentSlotSelected ? Theme.of(context).highlightColor : null, leading: selectedSlotId == null ? ReorderableDragStartListener( @@ -314,22 +316,23 @@ class _SlotFormWidgetStateNg extends State { }); }, ), - ListTile( - leading: const Icon(Icons.add), - title: Text( - i18n.newSet, - style: Theme.of(context).textTheme.titleMedium, + if (!widget.day.isRest) + ListTile( + leading: const Icon(Icons.add), + title: Text( + i18n.addSet, + style: Theme.of(context).textTheme.titleMedium, + ), + onTap: () async { + final newSlot = await provider.addSlot(Slot.withData( + day: widget.day.id, + order: widget.slots.length + 1, + )); + setState(() { + selectedSlotId = newSlot.id; + }); + }, ), - onTap: () async { - final newSlot = await provider.addSlot(Slot.withData( - day: widget.dayId, - order: widget.slots.length + 1, - )); - setState(() { - selectedSlotId = newSlot.id; - }); - }, - ), ], ); } diff --git a/lib/widgets/routines/routine_edit.dart b/lib/widgets/routines/routine_edit.dart index 7af7725f..2f60f821 100644 --- a/lib/widgets/routines/routine_edit.dart +++ b/lib/widgets/routines/routine_edit.dart @@ -16,9 +16,13 @@ * along with this program. If not, see . */ +import 'package:collection/collection.dart'; import 'package:flutter/material.dart'; +import 'package:flutter_gen/gen_l10n/app_localizations.dart'; +import 'package:provider/provider.dart'; import 'package:wger/models/workouts/day.dart'; import 'package:wger/models/workouts/routine.dart'; +import 'package:wger/providers/routines.dart'; import 'package:wger/widgets/routines/forms/day.dart'; import 'package:wger/widgets/routines/forms/routine.dart'; import 'package:wger/widgets/routines/routine_detail.dart'; @@ -33,13 +37,26 @@ class RoutineEdit extends StatefulWidget { } class _RoutineEditState extends State { + late Future _dataFuture; int? selectedDayId; + @override + void initState() { + super.initState(); + _dataFuture = context + .read() + .fetchAndSetRoutineFull(widget._routine.id!); // Initialize the Future here + } + @override Widget build(BuildContext context) { + final i18n = AppLocalizations.of(context); + + final provider = context.read(); + Day? selectedDay; if (selectedDayId != null) { - selectedDay = widget._routine.days.firstWhere((day) => day.id == selectedDayId); + selectedDay = widget._routine.days.firstWhereOrNull((day) => day.id == selectedDayId); } return Padding( @@ -49,6 +66,10 @@ class _RoutineEditState extends State { children: [ RoutineForm(widget._routine), Container(height: 10), + Text( + i18n.routineDays, + style: Theme.of(context).textTheme.titleLarge, + ), ReorderableDaysList( routineId: widget._routine.id!, days: widget._routine.days, @@ -69,13 +90,37 @@ class _RoutineEditState extends State { day: selectedDay, routineId: widget._routine.id!, ), - const SizedBox(height: 50), + const SizedBox(height: 25), Text( - 'Resulting routine', + i18n.resultingRoutine, style: Theme.of(context).textTheme.titleLarge, ), + IconButton( + onPressed: () { + setState(() { + _dataFuture = provider.fetchAndSetRoutineFull(widget._routine.id!); + }); + }, + icon: const Icon(Icons.refresh), + ), const Divider(), - RoutineDetail(widget._routine), + FutureBuilder( + future: _dataFuture, + builder: (BuildContext context, AsyncSnapshot snapshot) { + if (snapshot.connectionState == ConnectionState.waiting) { + return const SizedBox( + height: 200, + child: Center( + child: CircularProgressIndicator(), + ), + ); + } else if (snapshot.hasData) { + return RoutineDetail(snapshot.data!); + } + return const Text('No data available'); + }, + ), + // RoutineDetail(widget._routine), ], ), ),