From 04c6d6c6436d28c5b9d30283a1e30c97033db2a4 Mon Sep 17 00:00:00 2001 From: Roland Geider Date: Fri, 15 Nov 2024 18:03:06 +0100 Subject: [PATCH] Save values to server --- lib/models/workouts/slot_entry.dart | 4 +- lib/models/workouts/slot_entry.g.dart | 8 +- lib/providers/routines.dart | 33 ++++ lib/widgets/routines/forms/slot.dart | 228 +++++++++++++++++--------- 4 files changed, 185 insertions(+), 88 deletions(-) diff --git a/lib/models/workouts/slot_entry.dart b/lib/models/workouts/slot_entry.dart index d1c2a339..c20df030 100644 --- a/lib/models/workouts/slot_entry.dart +++ b/lib/models/workouts/slot_entry.dart @@ -63,10 +63,10 @@ class SlotEntry { late num repetitionRounding; @JsonKey(required: true, name: 'reps_configs') - late List repsConfig; + late List repsConfigs; @JsonKey(required: true, name: 'max_reps_configs') - late List maxRepsConfig; + late List maxRepsConfigs; @JsonKey(required: true, name: 'weight_unit') late int weightUnitId; diff --git a/lib/models/workouts/slot_entry.g.dart b/lib/models/workouts/slot_entry.g.dart index 44b2d60e..3bb6c800 100644 --- a/lib/models/workouts/slot_entry.g.dart +++ b/lib/models/workouts/slot_entry.g.dart @@ -43,10 +43,10 @@ SlotEntry _$SlotEntryFromJson(Map json) { weightRounding: stringToNum(json['weight_rounding'] as String?), comment: json['comment'] as String, ) - ..repsConfig = (json['reps_configs'] as List) + ..repsConfigs = (json['reps_configs'] as List) .map((e) => BaseConfig.fromJson(e as Map)) .toList() - ..maxRepsConfig = (json['max_reps_configs'] as List) + ..maxRepsConfigs = (json['max_reps_configs'] as List) .map((e) => BaseConfig.fromJson(e as Map)) .toList() ..weightConfigs = (json['weight_configs'] as List) @@ -79,8 +79,8 @@ Map _$SlotEntryToJson(SlotEntry instance) => { 'exercise': instance.exerciseId, 'repetition_unit': instance.repetitionUnitId, 'repetition_rounding': instance.repetitionRounding, - 'reps_configs': instance.repsConfig, - 'max_reps_configs': instance.maxRepsConfig, + 'reps_configs': instance.repsConfigs, + 'max_reps_configs': instance.maxRepsConfigs, 'weight_unit': instance.weightUnitId, 'weight_rounding': instance.weightRounding, 'weight_configs': instance.weightConfigs, diff --git a/lib/providers/routines.dart b/lib/providers/routines.dart index ea573bab..c159d0e5 100644 --- a/lib/providers/routines.dart +++ b/lib/providers/routines.dart @@ -22,8 +22,10 @@ import 'dart:developer' as dev; import 'package:flutter/material.dart'; import 'package:shared_preferences/shared_preferences.dart'; import 'package:wger/exceptions/http_exception.dart'; +import 'package:wger/exceptions/no_such_entry_exception.dart'; import 'package:wger/helpers/consts.dart'; import 'package:wger/models/exercises/exercise.dart'; +import 'package:wger/models/workouts/base_config.dart'; import 'package:wger/models/workouts/day.dart'; import 'package:wger/models/workouts/day_data.dart'; import 'package:wger/models/workouts/log.dart'; @@ -49,6 +51,9 @@ class RoutinesProvider with ChangeNotifier { static const _sessionUrlPath = 'workoutsession'; static const _weightUnitUrlPath = 'setting-weightunit'; static const _repetitionUnitUrlPath = 'setting-repetitionunit'; + static const _routineConfigSets = 'sets-config'; + static const _routineConfigWeights = 'weights-config'; + static const _routineConfigReps = 'reps-config'; Routine? _currentPlan; final ExercisesProvider _exercises; @@ -481,6 +486,34 @@ class RoutinesProvider with ChangeNotifier { notifyListeners(); } + String getConfigUrl(String type) { + switch (type) { + case 'sets': + return _routineConfigSets; + case 'weights': + return _routineConfigWeights; + case 'reps': + return _routineConfigReps; + } + throw const NoSuchEntryException(); + } + + Future editConfig(BaseConfig config, String type) async { + await baseProvider.patch( + config.toJson(), + baseProvider.makeUrl(getConfigUrl(type), id: config.id), + ); + notifyListeners(); + } + + Future addConfig(BaseConfig config, String type) async { + await baseProvider.post( + config.toJson(), + baseProvider.makeUrl(getConfigUrl(type)), + ); + notifyListeners(); + } + Future fetchComputedSettings(Slot workoutSet) async { final data = await baseProvider.fetch( baseProvider.makeUrl( diff --git a/lib/widgets/routines/forms/slot.dart b/lib/widgets/routines/forms/slot.dart index 1504d853..944d8d0a 100644 --- a/lib/widgets/routines/forms/slot.dart +++ b/lib/widgets/routines/forms/slot.dart @@ -16,6 +16,8 @@ * along with this program. If not, see . */ +import 'dart:developer'; + import 'package:flutter/material.dart'; import 'package:flutter_gen/gen_l10n/app_localizations.dart'; import 'package:flutter_typeahead/flutter_typeahead.dart'; @@ -31,62 +33,116 @@ import 'package:wger/screens/add_exercise_screen.dart'; import 'package:wger/widgets/exercises/images.dart'; import 'package:wger/widgets/routines/forms.dart'; -class SlotDetailWidget extends StatelessWidget { - final int index; - final Slot slot; +class SlotEntryForm extends StatelessWidget { + final SlotEntry entry; - SlotDetailWidget(this.slot, this.index, {super.key}); + final setsController = TextEditingController(); + final weightController = TextEditingController(); + final repsController = TextEditingController(); - Widget getHeader() { - return Row( - children: [ - Expanded(child: Text('Set ${index + 1}')), - TextButton.icon( - onPressed: () {}, - icon: const Icon(Icons.add), - label: const Text('superset'), - ), - TextButton.icon( - onPressed: () {}, - icon: const Icon(Icons.ssid_chart), - label: const Text('progression'), - ) - ], - ); + SlotEntryForm(this.entry, {super.key}) { + if (entry.setNrConfigs.isNotEmpty) { + setsController.text = entry.setNrConfigs.first.value.toString(); + } + if (entry.weightConfigs.isNotEmpty) { + weightController.text = entry.weightConfigs.first.value.toString(); + } + if (entry.repsConfigs.isNotEmpty) { + repsController.text = entry.repsConfigs.first.value.toString(); + } } - Widget getEntryRow(SlotEntry entry, String languageCode) { - return Column( - children: [ - Text(entry.exerciseObj.getExercise(languageCode).name), - TextFormField( - decoration: InputDecoration(labelText: 'Sets'), - ), - TextFormField( - decoration: InputDecoration(labelText: 'Weight'), - ), - TextFormField( - decoration: InputDecoration(labelText: 'Reps'), - ), - ], - ); - } + final _form = GlobalKey(); @override build(BuildContext context) { + final i18n = AppLocalizations.of(context); final languageCode = Localizations.localeOf(context).languageCode; - return Column( - crossAxisAlignment: CrossAxisAlignment.start, - children: [ - getHeader(), - ...slot.entries.map((entry) => getEntryRow(entry, languageCode)), - SizedBox(height: 30), - ], + return Form( + key: _form, + child: Column( + children: [ + Text(entry.exerciseObj.getExercise(languageCode).name), + TextFormField( + controller: setsController, + keyboardType: TextInputType.number, + decoration: InputDecoration(labelText: 'Sets'), + ), + TextFormField( + controller: weightController, + keyboardType: TextInputType.number, + decoration: InputDecoration(labelText: i18n.weight), + ), + TextFormField( + controller: repsController, + keyboardType: TextInputType.number, + decoration: InputDecoration(labelText: i18n.repetitions), + ), + const SizedBox(height: 5), + OutlinedButton( + key: const Key(SUBMIT_BUTTON_KEY_NAME), + child: Text(AppLocalizations.of(context).save), + onPressed: () { + if (!_form.currentState!.validate()) { + return; + } + _form.currentState!.save(); + + final provider = Provider.of(context, listen: false); + if (weightController.text.isNotEmpty) { + log('process weight...'); + if (entry.weightConfigs.isNotEmpty) { + log('update first config'); + entry.weightConfigs.first.value = num.parse(weightController.text); + provider.editConfig(entry.weightConfigs.first, 'weights'); + } else { + log('creating config'); + } + } + + if (setsController.text.isNotEmpty) { + log('process sets...'); + if (entry.setNrConfigs.isNotEmpty) { + entry.setNrConfigs.first.value = num.parse(setsController.text); + provider.editConfig(entry.setNrConfigs.first, 'sets'); + } else { + log('creating config'); + } + } + if (repsController.text.isNotEmpty) { + log('process reps...'); + if (entry.repsConfigs.isNotEmpty) { + entry.repsConfigs.first.value = num.parse(repsController.text); + provider.editConfig(entry.repsConfigs.first, 'reps'); + } else { + log('creating config'); + } + } + }, + ), + const SizedBox(height: 15), + ], + ), ); } } +class SlotDetailWidget extends StatelessWidget { + final Slot slot; + + const SlotDetailWidget(this.slot, {super.key}); + + @override + build(BuildContext context) => Column( + crossAxisAlignment: CrossAxisAlignment.start, + children: [ + ...slot.entries.map((entry) => SlotEntryForm(entry)), + const SizedBox(height: 30), + ], + ); +} + class ReorderableSlotList extends StatefulWidget { final List slots; @@ -130,48 +186,56 @@ class _SlotFormWidgetStateNg extends State { final slot = widget.slots[index]; final isSlotSelected = slot.id == selectedSlotId; - // return SlotDetailTile(slot, index, key: ValueKey(slot.id)); - return Card( key: ValueKey(slot.id), - child: ListTile( - title: Text('Set ${index + 1}'), - tileColor: isSlotSelected ? Theme.of(context).highlightColor : null, - leading: ReorderableDragStartListener( - index: index, - child: const Icon(Icons.drag_handle), - ), - subtitle: Column( - crossAxisAlignment: CrossAxisAlignment.start, - children: [ - ...slot.entries.map((e) => Text(e.exerciseObj.getExercise(languageCode).name)), - ], - ), - trailing: Row( - mainAxisSize: MainAxisSize.min, - children: [ - IconButton( - onPressed: () { - setState(() { - if (selectedSlotId == slot.id) { - selectedSlotId = null; - } else { - selectedSlotId = slot.id; - } - }); - // widget.onDaySelected(day.id!); - }, - icon: isSlotSelected ? const Icon(Icons.edit_off) : const Icon(Icons.edit), + child: Column( + children: [ + ListTile( + title: Text('Set ${index + 1}'), + tileColor: isSlotSelected ? Theme.of(context).highlightColor : null, + leading: selectedSlotId == null + ? ReorderableDragStartListener( + index: index, + child: const Icon(Icons.drag_handle), + ) + : const Icon(Icons.block), + // : const Icon(Icons.filter_list_off), + subtitle: Column( + crossAxisAlignment: CrossAxisAlignment.start, + children: [ + ...slot.entries + .map((e) => Text(e.exerciseObj.getExercise(languageCode).name)), + ], ), - IconButton( - icon: const Icon(Icons.delete), - onPressed: () { - // widget._showDeleteConfirmationDialog( - // context, day); // Call the dialog function - }, + trailing: Row( + mainAxisSize: MainAxisSize.min, + children: [ + IconButton( + onPressed: () { + setState(() { + if (selectedSlotId == slot.id) { + selectedSlotId = null; + } else { + selectedSlotId = slot.id; + } + }); + // widget.onDaySelected(day.id!); + }, + icon: + isSlotSelected ? const Icon(Icons.edit_off) : const Icon(Icons.edit), + ), + IconButton( + icon: const Icon(Icons.delete), + onPressed: () { + // widget._showDeleteConfirmationDialog( + // context, day); // Call the dialog function + }, + ), + ], ), - ], - ), + ), + if (isSlotSelected) SlotDetailWidget(slot), + ], ), ); }, @@ -198,7 +262,7 @@ class _SlotFormWidgetStateNg extends State { AppLocalizations.of(context).newSet, style: Theme.of(context).textTheme.titleMedium, ), - onTap: () async {}, + onTap: () {}, ), if (selectedSlot != null) Text(selectedSlot.id!.toString()), ],