mirror of
https://github.com/wger-project/flutter.git
synced 2026-02-18 00:17:48 +01:00
Save values to server
This commit is contained in:
@@ -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;
|
||||
|
||||
@@ -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,
|
||||
|
||||
@@ -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(
|
||||
|
||||
@@ -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()),
|
||||
],
|
||||
|
||||
Reference in New Issue
Block a user