Skip to content

Commit dbd3fa9

Browse files
committed
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
1 parent d9b4d66 commit dbd3fa9

File tree

4 files changed

+63
-62
lines changed

4 files changed

+63
-62
lines changed

lib/models/nutrition/nutritional_plan.dart

Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -19,6 +19,7 @@
1919
import 'package:collection/collection.dart';
2020
import 'package:flutter/widgets.dart';
2121
import 'package:json_annotation/json_annotation.dart';
22+
import 'package:logging/logging.dart';
2223
import 'package:wger/helpers/consts.dart';
2324
import 'package:wger/helpers/json.dart';
2425
import 'package:wger/l10n/generated/app_localizations.dart';
@@ -32,6 +33,8 @@ part 'nutritional_plan.g.dart';
3233

3334
@JsonSerializable(explicitToJson: true)
3435
class NutritionalPlan {
36+
final _logger = Logger('NutritionalPlan Model');
37+
3538
@JsonKey(required: true)
3639
int? id;
3740

@@ -88,6 +91,14 @@ class NutritionalPlan {
8891
}) : creationDate = creationDate ?? DateTime.now() {
8992
this.meals = meals ?? [];
9093
this.diaryEntries = diaryEntries ?? [];
94+
95+
if (endDate != null && endDate!.isBefore(startDate)) {
96+
_logger.warning(
97+
'The end date of a nutritional plan is before the start. Setting to null! '
98+
'PlanId: $id, startDate: $startDate, endDate: $endDate',
99+
);
100+
endDate = null;
101+
}
91102
}
92103

93104
NutritionalPlan.empty() {

lib/widgets/nutrition/forms.dart

Lines changed: 46 additions & 56 deletions
Original file line numberDiff line numberDiff line change
@@ -500,27 +500,13 @@ class PlanForm extends StatefulWidget {
500500
class _PlanFormState extends State<PlanForm> {
501501
final _form = GlobalKey<FormState>();
502502

503-
bool _onlyLogging = true;
504503
GoalType _goalType = GoalType.meals;
505-
506-
final _descriptionController = TextEditingController();
507-
final _startDateController = TextEditingController();
508-
final _endDateController = TextEditingController();
509-
final TextEditingController colorController = TextEditingController();
510-
511504
GoalType? selectedGoal;
512505

513506
@override
514507
void initState() {
515508
super.initState();
516509

517-
_onlyLogging = widget._plan.onlyLogging;
518-
_descriptionController.text = widget._plan.description;
519-
_startDateController.text = dateToYYYYMMDD(widget._plan.startDate)!;
520-
// ignore invalid enddates should the server gives us one
521-
if (widget._plan.endDate != null && widget._plan.endDate!.isAfter(widget._plan.startDate)) {
522-
_endDateController.text = dateToYYYYMMDD(widget._plan.endDate)!;
523-
}
524510
if (widget._plan.hasAnyAdvancedGoals) {
525511
_goalType = GoalType.advanced;
526512
} else if (widget._plan.hasAnyGoals) {
@@ -530,17 +516,10 @@ class _PlanFormState extends State<PlanForm> {
530516
}
531517
}
532518

533-
@override
534-
void dispose() {
535-
_descriptionController.dispose();
536-
_startDateController.dispose();
537-
_endDateController.dispose();
538-
colorController.dispose();
539-
super.dispose();
540-
}
541-
542519
@override
543520
Widget build(BuildContext context) {
521+
final dateFormat = DateFormat.yMd(Localizations.localeOf(context).languageCode);
522+
544523
return Form(
545524
key: _form,
546525
child: ListView(
@@ -551,7 +530,9 @@ class _PlanFormState extends State<PlanForm> {
551530
decoration: InputDecoration(
552531
labelText: AppLocalizations.of(context).description,
553532
),
554-
controller: _descriptionController,
533+
controller: TextEditingController(
534+
text: widget._plan.description,
535+
),
555536
onSaved: (newValue) {
556537
widget._plan.description = newValue!;
557538
},
@@ -560,10 +541,15 @@ class _PlanFormState extends State<PlanForm> {
560541
TextFormField(
561542
key: const Key('field-start-date'),
562543
decoration: InputDecoration(
563-
labelText: AppLocalizations.of(context).start,
564-
hintText: 'YYYY-MM-DD',
544+
labelText: AppLocalizations.of(context).startDate,
545+
suffixIcon: const Icon(
546+
Icons.calendar_today,
547+
key: Key('calendarIcon'),
548+
),
549+
),
550+
controller: TextEditingController(
551+
text: dateFormat.format(widget._plan.startDate),
565552
),
566-
controller: _startDateController,
567553
readOnly: true,
568554
onTap: () async {
569555
// Stop keyboard from appearing
@@ -579,11 +565,18 @@ class _PlanFormState extends State<PlanForm> {
579565

580566
if (pickedDate != null) {
581567
setState(() {
582-
_startDateController.text = dateToYYYYMMDD(pickedDate)!;
583568
widget._plan.startDate = pickedDate;
584569
});
585570
}
586571
},
572+
validator: (value) {
573+
if (widget._plan.endDate != null &&
574+
widget._plan.endDate!.isBefore(widget._plan.startDate)) {
575+
return 'End date must be after start date';
576+
}
577+
578+
return null;
579+
},
587580
),
588581
// End Date
589582
Row(
@@ -593,11 +586,28 @@ class _PlanFormState extends State<PlanForm> {
593586
key: const Key('field-end-date'),
594587
decoration: InputDecoration(
595588
labelText: AppLocalizations.of(context).endDate,
596-
hintText: 'YYYY-MM-DD',
597589
helperText:
598590
'Tip: only for athletes with contest deadlines. Most users benefit from flexibility',
591+
suffixIcon: widget._plan.endDate == null
592+
? const Icon(
593+
Icons.calendar_today,
594+
key: Key('calendarIcon'),
595+
)
596+
: IconButton(
597+
icon: const Icon(Icons.clear),
598+
tooltip: 'Clear end date',
599+
onPressed: () {
600+
setState(() {
601+
widget._plan.endDate = null;
602+
});
603+
},
604+
),
605+
),
606+
controller: TextEditingController(
607+
text: widget._plan.endDate == null
608+
? ''
609+
: dateFormat.format(widget._plan.endDate!),
599610
),
600-
controller: _endDateController,
601611
readOnly: true,
602612
onTap: () async {
603613
// Stop keyboard from appearing
@@ -606,47 +616,30 @@ class _PlanFormState extends State<PlanForm> {
606616
// Open date picker
607617
final pickedDate = await showDatePicker(
608618
context: context,
609-
// if somehow the server has an invalid end date, default to null
610-
initialDate: (widget._plan.endDate != null &&
611-
widget._plan.endDate!.isAfter(widget._plan.startDate))
612-
? widget._plan.endDate!
613-
: null,
614-
firstDate: widget._plan.startDate
615-
.add(const Duration(days: 1)), // end must be after start
619+
initialDate: widget._plan.endDate,
620+
// end must be after start
621+
firstDate: widget._plan.startDate.add(const Duration(days: 1)),
616622
lastDate: DateTime(2100),
617623
);
618624

619625
if (pickedDate != null) {
620626
setState(() {
621-
_endDateController.text = dateToYYYYMMDD(pickedDate)!;
622627
widget._plan.endDate = pickedDate;
623628
});
624629
}
625630
},
626631
),
627632
),
628-
if (_endDateController.text.isNotEmpty)
629-
IconButton(
630-
icon: const Icon(Icons.clear),
631-
tooltip: 'Clear end date',
632-
onPressed: () {
633-
setState(() {
634-
_endDateController.text = '';
635-
widget._plan.endDate = null;
636-
});
637-
},
638-
),
639633
],
640634
),
641635
SwitchListTile(
642636
title: Text(AppLocalizations.of(context).onlyLogging),
643637
subtitle: Text(AppLocalizations.of(context).onlyLoggingHelpText),
644-
value: _onlyLogging,
638+
value: widget._plan.onlyLogging,
645639
onChanged: (value) {
646640
setState(() {
647-
_onlyLogging = !_onlyLogging;
641+
widget._plan.onlyLogging = value;
648642
});
649-
widget._plan.onlyLogging = value;
650643
},
651644
),
652645
Row(
@@ -658,7 +651,7 @@ class _PlanFormState extends State<PlanForm> {
658651
const SizedBox(width: 8),
659652
Expanded(
660653
child: DropdownButtonFormField<GoalType>(
661-
value: _goalType,
654+
initialValue: _goalType,
662655
items: GoalType.values
663656
.map(
664657
(e) => DropdownMenuItem<GoalType>(
@@ -766,9 +759,6 @@ class _PlanFormState extends State<PlanForm> {
766759
);
767760
}
768761
}
769-
770-
// Saving was successful, reset the data
771-
_descriptionController.clear();
772762
},
773763
),
774764
],

lib/widgets/routines/forms/routine.dart

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -110,7 +110,7 @@ class _RoutineFormState extends State<RoutineForm> {
110110
},
111111
decoration: InputDecoration(
112112
labelText: i18n.startDate,
113-
suffixIcon: Icon(
113+
suffixIcon: const Icon(
114114
Icons.calendar_today,
115115
key: Key('calendarIcon'),
116116
),

test/nutrition/nutritional_plan_form_test.dart

Lines changed: 5 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -38,6 +38,7 @@ void main() {
3838
id: 1,
3939
creationDate: DateTime(2021, 1, 1),
4040
startDate: DateTime(2021, 1, 1),
41+
endDate: DateTime(2021, 2, 10),
4142
description: 'test plan 1',
4243
);
4344
final plan2 = NutritionalPlan.empty();
@@ -80,11 +81,10 @@ void main() {
8081
await tester.pumpWidget(createHomeScreen(plan1));
8182
await tester.pumpAndSettle();
8283

83-
expect(
84-
find.text('test plan 1'),
85-
findsOneWidget,
86-
reason: 'Description of existing nutritional plan is filled in',
87-
);
84+
expect(find.text('test plan 1'), findsOneWidget, reason: 'Description is filled in');
85+
expect(find.text('1/1/2021'), findsOneWidget, reason: 'Start date is filled in');
86+
expect(find.text('2/10/2021'), findsOneWidget, reason: 'End date is filled in');
87+
8888
await tester.enterText(find.byKey(const Key('field-description')), 'New description');
8989
await tester.tap(find.byKey(const Key(SUBMIT_BUTTON_KEY_NAME)));
9090

0 commit comments

Comments
 (0)