From e57220938b64edf43ec3c5bb3b8ebe1a52f4692b Mon Sep 17 00:00:00 2001 From: Roland Geider Date: Wed, 16 Jun 2021 18:42:06 +0200 Subject: [PATCH 01/33] Start working on nutritional diary detail page --- lib/main.dart | 2 + lib/screens/nutritional_diary_screen.dart | 55 +++++++++++++++++++ .../nutrition/nutritional_diary_detail.dart | 44 +++++++++++++++ .../nutrition/nutritional_plan_detail.dart | 17 ++++-- test/nutritional_diary_test.dart | 44 +++++++++++++++ 5 files changed, 157 insertions(+), 5 deletions(-) create mode 100644 lib/screens/nutritional_diary_screen.dart create mode 100644 lib/widgets/nutrition/nutritional_diary_detail.dart create mode 100644 test/nutritional_diary_test.dart diff --git a/lib/main.dart b/lib/main.dart index 0a076ed3..89e7618b 100644 --- a/lib/main.dart +++ b/lib/main.dart @@ -30,6 +30,7 @@ import 'package:wger/screens/form_screen.dart'; import 'package:wger/screens/gallery_screen.dart'; import 'package:wger/screens/gym_mode.dart'; import 'package:wger/screens/home_tabs_screen.dart'; +import 'package:wger/screens/nutritional_diary_screen.dart'; import 'package:wger/screens/nutritional_plan_screen.dart'; import 'package:wger/screens/nutritional_plans_screen.dart'; import 'package:wger/screens/splash_screen.dart'; @@ -111,6 +112,7 @@ class MyApp extends StatelessWidget { GymModeScreen.routeName: (ctx) => GymModeScreen(), HomeTabsScreen.routeName: (ctx) => HomeTabsScreen(), NutritionScreen.routeName: (ctx) => NutritionScreen(), + NutritionalDiaryScreen.routeName: (ctx) => NutritionalDiaryScreen(), NutritionalPlanScreen.routeName: (ctx) => NutritionalPlanScreen(), WeightScreen.routeName: (ctx) => WeightScreen(), WorkoutPlanScreen.routeName: (ctx) => WorkoutPlanScreen(), diff --git a/lib/screens/nutritional_diary_screen.dart b/lib/screens/nutritional_diary_screen.dart new file mode 100644 index 00000000..34a680a8 --- /dev/null +++ b/lib/screens/nutritional_diary_screen.dart @@ -0,0 +1,55 @@ +/* + * This file is part of wger Workout Manager . + * Copyright (C) 2020, 2021 wger Team + * + * wger Workout Manager is free software: you can redistribute it and/or modify + * it under the terms of the GNU Affero General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * wger Workout Manager is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Affero General Public License for more details. + * + * You should have received a copy of the GNU Affero General Public License + * along with this program. If not, see . + */ + +import 'package:flutter/material.dart'; +import 'package:intl/intl.dart'; +import 'package:provider/provider.dart'; +import 'package:wger/models/nutrition/nutritional_plan.dart'; +import 'package:wger/providers/nutrition.dart'; +import 'package:wger/widgets/core/app_bar.dart'; +import 'package:wger/widgets/nutrition/nutritional_diary_detail.dart'; + +/// Arguments passed to the form screen +class NutritionalDiaryArguments { + /// Nutritional plan + final NutritionalPlan plan; + + /// Date to show data for + final DateTime date; + + NutritionalDiaryArguments(this.plan, this.date); +} + +class NutritionalDiaryScreen extends StatelessWidget { + static const routeName = '/nutritional-diary'; + + @override + Widget build(BuildContext context) { + final args = ModalRoute.of(context)!.settings.arguments as NutritionalDiaryArguments; + + return Scaffold( + appBar: WgerAppBar( + DateFormat.yMd(Localizations.localeOf(context).languageCode).format(args.date), + ), + body: Consumer( + builder: (context, nutritionProvider, child) => + NutritionalDiaryDetailWidget(args.plan, args.date), + ), + ); + } +} diff --git a/lib/widgets/nutrition/nutritional_diary_detail.dart b/lib/widgets/nutrition/nutritional_diary_detail.dart new file mode 100644 index 00000000..f21282fe --- /dev/null +++ b/lib/widgets/nutrition/nutritional_diary_detail.dart @@ -0,0 +1,44 @@ +/* + * This file is part of wger Workout Manager . + * Copyright (C) 2020, 2021 wger Team + * + * wger Workout Manager is free software: you can redistribute it and/or modify + * it under the terms of the GNU Affero General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * wger Workout Manager is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Affero General Public License for more details. + * + * You should have received a copy of the GNU Affero General Public License + * along with this program. If not, see . + */ + +import 'package:flutter/material.dart'; +import 'package:wger/models/nutrition/nutritional_plan.dart'; +import 'package:wger/widgets/nutrition/charts.dart'; + +class NutritionalDiaryDetailWidget extends StatelessWidget { + final NutritionalPlan _nutritionalPlan; + final DateTime _date; + NutritionalDiaryDetailWidget(this._nutritionalPlan, this._date); + + @override + Widget build(BuildContext context) { + final nutritionalValues = _nutritionalPlan.nutritionalValues; + + return Column( + children: [ + Text('Detail view for nutritional plan'), + Text('Content here'), + Container( + padding: EdgeInsets.all(15), + height: 220, + child: NutritionalPlanPieChartWidget(nutritionalValues), + ), + ], + ); + } +} diff --git a/lib/widgets/nutrition/nutritional_plan_detail.dart b/lib/widgets/nutrition/nutritional_plan_detail.dart index b0c68872..d0e72309 100644 --- a/lib/widgets/nutrition/nutritional_plan_detail.dart +++ b/lib/widgets/nutrition/nutritional_plan_detail.dart @@ -22,6 +22,7 @@ import 'package:intl/intl.dart'; import 'package:wger/models/nutrition/nutritional_plan.dart'; import 'package:wger/models/nutrition/nutritrional_values.dart'; import 'package:wger/screens/form_screen.dart'; +import 'package:wger/screens/nutritional_diary_screen.dart'; import 'package:wger/widgets/nutrition/charts.dart'; import 'package:wger/widgets/nutrition/forms.dart'; import 'package:wger/widgets/nutrition/meal.dart'; @@ -150,7 +151,7 @@ class NutritionalPlanDetailWidget extends StatelessWidget { ), ), ..._nutritionalPlan.logEntriesValues.entries - .map((entry) => NutritionDiaryEntry(entry.key, entry.value)) + .map((entry) => NutritionDiaryEntry(entry.key, entry.value, _nutritionalPlan)) .toList() .reversed, ], @@ -165,10 +166,12 @@ class NutritionalPlanDetailWidget extends StatelessWidget { class NutritionDiaryEntry extends StatelessWidget { final DateTime date; final NutritionalValues values; + final NutritionalPlan plan; NutritionDiaryEntry( this.date, this.values, + this.plan, ); @override @@ -178,10 +181,14 @@ class NutritionDiaryEntry extends StatelessWidget { child: Column( crossAxisAlignment: CrossAxisAlignment.end, children: [ - Text( - DateFormat.yMd(Localizations.localeOf(context).languageCode).format(date), - style: TextStyle(fontWeight: FontWeight.bold), - ), + TextButton( + onPressed: () => Navigator.of(context).pushNamed( + NutritionalDiaryScreen.routeName, + arguments: NutritionalDiaryArguments(plan, date), + ), + child: Text( + DateFormat.yMd(Localizations.localeOf(context).languageCode).format(date), + )), Text(values.energy.toStringAsFixed(0)), Text(values.protein.toStringAsFixed(0)), Text(values.carbohydrates.toStringAsFixed(0)), diff --git a/test/nutritional_diary_test.dart b/test/nutritional_diary_test.dart new file mode 100644 index 00000000..d4c0e71c --- /dev/null +++ b/test/nutritional_diary_test.dart @@ -0,0 +1,44 @@ +/* + * This file is part of wger Workout Manager . + * Copyright (C) 2020, 2021 wger Team + * + * wger Workout Manager is free software: you can redistribute it and/or modify + * it under the terms of the GNU Affero General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * wger Workout Manager is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Affero General Public License for more details. + * + * You should have received a copy of the GNU Affero General Public License + * along with this program. If not, see . + */ + +import 'package:flutter/material.dart'; +import 'package:flutter_gen/gen_l10n/app_localizations.dart'; +import 'package:flutter_test/flutter_test.dart'; +import 'package:wger/widgets/nutrition/charts.dart'; +import 'package:wger/widgets/nutrition/nutritional_diary_detail.dart'; + +import '../test_data/nutritional_plans.dart'; + +void main() { + Widget getWidget({locale = 'en'}) { + final plan = getNutritionalPlan(); + + return MaterialApp( + locale: Locale(locale), + localizationsDelegates: AppLocalizations.localizationsDelegates, + supportedLocales: AppLocalizations.supportedLocales, + home: NutritionalDiaryDetailWidget(plan, DateTime(2021, 6, 1)), + ); + } + + testWidgets('Test the detail view for the nutritional plan', (WidgetTester tester) async { + await tester.pumpWidget(getWidget()); + + expect(find.byType(NutritionalPlanPieChartWidget), findsOneWidget); + }); +} From 5f496bca80088806cd5960e6b569f72a1cb69872 Mon Sep 17 00:00:00 2001 From: Antoine Vibien Date: Mon, 2 Aug 2021 07:49:20 +0000 Subject: [PATCH 02/33] Translated using Weblate (French) Currently translated at 96.9% (126 of 130 strings) Translation: wger Workout Manager/Mobile App Translate-URL: https://hosted.weblate.org/projects/wger/mobile/fr/ --- lib/l10n/app_fr.arb | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/lib/l10n/app_fr.arb b/lib/l10n/app_fr.arb index 09f68236..7190915e 100644 --- a/lib/l10n/app_fr.arb +++ b/lib/l10n/app_fr.arb @@ -448,5 +448,7 @@ "usernameValidChars": "Un nom d'utilisateur ne peut contenir que des lettres, des chiffres et les caractères @, +, ., - et _", "@usernameValidChars": { "description": "Error message when the user tries to register a username with forbidden characters" - } + }, + "timeStartAhead": "L'heure de début doit être antérieure à l'heure de fin.", + "@timeStartAhead": {} } From 8ef5e8ba7725c99751246f452fd88f1adedabcb9 Mon Sep 17 00:00:00 2001 From: "J. Lavoie" Date: Sun, 8 Aug 2021 04:47:59 +0000 Subject: [PATCH 03/33] Translated using Weblate (French) Currently translated at 97.6% (127 of 130 strings) Translation: wger Workout Manager/Mobile App Translate-URL: https://hosted.weblate.org/projects/wger/mobile/fr/ --- lib/l10n/app_fr.arb | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/lib/l10n/app_fr.arb b/lib/l10n/app_fr.arb index 7190915e..e42bd3ae 100644 --- a/lib/l10n/app_fr.arb +++ b/lib/l10n/app_fr.arb @@ -450,5 +450,9 @@ "description": "Error message when the user tries to register a username with forbidden characters" }, "timeStartAhead": "L'heure de début doit être antérieure à l'heure de fin.", - "@timeStartAhead": {} + "@timeStartAhead": {}, + "supersetWith": "supersérie avec", + "@supersetWith": { + "description": "Text used between exercise cards when adding a new set. Translate as something like 'in a superset with'" + } } From d323f9eda452948093be0af024cfeb20cfd0a450 Mon Sep 17 00:00:00 2001 From: Khushbu Bora <72873874+KhushbuBora@users.noreply.github.com> Date: Tue, 10 Aug 2021 13:54:53 +0530 Subject: [PATCH 04/33] Update gym_mode.dart Added focusNode property to close the keyboard when user press on save button --- lib/widgets/workouts/gym_mode.dart | 89 +++++++++++++++++++++--------- 1 file changed, 62 insertions(+), 27 deletions(-) diff --git a/lib/widgets/workouts/gym_mode.dart b/lib/widgets/workouts/gym_mode.dart index fd05ae99..7b823f23 100644 --- a/lib/widgets/workouts/gym_mode.dart +++ b/lib/widgets/workouts/gym_mode.dart @@ -88,8 +88,8 @@ class _GymModeState extends State { for (var set in widget._workoutDay.sets) { var firstPage = true; for (var setting in set.settingsComputed) { - final exercise = - Provider.of(context, listen: false).findById(setting.exerciseId); + final exercise = Provider.of(context, listen: false) + .findById(setting.exerciseId); if (firstPage) { _exercisePages[exercise.name] = currentPage; @@ -108,8 +108,10 @@ class _GymModeState extends State { // Returns the list of exercise overview, sets and pause pages List getContent() { - final exerciseProvider = Provider.of(context, listen: false); - final workoutProvider = Provider.of(context, listen: false); + final exerciseProvider = + Provider.of(context, listen: false); + final workoutProvider = + Provider.of(context, listen: false); var currentElement = 1; List out = []; @@ -200,7 +202,10 @@ class StartPage extends StatelessWidget { s.exerciseObj.name, style: Theme.of(context).textTheme.headline6, ), - ...set.getSmartRepr(s.exerciseObj).map((e) => Text(e)).toList(), + ...set + .getSmartRepr(s.exerciseObj) + .map((e) => Text(e)) + .toList(), SizedBox(height: 15), ], ); @@ -215,7 +220,8 @@ class StartPage extends StatelessWidget { ElevatedButton( child: Text(AppLocalizations.of(context).start), onPressed: () { - _controller.nextPage(duration: Duration(milliseconds: 200), curve: Curves.bounceIn); + _controller.nextPage( + duration: Duration(milliseconds: 200), curve: Curves.bounceIn); }, ), NavigationFooter( @@ -266,6 +272,8 @@ class _LogPageState extends State { final _weightController = TextEditingController(); var _detailed = false; + FocusNode focusNode = new FocusNode(); + @override void initState() { super.initState(); @@ -304,9 +312,11 @@ class _LogPageState extends State { enabled: true, controller: _repsController, keyboardType: TextInputType.number, + focusNode: focusNode, onFieldSubmitted: (_) {}, onSaved: (newValue) { widget._log.reps = int.parse(newValue!); + focusNode.unfocus(); }, validator: (value) { try { @@ -345,7 +355,8 @@ class _LogPageState extends State { ), onPressed: () { try { - double newValue = double.parse(_weightController.text) - (2 * minPlateWeight); + double newValue = + double.parse(_weightController.text) - (2 * minPlateWeight); if (newValue > 0) { setState(() { widget._log.weight = newValue; @@ -393,7 +404,8 @@ class _LogPageState extends State { ), onPressed: () { try { - double newValue = double.parse(_weightController.text) + (2 * minPlateWeight); + double newValue = + double.parse(_weightController.text) + (2 * minPlateWeight); setState(() { widget._log.weight = newValue; _weightController.text = newValue.toString(); @@ -464,7 +476,8 @@ class _LogPageState extends State { // Save the entry on the server try { - await Provider.of(context, listen: false).addLog(widget._log); + await Provider.of(context, listen: false) + .addLog(widget._log); ScaffoldMessenger.of(context).showSnackBar( SnackBar( duration: Duration(seconds: 2), // default is 4 @@ -498,11 +511,14 @@ class _LogPageState extends State { style: Theme.of(context).textTheme.headline6, textAlign: TextAlign.center, ), - ...widget._workoutPlan.filterLogsByExercise(widget._exercise, unique: true).map((log) { + ...widget._workoutPlan + .filterLogsByExercise(widget._exercise, unique: true) + .map((log) { return ListTile( title: Text(log.singleLogRepTextNoNl), - subtitle: - Text(DateFormat.yMd(Localizations.localeOf(context).languageCode).format(log.date)), + subtitle: Text( + DateFormat.yMd(Localizations.localeOf(context).languageCode) + .format(log.date)), trailing: Icon(Icons.copy), onTap: () { setState(() { @@ -555,7 +571,8 @@ class _LogPageState extends State { shape: BoxShape.circle, ), child: Padding( - padding: const EdgeInsets.symmetric(horizontal: 3), + padding: + const EdgeInsets.symmetric(horizontal: 3), child: SizedBox( height: 35, width: 35, @@ -563,7 +580,8 @@ class _LogPageState extends State { alignment: Alignment.center, child: Text( key.toString(), - style: TextStyle(fontWeight: FontWeight.bold), + style: TextStyle( + fontWeight: FontWeight.bold), ), ), ), @@ -576,7 +594,8 @@ class _LogPageState extends State { .toList() ], ) - : MutedText(AppLocalizations.of(context).plateCalculatorNotDivisible), + : MutedText( + AppLocalizations.of(context).plateCalculatorNotDivisible), ), SizedBox(height: 3), ], @@ -606,11 +625,16 @@ class _LogPageState extends State { ), SizedBox(height: 10), Expanded( - child: (widget._workoutPlan.filterLogsByExercise(widget._exercise).length > 0) + child: (widget._workoutPlan + .filterLogsByExercise(widget._exercise) + .length > + 0) ? getPastLogs() : Container()), // Only show calculator for barbell - if (widget._log.exerciseObj.equipment.map((e) => e.id).contains(ID_EQUIPMENT_BARBELL)) + if (widget._log.exerciseObj.equipment + .map((e) => e.id) + .contains(ID_EQUIPMENT_BARBELL)) getPlates(), Padding( padding: const EdgeInsets.symmetric(horizontal: 15), @@ -671,7 +695,9 @@ class ExerciseOverview extends StatelessWidget { child: ListView( scrollDirection: Axis.horizontal, children: [ - ..._exercise.images.map((e) => ExerciseImageWidget(image: e)).toList(), + ..._exercise.images + .map((e) => ExerciseImageWidget(image: e)) + .toList(), ], ), ), @@ -797,7 +823,8 @@ class _SessionPageState extends State { onFieldSubmitted: (_) {}, onTap: () async { // Stop keyboard from appearing - FocusScope.of(context).requestFocus(new FocusNode()); + FocusScope.of(context) + .requestFocus(new FocusNode()); // Open time picker var pickedTime = await showTimePicker( @@ -806,17 +833,21 @@ class _SessionPageState extends State { ); if (pickedTime != null) { - timeStartController.text = timeToString(pickedTime)!; + timeStartController.text = + timeToString(pickedTime)!; } }, onSaved: (newValue) { _session.timeStart = stringToTime(newValue); }, validator: (_) { - TimeOfDay startTime = stringToTime(timeStartController.text); - TimeOfDay endTime = stringToTime(timeEndController.text); + TimeOfDay startTime = + stringToTime(timeStartController.text); + TimeOfDay endTime = + stringToTime(timeEndController.text); if (startTime.isAfter(endTime)) { - return AppLocalizations.of(context).timeStartAhead; + return AppLocalizations.of(context) + .timeStartAhead; } return null; }), @@ -824,8 +855,8 @@ class _SessionPageState extends State { SizedBox(width: 10), Flexible( child: TextFormField( - decoration: - InputDecoration(labelText: AppLocalizations.of(context).timeEnd), + decoration: InputDecoration( + labelText: AppLocalizations.of(context).timeEnd), controller: timeEndController, onFieldSubmitted: (_) {}, onTap: () async { @@ -859,7 +890,8 @@ class _SessionPageState extends State { // Save the entry on the server try { - await Provider.of(context, listen: false) + await Provider.of(context, + listen: false) .addSession(_session); Navigator.of(context).pop(); } on WgerHttpException catch (error) { @@ -952,7 +984,10 @@ class _TimerWidgetState extends State { child: Center( child: Text( DateFormat('m:ss').format(today.add(Duration(seconds: _seconds))), - style: Theme.of(context).textTheme.headline1!.copyWith(color: wgerPrimaryColor), + style: Theme.of(context) + .textTheme + .headline1! + .copyWith(color: wgerPrimaryColor), ), ), ), From dc701358d1e012f19f2db5e6868b6495b550e880 Mon Sep 17 00:00:00 2001 From: Khushbu Bora <72873874+KhushbuBora@users.noreply.github.com> Date: Wed, 11 Aug 2021 13:59:59 +0530 Subject: [PATCH 05/33] Update gym_mode.dart --- lib/widgets/workouts/gym_mode.dart | 92 +++++++++--------------------- 1 file changed, 27 insertions(+), 65 deletions(-) diff --git a/lib/widgets/workouts/gym_mode.dart b/lib/widgets/workouts/gym_mode.dart index 7b823f23..0d19bd03 100644 --- a/lib/widgets/workouts/gym_mode.dart +++ b/lib/widgets/workouts/gym_mode.dart @@ -15,7 +15,6 @@ * You should have received a copy of the GNU Affero General Public License * along with this program. If not, see . */ - import 'dart:async'; import 'package:flutter/material.dart'; @@ -46,11 +45,9 @@ import 'package:wger/widgets/workouts/forms.dart'; class GymMode extends StatefulWidget { final Day _workoutDay; late TimeOfDay _start; - GymMode(this._workoutDay) { _start = TimeOfDay.now(); } - @override _GymModeState createState() => _GymModeState(); } @@ -60,11 +57,9 @@ class _GymModeState extends State { /// Map with the first (navigation) page for each exercise Map _exercisePages = new Map(); - PageController _controller = PageController( initialPage: 0, ); - @override void dispose() { _controller.dispose(); @@ -74,12 +69,10 @@ class _GymModeState extends State { @override void initState() { super.initState(); - // Calculate amount of elements for progress indicator for (var set in widget._workoutDay.sets) { _totalElements = _totalElements + set.settingsComputed.length; } - // Calculate the pages for the navigation // // This duplicates the code below in the getContent method, but it seems to @@ -88,8 +81,8 @@ class _GymModeState extends State { for (var set in widget._workoutDay.sets) { var firstPage = true; for (var setting in set.settingsComputed) { - final exercise = Provider.of(context, listen: false) - .findById(setting.exerciseId); + final exercise = + Provider.of(context, listen: false).findById(setting.exerciseId); if (firstPage) { _exercisePages[exercise.name] = currentPage; @@ -108,10 +101,8 @@ class _GymModeState extends State { // Returns the list of exercise overview, sets and pause pages List getContent() { - final exerciseProvider = - Provider.of(context, listen: false); - final workoutProvider = - Provider.of(context, listen: false); + final exerciseProvider = Provider.of(context, listen: false); + final workoutProvider = Provider.of(context, listen: false); var currentElement = 1; List out = []; @@ -202,10 +193,7 @@ class StartPage extends StatelessWidget { s.exerciseObj.name, style: Theme.of(context).textTheme.headline6, ), - ...set - .getSmartRepr(s.exerciseObj) - .map((e) => Text(e)) - .toList(), + ...set.getSmartRepr(s.exerciseObj).map((e) => Text(e)).toList(), SizedBox(height: 15), ], ); @@ -220,8 +208,7 @@ class StartPage extends StatelessWidget { ElevatedButton( child: Text(AppLocalizations.of(context).start), onPressed: () { - _controller.nextPage( - duration: Duration(milliseconds: 200), curve: Curves.bounceIn); + _controller.nextPage(duration: Duration(milliseconds: 200), curve: Curves.bounceIn); }, ), NavigationFooter( @@ -355,8 +342,7 @@ class _LogPageState extends State { ), onPressed: () { try { - double newValue = - double.parse(_weightController.text) - (2 * minPlateWeight); + double newValue = double.parse(_weightController.text) - (2 * minPlateWeight); if (newValue > 0) { setState(() { widget._log.weight = newValue; @@ -404,8 +390,7 @@ class _LogPageState extends State { ), onPressed: () { try { - double newValue = - double.parse(_weightController.text) + (2 * minPlateWeight); + double newValue = double.parse(_weightController.text) + (2 * minPlateWeight); setState(() { widget._log.weight = newValue; _weightController.text = newValue.toString(); @@ -476,8 +461,7 @@ class _LogPageState extends State { // Save the entry on the server try { - await Provider.of(context, listen: false) - .addLog(widget._log); + await Provider.of(context, listen: false).addLog(widget._log); ScaffoldMessenger.of(context).showSnackBar( SnackBar( duration: Duration(seconds: 2), // default is 4 @@ -511,14 +495,11 @@ class _LogPageState extends State { style: Theme.of(context).textTheme.headline6, textAlign: TextAlign.center, ), - ...widget._workoutPlan - .filterLogsByExercise(widget._exercise, unique: true) - .map((log) { + ...widget._workoutPlan.filterLogsByExercise(widget._exercise, unique: true).map((log) { return ListTile( title: Text(log.singleLogRepTextNoNl), - subtitle: Text( - DateFormat.yMd(Localizations.localeOf(context).languageCode) - .format(log.date)), + subtitle: + Text(DateFormat.yMd(Localizations.localeOf(context).languageCode).format(log.date)), trailing: Icon(Icons.copy), onTap: () { setState(() { @@ -571,8 +552,7 @@ class _LogPageState extends State { shape: BoxShape.circle, ), child: Padding( - padding: - const EdgeInsets.symmetric(horizontal: 3), + padding: const EdgeInsets.symmetric(horizontal: 3), child: SizedBox( height: 35, width: 35, @@ -580,8 +560,7 @@ class _LogPageState extends State { alignment: Alignment.center, child: Text( key.toString(), - style: TextStyle( - fontWeight: FontWeight.bold), + style: TextStyle(fontWeight: FontWeight.bold), ), ), ), @@ -594,8 +573,7 @@ class _LogPageState extends State { .toList() ], ) - : MutedText( - AppLocalizations.of(context).plateCalculatorNotDivisible), + : MutedText(AppLocalizations.of(context).plateCalculatorNotDivisible), ), SizedBox(height: 3), ], @@ -625,16 +603,11 @@ class _LogPageState extends State { ), SizedBox(height: 10), Expanded( - child: (widget._workoutPlan - .filterLogsByExercise(widget._exercise) - .length > - 0) + child: (widget._workoutPlan.filterLogsByExercise(widget._exercise).length > 0) ? getPastLogs() : Container()), // Only show calculator for barbell - if (widget._log.exerciseObj.equipment - .map((e) => e.id) - .contains(ID_EQUIPMENT_BARBELL)) + if (widget._log.exerciseObj.equipment.map((e) => e.id).contains(ID_EQUIPMENT_BARBELL)) getPlates(), Padding( padding: const EdgeInsets.symmetric(horizontal: 15), @@ -695,9 +668,7 @@ class ExerciseOverview extends StatelessWidget { child: ListView( scrollDirection: Axis.horizontal, children: [ - ..._exercise.images - .map((e) => ExerciseImageWidget(image: e)) - .toList(), + ..._exercise.images.map((e) => ExerciseImageWidget(image: e)).toList(), ], ), ), @@ -823,8 +794,7 @@ class _SessionPageState extends State { onFieldSubmitted: (_) {}, onTap: () async { // Stop keyboard from appearing - FocusScope.of(context) - .requestFocus(new FocusNode()); + FocusScope.of(context).requestFocus(new FocusNode()); // Open time picker var pickedTime = await showTimePicker( @@ -833,21 +803,17 @@ class _SessionPageState extends State { ); if (pickedTime != null) { - timeStartController.text = - timeToString(pickedTime)!; + timeStartController.text = timeToString(pickedTime)!; } }, onSaved: (newValue) { _session.timeStart = stringToTime(newValue); }, validator: (_) { - TimeOfDay startTime = - stringToTime(timeStartController.text); - TimeOfDay endTime = - stringToTime(timeEndController.text); + TimeOfDay startTime = stringToTime(timeStartController.text); + TimeOfDay endTime = stringToTime(timeEndController.text); if (startTime.isAfter(endTime)) { - return AppLocalizations.of(context) - .timeStartAhead; + return AppLocalizations.of(context).timeStartAhead; } return null; }), @@ -855,8 +821,8 @@ class _SessionPageState extends State { SizedBox(width: 10), Flexible( child: TextFormField( - decoration: InputDecoration( - labelText: AppLocalizations.of(context).timeEnd), + decoration: + InputDecoration(labelText: AppLocalizations.of(context).timeEnd), controller: timeEndController, onFieldSubmitted: (_) {}, onTap: () async { @@ -890,8 +856,7 @@ class _SessionPageState extends State { // Save the entry on the server try { - await Provider.of(context, - listen: false) + await Provider.of(context, listen: false) .addSession(_session); Navigator.of(context).pop(); } on WgerHttpException catch (error) { @@ -984,10 +949,7 @@ class _TimerWidgetState extends State { child: Center( child: Text( DateFormat('m:ss').format(today.add(Duration(seconds: _seconds))), - style: Theme.of(context) - .textTheme - .headline1! - .copyWith(color: wgerPrimaryColor), + style: Theme.of(context).textTheme.headline1!.copyWith(color: wgerPrimaryColor), ), ), ), From 16b2b7c10d17cdde2f672b46fd7967791a4f8d5a Mon Sep 17 00:00:00 2001 From: Khushbu Bora <72873874+KhushbuBora@users.noreply.github.com> Date: Wed, 11 Aug 2021 15:42:19 +0530 Subject: [PATCH 06/33] Update gym_mode.dart --- lib/widgets/workouts/gym_mode.dart | 10 +++++++++- 1 file changed, 9 insertions(+), 1 deletion(-) diff --git a/lib/widgets/workouts/gym_mode.dart b/lib/widgets/workouts/gym_mode.dart index 0d19bd03..660f3134 100644 --- a/lib/widgets/workouts/gym_mode.dart +++ b/lib/widgets/workouts/gym_mode.dart @@ -259,12 +259,14 @@ class _LogPageState extends State { final _weightController = TextEditingController(); var _detailed = false; - FocusNode focusNode = new FocusNode(); + late FocusNode focusNode; @override void initState() { super.initState(); + focusNode = FocusNode(); + if (widget._setting.reps != null) { _repsController.text = widget._setting.reps.toString(); } @@ -274,6 +276,12 @@ class _LogPageState extends State { } } + @override + void dispose() { + focusNode.dispose(); + super.dispose(); + } + Widget getRepsWidget() { return Row( children: [ From 51422b30531485ff1b8b490ad87841447b02925d Mon Sep 17 00:00:00 2001 From: Roland Geider Date: Sat, 24 Jul 2021 01:03:14 +0200 Subject: [PATCH 07/33] Use correct label when there are no nutritional plans --- lib/widgets/dashboard/widgets.dart | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/lib/widgets/dashboard/widgets.dart b/lib/widgets/dashboard/widgets.dart index 9d77adaf..e903ce41 100644 --- a/lib/widgets/dashboard/widgets.dart +++ b/lib/widgets/dashboard/widgets.dart @@ -156,7 +156,7 @@ class _DashboardNutritionWidgetState extends State { children: [ ListTile( title: Text( - _hasContent ? _plan!.description : AppLocalizations.of(context).labelWorkoutPlan, + _hasContent ? _plan!.description : AppLocalizations.of(context).nutritionalPlan, style: Theme.of(context).textTheme.headline4, ), subtitle: Text( From de84c24e72a3b971be8891dae707bcd3b92de1f3 Mon Sep 17 00:00:00 2001 From: Roland Geider Date: Sat, 24 Jul 2021 21:55:55 +0200 Subject: [PATCH 08/33] Don't use the canonical form endpoint This was removed on the server --- lib/providers/workout_plans.dart | 19 ++++++++----------- pubspec.lock | 12 ++++++------ 2 files changed, 14 insertions(+), 17 deletions(-) diff --git a/lib/providers/workout_plans.dart b/lib/providers/workout_plans.dart index a58084ce..796ab8a8 100644 --- a/lib/providers/workout_plans.dart +++ b/lib/providers/workout_plans.dart @@ -167,9 +167,9 @@ class WorkoutPlansProvider extends WgerBaseProvider with ChangeNotifier { /// Fetches a workout plan fully, i.e. with all corresponding child attributes Future fetchAndSetWorkoutPlanFull(int workoutId) async { - // Load a list of all settings so that we can search through it late + // Load a list of all settings so that we can search through it // - // This is a bit ugly, but makes saves sending lots of requests later on + // This is a bit ugly, but saves us sending lots of requests later on final allSettingsData = await fetch( makeUrl(_settingsUrlPath, query: {'limit': '1000'}), ); @@ -181,20 +181,17 @@ class WorkoutPlansProvider extends WgerBaseProvider with ChangeNotifier { plan = await fetchAndSetPlanSparse(workoutId); } - // Plan - final fullPlanData = await fetch( - makeUrl(_workoutPlansUrlPath, id: workoutId, objectMethod: 'canonical_representation'), - ); - // Days List days = []; - for (var dayData in fullPlanData['day_list']) { - final day = Day.fromJson(dayData['obj']); + final daysData = await fetch(makeUrl(_daysUrlPath, query: {'training': plan.id.toString()})); + for (final dayEntry in daysData['results']) { + final day = Day.fromJson(dayEntry); // Sets List sets = []; - for (var setData in dayData['set_list']) { - final workoutSet = Set.fromJson(setData['obj']); + final setData = await fetch(makeUrl(_setsUrlPath, query: {'exerciseday': day.id.toString()})); + for (final setEntry in setData['results']) { + final workoutSet = Set.fromJson(setEntry); fetchComputedSettings(workoutSet); // request! diff --git a/pubspec.lock b/pubspec.lock index 3fdc8aa5..358fc7ac 100644 --- a/pubspec.lock +++ b/pubspec.lock @@ -112,7 +112,7 @@ packages: name: camera url: "https://pub.dartlang.org" source: hosted - version: "0.8.1+4" + version: "0.8.1+5" camera_platform_interface: dependency: transitive description: @@ -189,7 +189,7 @@ packages: name: code_builder url: "https://pub.dartlang.org" source: hosted - version: "4.0.0" + version: "4.1.0" collection: dependency: "direct main" description: @@ -292,7 +292,7 @@ packages: name: flutter_keyboard_visibility url: "https://pub.dartlang.org" source: hosted - version: "5.0.2" + version: "5.0.3" flutter_keyboard_visibility_platform_interface: dependency: transitive description: @@ -433,14 +433,14 @@ packages: name: image_picker url: "https://pub.dartlang.org" source: hosted - version: "0.8.1+4" + version: "0.8.2" image_picker_for_web: dependency: transitive description: name: image_picker_for_web url: "https://pub.dartlang.org" source: hosted - version: "2.1.0" + version: "2.1.1" image_picker_platform_interface: dependency: transitive description: @@ -517,7 +517,7 @@ packages: name: mockito url: "https://pub.dartlang.org" source: hosted - version: "5.0.10" + version: "5.0.12" nested: dependency: transitive description: From 1771df3cf6948074331f43f6ecf7b757f8dbd474 Mon Sep 17 00:00:00 2001 From: Roland Geider Date: Wed, 11 Aug 2021 13:32:01 +0200 Subject: [PATCH 09/33] Update AUTHORS.md --- AUTHORS.md | 10 ++++++---- 1 file changed, 6 insertions(+), 4 deletions(-) diff --git a/AUTHORS.md b/AUTHORS.md index 0dc4c626..8e60b16a 100644 --- a/AUTHORS.md +++ b/AUTHORS.md @@ -3,10 +3,12 @@ ## Developers -* Roland Geider – https://github.com/rolandgeider -* Dylan Aird - https://github.com/Dolaned -* Jannik Norden - https://github.com/Jannik-dev -* Arun Muralidharan - https://github.com/arun-muralidharan +* Roland Geider – +* Dylan Aird - +* Jannik Norden - +* Arun Muralidharan - +* Khushbu Bora - + ## Translators From 1f30f10c0c65710341cca908baa8afee94527294 Mon Sep 17 00:00:00 2001 From: Roland Geider Date: Wed, 11 Aug 2021 19:48:04 +0200 Subject: [PATCH 10/33] Bump versions --- pubspec.lock | 68 ++++++++++++++++++++++++++-------------------------- pubspec.yaml | 20 ++++++++-------- 2 files changed, 44 insertions(+), 44 deletions(-) diff --git a/pubspec.lock b/pubspec.lock index 358fc7ac..cca494cd 100644 --- a/pubspec.lock +++ b/pubspec.lock @@ -35,7 +35,7 @@ packages: name: args url: "https://pub.dartlang.org" source: hosted - version: "2.0.0" + version: "2.2.0" async: dependency: transitive description: @@ -56,7 +56,7 @@ packages: name: build url: "https://pub.dartlang.org" source: hosted - version: "2.0.3" + version: "2.1.0" build_config: dependency: transitive description: @@ -84,14 +84,14 @@ packages: name: build_runner url: "https://pub.dartlang.org" source: hosted - version: "2.0.6" + version: "2.1.1" build_runner_core: dependency: transitive description: name: build_runner_core url: "https://pub.dartlang.org" source: hosted - version: "7.0.1" + version: "7.1.0" built_collection: dependency: transitive description: @@ -105,21 +105,21 @@ packages: name: built_value url: "https://pub.dartlang.org" source: hosted - version: "8.1.1" + version: "8.1.2" camera: dependency: "direct main" description: name: camera url: "https://pub.dartlang.org" source: hosted - version: "0.8.1+5" + version: "0.8.1+7" camera_platform_interface: dependency: transitive description: name: camera_platform_interface url: "https://pub.dartlang.org" source: hosted - version: "2.0.1" + version: "2.1.0" characters: dependency: transitive description: @@ -238,7 +238,7 @@ packages: name: dart_style url: "https://pub.dartlang.org" source: hosted - version: "2.0.2" + version: "2.0.3" fake_async: dependency: transitive description: @@ -278,14 +278,14 @@ packages: name: flutter_calendar_carousel url: "https://pub.dartlang.org" source: hosted - version: "2.0.1" + version: "2.0.3" flutter_html: dependency: "direct main" description: name: flutter_html url: "https://pub.dartlang.org" source: hosted - version: "2.1.0" + version: "2.1.1" flutter_keyboard_visibility: dependency: transitive description: @@ -313,7 +313,7 @@ packages: name: flutter_launcher_icons url: "https://pub.dartlang.org" source: hosted - version: "0.9.0" + version: "0.9.1" flutter_layout_grid: dependency: transitive description: @@ -433,14 +433,14 @@ packages: name: image_picker url: "https://pub.dartlang.org" source: hosted - version: "0.8.2" + version: "0.8.3+2" image_picker_for_web: dependency: transitive description: name: image_picker_for_web url: "https://pub.dartlang.org" source: hosted - version: "2.1.1" + version: "2.1.2" image_picker_platform_interface: dependency: transitive description: @@ -517,7 +517,7 @@ packages: name: mockito url: "https://pub.dartlang.org" source: hosted - version: "5.0.12" + version: "5.0.14" nested: dependency: transitive description: @@ -580,7 +580,7 @@ packages: name: path_provider_linux url: "https://pub.dartlang.org" source: hosted - version: "2.0.0" + version: "2.0.2" path_provider_platform_interface: dependency: transitive description: @@ -594,7 +594,7 @@ packages: name: path_provider_windows url: "https://pub.dartlang.org" source: hosted - version: "2.0.1" + version: "2.0.3" pedantic: dependency: transitive description: @@ -636,7 +636,7 @@ packages: name: process url: "https://pub.dartlang.org" source: hosted - version: "4.2.1" + version: "4.2.3" provider: dependency: "direct main" description: @@ -678,14 +678,14 @@ packages: name: shared_preferences_linux url: "https://pub.dartlang.org" source: hosted - version: "2.0.0" + version: "2.0.2" shared_preferences_macos: dependency: transitive description: name: shared_preferences_macos url: "https://pub.dartlang.org" source: hosted - version: "2.0.0" + version: "2.0.2" shared_preferences_platform_interface: dependency: transitive description: @@ -699,14 +699,14 @@ packages: name: shared_preferences_web url: "https://pub.dartlang.org" source: hosted - version: "2.0.0" + version: "2.0.1" shared_preferences_windows: dependency: transitive description: name: shared_preferences_windows url: "https://pub.dartlang.org" source: hosted - version: "2.0.0" + version: "2.0.2" shelf: dependency: transitive description: @@ -781,7 +781,7 @@ packages: name: table_calendar url: "https://pub.dartlang.org" source: hosted - version: "3.0.1" + version: "3.0.2" term_glyph: dependency: transitive description: @@ -830,14 +830,14 @@ packages: name: url_launcher_linux url: "https://pub.dartlang.org" source: hosted - version: "2.0.0" + version: "2.0.1" url_launcher_macos: dependency: transitive description: name: url_launcher_macos url: "https://pub.dartlang.org" source: hosted - version: "2.0.0" + version: "2.0.1" url_launcher_platform_interface: dependency: transitive description: @@ -851,14 +851,14 @@ packages: name: url_launcher_web url: "https://pub.dartlang.org" source: hosted - version: "2.0.1" + version: "2.0.2" url_launcher_windows: dependency: transitive description: name: url_launcher_windows url: "https://pub.dartlang.org" source: hosted - version: "2.0.0" + version: "2.0.1" vector_math: dependency: transitive description: @@ -872,7 +872,7 @@ packages: name: video_player url: "https://pub.dartlang.org" source: hosted - version: "2.1.10" + version: "2.1.12" video_player_platform_interface: dependency: transitive description: @@ -886,42 +886,42 @@ packages: name: video_player_web url: "https://pub.dartlang.org" source: hosted - version: "2.0.1" + version: "2.0.2" wakelock: dependency: transitive description: name: wakelock url: "https://pub.dartlang.org" source: hosted - version: "0.5.2" + version: "0.5.3+3" wakelock_macos: dependency: transitive description: name: wakelock_macos url: "https://pub.dartlang.org" source: hosted - version: "0.1.0+1" + version: "0.1.0+2" wakelock_platform_interface: dependency: transitive description: name: wakelock_platform_interface url: "https://pub.dartlang.org" source: hosted - version: "0.2.1+1" + version: "0.2.1+2" wakelock_web: dependency: transitive description: name: wakelock_web url: "https://pub.dartlang.org" source: hosted - version: "0.2.0+1" + version: "0.2.0+2" wakelock_windows: dependency: transitive description: name: wakelock_windows url: "https://pub.dartlang.org" source: hosted - version: "0.1.0" + version: "0.1.0+1" watcher: dependency: transitive description: @@ -942,7 +942,7 @@ packages: name: webview_flutter url: "https://pub.dartlang.org" source: hosted - version: "2.0.10" + version: "2.0.12" win32: dependency: transitive description: diff --git a/pubspec.yaml b/pubspec.yaml index af85ec6b..8cf74242 100644 --- a/pubspec.yaml +++ b/pubspec.yaml @@ -32,22 +32,22 @@ dependencies: sdk: flutter android_metadata: ^0.2.1 - camera: ^0.8.1+4 + camera: ^0.8.1+7 charts_flutter: ^0.11.0 collection: ^1.15.0-nullsafety.4 cupertino_icons: ^1.0.0 - flutter_calendar_carousel: ^2.0.1 - flutter_html: ^2.1.0 + flutter_calendar_carousel: ^2.0.3 + flutter_html: ^2.1.1 flutter_typeahead: ^3.2.0 font_awesome_flutter: ^9.1.0 http: ^0.13.2 - image_picker: ^0.8.1+4 + image_picker: ^0.8.3+2 intl: ^0.17.0 - json_annotation: ^4.0.0 + json_annotation: ^4.0.1 package_info: ^2.0.2 provider: ^5.0.0 shared_preferences: ^2.0.6 - table_calendar: ^3.0.1 + table_calendar: ^3.0.2 url_launcher: ^6.0.9 dev_dependencies: @@ -55,10 +55,10 @@ dev_dependencies: sdk: flutter #flutter_driver: # sdk: flutter - build_runner: ^2.0.6 - flutter_launcher_icons: ^0.9.0 - json_serializable: ^4.1.3 - mockito: ^5.0.10 + build_runner: ^2.1.1 + flutter_launcher_icons: ^0.9.1 + json_serializable: ^4.1.4 + mockito: ^5.0.14 network_image_mock: ^2.0.1 flutter_icons: From 3193bfd3ca991bd97f51c3c7e8ea851fc133373d Mon Sep 17 00:00:00 2001 From: Prakash Shekhar Date: Sun, 15 Aug 2021 02:48:51 -0400 Subject: [PATCH 11/33] Fixes #68 --- lib/widgets/dashboard/calendar.dart | 5 ++++- lib/widgets/workouts/workout_logs.dart | 3 +++ 2 files changed, 7 insertions(+), 1 deletion(-) diff --git a/lib/widgets/dashboard/calendar.dart b/lib/widgets/dashboard/calendar.dart index b04c2379..f69adc55 100644 --- a/lib/widgets/dashboard/calendar.dart +++ b/lib/widgets/dashboard/calendar.dart @@ -97,7 +97,7 @@ class _DashboardCalendarWidgetState extends State // Process workout sessions WorkoutPlansProvider plans = Provider.of(context, listen: false); - plans.fetchSessionData().then((entries) { + await plans.fetchSessionData().then((entries) { for (var entry in entries['results']) { final session = WorkoutSession.fromJson(entry); final date = DateFormatLists.format(session.date); @@ -134,6 +134,9 @@ class _DashboardCalendarWidgetState extends State )); } } + + // Add initial selected day to events list + _selectedEvents.value = _getEventsForDay(_selectedDay!); } @override diff --git a/lib/widgets/workouts/workout_logs.dart b/lib/widgets/workouts/workout_logs.dart index 5f4efc48..106e3786 100644 --- a/lib/widgets/workouts/workout_logs.dart +++ b/lib/widgets/workouts/workout_logs.dart @@ -142,6 +142,9 @@ class _WorkoutLogCalendarState extends State { ) ]; } + + // Add initial selected day to events list + _selectedEvents.value = _getEventsForDay(_selectedDay!); } List _getEventsForDay(DateTime day) { From d01986238fcfa4d2d4fe3cdf66348acacc54f12b Mon Sep 17 00:00:00 2001 From: Roland Geider Date: Mon, 16 Aug 2021 16:11:18 +0200 Subject: [PATCH 12/33] Show percent of energy and g per body-kg for macros --- lib/helpers/consts.dart | 9 + lib/l10n/app_en.arb | 10 + lib/models/nutrition/nutritional_plan.dart | 23 +++ lib/models/nutrition/nutritrional_values.dart | 8 + lib/providers/body_weight.dart | 7 +- .../nutrition/nutritional_plan_detail.dart | 183 +++++++++++++----- pubspec.lock | 2 +- 7 files changed, 194 insertions(+), 48 deletions(-) diff --git a/lib/helpers/consts.dart b/lib/helpers/consts.dart index 8d31d87d..81063c7f 100644 --- a/lib/helpers/consts.dart +++ b/lib/helpers/consts.dart @@ -60,3 +60,12 @@ const BAR_WEIGHT = 20; /// ID of the equipment entry for barbell const ID_EQUIPMENT_BARBELL = 1; + +/// kcal per gram of protein (approx) +const ENERGY_PROTEIN = 4; + +/// kcal per gram of carbohydrates (approx) +const ENERGY_CARBOHYDRATES = 4; + +/// kcal per gram of fat (approx) +const ENERGY_FAT = 9; diff --git a/lib/l10n/app_en.arb b/lib/l10n/app_en.arb index 1f56cfb8..8c6768bd 100644 --- a/lib/l10n/app_en.arb +++ b/lib/l10n/app_en.arb @@ -289,6 +289,16 @@ "@kcal": { "description": "Energy in a meal in kilocalories, kcal" }, + "macronutrients": "Macronutrients", + "percentEnergy": "Percent of energy", + "gPerBodyKg": "g per body kg", + "@gPerBodyKg": { + "description": "Label used for total sums of e.g. calories or similar in grams per Kg of body weight" + }, + "total": "Total", + "@total": { + "description": "Label used for total sums of e.g. calories or similar" + }, "kJ": "kJ", "@kJ": { "description": "Energy in a meal in kilo joules, kJ" diff --git a/lib/models/nutrition/nutritional_plan.dart b/lib/models/nutrition/nutritional_plan.dart index 1ab59032..7b08637d 100644 --- a/lib/models/nutrition/nutritional_plan.dart +++ b/lib/models/nutrition/nutritional_plan.dart @@ -17,6 +17,7 @@ */ import 'package:json_annotation/json_annotation.dart'; +import 'package:wger/helpers/consts.dart'; import 'package:wger/helpers/json.dart'; import 'package:wger/models/nutrition/log.dart'; import 'package:wger/models/nutrition/meal.dart'; @@ -74,6 +75,28 @@ class NutritionalPlan { return out; } + /// Calculates the percentage each macro nutrient adds to the total energy + BaseNutritionalValues energyPercentage(NutritionalValues values) { + return BaseNutritionalValues( + values.protein > 0 ? ((values.protein * ENERGY_PROTEIN * 100) / values.energy) : 0, + values.carbohydrates > 0 + ? ((values.carbohydrates * ENERGY_CARBOHYDRATES * 100) / values.energy) + : 0, + values.fat > 0 ? ((values.fat * ENERGY_FAT * 100) / values.energy) : 0, + ); + } + + /// Calculates the grams per body-kg for each macro nutrient + BaseNutritionalValues gPerBodyKg(num weight, NutritionalValues values) { + assert(weight > 0); + + return BaseNutritionalValues( + values.protein / weight, + values.carbohydrates / weight, + values.fat / weight, + ); + } + Map get logEntriesValues { var out = {}; for (var log in logs) { diff --git a/lib/models/nutrition/nutritrional_values.dart b/lib/models/nutrition/nutritrional_values.dart index a64b16fb..58aca5c4 100644 --- a/lib/models/nutrition/nutritrional_values.dart +++ b/lib/models/nutrition/nutritrional_values.dart @@ -88,3 +88,11 @@ class NutritionalValues { return 'e: $energy, p: $protein, c: $carbohydrates, cS: $carbohydratesSugar, f: $fat, fS: $fatSaturated, fi: $fibres, s: $sodium'; } } + +class BaseNutritionalValues { + double protein = 0; + double carbohydrates = 0; + double fat = 0; + + BaseNutritionalValues(this.protein, this.carbohydrates, this.fat); +} diff --git a/lib/providers/body_weight.dart b/lib/providers/body_weight.dart index 5d6461f3..d6e65aa3 100644 --- a/lib/providers/body_weight.dart +++ b/lib/providers/body_weight.dart @@ -40,6 +40,11 @@ class BodyWeightProvider extends WgerBaseProvider with ChangeNotifier { _entries = []; } + /// Returns the latest (newest) weight entry or null if there are no entries + WeightEntry? getLastEntry() { + return this._entries.length > 0 ? this._entries.last : null; + } + WeightEntry findById(int id) { return _entries.firstWhere((plan) => plan.id == id); } @@ -47,7 +52,7 @@ class BodyWeightProvider extends WgerBaseProvider with ChangeNotifier { WeightEntry? findByDate(DateTime date) { try { return _entries.firstWhere((plan) => plan.date == date); - } on StateError catch (e) { + } on StateError { return null; } } diff --git a/lib/widgets/nutrition/nutritional_plan_detail.dart b/lib/widgets/nutrition/nutritional_plan_detail.dart index b0c68872..6c104748 100644 --- a/lib/widgets/nutrition/nutritional_plan_detail.dart +++ b/lib/widgets/nutrition/nutritional_plan_detail.dart @@ -19,9 +19,12 @@ import 'package:flutter/material.dart'; import 'package:flutter_gen/gen_l10n/app_localizations.dart'; import 'package:intl/intl.dart'; +import 'package:provider/provider.dart'; import 'package:wger/models/nutrition/nutritional_plan.dart'; import 'package:wger/models/nutrition/nutritrional_values.dart'; +import 'package:wger/providers/body_weight.dart'; import 'package:wger/screens/form_screen.dart'; +import 'package:wger/theme/theme.dart'; import 'package:wger/widgets/nutrition/charts.dart'; import 'package:wger/widgets/nutrition/forms.dart'; import 'package:wger/widgets/nutrition/meal.dart'; @@ -29,10 +32,16 @@ import 'package:wger/widgets/nutrition/meal.dart'; class NutritionalPlanDetailWidget extends StatelessWidget { final NutritionalPlan _nutritionalPlan; NutritionalPlanDetailWidget(this._nutritionalPlan); + static const double tablePadding = 7; @override Widget build(BuildContext context) { final nutritionalValues = _nutritionalPlan.nutritionalValues; + final valuesPercentage = _nutritionalPlan.energyPercentage(nutritionalValues); + final lastWeightEntry = Provider.of(context, listen: false).getLastEntry(); + final valuesGperKg = lastWeightEntry != null + ? _nutritionalPlan.gPerBodyKg(lastWeightEntry.weight, nutritionalValues) + : null; return SliverList( delegate: SliverChildListDelegate( @@ -60,54 +69,135 @@ class NutritionalPlanDetailWidget extends StatelessWidget { height: 220, child: NutritionalPlanPieChartWidget(nutritionalValues), ), - Container( - width: double.infinity, - child: Row( - mainAxisAlignment: MainAxisAlignment.center, + Padding( + padding: const EdgeInsets.symmetric(horizontal: 10), + child: Table( + defaultVerticalAlignment: TableCellVerticalAlignment.middle, + border: TableBorder( + horizontalInside: BorderSide(width: 1, color: wgerTextMuted), + ), + columnWidths: {0: FractionColumnWidth(0.4)}, children: [ - Padding( - padding: const EdgeInsets.only(right: 10), - child: Column( - crossAxisAlignment: CrossAxisAlignment.end, - children: [ - Text(AppLocalizations.of(context).energy), - Text(AppLocalizations.of(context).protein), - Text(AppLocalizations.of(context).carbohydrates), - Text(AppLocalizations.of(context).sugars), - Text(AppLocalizations.of(context).fat), - Text(AppLocalizations.of(context).saturatedFat), - Text(AppLocalizations.of(context).fibres), - Text(AppLocalizations.of(context).sodium), - ], - ), - ), - Padding( - padding: const EdgeInsets.only(right: 3), - child: Column( - crossAxisAlignment: CrossAxisAlignment.end, - children: [ - Text(nutritionalValues.energy.toStringAsFixed(0)), - Text(nutritionalValues.protein.toStringAsFixed(0)), - Text(nutritionalValues.carbohydrates.toStringAsFixed(0)), - Text(nutritionalValues.carbohydratesSugar.toStringAsFixed(0)), - Text(nutritionalValues.fat.toStringAsFixed(0)), - Text(nutritionalValues.fatSaturated.toStringAsFixed(0)), - Text(nutritionalValues.fibres.toStringAsFixed(0)), - Text(nutritionalValues.sodium.toStringAsFixed(0)), - ], - ), - ), - Column( - crossAxisAlignment: CrossAxisAlignment.start, + TableRow( children: [ - Text(AppLocalizations.of(context).kcal), - Text(AppLocalizations.of(context).g), - Text(AppLocalizations.of(context).g), - Text(AppLocalizations.of(context).g), - Text(AppLocalizations.of(context).g), - Text(AppLocalizations.of(context).g), - Text(AppLocalizations.of(context).g), - Text(AppLocalizations.of(context).g), + Padding( + padding: const EdgeInsets.symmetric(vertical: tablePadding), + child: Text( + AppLocalizations.of(context).macronutrients, + style: TextStyle(fontWeight: FontWeight.bold), + ), + ), + Text( + AppLocalizations.of(context).total, + style: TextStyle(fontWeight: FontWeight.bold), + ), + Text( + AppLocalizations.of(context).percentEnergy, + style: TextStyle(fontWeight: FontWeight.bold), + ), + Text( + AppLocalizations.of(context).gPerBodyKg, + style: TextStyle(fontWeight: FontWeight.bold), + ), + ], + ), + TableRow( + children: [ + Padding( + padding: const EdgeInsets.symmetric(vertical: tablePadding), + child: Text(AppLocalizations.of(context).energy), + ), + Text( + nutritionalValues.energy.toStringAsFixed(0) + + AppLocalizations.of(context).kcal, + ), + Text(''), + Text(''), + ], + ), + TableRow( + children: [ + Padding( + padding: const EdgeInsets.symmetric(vertical: tablePadding), + child: Text(AppLocalizations.of(context).protein), + ), + Text(nutritionalValues.protein.toStringAsFixed(0) + + AppLocalizations.of(context).g), + Text(valuesPercentage.protein.toStringAsFixed(1)), + valuesGperKg != null ? Text(valuesGperKg.protein.toStringAsFixed(1)) : Text(''), + ], + ), + TableRow( + children: [ + Padding( + padding: const EdgeInsets.symmetric(vertical: tablePadding), + child: Text(AppLocalizations.of(context).carbohydrates), + ), + Text(nutritionalValues.carbohydrates.toStringAsFixed(0) + + AppLocalizations.of(context).g), + Text(valuesPercentage.carbohydrates.toStringAsFixed(1)), + valuesGperKg != null + ? Text(valuesGperKg.carbohydrates.toStringAsFixed(1)) + : Text(''), + ], + ), + TableRow( + children: [ + Padding( + padding: const EdgeInsets.symmetric(vertical: tablePadding, horizontal: 12), + child: Text(AppLocalizations.of(context).sugars), + ), + Text(nutritionalValues.carbohydratesSugar.toStringAsFixed(0) + + AppLocalizations.of(context).g), + Text(''), + Text(''), + ], + ), + TableRow( + children: [ + Padding( + padding: const EdgeInsets.symmetric(vertical: tablePadding), + child: Text(AppLocalizations.of(context).fat), + ), + Text(nutritionalValues.fat.toStringAsFixed(0) + AppLocalizations.of(context).g), + Text(valuesPercentage.fat.toStringAsFixed(1)), + valuesGperKg != null ? Text(valuesGperKg.fat.toStringAsFixed(1)) : Text(''), + ], + ), + TableRow( + children: [ + Padding( + padding: const EdgeInsets.symmetric(vertical: tablePadding, horizontal: 12), + child: Text(AppLocalizations.of(context).saturatedFat), + ), + Text(nutritionalValues.fatSaturated.toStringAsFixed(0) + + AppLocalizations.of(context).g), + Text(''), + Text(''), + ], + ), + TableRow( + children: [ + Padding( + padding: const EdgeInsets.symmetric(vertical: tablePadding), + child: Text(AppLocalizations.of(context).fibres), + ), + Text(nutritionalValues.fibres.toStringAsFixed(0) + + AppLocalizations.of(context).g), + Text(''), + Text(''), + ], + ), + TableRow( + children: [ + Padding( + padding: const EdgeInsets.symmetric(vertical: tablePadding), + child: Text(AppLocalizations.of(context).sodium), + ), + Text(nutritionalValues.sodium.toStringAsFixed(0) + + AppLocalizations.of(context).g), + Text(''), + Text(''), ], ), ], @@ -116,6 +206,7 @@ class NutritionalPlanDetailWidget extends StatelessWidget { Padding(padding: const EdgeInsets.all(8.0)), Text( AppLocalizations.of(context).nutritionalDiary, + textAlign: TextAlign.center, style: Theme.of(context).textTheme.headline6, ), Container( diff --git a/pubspec.lock b/pubspec.lock index cca494cd..7032070e 100644 --- a/pubspec.lock +++ b/pubspec.lock @@ -872,7 +872,7 @@ packages: name: video_player url: "https://pub.dartlang.org" source: hosted - version: "2.1.12" + version: "2.1.13" video_player_platform_interface: dependency: transitive description: From a1660747f8bd4418be3c5a6feb7189dc306cf63a Mon Sep 17 00:00:00 2001 From: Roland Geider Date: Mon, 16 Aug 2021 16:43:39 +0200 Subject: [PATCH 13/33] Simplify widgets --- lib/widgets/nutrition/nutritional_plan_detail.dart | 8 +++----- 1 file changed, 3 insertions(+), 5 deletions(-) diff --git a/lib/widgets/nutrition/nutritional_plan_detail.dart b/lib/widgets/nutrition/nutritional_plan_detail.dart index 6c104748..1fda445d 100644 --- a/lib/widgets/nutrition/nutritional_plan_detail.dart +++ b/lib/widgets/nutrition/nutritional_plan_detail.dart @@ -124,7 +124,7 @@ class NutritionalPlanDetailWidget extends StatelessWidget { Text(nutritionalValues.protein.toStringAsFixed(0) + AppLocalizations.of(context).g), Text(valuesPercentage.protein.toStringAsFixed(1)), - valuesGperKg != null ? Text(valuesGperKg.protein.toStringAsFixed(1)) : Text(''), + Text(valuesGperKg != null ? valuesGperKg.protein.toStringAsFixed(1) : ''), ], ), TableRow( @@ -136,9 +136,7 @@ class NutritionalPlanDetailWidget extends StatelessWidget { Text(nutritionalValues.carbohydrates.toStringAsFixed(0) + AppLocalizations.of(context).g), Text(valuesPercentage.carbohydrates.toStringAsFixed(1)), - valuesGperKg != null - ? Text(valuesGperKg.carbohydrates.toStringAsFixed(1)) - : Text(''), + Text(valuesGperKg != null ? valuesGperKg.carbohydrates.toStringAsFixed(1) : ''), ], ), TableRow( @@ -161,7 +159,7 @@ class NutritionalPlanDetailWidget extends StatelessWidget { ), Text(nutritionalValues.fat.toStringAsFixed(0) + AppLocalizations.of(context).g), Text(valuesPercentage.fat.toStringAsFixed(1)), - valuesGperKg != null ? Text(valuesGperKg.fat.toStringAsFixed(1)) : Text(''), + Text(valuesGperKg != null ? valuesGperKg.fat.toStringAsFixed(1) : ''), ], ), TableRow( From c00a755f0d627a1ebb534cd31fd4dc2cbefecd69 Mon Sep 17 00:00:00 2001 From: Roland Geider Date: Mon, 16 Aug 2021 18:05:12 +0200 Subject: [PATCH 14/33] Reset the plan's logs when loading new ones --- lib/providers/nutrition.dart | 1 + 1 file changed, 1 insertion(+) diff --git a/lib/providers/nutrition.dart b/lib/providers/nutrition.dart index e14950db..8046efa8 100644 --- a/lib/providers/nutrition.dart +++ b/lib/providers/nutrition.dart @@ -346,6 +346,7 @@ class NutritionPlansProvider extends WgerBaseProvider with ChangeNotifier { makeUrl(_nutritionDiaryPath, query: {'plan': plan.id.toString(), 'limit': '1000'}), ); + plan.logs = []; for (var logData in data['results']) { var log = Log.fromJson(logData); final ingredient = await fetchIngredient(log.ingredientId); From 1222a70e61dcd4c3745e92199471562bf7c27857 Mon Sep 17 00:00:00 2001 From: Roland Geider Date: Mon, 16 Aug 2021 18:29:38 +0200 Subject: [PATCH 15/33] Add content to the diary detail page --- lib/l10n/app_en.arb | 9 + lib/models/nutrition/nutritional_plan.dart | 23 ++ lib/screens/nutritional_diary_screen.dart | 8 +- .../nutrition/nutritional_diary_detail.dart | 215 +++++++++++++++++- .../nutrition/nutritional_plan_detail.dart | 2 +- 5 files changed, 252 insertions(+), 5 deletions(-) diff --git a/lib/l10n/app_en.arb b/lib/l10n/app_en.arb index 8c6768bd..08482872 100644 --- a/lib/l10n/app_en.arb +++ b/lib/l10n/app_en.arb @@ -290,6 +290,15 @@ "description": "Energy in a meal in kilocalories, kcal" }, "macronutrients": "Macronutrients", + "planned": "Planned", + "@planned": { + "description": "Header for the column of 'planned' nutritional values, i.e. what should be eaten" + }, + "logged": "Logged", + "@logged": { + "description": "Header for the column of 'logged' nutritional values, i.e. what was eaten" + }, + "difference": "Difference", "percentEnergy": "Percent of energy", "gPerBodyKg": "g per body kg", "@gPerBodyKg": { diff --git a/lib/models/nutrition/nutritional_plan.dart b/lib/models/nutrition/nutritional_plan.dart index 7b08637d..810db753 100644 --- a/lib/models/nutrition/nutritional_plan.dart +++ b/lib/models/nutrition/nutritional_plan.dart @@ -111,4 +111,27 @@ class NutritionalPlan { return out; } + + /// Returns the nutritional values for the given date + NutritionalValues? getValuesForDate(DateTime date) { + final values = this.logEntriesValues; + final dateKey = DateTime(date.year, date.month, date.day); + + return values.containsKey(dateKey) ? values[dateKey] : null; + } + + /// Returns the nutritional logs for the given date + List getLogsForDate(DateTime date) { + List out = []; + for (var log in logs) { + final dateKey = DateTime(date.year, date.month, date.day); + final logKey = DateTime(log.datetime.year, log.datetime.month, log.datetime.day); + + if (dateKey == logKey) { + out.add(log); + } + } + + return out; + } } diff --git a/lib/screens/nutritional_diary_screen.dart b/lib/screens/nutritional_diary_screen.dart index 34a680a8..475436fd 100644 --- a/lib/screens/nutritional_diary_screen.dart +++ b/lib/screens/nutritional_diary_screen.dart @@ -47,8 +47,12 @@ class NutritionalDiaryScreen extends StatelessWidget { DateFormat.yMd(Localizations.localeOf(context).languageCode).format(args.date), ), body: Consumer( - builder: (context, nutritionProvider, child) => - NutritionalDiaryDetailWidget(args.plan, args.date), + builder: (context, nutritionProvider, child) => SingleChildScrollView( + child: Padding( + padding: const EdgeInsets.all(8.0), + child: NutritionalDiaryDetailWidget(args.plan, args.date), + ), + ), ), ); } diff --git a/lib/widgets/nutrition/nutritional_diary_detail.dart b/lib/widgets/nutrition/nutritional_diary_detail.dart index f21282fe..6bed8869 100644 --- a/lib/widgets/nutrition/nutritional_diary_detail.dart +++ b/lib/widgets/nutrition/nutritional_diary_detail.dart @@ -17,17 +17,220 @@ */ import 'package:flutter/material.dart'; +import 'package:flutter_gen/gen_l10n/app_localizations.dart'; +import 'package:intl/intl.dart'; +import 'package:wger/models/nutrition/log.dart'; import 'package:wger/models/nutrition/nutritional_plan.dart'; +import 'package:wger/models/nutrition/nutritrional_values.dart'; +import 'package:wger/theme/theme.dart'; +import 'package:wger/widgets/core/core.dart'; import 'package:wger/widgets/nutrition/charts.dart'; class NutritionalDiaryDetailWidget extends StatelessWidget { final NutritionalPlan _nutritionalPlan; final DateTime _date; + static const double tablePadding = 7; NutritionalDiaryDetailWidget(this._nutritionalPlan, this._date); + Widget getTable( + NutritionalValues valuesTotal, + NutritionalValues valuesDate, + BuildContext context, + ) { + return Table( + defaultVerticalAlignment: TableCellVerticalAlignment.middle, + border: TableBorder( + horizontalInside: BorderSide(width: 1, color: wgerTextMuted), + ), + columnWidths: {0: FractionColumnWidth(0.4)}, + children: [ + TableRow( + children: [ + Padding( + padding: const EdgeInsets.symmetric(vertical: tablePadding), + child: Text( + AppLocalizations.of(context).macronutrients, + style: TextStyle(fontWeight: FontWeight.bold), + ), + ), + Text( + AppLocalizations.of(context).planned, + style: TextStyle(fontWeight: FontWeight.bold), + ), + Text( + AppLocalizations.of(context).logged, + style: TextStyle(fontWeight: FontWeight.bold), + ), + Text( + AppLocalizations.of(context).difference, + style: TextStyle(fontWeight: FontWeight.bold), + ), + ], + ), + TableRow( + children: [ + Padding( + padding: const EdgeInsets.symmetric(vertical: tablePadding), + child: Text(AppLocalizations.of(context).energy), + ), + Text( + valuesTotal.energy.toStringAsFixed(0) + AppLocalizations.of(context).kcal, + ), + Text( + valuesDate.energy.toStringAsFixed(0) + AppLocalizations.of(context).kcal, + ), + Text((valuesDate.energy - valuesTotal.energy).toStringAsFixed(0)), + ], + ), + TableRow( + children: [ + Padding( + padding: const EdgeInsets.symmetric(vertical: tablePadding), + child: Text(AppLocalizations.of(context).protein), + ), + Text(valuesTotal.protein.toStringAsFixed(0) + AppLocalizations.of(context).g), + Text(valuesDate.protein.toStringAsFixed(0) + AppLocalizations.of(context).g), + Text((valuesDate.protein - valuesTotal.protein).toStringAsFixed(0)), + ], + ), + TableRow( + children: [ + Padding( + padding: const EdgeInsets.symmetric(vertical: tablePadding), + child: Text(AppLocalizations.of(context).carbohydrates), + ), + Text(valuesTotal.carbohydrates.toStringAsFixed(0) + AppLocalizations.of(context).g), + Text(valuesDate.carbohydrates.toStringAsFixed(0) + AppLocalizations.of(context).g), + Text((valuesDate.carbohydrates - valuesTotal.carbohydrates).toStringAsFixed(0)), + ], + ), + TableRow( + children: [ + Padding( + padding: const EdgeInsets.symmetric(vertical: tablePadding, horizontal: 12), + child: Text(AppLocalizations.of(context).sugars), + ), + Text( + valuesTotal.carbohydratesSugar.toStringAsFixed(0) + AppLocalizations.of(context).g), + Text(valuesDate.carbohydratesSugar.toStringAsFixed(0) + AppLocalizations.of(context).g), + Text((valuesDate.carbohydratesSugar - valuesTotal.carbohydratesSugar) + .toStringAsFixed(0)), + ], + ), + TableRow( + children: [ + Padding( + padding: const EdgeInsets.symmetric(vertical: tablePadding), + child: Text(AppLocalizations.of(context).fat), + ), + Text(valuesTotal.fat.toStringAsFixed(0) + AppLocalizations.of(context).g), + Text(valuesDate.fat.toStringAsFixed(0) + AppLocalizations.of(context).g), + Text((valuesDate.fat - valuesTotal.fat).toStringAsFixed(0)), + ], + ), + TableRow( + children: [ + Padding( + padding: const EdgeInsets.symmetric(vertical: tablePadding, horizontal: 12), + child: Text(AppLocalizations.of(context).saturatedFat), + ), + Text(valuesTotal.fatSaturated.toStringAsFixed(0) + AppLocalizations.of(context).g), + Text(valuesDate.fatSaturated.toStringAsFixed(0) + AppLocalizations.of(context).g), + Text((valuesDate.fatSaturated - valuesTotal.fatSaturated).toStringAsFixed(0)), + ], + ), + TableRow( + children: [ + Padding( + padding: const EdgeInsets.symmetric(vertical: tablePadding), + child: Text(AppLocalizations.of(context).fibres), + ), + Text(valuesTotal.fibres.toStringAsFixed(0) + AppLocalizations.of(context).g), + Text(valuesDate.fibres.toStringAsFixed(0) + AppLocalizations.of(context).g), + Text((valuesDate.fibres - valuesTotal.fibres).toStringAsFixed(0)), + ], + ), + TableRow( + children: [ + Padding( + padding: const EdgeInsets.symmetric(vertical: tablePadding), + child: Text(AppLocalizations.of(context).sodium), + ), + Text(valuesTotal.sodium.toStringAsFixed(0) + AppLocalizations.of(context).g), + Text(valuesDate.sodium.toStringAsFixed(0) + AppLocalizations.of(context).g), + Text((valuesDate.sodium - valuesTotal.sodium).toStringAsFixed(0)), + ], + ), + ], + ); + } + + List getEntriesTable(List logs, BuildContext context) { + return logs.map((log) { + final values = log.nutritionalValues; + + return Row( + crossAxisAlignment: CrossAxisAlignment.start, + mainAxisAlignment: MainAxisAlignment.start, + children: [ + Text( + DateFormat.Hm(Localizations.localeOf(context).languageCode).format(log.datetime), + style: TextStyle(fontWeight: FontWeight.bold), + ), + SizedBox(width: 8), + Expanded( + child: Column( + crossAxisAlignment: CrossAxisAlignment.start, + children: [ + Text( + log.amount.toStringAsFixed(0) + + AppLocalizations.of(context).g + + ' ' + + log.ingredientObj.name, + overflow: TextOverflow.ellipsis, + ), + SizedBox(height: 4), + MutedText( + '${AppLocalizations.of(context).energy}: ' + '${values.energy.toStringAsFixed(0)}' + '${AppLocalizations.of(context).kcal}', + ), + MutedText( + '${AppLocalizations.of(context).protein}: ' + '${values.protein.toStringAsFixed(0)}' + '${AppLocalizations.of(context).g}', + ), + MutedText( + '${AppLocalizations.of(context).carbohydrates}: ' + '${values.carbohydrates.toStringAsFixed(0)} ' + '${AppLocalizations.of(context).g} ' + '(${values.carbohydratesSugar.toStringAsFixed(0)} ${AppLocalizations.of(context).sugars})', + ), + MutedText( + '${AppLocalizations.of(context).fat}: ' + '${values.fat.toStringAsFixed(0)}' + '${AppLocalizations.of(context).g} ' + '(${values.fatSaturated.toStringAsFixed(0)} ${AppLocalizations.of(context).saturatedFat})', + ), + SizedBox(height: 12), + ], + ), + ), + IconButton(onPressed: null, icon: Icon(Icons.edit)), + ], + ); + }).toList(); + } + @override Widget build(BuildContext context) { - final nutritionalValues = _nutritionalPlan.nutritionalValues; + final valuesTotal = _nutritionalPlan.nutritionalValues; + final valuesDate = _nutritionalPlan.getValuesForDate(this._date); + final logs = _nutritionalPlan.getLogsForDate(this._date); + + if (valuesDate == null) { + return Text(''); + } return Column( children: [ @@ -36,8 +239,16 @@ class NutritionalDiaryDetailWidget extends StatelessWidget { Container( padding: EdgeInsets.all(15), height: 220, - child: NutritionalPlanPieChartWidget(nutritionalValues), + child: NutritionalPlanPieChartWidget(valuesDate), ), + getTable(valuesTotal, valuesDate, context), + SizedBox(height: 10), + Text( + AppLocalizations.of(context).labelWorkoutLogs, + textAlign: TextAlign.center, + style: Theme.of(context).textTheme.headline6, + ), + ...getEntriesTable(logs, context), ], ); } diff --git a/lib/widgets/nutrition/nutritional_plan_detail.dart b/lib/widgets/nutrition/nutritional_plan_detail.dart index 29fdc79b..012bf71d 100644 --- a/lib/widgets/nutrition/nutritional_plan_detail.dart +++ b/lib/widgets/nutrition/nutritional_plan_detail.dart @@ -24,8 +24,8 @@ import 'package:wger/models/nutrition/nutritional_plan.dart'; import 'package:wger/models/nutrition/nutritrional_values.dart'; import 'package:wger/providers/body_weight.dart'; import 'package:wger/screens/form_screen.dart'; -import 'package:wger/theme/theme.dart'; import 'package:wger/screens/nutritional_diary_screen.dart'; +import 'package:wger/theme/theme.dart'; import 'package:wger/widgets/nutrition/charts.dart'; import 'package:wger/widgets/nutrition/forms.dart'; import 'package:wger/widgets/nutrition/meal.dart'; From b7e1fed3b2e914e4230fb596873a8fae421b5f20 Mon Sep 17 00:00:00 2001 From: Roland Geider Date: Mon, 16 Aug 2021 18:30:04 +0200 Subject: [PATCH 16/33] Remove placeholder text --- lib/widgets/nutrition/nutritional_diary_detail.dart | 2 -- 1 file changed, 2 deletions(-) diff --git a/lib/widgets/nutrition/nutritional_diary_detail.dart b/lib/widgets/nutrition/nutritional_diary_detail.dart index 6bed8869..3080166a 100644 --- a/lib/widgets/nutrition/nutritional_diary_detail.dart +++ b/lib/widgets/nutrition/nutritional_diary_detail.dart @@ -234,8 +234,6 @@ class NutritionalDiaryDetailWidget extends StatelessWidget { return Column( children: [ - Text('Detail view for nutritional plan'), - Text('Content here'), Container( padding: EdgeInsets.all(15), height: 220, From d35e46514ea780bcd02bc44b883989964dd6cac8 Mon Sep 17 00:00:00 2001 From: Roland Geider Date: Mon, 16 Aug 2021 18:50:30 +0200 Subject: [PATCH 17/33] Allow deleting nutritional diary entries --- lib/providers/nutrition.dart | 7 +++++++ lib/widgets/nutrition/nutritional_diary_detail.dart | 9 ++++++++- 2 files changed, 15 insertions(+), 1 deletion(-) diff --git a/lib/providers/nutrition.dart b/lib/providers/nutrition.dart index 8046efa8..46b45a69 100644 --- a/lib/providers/nutrition.dart +++ b/lib/providers/nutrition.dart @@ -339,6 +339,13 @@ class NutritionPlansProvider extends WgerBaseProvider with ChangeNotifier { notifyListeners(); } + /// Deletes a log entry + Future deleteLog(int logId, int planId) async { + final plan = findById(planId); + plan.logs.removeWhere((element) => element.id == logId); + notifyListeners(); + } + /// Load nutrition diary entries for plan Future fetchAndSetLogs(NutritionalPlan plan) async { // TODO: update fetch to that it can use the pagination diff --git a/lib/widgets/nutrition/nutritional_diary_detail.dart b/lib/widgets/nutrition/nutritional_diary_detail.dart index 3080166a..1a25ad55 100644 --- a/lib/widgets/nutrition/nutritional_diary_detail.dart +++ b/lib/widgets/nutrition/nutritional_diary_detail.dart @@ -19,9 +19,11 @@ import 'package:flutter/material.dart'; import 'package:flutter_gen/gen_l10n/app_localizations.dart'; import 'package:intl/intl.dart'; +import 'package:provider/provider.dart'; import 'package:wger/models/nutrition/log.dart'; import 'package:wger/models/nutrition/nutritional_plan.dart'; import 'package:wger/models/nutrition/nutritrional_values.dart'; +import 'package:wger/providers/nutrition.dart'; import 'package:wger/theme/theme.dart'; import 'package:wger/widgets/core/core.dart'; import 'package:wger/widgets/nutrition/charts.dart'; @@ -216,7 +218,12 @@ class NutritionalDiaryDetailWidget extends StatelessWidget { ], ), ), - IconButton(onPressed: null, icon: Icon(Icons.edit)), + IconButton( + onPressed: () { + Provider.of(context, listen: false) + .deleteLog(log.id!, _nutritionalPlan.id!); + }, + icon: Icon(Icons.delete_outline)), ], ); }).toList(); From d0a4381d50ce77351f0c0702072282b51329511f Mon Sep 17 00:00:00 2001 From: Roland Geider Date: Mon, 16 Aug 2021 19:01:55 +0200 Subject: [PATCH 18/33] Somewhat improve alignment of clickable buttons for detail page --- lib/widgets/nutrition/nutritional_plan_detail.dart | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/lib/widgets/nutrition/nutritional_plan_detail.dart b/lib/widgets/nutrition/nutritional_plan_detail.dart index 012bf71d..d4f3be57 100644 --- a/lib/widgets/nutrition/nutritional_plan_detail.dart +++ b/lib/widgets/nutrition/nutritional_plan_detail.dart @@ -223,7 +223,7 @@ class NutritionalPlanDetailWidget extends StatelessWidget { child: Column( crossAxisAlignment: CrossAxisAlignment.end, children: [ - Text(''), + TextButton(onPressed: () => null, child: Text('')), Text( '${AppLocalizations.of(context).energy} (${AppLocalizations.of(context).kcal})'), Text( From f2cda9faae55cd17dd5810f4127019b952e6b56d Mon Sep 17 00:00:00 2001 From: Roland Geider Date: Mon, 16 Aug 2021 19:02:11 +0200 Subject: [PATCH 19/33] Remove (wrong) header, content is clear --- lib/widgets/nutrition/nutritional_diary_detail.dart | 7 +------ 1 file changed, 1 insertion(+), 6 deletions(-) diff --git a/lib/widgets/nutrition/nutritional_diary_detail.dart b/lib/widgets/nutrition/nutritional_diary_detail.dart index 1a25ad55..8262ebf0 100644 --- a/lib/widgets/nutrition/nutritional_diary_detail.dart +++ b/lib/widgets/nutrition/nutritional_diary_detail.dart @@ -247,12 +247,7 @@ class NutritionalDiaryDetailWidget extends StatelessWidget { child: NutritionalPlanPieChartWidget(valuesDate), ), getTable(valuesTotal, valuesDate, context), - SizedBox(height: 10), - Text( - AppLocalizations.of(context).labelWorkoutLogs, - textAlign: TextAlign.center, - style: Theme.of(context).textTheme.headline6, - ), + SizedBox(height: 15), ...getEntriesTable(logs, context), ], ); From e8c5b09c5aa6082564296d917d749bb86e170362 Mon Sep 17 00:00:00 2001 From: Roland Geider Date: Mon, 16 Aug 2021 19:07:49 +0200 Subject: [PATCH 20/33] Actually delete the logs from the server... --- lib/providers/nutrition.dart | 2 ++ 1 file changed, 2 insertions(+) diff --git a/lib/providers/nutrition.dart b/lib/providers/nutrition.dart index 46b45a69..5edc496a 100644 --- a/lib/providers/nutrition.dart +++ b/lib/providers/nutrition.dart @@ -341,6 +341,8 @@ class NutritionPlansProvider extends WgerBaseProvider with ChangeNotifier { /// Deletes a log entry Future deleteLog(int logId, int planId) async { + await deleteRequest(_nutritionDiaryPath, logId); + final plan = findById(planId); plan.logs.removeWhere((element) => element.id == logId); notifyListeners(); From d8adbb8e7c75d2cd6a9d0b9c75dbf53b24c38494 Mon Sep 17 00:00:00 2001 From: Roland Geider Date: Tue, 17 Aug 2021 11:07:01 +0200 Subject: [PATCH 21/33] Nutritional view now needs the body weight provider as well --- test/nutritional_plan_screen_test.dart | 12 ++++++++++-- 1 file changed, 10 insertions(+), 2 deletions(-) diff --git a/test/nutritional_plan_screen_test.dart b/test/nutritional_plan_screen_test.dart index 4a0461e5..98bb314e 100644 --- a/test/nutritional_plan_screen_test.dart +++ b/test/nutritional_plan_screen_test.dart @@ -20,6 +20,7 @@ import 'package:flutter/material.dart'; import 'package:flutter_gen/gen_l10n/app_localizations.dart'; import 'package:flutter_test/flutter_test.dart'; import 'package:provider/provider.dart'; +import 'package:wger/providers/body_weight.dart'; import 'package:wger/providers/nutrition.dart'; import 'package:wger/screens/nutritional_plan_screen.dart'; import 'package:wger/widgets/nutrition/charts.dart'; @@ -35,8 +36,15 @@ void main() { final plan = getNutritionalPlan(); - return ChangeNotifierProvider( - create: (context) => NutritionPlansProvider(testAuthProvider, [], client), + return MultiProvider( + providers: [ + ChangeNotifierProvider( + create: (context) => NutritionPlansProvider(testAuthProvider, [], client), + ), + ChangeNotifierProvider( + create: (context) => BodyWeightProvider(testAuthProvider, [], client), + ), + ], child: MaterialApp( locale: Locale(locale), localizationsDelegates: AppLocalizations.localizationsDelegates, From c191b3e27e6de9f43620eb564060c928cf4b2b97 Mon Sep 17 00:00:00 2001 From: Roland Geider Date: Tue, 17 Aug 2021 11:24:34 +0200 Subject: [PATCH 22/33] Wrap detail diary widget in a card This is needed because the icon button needs a parent that is a Material widget --- test/nutritional_diary_test.dart | 6 +++--- test_data/nutritional_plans.dart | 6 ++++++ 2 files changed, 9 insertions(+), 3 deletions(-) diff --git a/test/nutritional_diary_test.dart b/test/nutritional_diary_test.dart index d4c0e71c..497c8180 100644 --- a/test/nutritional_diary_test.dart +++ b/test/nutritional_diary_test.dart @@ -26,13 +26,13 @@ import '../test_data/nutritional_plans.dart'; void main() { Widget getWidget({locale = 'en'}) { - final plan = getNutritionalPlan(); - return MaterialApp( locale: Locale(locale), localizationsDelegates: AppLocalizations.localizationsDelegates, supportedLocales: AppLocalizations.supportedLocales, - home: NutritionalDiaryDetailWidget(plan, DateTime(2021, 6, 1)), + home: Card( + child: NutritionalDiaryDetailWidget(getNutritionalPlan(), DateTime(2021, 6, 1)), + ), ); } diff --git a/test_data/nutritional_plans.dart b/test_data/nutritional_plans.dart index 37ac1d17..4f95e13c 100644 --- a/test_data/nutritional_plans.dart +++ b/test_data/nutritional_plans.dart @@ -18,6 +18,7 @@ import 'package:flutter/material.dart'; import 'package:wger/models/nutrition/ingredient.dart'; +import 'package:wger/models/nutrition/log.dart'; import 'package:wger/models/nutrition/meal.dart'; import 'package:wger/models/nutrition/meal_item.dart'; import 'package:wger/models/nutrition/nutritional_plan.dart'; @@ -102,5 +103,10 @@ NutritionalPlan getNutritionalPlan() { ); plan.meals = [meal1, meal2]; + // Add logs + plan.logs.add(Log.fromMealItem(mealItem1, 1, DateTime(2021, 6, 1))); + plan.logs.add(Log.fromMealItem(mealItem2, 1, DateTime(2021, 6, 2))); + plan.logs.add(Log.fromMealItem(mealItem3, 1, DateTime(2021, 6, 3))); + return plan; } From 0462e41b20653479a566f4997e0e69546333a0b2 Mon Sep 17 00:00:00 2001 From: Roland Geider Date: Tue, 17 Aug 2021 11:45:52 +0200 Subject: [PATCH 23/33] Expand test for nutrition diary detail view --- test/nutritional_diary_test.dart | 19 +++++++++++++++++-- test/nutritional_plan_model_test.dart | 4 ++-- test_data/nutritional_plans.dart | 14 +++++++------- 3 files changed, 26 insertions(+), 11 deletions(-) diff --git a/test/nutritional_diary_test.dart b/test/nutritional_diary_test.dart index 497c8180..55cb637c 100644 --- a/test/nutritional_diary_test.dart +++ b/test/nutritional_diary_test.dart @@ -30,8 +30,10 @@ void main() { locale: Locale(locale), localizationsDelegates: AppLocalizations.localizationsDelegates, supportedLocales: AppLocalizations.supportedLocales, - home: Card( - child: NutritionalDiaryDetailWidget(getNutritionalPlan(), DateTime(2021, 6, 1)), + home: SingleChildScrollView( + child: Card( + child: NutritionalDiaryDetailWidget(getNutritionalPlan(), DateTime(2021, 6, 1)), + ), ), ); } @@ -40,5 +42,18 @@ void main() { await tester.pumpWidget(getWidget()); expect(find.byType(NutritionalPlanPieChartWidget), findsOneWidget); + expect(find.byType(Table), findsOneWidget); + + expect(find.text('519kcal'), findsOneWidget, reason: 'find total energy'); + expect(find.text('6g'), findsOneWidget, reason: 'find grams of protein'); + expect(find.text('18g'), findsOneWidget, reason: 'find grams of carbs'); + expect(find.text('4g'), findsOneWidget, reason: 'find grams of sugar'); + expect(find.text('29g'), findsOneWidget, reason: 'find grams of fat'); + expect(find.text('14g'), findsOneWidget, reason: 'find grams of saturated fat'); + expect(find.text('50g'), findsOneWidget, reason: 'find grams of fibre'); + + expect(find.text('100g Water'), findsOneWidget, reason: 'Name of ingredient'); + expect(find.text('75g Burger soup'), findsOneWidget, reason: 'Name of ingredient'); + expect(find.byIcon(Icons.delete_outline), findsNWidgets(2)); }); } diff --git a/test/nutritional_plan_model_test.dart b/test/nutritional_plan_model_test.dart index 2c69569c..3ff9d69a 100644 --- a/test/nutritional_plan_model_test.dart +++ b/test/nutritional_plan_model_test.dart @@ -25,14 +25,14 @@ void main() { group('model tests', () { test('Test the nutritionalValues method for nutritional plans', () { final plan = getNutritionalPlan(); - final values = NutritionalValues.values(4118.75, 28.75, 347.5, 9.5, 41.0, 31.75, 41.5, 30.0); + final values = NutritionalValues.values(4118.75, 32.75, 347.5, 9.5, 59.0, 37.75, 52.5, 30.5); expect(plan.nutritionalValues, values); }); test('Test the nutritionalValues method for meals', () { final plan = getNutritionalPlan(); final meal = plan.meals.first; - final values = NutritionalValues.values(518.75, 1.75, 17.5, 3.5, 11.0, 7.75, 38.5, 0.0); + final values = NutritionalValues.values(518.75, 5.75, 17.5, 3.5, 29.0, 13.75, 49.5, 0.5); expect(meal.nutritionalValues, values); }); }); diff --git a/test_data/nutritional_plans.dart b/test_data/nutritional_plans.dart index 4f95e13c..488ee77a 100644 --- a/test_data/nutritional_plans.dart +++ b/test_data/nutritional_plans.dart @@ -30,11 +30,11 @@ final ingredient1 = Ingredient( energy: 500, carbohydrates: 10, carbohydratesSugar: 2, - protein: 1, - fat: 2, - fatSaturated: 1, - fibres: 1, - sodium: 0, + protein: 5, + fat: 20, + fatSaturated: 7, + fibres: 12, + sodium: 0.5, ); final ingredient2 = Ingredient( id: 2, @@ -105,8 +105,8 @@ NutritionalPlan getNutritionalPlan() { // Add logs plan.logs.add(Log.fromMealItem(mealItem1, 1, DateTime(2021, 6, 1))); - plan.logs.add(Log.fromMealItem(mealItem2, 1, DateTime(2021, 6, 2))); - plan.logs.add(Log.fromMealItem(mealItem3, 1, DateTime(2021, 6, 3))); + plan.logs.add(Log.fromMealItem(mealItem2, 1, DateTime(2021, 6, 1))); + plan.logs.add(Log.fromMealItem(mealItem3, 1, DateTime(2021, 6, 10))); return plan; } From d6db7a1a750a0cf99f17fe50bc03a57e0dd160fe Mon Sep 17 00:00:00 2001 From: Roland Geider Date: Tue, 17 Aug 2021 11:54:51 +0200 Subject: [PATCH 24/33] Move chart and table to card --- .../nutrition/nutritional_diary_detail.dart | 19 ++++++++++++++----- 1 file changed, 14 insertions(+), 5 deletions(-) diff --git a/lib/widgets/nutrition/nutritional_diary_detail.dart b/lib/widgets/nutrition/nutritional_diary_detail.dart index 8262ebf0..224bf17e 100644 --- a/lib/widgets/nutrition/nutritional_diary_detail.dart +++ b/lib/widgets/nutrition/nutritional_diary_detail.dart @@ -241,12 +241,21 @@ class NutritionalDiaryDetailWidget extends StatelessWidget { return Column( children: [ - Container( - padding: EdgeInsets.all(15), - height: 220, - child: NutritionalPlanPieChartWidget(valuesDate), + Card( + child: Column( + children: [ + Container( + padding: EdgeInsets.all(15), + height: 220, + child: NutritionalPlanPieChartWidget(valuesDate), + ), + Padding( + padding: const EdgeInsets.all(8.0), + child: getTable(valuesTotal, valuesDate, context), + ), + ], + ), ), - getTable(valuesTotal, valuesDate, context), SizedBox(height: 15), ...getEntriesTable(logs, context), ], From e18e1a0aeac4c8d7eedf1c73ecb235289a32728f Mon Sep 17 00:00:00 2001 From: Roland Geider Date: Tue, 17 Aug 2021 11:57:24 +0200 Subject: [PATCH 25/33] Don't show the global app bar for the diary entries --- lib/screens/nutritional_diary_screen.dart | 5 ++--- 1 file changed, 2 insertions(+), 3 deletions(-) diff --git a/lib/screens/nutritional_diary_screen.dart b/lib/screens/nutritional_diary_screen.dart index 475436fd..feac4d2b 100644 --- a/lib/screens/nutritional_diary_screen.dart +++ b/lib/screens/nutritional_diary_screen.dart @@ -21,7 +21,6 @@ import 'package:intl/intl.dart'; import 'package:provider/provider.dart'; import 'package:wger/models/nutrition/nutritional_plan.dart'; import 'package:wger/providers/nutrition.dart'; -import 'package:wger/widgets/core/app_bar.dart'; import 'package:wger/widgets/nutrition/nutritional_diary_detail.dart'; /// Arguments passed to the form screen @@ -43,8 +42,8 @@ class NutritionalDiaryScreen extends StatelessWidget { final args = ModalRoute.of(context)!.settings.arguments as NutritionalDiaryArguments; return Scaffold( - appBar: WgerAppBar( - DateFormat.yMd(Localizations.localeOf(context).languageCode).format(args.date), + appBar: AppBar( + title: Text(DateFormat.yMd(Localizations.localeOf(context).languageCode).format(args.date)), ), body: Consumer( builder: (context, nutritionProvider, child) => SingleChildScrollView( From 71f3c24005d0373ea96b4b6dca41e0e0c37fa542 Mon Sep 17 00:00:00 2001 From: Roland Geider Date: Tue, 17 Aug 2021 12:06:05 +0200 Subject: [PATCH 26/33] Add helpter to output nutritional values This also makes the regular nutritional plan detail view less noisy --- lib/widgets/nutrition/helpers.dart | 50 ++++++++++++++ lib/widgets/nutrition/meal.dart | 66 +------------------ .../nutrition/nutritional_diary_detail.dart | 25 +------ 3 files changed, 55 insertions(+), 86 deletions(-) create mode 100644 lib/widgets/nutrition/helpers.dart diff --git a/lib/widgets/nutrition/helpers.dart b/lib/widgets/nutrition/helpers.dart new file mode 100644 index 00000000..ab360d4b --- /dev/null +++ b/lib/widgets/nutrition/helpers.dart @@ -0,0 +1,50 @@ +/* + * This file is part of wger Workout Manager . + * Copyright (C) 2020, 2021 wger Team + * + * wger Workout Manager is free software: you can redistribute it and/or modify + * it under the terms of the GNU Affero General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * wger Workout Manager is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Affero General Public License for more details. + * + * You should have received a copy of the GNU Affero General Public License + * along with this program. If not, see . + */ + +import 'package:flutter/cupertino.dart'; +import 'package:flutter_gen/gen_l10n/app_localizations.dart'; +import 'package:wger/models/nutrition/nutritrional_values.dart'; +import 'package:wger/widgets/core/core.dart'; + +List getMutedNutritionalValues(NutritionalValues values, BuildContext context) { + List out = [ + MutedText( + '${AppLocalizations.of(context).energy}: ' + '${values.energy.toStringAsFixed(0)}' + '${AppLocalizations.of(context).kcal}', + ), + MutedText( + '${AppLocalizations.of(context).protein}: ' + '${values.protein.toStringAsFixed(0)}' + '${AppLocalizations.of(context).g}', + ), + MutedText( + '${AppLocalizations.of(context).carbohydrates}: ' + '${values.carbohydrates.toStringAsFixed(0)} ' + '${AppLocalizations.of(context).g} ' + '(${values.carbohydratesSugar.toStringAsFixed(0)} ${AppLocalizations.of(context).sugars})', + ), + MutedText( + '${AppLocalizations.of(context).fat}: ' + '${values.fat.toStringAsFixed(0)}' + '${AppLocalizations.of(context).g} ' + '(${values.fatSaturated.toStringAsFixed(0)} ${AppLocalizations.of(context).saturatedFat})', + ), + ]; + return out; +} diff --git a/lib/widgets/nutrition/meal.dart b/lib/widgets/nutrition/meal.dart index 54335df8..46751c5c 100644 --- a/lib/widgets/nutrition/meal.dart +++ b/lib/widgets/nutrition/meal.dart @@ -25,8 +25,8 @@ import 'package:wger/models/nutrition/meal_item.dart'; import 'package:wger/providers/nutrition.dart'; import 'package:wger/screens/form_screen.dart'; import 'package:wger/theme/theme.dart'; -import 'package:wger/widgets/core/core.dart'; import 'package:wger/widgets/nutrition/forms.dart'; +import 'package:wger/widgets/nutrition/helpers.dart'; class MealWidget extends StatefulWidget { final Meal _meal; @@ -117,38 +117,6 @@ class _MealWidgetState extends State { ], ), Divider(), - if (_expanded) - Padding( - padding: const EdgeInsets.all(5), - child: Row( - children: [ - Expanded( - child: MutedText( - AppLocalizations.of(context).energy, - textAlign: TextAlign.center, - ), - ), - Expanded( - child: MutedText( - AppLocalizations.of(context).protein, - textAlign: TextAlign.center, - ), - ), - Expanded( - child: MutedText( - AppLocalizations.of(context).carbohydrates, - textAlign: TextAlign.center, - ), - ), - Expanded( - child: MutedText( - AppLocalizations.of(context).fat, - textAlign: TextAlign.center, - ), - ), - ], - ), - ), ...widget._meal.mealItems.map((item) => MealItemWidget(item, _expanded)).toList(), OutlinedButton( child: Text(AppLocalizations.of(context).addIngredient), @@ -192,10 +160,10 @@ class MealItemWidget extends StatelessWidget { return Container( padding: EdgeInsets.all(5), child: Column( + crossAxisAlignment: CrossAxisAlignment.start, children: [ Container( width: double.infinity, - padding: EdgeInsets.symmetric(vertical: 5), child: Row( mainAxisAlignment: MainAxisAlignment.spaceBetween, children: [ @@ -226,35 +194,7 @@ class MealItemWidget extends StatelessWidget { ], ), ), - if (_expanded) - Row( - children: [ - Expanded( - child: MutedText( - '${values.energy.toStringAsFixed(0)} ${AppLocalizations.of(context).kcal}', - textAlign: TextAlign.center, - ), - ), - Expanded( - child: MutedText( - '${values.protein.toStringAsFixed(0)}${AppLocalizations.of(context).g}', - textAlign: TextAlign.center, - ), - ), - Expanded( - child: MutedText( - '${values.carbohydrates.toStringAsFixed(0)}${AppLocalizations.of(context).g}', - textAlign: TextAlign.center, - ), - ), - Expanded( - child: MutedText( - '${values.fat.toStringAsFixed(0)}${AppLocalizations.of(context).g}', - textAlign: TextAlign.center, - ), - ), - ], - ), + if (_expanded) ...getMutedNutritionalValues(values, context), ], ), ); diff --git a/lib/widgets/nutrition/nutritional_diary_detail.dart b/lib/widgets/nutrition/nutritional_diary_detail.dart index 224bf17e..c7af909f 100644 --- a/lib/widgets/nutrition/nutritional_diary_detail.dart +++ b/lib/widgets/nutrition/nutritional_diary_detail.dart @@ -25,8 +25,8 @@ import 'package:wger/models/nutrition/nutritional_plan.dart'; import 'package:wger/models/nutrition/nutritrional_values.dart'; import 'package:wger/providers/nutrition.dart'; import 'package:wger/theme/theme.dart'; -import 'package:wger/widgets/core/core.dart'; import 'package:wger/widgets/nutrition/charts.dart'; +import 'package:wger/widgets/nutrition/helpers.dart'; class NutritionalDiaryDetailWidget extends StatelessWidget { final NutritionalPlan _nutritionalPlan; @@ -192,28 +192,7 @@ class NutritionalDiaryDetailWidget extends StatelessWidget { overflow: TextOverflow.ellipsis, ), SizedBox(height: 4), - MutedText( - '${AppLocalizations.of(context).energy}: ' - '${values.energy.toStringAsFixed(0)}' - '${AppLocalizations.of(context).kcal}', - ), - MutedText( - '${AppLocalizations.of(context).protein}: ' - '${values.protein.toStringAsFixed(0)}' - '${AppLocalizations.of(context).g}', - ), - MutedText( - '${AppLocalizations.of(context).carbohydrates}: ' - '${values.carbohydrates.toStringAsFixed(0)} ' - '${AppLocalizations.of(context).g} ' - '(${values.carbohydratesSugar.toStringAsFixed(0)} ${AppLocalizations.of(context).sugars})', - ), - MutedText( - '${AppLocalizations.of(context).fat}: ' - '${values.fat.toStringAsFixed(0)}' - '${AppLocalizations.of(context).g} ' - '(${values.fatSaturated.toStringAsFixed(0)} ${AppLocalizations.of(context).saturatedFat})', - ), + ...getMutedNutritionalValues(values, context), SizedBox(height: 12), ], ), From 42cd01ec89f7e08f70fcf16802845b306d502f43 Mon Sep 17 00:00:00 2001 From: Jacob Date: Thu, 19 Aug 2021 06:44:19 +0000 Subject: [PATCH 27/33] Translated using Weblate (Polish) Currently translated at 10.9% (15 of 137 strings) Translation: wger Workout Manager/Mobile App Translate-URL: https://hosted.weblate.org/projects/wger/mobile/pl/ --- lib/l10n/app_pl.arb | 55 ++++++++++++++++++++++++++++++++++++++++++++- 1 file changed, 54 insertions(+), 1 deletion(-) diff --git a/lib/l10n/app_pl.arb b/lib/l10n/app_pl.arb index 0967ef42..bd5cb272 100644 --- a/lib/l10n/app_pl.arb +++ b/lib/l10n/app_pl.arb @@ -1 +1,54 @@ -{} +{ + "invalidUsername": "Podaj poprawną nazwę użytkownika", + "@invalidUsername": { + "description": "Error message when the user enters an invalid username" + }, + "username": "Nazwa użytkownika", + "@username": {}, + "email": "Adres email", + "@email": {}, + "invalidEmail": "Podaj poprawny adres email", + "@invalidEmail": { + "description": "Error message when the user enters an invalid email" + }, + "confirmPassword": "Potwierdź hasło", + "@confirmPassword": {}, + "password": "Hasło", + "@password": {}, + "passwordTooShort": "Hasło jest zbyt krótkie", + "@passwordTooShort": { + "description": "Error message when the user a password that is too short" + }, + "passwordsDontMatch": "Hasła nie są identyczne", + "@passwordsDontMatch": { + "description": "Error message when the user enters two different passwords during registration" + }, + "usernameValidChars": "Nazwa użytkownika może zawierać tylko litery, cyfry i znaki specjalne: @,+,.,-, _", + "@usernameValidChars": { + "description": "Error message when the user tries to register a username with forbidden characters" + }, + "invalidUrl": "Wpisz poprawny adres URL", + "@invalidUrl": { + "description": "Error message when the user enters an invalid URL, e.g. in the login form" + }, + "useCustomServer": "Używaj niestandardowe o serwera", + "@useCustomServer": { + "description": "Toggle button allowing users to switch between the default and a custom wger server" + }, + "useDefaultServer": "Używaj domyślnego serwera", + "@useDefaultServer": { + "description": "Toggle button allowing users to switch between the default and a custom wger server" + }, + "register": "Zarejestruj się", + "@register": { + "description": "Text for registration button" + }, + "logout": "Wyloguj się", + "@logout": { + "description": "Text for logout button" + }, + "login": "Zaloguj się", + "@login": { + "description": "Text for login button" + } +} From 6fe200ce4b473439361f3bb0a0ce64fa2d72952c Mon Sep 17 00:00:00 2001 From: Roland Geider Date: Fri, 20 Aug 2021 12:18:38 +0000 Subject: [PATCH 28/33] Translated using Weblate (German) Currently translated at 100.0% (137 of 137 strings) Translation: wger Workout Manager/Mobile App Translate-URL: https://hosted.weblate.org/projects/wger/mobile/de/ --- lib/l10n/app_de.arb | 24 +++++++++++++++++++++++- 1 file changed, 23 insertions(+), 1 deletion(-) diff --git a/lib/l10n/app_de.arb b/lib/l10n/app_de.arb index 2814e859..2839bd82 100644 --- a/lib/l10n/app_de.arb +++ b/lib/l10n/app_de.arb @@ -467,5 +467,27 @@ "supersetWith": "Supersatz mit", "@supersetWith": { "description": "Text used between exercise cards when adding a new set. Translate as something like 'in a superset with'" - } + }, + "total": "Gesamt", + "@total": { + "description": "Label used for total sums of e.g. calories or similar" + }, + "gPerBodyKg": "g pro Körper-kg", + "@gPerBodyKg": { + "description": "Label used for total sums of e.g. calories or similar in grams per Kg of body weight" + }, + "percentEnergy": "Prozent der Energie", + "@percentEnergy": {}, + "difference": "Unterschied", + "@difference": {}, + "logged": "Protokolliert", + "@logged": { + "description": "Header for the column of 'logged' nutritional values, i.e. what was eaten" + }, + "planned": "Geplant", + "@planned": { + "description": "Header for the column of 'planned' nutritional values, i.e. what should be eaten" + }, + "macronutrients": "Makronährstoffe", + "@macronutrients": {} } From 87a1805179c4409af4836fffdd455ba4fe500062 Mon Sep 17 00:00:00 2001 From: Roland Geider Date: Fri, 20 Aug 2021 12:20:35 +0000 Subject: [PATCH 29/33] Translated using Weblate (Spanish) Currently translated at 91.2% (125 of 137 strings) Translation: wger Workout Manager/Mobile App Translate-URL: https://hosted.weblate.org/projects/wger/mobile/es/ --- lib/l10n/app_es.arb | 12 +++++++++++- 1 file changed, 11 insertions(+), 1 deletion(-) diff --git a/lib/l10n/app_es.arb b/lib/l10n/app_es.arb index 45d8185c..170b4c96 100644 --- a/lib/l10n/app_es.arb +++ b/lib/l10n/app_es.arb @@ -429,5 +429,15 @@ "usernameValidChars": "El nombre de usuario sólo puede contener letras, dígitos y los caracteres @, +, ., - y _", "@usernameValidChars": { "description": "Error message when the user tries to register a username with forbidden characters" - } + }, + "total": "Total", + "@total": { + "description": "Label used for total sums of e.g. calories or similar" + }, + "percentEnergy": "Porcentaje de energía", + "@percentEnergy": {}, + "difference": "Diferencia", + "@difference": {}, + "macronutrients": "Macronutrientes", + "@macronutrients": {} } From 547b2330d77442edf5f5210a0d427eb3b950a7c8 Mon Sep 17 00:00:00 2001 From: Tymofii Lytvynenko Date: Mon, 23 Aug 2021 10:00:19 +0000 Subject: [PATCH 30/33] Translated using Weblate (Ukrainian) Currently translated at 100.0% (4 of 4 strings) Translation: wger Workout Manager/Play Store Translate-URL: https://hosted.weblate.org/projects/wger/play-store/uk/ --- .../metadata/android/uk/full_description.txt | 39 +++++++++++++++++++ .../metadata/android/uk/short_description.txt | 1 + 2 files changed, 40 insertions(+) create mode 100644 android/fastlane/metadata/android/uk/full_description.txt create mode 100644 android/fastlane/metadata/android/uk/short_description.txt diff --git a/android/fastlane/metadata/android/uk/full_description.txt b/android/fastlane/metadata/android/uk/full_description.txt new file mode 100644 index 00000000..7ac5c0da --- /dev/null +++ b/android/fastlane/metadata/android/uk/full_description.txt @@ -0,0 +1,39 @@ +Від поціновувачів фітнесу поціновувачам фітнесу - організуйте своє здоров'я з WGER, вашим менеджером тренувань! + +Ви вже знайшли свій фітнес-застосунок №1 і любите створювати свої власні спортивні програми? Неважливо, який ви спортивний звір - у всіх нас є щось спільне: ми любимо стежити за даними про своє здоров'я <3 + +Тому ми не засуджуємо вас за те, що ви все ще ведете свій фітнес-шлях за допомогою зручного маленького журналу тренувань, але ласкаво просимо у 2021 рік! + +Ми розробили для вас 100% безплатний цифровий застосунок для відстеження стану здоров'я та фітнесу, скоротивши його до найнеобхідніших функцій, щоб полегшити вам життя. Почніть, продовжуйте тренуватися і відзначайте свій спортивний поступ! + +wger - це проєкт з відкритим вихідним кодом і все про: +* Ваше тіло +* Ваші тренування +* Ваш поступ +* Ваші дані + +Ваше тіло: +Не потрібно шукати інгредієнти улюблених ласощів - вибирайте щоденні страви з більш ніж 78000 продуктів і дивіться їх харчову цінність. Додавайте страви в план харчування і стежте за своїм раціоном в календарі. + +Ваші тренування: +Ви знаєте, що найкраще підходить для вашого тіла. Створюйте свої власні тренування з чимраз більшого розмаїття 200 різних вправ. Потім використовуйте режим тренажерного залу, щоб скеровувати вас під час тренування, одночасно реєструючи вагові показники одним дотиком. + +Ваш поступ: +Ніколи не втрачайте з уваги свої цілі. Відстежуйте свою вагу і ведіть статистику. + +Ваші дані: +wger - це ваш персональний фітнес-щоденник - але ваші дані належать вам. Використовуйте REST API для доступу до них і робіть з ними дивовижні речі. + +Зверніть увагу: цей безплатний застосунок не ґрунтується на додатковому фінансуванні, і ми не просимо вас жертвувати гроші. Більш того, це проєкт спільноти яка постійно зростає. Отже будьте готові до появи нових функцій в будь-який час! + +#OpenSource #ВідкритеДжерело - що це значить? + +Відкритий вихідний код означає, що весь вихідний код цього застосунку і сервера, з яким він працює, вільний і доступний кожному: +* Ви хочете запустити wger на власному сервері для себе або свого спортзалу? Вперед! +* Вам не вистачає якоїсь функції й ви хочете її здійснити? Почніть прямо зараз! +* Хочете перевірити, що нічого нікуди не надсилається? Ви можете! + +Приєднуйтесь до нашої спільноти та станьте частиною ентузіастів спорту й IT-ґіків з усього світу. Ми продовжуємо працювати над коригуванням і оптимізацією застосунку відповідно до наших потреб. Ми любимо ваш внесок, тому не соромтеся вступати в спільноту в будь-який час і висловлювати свої побажання та ідеї! + +-> погляньте вихідний код на https://github.com/wger-project +-> задавайте свої питання або просто привітайтеся на нашому сервері discord https://discord.gg/rPWFv6W diff --git a/android/fastlane/metadata/android/uk/short_description.txt b/android/fastlane/metadata/android/uk/short_description.txt new file mode 100644 index 00000000..c4ae9384 --- /dev/null +++ b/android/fastlane/metadata/android/uk/short_description.txt @@ -0,0 +1 @@ +Фітнес/тренування, харчування та відстеження ваги From 2af32737185954ea6a3d1aa60aab1f41e137f080 Mon Sep 17 00:00:00 2001 From: Roland Geider Date: Tue, 17 Aug 2021 14:58:28 +0200 Subject: [PATCH 31/33] No need to run gen-l10n anymore --- README.md | 4 ---- l10n.README | 1 - 2 files changed, 5 deletions(-) delete mode 100644 l10n.README diff --git a/README.md b/README.md index 26a8e7d2..5711fedb 100644 --- a/README.md +++ b/README.md @@ -52,10 +52,6 @@ You can later list all the registered users with: ``python3 manage.py list-users ### 4 -Generate translation files with ``flutter gen-l10n`` - - -### 5 Start the application with ``flutter run`` or use your IDE (please note that depending on how you run your emulator you will need to change the IP address of the server) diff --git a/l10n.README b/l10n.README deleted file mode 100644 index e56a60bb..00000000 --- a/l10n.README +++ /dev/null @@ -1 +0,0 @@ -flutter gen-l10n \ No newline at end of file From 35435c9345c77edeb9d1e3719c9e2882b9987744 Mon Sep 17 00:00:00 2001 From: Roland Geider Date: Wed, 25 Aug 2021 16:40:26 +0200 Subject: [PATCH 32/33] Rename Ukrainian language folder This is needed for Google's playstore --- .../fastlane/metadata/android/{uk => uk-UK}/full_description.txt | 0 .../fastlane/metadata/android/{uk => uk-UK}/short_description.txt | 0 2 files changed, 0 insertions(+), 0 deletions(-) rename android/fastlane/metadata/android/{uk => uk-UK}/full_description.txt (100%) rename android/fastlane/metadata/android/{uk => uk-UK}/short_description.txt (100%) diff --git a/android/fastlane/metadata/android/uk/full_description.txt b/android/fastlane/metadata/android/uk-UK/full_description.txt similarity index 100% rename from android/fastlane/metadata/android/uk/full_description.txt rename to android/fastlane/metadata/android/uk-UK/full_description.txt diff --git a/android/fastlane/metadata/android/uk/short_description.txt b/android/fastlane/metadata/android/uk-UK/short_description.txt similarity index 100% rename from android/fastlane/metadata/android/uk/short_description.txt rename to android/fastlane/metadata/android/uk-UK/short_description.txt From b23c417da598c179972d08cccb759f8e5545fdfc Mon Sep 17 00:00:00 2001 From: Roland Geider Date: Wed, 25 Aug 2021 16:41:36 +0200 Subject: [PATCH 33/33] Remove explicit language generation This is not needed anymore --- .github/workflows/android-release.yml | 3 --- .github/workflows/ci.yml | 3 --- 2 files changed, 6 deletions(-) diff --git a/.github/workflows/android-release.yml b/.github/workflows/android-release.yml index 50dbf141..0bdc821c 100644 --- a/.github/workflows/android-release.yml +++ b/.github/workflows/android-release.yml @@ -39,9 +39,6 @@ jobs: - name: Install Flutter dependencies run: flutter pub get - - name: Generated translation files - run: flutter gen-l10n - - name: Bump version uses: maierj/fastlane-action@v2.0.0 with: diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index b9b0277c..71fd1a58 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -21,9 +21,6 @@ jobs: - name: Install app dependencies run: flutter pub get - - name: Generated translation files - run: flutter gen-l10n - - name: Test app run: flutter test --coverage