mirror of
https://github.com/wger-project/flutter.git
synced 2026-02-18 00:17:48 +01:00
Refactor PlanForm
* Date values are now localized * Remove text controllers since we were setting the values in the plan object directly as well as setting them in the controllers anyway. Note that this is still not an ideal solution since if we change something in the form and close it, the changes are still reflected in the UI, just not preserved to the server. * Move basic date sanity cheks to the model
This commit is contained in:
@@ -19,6 +19,7 @@
|
||||
import 'package:collection/collection.dart';
|
||||
import 'package:flutter/widgets.dart';
|
||||
import 'package:json_annotation/json_annotation.dart';
|
||||
import 'package:logging/logging.dart';
|
||||
import 'package:wger/helpers/consts.dart';
|
||||
import 'package:wger/helpers/json.dart';
|
||||
import 'package:wger/l10n/generated/app_localizations.dart';
|
||||
@@ -32,6 +33,8 @@ part 'nutritional_plan.g.dart';
|
||||
|
||||
@JsonSerializable(explicitToJson: true)
|
||||
class NutritionalPlan {
|
||||
final _logger = Logger('NutritionalPlan Model');
|
||||
|
||||
@JsonKey(required: true)
|
||||
int? id;
|
||||
|
||||
@@ -88,6 +91,14 @@ class NutritionalPlan {
|
||||
}) : creationDate = creationDate ?? DateTime.now() {
|
||||
this.meals = meals ?? [];
|
||||
this.diaryEntries = diaryEntries ?? [];
|
||||
|
||||
if (endDate != null && endDate!.isBefore(startDate)) {
|
||||
_logger.warning(
|
||||
'The end date of a nutritional plan is before the start. Setting to null! '
|
||||
'PlanId: $id, startDate: $startDate, endDate: $endDate',
|
||||
);
|
||||
endDate = null;
|
||||
}
|
||||
}
|
||||
|
||||
NutritionalPlan.empty() {
|
||||
|
||||
@@ -500,27 +500,13 @@ class PlanForm extends StatefulWidget {
|
||||
class _PlanFormState extends State<PlanForm> {
|
||||
final _form = GlobalKey<FormState>();
|
||||
|
||||
bool _onlyLogging = true;
|
||||
GoalType _goalType = GoalType.meals;
|
||||
|
||||
final _descriptionController = TextEditingController();
|
||||
final _startDateController = TextEditingController();
|
||||
final _endDateController = TextEditingController();
|
||||
final TextEditingController colorController = TextEditingController();
|
||||
|
||||
GoalType? selectedGoal;
|
||||
|
||||
@override
|
||||
void initState() {
|
||||
super.initState();
|
||||
|
||||
_onlyLogging = widget._plan.onlyLogging;
|
||||
_descriptionController.text = widget._plan.description;
|
||||
_startDateController.text = dateToYYYYMMDD(widget._plan.startDate)!;
|
||||
// ignore invalid enddates should the server gives us one
|
||||
if (widget._plan.endDate != null && widget._plan.endDate!.isAfter(widget._plan.startDate)) {
|
||||
_endDateController.text = dateToYYYYMMDD(widget._plan.endDate)!;
|
||||
}
|
||||
if (widget._plan.hasAnyAdvancedGoals) {
|
||||
_goalType = GoalType.advanced;
|
||||
} else if (widget._plan.hasAnyGoals) {
|
||||
@@ -530,17 +516,10 @@ class _PlanFormState extends State<PlanForm> {
|
||||
}
|
||||
}
|
||||
|
||||
@override
|
||||
void dispose() {
|
||||
_descriptionController.dispose();
|
||||
_startDateController.dispose();
|
||||
_endDateController.dispose();
|
||||
colorController.dispose();
|
||||
super.dispose();
|
||||
}
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
final dateFormat = DateFormat.yMd(Localizations.localeOf(context).languageCode);
|
||||
|
||||
return Form(
|
||||
key: _form,
|
||||
child: ListView(
|
||||
@@ -551,7 +530,9 @@ class _PlanFormState extends State<PlanForm> {
|
||||
decoration: InputDecoration(
|
||||
labelText: AppLocalizations.of(context).description,
|
||||
),
|
||||
controller: _descriptionController,
|
||||
controller: TextEditingController(
|
||||
text: widget._plan.description,
|
||||
),
|
||||
onSaved: (newValue) {
|
||||
widget._plan.description = newValue!;
|
||||
},
|
||||
@@ -560,10 +541,15 @@ class _PlanFormState extends State<PlanForm> {
|
||||
TextFormField(
|
||||
key: const Key('field-start-date'),
|
||||
decoration: InputDecoration(
|
||||
labelText: AppLocalizations.of(context).start,
|
||||
hintText: 'YYYY-MM-DD',
|
||||
labelText: AppLocalizations.of(context).startDate,
|
||||
suffixIcon: const Icon(
|
||||
Icons.calendar_today,
|
||||
key: Key('calendarIcon'),
|
||||
),
|
||||
),
|
||||
controller: TextEditingController(
|
||||
text: dateFormat.format(widget._plan.startDate),
|
||||
),
|
||||
controller: _startDateController,
|
||||
readOnly: true,
|
||||
onTap: () async {
|
||||
// Stop keyboard from appearing
|
||||
@@ -579,11 +565,18 @@ class _PlanFormState extends State<PlanForm> {
|
||||
|
||||
if (pickedDate != null) {
|
||||
setState(() {
|
||||
_startDateController.text = dateToYYYYMMDD(pickedDate)!;
|
||||
widget._plan.startDate = pickedDate;
|
||||
});
|
||||
}
|
||||
},
|
||||
validator: (value) {
|
||||
if (widget._plan.endDate != null &&
|
||||
widget._plan.endDate!.isBefore(widget._plan.startDate)) {
|
||||
return 'End date must be after start date';
|
||||
}
|
||||
|
||||
return null;
|
||||
},
|
||||
),
|
||||
// End Date
|
||||
Row(
|
||||
@@ -593,11 +586,28 @@ class _PlanFormState extends State<PlanForm> {
|
||||
key: const Key('field-end-date'),
|
||||
decoration: InputDecoration(
|
||||
labelText: AppLocalizations.of(context).endDate,
|
||||
hintText: 'YYYY-MM-DD',
|
||||
helperText:
|
||||
'Tip: only for athletes with contest deadlines. Most users benefit from flexibility',
|
||||
suffixIcon: widget._plan.endDate == null
|
||||
? const Icon(
|
||||
Icons.calendar_today,
|
||||
key: Key('calendarIcon'),
|
||||
)
|
||||
: IconButton(
|
||||
icon: const Icon(Icons.clear),
|
||||
tooltip: 'Clear end date',
|
||||
onPressed: () {
|
||||
setState(() {
|
||||
widget._plan.endDate = null;
|
||||
});
|
||||
},
|
||||
),
|
||||
),
|
||||
controller: TextEditingController(
|
||||
text: widget._plan.endDate == null
|
||||
? ''
|
||||
: dateFormat.format(widget._plan.endDate!),
|
||||
),
|
||||
controller: _endDateController,
|
||||
readOnly: true,
|
||||
onTap: () async {
|
||||
// Stop keyboard from appearing
|
||||
@@ -606,47 +616,30 @@ class _PlanFormState extends State<PlanForm> {
|
||||
// Open date picker
|
||||
final pickedDate = await showDatePicker(
|
||||
context: context,
|
||||
// if somehow the server has an invalid end date, default to null
|
||||
initialDate: (widget._plan.endDate != null &&
|
||||
widget._plan.endDate!.isAfter(widget._plan.startDate))
|
||||
? widget._plan.endDate!
|
||||
: null,
|
||||
firstDate: widget._plan.startDate
|
||||
.add(const Duration(days: 1)), // end must be after start
|
||||
initialDate: widget._plan.endDate,
|
||||
// end must be after start
|
||||
firstDate: widget._plan.startDate.add(const Duration(days: 1)),
|
||||
lastDate: DateTime(2100),
|
||||
);
|
||||
|
||||
if (pickedDate != null) {
|
||||
setState(() {
|
||||
_endDateController.text = dateToYYYYMMDD(pickedDate)!;
|
||||
widget._plan.endDate = pickedDate;
|
||||
});
|
||||
}
|
||||
},
|
||||
),
|
||||
),
|
||||
if (_endDateController.text.isNotEmpty)
|
||||
IconButton(
|
||||
icon: const Icon(Icons.clear),
|
||||
tooltip: 'Clear end date',
|
||||
onPressed: () {
|
||||
setState(() {
|
||||
_endDateController.text = '';
|
||||
widget._plan.endDate = null;
|
||||
});
|
||||
},
|
||||
),
|
||||
],
|
||||
),
|
||||
SwitchListTile(
|
||||
title: Text(AppLocalizations.of(context).onlyLogging),
|
||||
subtitle: Text(AppLocalizations.of(context).onlyLoggingHelpText),
|
||||
value: _onlyLogging,
|
||||
value: widget._plan.onlyLogging,
|
||||
onChanged: (value) {
|
||||
setState(() {
|
||||
_onlyLogging = !_onlyLogging;
|
||||
widget._plan.onlyLogging = value;
|
||||
});
|
||||
widget._plan.onlyLogging = value;
|
||||
},
|
||||
),
|
||||
Row(
|
||||
@@ -658,7 +651,7 @@ class _PlanFormState extends State<PlanForm> {
|
||||
const SizedBox(width: 8),
|
||||
Expanded(
|
||||
child: DropdownButtonFormField<GoalType>(
|
||||
value: _goalType,
|
||||
initialValue: _goalType,
|
||||
items: GoalType.values
|
||||
.map(
|
||||
(e) => DropdownMenuItem<GoalType>(
|
||||
@@ -766,9 +759,6 @@ class _PlanFormState extends State<PlanForm> {
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
// Saving was successful, reset the data
|
||||
_descriptionController.clear();
|
||||
},
|
||||
),
|
||||
],
|
||||
|
||||
@@ -110,7 +110,7 @@ class _RoutineFormState extends State<RoutineForm> {
|
||||
},
|
||||
decoration: InputDecoration(
|
||||
labelText: i18n.startDate,
|
||||
suffixIcon: Icon(
|
||||
suffixIcon: const Icon(
|
||||
Icons.calendar_today,
|
||||
key: Key('calendarIcon'),
|
||||
),
|
||||
|
||||
@@ -38,6 +38,7 @@ void main() {
|
||||
id: 1,
|
||||
creationDate: DateTime(2021, 1, 1),
|
||||
startDate: DateTime(2021, 1, 1),
|
||||
endDate: DateTime(2021, 2, 10),
|
||||
description: 'test plan 1',
|
||||
);
|
||||
final plan2 = NutritionalPlan.empty();
|
||||
@@ -80,11 +81,10 @@ void main() {
|
||||
await tester.pumpWidget(createHomeScreen(plan1));
|
||||
await tester.pumpAndSettle();
|
||||
|
||||
expect(
|
||||
find.text('test plan 1'),
|
||||
findsOneWidget,
|
||||
reason: 'Description of existing nutritional plan is filled in',
|
||||
);
|
||||
expect(find.text('test plan 1'), findsOneWidget, reason: 'Description is filled in');
|
||||
expect(find.text('1/1/2021'), findsOneWidget, reason: 'Start date is filled in');
|
||||
expect(find.text('2/10/2021'), findsOneWidget, reason: 'End date is filled in');
|
||||
|
||||
await tester.enterText(find.byKey(const Key('field-description')), 'New description');
|
||||
await tester.tap(find.byKey(const Key(SUBMIT_BUTTON_KEY_NAME)));
|
||||
|
||||
|
||||
Reference in New Issue
Block a user