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 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 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/android/fastlane/metadata/android/uk-UK/full_description.txt b/android/fastlane/metadata/android/uk-UK/full_description.txt new file mode 100644 index 00000000..7ac5c0da --- /dev/null +++ b/android/fastlane/metadata/android/uk-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-UK/short_description.txt b/android/fastlane/metadata/android/uk-UK/short_description.txt new file mode 100644 index 00000000..c4ae9384 --- /dev/null +++ b/android/fastlane/metadata/android/uk-UK/short_description.txt @@ -0,0 +1 @@ +Фітнес/тренування, харчування та відстеження ваги 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 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_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": {} } diff --git a/lib/l10n/app_en.arb b/lib/l10n/app_en.arb index 1f56cfb8..08482872 100644 --- a/lib/l10n/app_en.arb +++ b/lib/l10n/app_en.arb @@ -289,6 +289,25 @@ "@kcal": { "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": { + "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/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": {} } diff --git a/lib/l10n/app_fr.arb b/lib/l10n/app_fr.arb index 09f68236..e42bd3ae 100644 --- a/lib/l10n/app_fr.arb +++ b/lib/l10n/app_fr.arb @@ -448,5 +448,11 @@ "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": {}, + "supersetWith": "supersérie avec", + "@supersetWith": { + "description": "Text used between exercise cards when adding a new set. Translate as something like 'in a superset with'" } } 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" + } +} diff --git a/lib/main.dart b/lib/main.dart index dbdfcb7d..852bc866 100644 --- a/lib/main.dart +++ b/lib/main.dart @@ -31,6 +31,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'; @@ -118,6 +119,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/models/nutrition/nutritional_plan.dart b/lib/models/nutrition/nutritional_plan.dart index 1ab59032..810db753 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) { @@ -88,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/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/providers/nutrition.dart b/lib/providers/nutrition.dart index e14950db..5edc496a 100644 --- a/lib/providers/nutrition.dart +++ b/lib/providers/nutrition.dart @@ -339,6 +339,15 @@ class NutritionPlansProvider extends WgerBaseProvider with ChangeNotifier { notifyListeners(); } + /// 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(); + } + /// Load nutrition diary entries for plan Future fetchAndSetLogs(NutritionalPlan plan) async { // TODO: update fetch to that it can use the pagination @@ -346,6 +355,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); diff --git a/lib/screens/nutritional_diary_screen.dart b/lib/screens/nutritional_diary_screen.dart new file mode 100644 index 00000000..feac4d2b --- /dev/null +++ b/lib/screens/nutritional_diary_screen.dart @@ -0,0 +1,58 @@ +/* + * 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/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: AppBar( + title: Text(DateFormat.yMd(Localizations.localeOf(context).languageCode).format(args.date)), + ), + body: Consumer( + builder: (context, nutritionProvider, child) => SingleChildScrollView( + child: Padding( + padding: const EdgeInsets.all(8.0), + child: NutritionalDiaryDetailWidget(args.plan, args.date), + ), + ), + ), + ); + } +} 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/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 new file mode 100644 index 00000000..c7af909f --- /dev/null +++ b/lib/widgets/nutrition/nutritional_diary_detail.dart @@ -0,0 +1,243 @@ +/* + * 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: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/nutrition/charts.dart'; +import 'package:wger/widgets/nutrition/helpers.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), + ...getMutedNutritionalValues(values, context), + SizedBox(height: 12), + ], + ), + ), + IconButton( + onPressed: () { + Provider.of(context, listen: false) + .deleteLog(log.id!, _nutritionalPlan.id!); + }, + icon: Icon(Icons.delete_outline)), + ], + ); + }).toList(); + } + + @override + Widget build(BuildContext context) { + final valuesTotal = _nutritionalPlan.nutritionalValues; + final valuesDate = _nutritionalPlan.getValuesForDate(this._date); + final logs = _nutritionalPlan.getLogsForDate(this._date); + + if (valuesDate == null) { + return Text(''); + } + + return Column( + children: [ + 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), + ), + ], + ), + ), + SizedBox(height: 15), + ...getEntriesTable(logs, context), + ], + ); + } +} diff --git a/lib/widgets/nutrition/nutritional_plan_detail.dart b/lib/widgets/nutrition/nutritional_plan_detail.dart index b0c68872..d4f3be57 100644 --- a/lib/widgets/nutrition/nutritional_plan_detail.dart +++ b/lib/widgets/nutrition/nutritional_plan_detail.dart @@ -19,9 +19,13 @@ 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/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'; @@ -29,10 +33,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 +70,133 @@ 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)), + Text(valuesGperKg != null ? valuesGperKg.protein.toStringAsFixed(1) : ''), + ], + ), + 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)), + Text(valuesGperKg != null ? valuesGperKg.carbohydrates.toStringAsFixed(1) : ''), + ], + ), + 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)), + Text(valuesGperKg != null ? valuesGperKg.fat.toStringAsFixed(1) : ''), + ], + ), + 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 +205,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( @@ -133,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( @@ -150,7 +240,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 +255,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 +270,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/lib/widgets/workouts/gym_mode.dart b/lib/widgets/workouts/gym_mode.dart index fd05ae99..660f3134 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 @@ -266,10 +259,14 @@ class _LogPageState extends State { final _weightController = TextEditingController(); var _detailed = false; + late FocusNode focusNode; + @override void initState() { super.initState(); + focusNode = FocusNode(); + if (widget._setting.reps != null) { _repsController.text = widget._setting.reps.toString(); } @@ -279,6 +276,12 @@ class _LogPageState extends State { } } + @override + void dispose() { + focusNode.dispose(); + super.dispose(); + } + Widget getRepsWidget() { return Row( children: [ @@ -304,9 +307,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 { 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) { diff --git a/pubspec.lock b/pubspec.lock index fefcef7c..7032070e 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: @@ -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.12" + version: "2.1.13" 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: diff --git a/test/nutritional_diary_test.dart b/test/nutritional_diary_test.dart new file mode 100644 index 00000000..55cb637c --- /dev/null +++ b/test/nutritional_diary_test.dart @@ -0,0 +1,59 @@ +/* + * 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'}) { + return MaterialApp( + locale: Locale(locale), + localizationsDelegates: AppLocalizations.localizationsDelegates, + supportedLocales: AppLocalizations.supportedLocales, + home: SingleChildScrollView( + child: Card( + child: NutritionalDiaryDetailWidget(getNutritionalPlan(), 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); + 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/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, diff --git a/test_data/nutritional_plans.dart b/test_data/nutritional_plans.dart index 37ac1d17..488ee77a 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'; @@ -29,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, @@ -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, 1))); + plan.logs.add(Log.fromMealItem(mealItem3, 1, DateTime(2021, 6, 10))); + return plan; }