diff --git a/lib/models/nutrition/meal.dart b/lib/models/nutrition/meal.dart index 433698aa..6b5f6543 100644 --- a/lib/models/nutrition/meal.dart +++ b/lib/models/nutrition/meal.dart @@ -36,6 +36,9 @@ class Meal { @JsonKey(toJson: timeToString, fromJson: stringToTime) TimeOfDay? time; + @JsonKey(name: 'name') + late String name; + @JsonKey(ignore: true, name: 'meal_items', defaultValue: []) List mealItems = []; @@ -43,6 +46,7 @@ class Meal { this.id, int? plan, TimeOfDay? time, + String? name, List? mealItems, }) { if (plan != null) { @@ -52,10 +56,12 @@ class Meal { this.mealItems = mealItems ?? []; this.time = time ?? TimeOfDay.now(); + this.name = name ?? ''; } // Boilerplate factory Meal.fromJson(Map json) => _$MealFromJson(json); + Map toJson() => _$MealToJson(this); /// Calculations diff --git a/lib/models/nutrition/meal.g.dart b/lib/models/nutrition/meal.g.dart index 20ced4e7..3d079787 100644 --- a/lib/models/nutrition/meal.g.dart +++ b/lib/models/nutrition/meal.g.dart @@ -10,6 +10,7 @@ Meal _$MealFromJson(Map json) { return Meal( id: json['id'] as int?, time: stringToTime(json['time'] as String?), + name: json['name'] as String?, )..planId = json['plan'] as int; } @@ -17,4 +18,5 @@ Map _$MealToJson(Meal instance) => { 'id': instance.id, 'plan': instance.planId, 'time': timeToString(instance.time), + 'name': instance.name, }; diff --git a/lib/widgets/nutrition/forms.dart b/lib/widgets/nutrition/forms.dart index 4c40ae68..949c1e16 100644 --- a/lib/widgets/nutrition/forms.dart +++ b/lib/widgets/nutrition/forms.dart @@ -36,10 +36,12 @@ class MealForm extends StatelessWidget { final _form = GlobalKey(); final _timeController = TextEditingController(); + final _nameController = TextEditingController(); MealForm(this._planId, [meal]) { _meal = meal ?? Meal(plan: _planId); _timeController.text = timeToString(_meal.time)!; + _nameController.text = _meal.name; } @override @@ -71,6 +73,16 @@ class MealForm extends StatelessWidget { }, onFieldSubmitted: (_) {}, ), + TextFormField( + maxLength: 25, + key: Key('field-name'), + decoration: InputDecoration(labelText: AppLocalizations.of(context).name), + controller: _nameController, + onSaved: (newValue) { + _meal.name = newValue as String; + }, + onFieldSubmitted: (_) {}, + ), ElevatedButton( key: Key(SUBMIT_BUTTON_KEY_NAME), child: Text(AppLocalizations.of(context).save), @@ -82,8 +94,7 @@ class MealForm extends StatelessWidget { try { _meal.id == null - ? Provider.of(context, listen: false) - .addMeal(_meal, _planId) + ? Provider.of(context, listen: false).addMeal(_meal, _planId) : Provider.of(context, listen: false).editMeal(_meal); } on WgerHttpException catch (error) { showHttpExceptionErrorDialog(error, context); @@ -178,8 +189,7 @@ class MealItemForm extends StatelessWidget { _form.currentState!.save(); try { - Provider.of(context, listen: false) - .addMealItem(_mealItem, _meal); + Provider.of(context, listen: false).addMealItem(_mealItem, _meal); } on WgerHttpException catch (error) { showHttpExceptionErrorDialog(error, context); } catch (error) { @@ -238,8 +248,7 @@ class PlanForm extends StatelessWidget { await Provider.of(context, listen: false).editPlan(_plan); Navigator.of(context).pop(); } else { - _plan = await Provider.of(context, listen: false) - .addPlan(_plan); + _plan = await Provider.of(context, listen: false).addPlan(_plan); Navigator.of(context).pushReplacementNamed( NutritionalPlanScreen.routeName, arguments: _plan, diff --git a/lib/widgets/nutrition/meal.dart b/lib/widgets/nutrition/meal.dart index 32cd3bf4..265d02ce 100644 --- a/lib/widgets/nutrition/meal.dart +++ b/lib/widgets/nutrition/meal.dart @@ -41,6 +41,7 @@ class MealWidget extends StatefulWidget { class _MealWidgetState extends State { bool _expanded = false; + void _toggleExpanded() { setState(() { _expanded = !_expanded; @@ -63,8 +64,7 @@ class _MealWidgetState extends State { IconButton( onPressed: () { // Delete the meal - Provider.of(context, listen: false) - .deleteMeal(widget._meal); + Provider.of(context, listen: false).deleteMeal(widget._meal); // and inform the user ScaffoldMessenger.of(context).showSnackBar( @@ -88,8 +88,7 @@ class _MealWidgetState extends State { icon: const Icon(Icons.history_edu), color: Colors.white, onPressed: () { - Provider.of(context, listen: false) - .logMealToDiary(widget._meal); + Provider.of(context, listen: false).logMealToDiary(widget._meal); ScaffoldMessenger.of(context).showSnackBar( SnackBar( content: Text( @@ -178,8 +177,7 @@ class MealItemWidget extends StatelessWidget { iconSize: ICON_SIZE_SMALL, onPressed: () { // Delete the meal item - Provider.of(context, listen: false) - .deleteMealItem(_item); + Provider.of(context, listen: false).deleteMealItem(_item); // and inform the user ScaffoldMessenger.of(context).showSnackBar( @@ -221,20 +219,30 @@ class DismissibleMealHeader extends StatelessWidget { child: Container( padding: const EdgeInsets.all(10), decoration: BoxDecoration(color: Colors.white), - child: Row( + child: Column( + crossAxisAlignment: CrossAxisAlignment.start, children: [ - Expanded( - child: Text( - _meal.time!.format(context), + if (_meal.name != '') + Text( + _meal.name, style: Theme.of(context).textTheme.headline5, ), - ), - IconButton( - visualDensity: VisualDensity.compact, - icon: _expanded ? Icon(Icons.unfold_less) : Icon(Icons.unfold_more), - onPressed: () { - _toggle(); - }, + Row( + children: [ + Expanded( + child: Text( + _meal.time!.format(context), + style: Theme.of(context).textTheme.headline5, + ), + ), + IconButton( + visualDensity: VisualDensity.compact, + icon: _expanded ? Icon(Icons.unfold_less) : Icon(Icons.unfold_more), + onPressed: () { + _toggle(); + }, + ), + ], ), ], ), diff --git a/test/nutritional_meal_form_test.dart b/test/nutritional_meal_form_test.dart index 6f351a3c..0c74c145 100644 --- a/test/nutritional_meal_form_test.dart +++ b/test/nutritional_meal_form_test.dart @@ -72,7 +72,7 @@ void main() { await tester.pumpWidget(createHomeScreen(meal1)); await tester.pumpAndSettle(); - expect(find.byType(TextFormField), findsOneWidget); + expect(find.byType(TextFormField), findsNWidgets(2)); expect(find.byType(ElevatedButton), findsOneWidget); expect(find.byKey(Key(SUBMIT_BUTTON_KEY_NAME)), findsOneWidget); }); @@ -86,7 +86,15 @@ void main() { findsOneWidget, reason: 'Time of existing meal is filled in', ); + + expect( + find.text(('Initial Name 1')), + findsOneWidget, + reason: 'Time of existing meal is filled in', + ); + await tester.enterText(find.byKey(Key('field-time')), '12:34'); + await tester.enterText(find.byKey(Key('field-name')), 'test meal'); await tester.tap(find.byKey(Key(SUBMIT_BUTTON_KEY_NAME))); // Correct method was called @@ -105,6 +113,7 @@ void main() { ); await tester.enterText(find.byKey(Key('field-time')), '08:00'); + await tester.enterText(find.byKey(Key('field-name')), 'test meal'); await tester.tap(find.byKey(Key(SUBMIT_BUTTON_KEY_NAME))); // Correct method was called diff --git a/test_data/nutritional_plans.dart b/test_data/nutritional_plans.dart index 488ee77a..3983b92b 100644 --- a/test_data/nutritional_plans.dart +++ b/test_data/nutritional_plans.dart @@ -86,6 +86,7 @@ NutritionalPlan getNutritionalPlan() { id: 1, plan: 1, time: TimeOfDay(hour: 17, minute: 0), + name: 'Initial Name 1', ); meal1.mealItems = [mealItem1, mealItem2]; @@ -93,6 +94,7 @@ NutritionalPlan getNutritionalPlan() { id: 2, plan: 1, time: TimeOfDay(hour: 22, minute: 5), + name: 'Initial Name 2', ); meal2.mealItems = [mealItem3];