Save values to server

This commit is contained in:
Roland Geider
2024-11-15 18:03:06 +01:00
parent 23119e68d5
commit 04c6d6c643
4 changed files with 185 additions and 88 deletions

View File

@@ -63,10 +63,10 @@ class SlotEntry {
late num repetitionRounding;
@JsonKey(required: true, name: 'reps_configs')
late List<BaseConfig> repsConfig;
late List<BaseConfig> repsConfigs;
@JsonKey(required: true, name: 'max_reps_configs')
late List<BaseConfig> maxRepsConfig;
late List<BaseConfig> maxRepsConfigs;
@JsonKey(required: true, name: 'weight_unit')
late int weightUnitId;

View File

@@ -43,10 +43,10 @@ SlotEntry _$SlotEntryFromJson(Map<String, dynamic> json) {
weightRounding: stringToNum(json['weight_rounding'] as String?),
comment: json['comment'] as String,
)
..repsConfig = (json['reps_configs'] as List<dynamic>)
..repsConfigs = (json['reps_configs'] as List<dynamic>)
.map((e) => BaseConfig.fromJson(e as Map<String, dynamic>))
.toList()
..maxRepsConfig = (json['max_reps_configs'] as List<dynamic>)
..maxRepsConfigs = (json['max_reps_configs'] as List<dynamic>)
.map((e) => BaseConfig.fromJson(e as Map<String, dynamic>))
.toList()
..weightConfigs = (json['weight_configs'] as List<dynamic>)
@@ -79,8 +79,8 @@ Map<String, dynamic> _$SlotEntryToJson(SlotEntry instance) => <String, dynamic>{
'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,

View File

@@ -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<void> editConfig(BaseConfig config, String type) async {
await baseProvider.patch(
config.toJson(),
baseProvider.makeUrl(getConfigUrl(type), id: config.id),
);
notifyListeners();
}
Future<void> addConfig(BaseConfig config, String type) async {
await baseProvider.post(
config.toJson(),
baseProvider.makeUrl(getConfigUrl(type)),
);
notifyListeners();
}
Future<void> fetchComputedSettings(Slot workoutSet) async {
final data = await baseProvider.fetch(
baseProvider.makeUrl(

View File

@@ -16,6 +16,8 @@
* along with this program. If not, see <http://www.gnu.org/licenses/>.
*/
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<FormState>();
@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<RoutinesProvider>(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<Slot> slots;
@@ -130,48 +186,56 @@ class _SlotFormWidgetStateNg extends State<ReorderableSlotList> {
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<ReorderableSlotList> {
AppLocalizations.of(context).newSet,
style: Theme.of(context).textTheme.titleMedium,
),
onTap: () async {},
onTap: () {},
),
if (selectedSlot != null) Text(selectedSlot.id!.toString()),
],