diff --git a/integration_test/5_nutritional_plan.dart b/integration_test/5_nutritional_plan.dart index b41f3471..57536b3f 100644 --- a/integration_test/5_nutritional_plan.dart +++ b/integration_test/5_nutritional_plan.dart @@ -89,9 +89,9 @@ Widget createNutritionalPlanScreen({locale = 'en'}) { ); // Add logs - plan.logs.add(Log.fromMealItem(mealItem1, 1, 1, DateTime(2021, 6, 1))); - plan.logs.add(Log.fromMealItem(mealItem2, 1, 1, DateTime(2021, 6, 1))); - plan.logs.add(Log.fromMealItem(mealItem3, 1, 1, DateTime(2021, 6, 10))); + plan.diaryEntries.add(Log.fromMealItem(mealItem1, 1, 1, DateTime(2021, 6, 1))); + plan.diaryEntries.add(Log.fromMealItem(mealItem2, 1, 1, DateTime(2021, 6, 1))); + plan.diaryEntries.add(Log.fromMealItem(mealItem3, 1, 1, DateTime(2021, 6, 10))); return MultiProvider( providers: [ diff --git a/lib/l10n/app_en.arb b/lib/l10n/app_en.arb index aa327a4a..4644b7d0 100644 --- a/lib/l10n/app_en.arb +++ b/lib/l10n/app_en.arb @@ -336,10 +336,6 @@ "@energyShort": { "description": "The first letter or short name of the word 'Energy', used in overviews" }, - "kcal": "kcal", - "@kcal": { - "description": "Energy in a meal in kilocalories, kcal" - }, "macronutrients": "Macronutrients", "@macronutrients": {}, "planned": "Planned", @@ -350,6 +346,7 @@ "@logged": { "description": "Header for the column of 'logged' nutritional values, i.e. what was eaten" }, + "loggedToday": "LoggedToday", "weekAverage": "7 day average", "@weekAverage": { "description": "Header for the column of '7 day average' nutritional values, i.e. what was logged last week" @@ -366,6 +363,18 @@ "@total": { "description": "Label used for total sums of e.g. calories or similar" }, + "kcal": "kcal", + "@kcal": { + "description": "Energy in a meal in kilocalories, kcal" + }, + "kcalValue": "{value} kcal", + "@kcalValue": { + "description": "A value in kcal, e.g. 500 kcal", + "type": "text", + "placeholders": { + "value": {} + } + }, "kJ": "kJ", "@kJ": { "description": "Energy in a meal in kilo joules, kJ" @@ -374,6 +383,22 @@ "@g": { "description": "Abbreviation for gram" }, + "gValue": "{value} g", + "@gValue": { + "description": "A value in grams, e.g. 5 g", + "type": "text", + "placeholders": { + "value": {} + } + }, + "percentValue": "{value} %", + "@percentValue": { + "description": "A value in percent, e.g. 10 %", + "type": "text", + "placeholders": { + "value": {} + } + }, "protein": "Protein", "@protein": {}, "proteinShort": "P", diff --git a/lib/models/exercises/exercise_api.freezed.dart b/lib/models/exercises/exercise_api.freezed.dart index 2e41abb3..22c895ed 100644 --- a/lib/models/exercises/exercise_api.freezed.dart +++ b/lib/models/exercises/exercise_api.freezed.dart @@ -12,7 +12,7 @@ part of 'exercise_api.dart'; T _$identity(T value) => value; final _privateConstructorUsedError = UnsupportedError( - 'It seems like you constructed your class using `MyClass._()`. This constructor is only meant to be used by freezed and you are not supposed to need it nor use it.\nPlease check the documentation here for more information: https://github.com/rrousselGit/freezed#custom-getters-and-methods'); + 'It seems like you constructed your class using `MyClass._()`. This constructor is only meant to be used by freezed and you are not supposed to need it nor use it.\nPlease check the documentation here for more information: https://github.com/rrousselGit/freezed#adding-getters-and-methods-to-our-models'); ExerciseApiData _$ExerciseApiDataFromJson(Map json) { return _ExerciseBaseData.fromJson(json); diff --git a/lib/models/exercises/ingredient_api.freezed.dart b/lib/models/exercises/ingredient_api.freezed.dart index 32131031..f75f492b 100644 --- a/lib/models/exercises/ingredient_api.freezed.dart +++ b/lib/models/exercises/ingredient_api.freezed.dart @@ -12,7 +12,7 @@ part of 'ingredient_api.dart'; T _$identity(T value) => value; final _privateConstructorUsedError = UnsupportedError( - 'It seems like you constructed your class using `MyClass._()`. This constructor is only meant to be used by freezed and you are not supposed to need it nor use it.\nPlease check the documentation here for more information: https://github.com/rrousselGit/freezed#custom-getters-and-methods'); + 'It seems like you constructed your class using `MyClass._()`. This constructor is only meant to be used by freezed and you are not supposed to need it nor use it.\nPlease check the documentation here for more information: https://github.com/rrousselGit/freezed#adding-getters-and-methods-to-our-models'); IngredientApiSearchDetails _$IngredientApiSearchDetailsFromJson(Map json) { return _IngredientApiSearchDetails.fromJson(json); diff --git a/lib/models/nutrition/log.dart b/lib/models/nutrition/log.dart index 8a30650d..027b3033 100644 --- a/lib/models/nutrition/log.dart +++ b/lib/models/nutrition/log.dart @@ -69,7 +69,7 @@ class Log { Log.fromMealItem(MealItem mealItem, this.planId, this.mealId, [DateTime? dateTime]) { ingredientId = mealItem.ingredientId; - ingredientObj = mealItem.ingredientObj; + ingredientObj = mealItem.ingredient; weightUnitId = mealItem.weightUnitId; datetime = dateTime ?? DateTime.now(); amount = mealItem.amount; diff --git a/lib/models/nutrition/meal.dart b/lib/models/nutrition/meal.dart index eff3f3d1..d0a6b53a 100644 --- a/lib/models/nutrition/meal.dart +++ b/lib/models/nutrition/meal.dart @@ -16,10 +16,11 @@ * along with this program. If not, see . */ -import 'package:clock/clock.dart'; import 'package:flutter/material.dart'; import 'package:json_annotation/json_annotation.dart'; import 'package:wger/helpers/json.dart'; +import 'package:wger/helpers/misc.dart'; +import 'package:wger/models/nutrition/log.dart'; import 'package:wger/models/nutrition/meal_item.dart'; import 'package:wger/models/nutrition/nutritional_values.dart'; @@ -39,33 +40,32 @@ class Meal { @JsonKey(name: 'name') late String name; - @JsonKey(includeFromJson: false, includeToJson: false, name: 'meal_items', defaultValue: []) + @JsonKey(includeFromJson: false, includeToJson: false, defaultValue: []) List mealItems = []; + @JsonKey(includeFromJson: false, includeToJson: false, defaultValue: []) + List diaryEntries = []; + Meal({ this.id, int? plan, - TimeOfDay? time, + this.time, String? name, List? mealItems, + List? diaryEntries, }) { if (plan != null) { planId = plan; } this.mealItems = mealItems ?? []; - - this.time = time ?? TimeOfDay.fromDateTime(clock.now()); + this.diaryEntries = diaryEntries ?? []; + //this.time = time ?? TimeOfDay.fromDateTime(clock.now()); this.name = name ?? ''; } - // Boilerplate - factory Meal.fromJson(Map json) => _$MealFromJson(json); - - Map toJson() => _$MealToJson(this); - /// Calculations - NutritionalValues get nutritionalValues { + NutritionalValues get plannedNutritionalValues { // This is already done on the server. It might be better to read it from there. var out = NutritionalValues(); @@ -75,4 +75,20 @@ class Meal { return out; } + + /// Returns the logged nutritional values for today + NutritionalValues get loggedNutritionalValuesToday { + var out = NutritionalValues(); + + for (final item in diaryEntries.where((l) => l.datetime.isSameDayAs(DateTime.now()))) { + out += item.nutritionalValues; + } + + return out; + } + + // Boilerplate + factory Meal.fromJson(Map json) => _$MealFromJson(json); + + Map toJson() => _$MealToJson(this); } diff --git a/lib/models/nutrition/meal_item.dart b/lib/models/nutrition/meal_item.dart index c6ae6773..d484283f 100644 --- a/lib/models/nutrition/meal_item.dart +++ b/lib/models/nutrition/meal_item.dart @@ -36,7 +36,7 @@ class MealItem { late int ingredientId; @JsonKey(includeFromJson: false, includeToJson: false) - late Ingredient ingredientObj; + late Ingredient ingredient; @JsonKey(required: false, name: 'weight_unit') int? weightUnitId; @@ -59,7 +59,7 @@ class MealItem { this.mealId = mealId; } if (ingredient != null) { - ingredientObj = ingredient; + this.ingredient = ingredient; ingredientId = ingredient.id; } } @@ -79,14 +79,14 @@ class MealItem { //final weight = this.weightUnit == null ? amount : amount * weightUnit.amount * weightUnit.grams; final weight = amount; - out.energy = ingredientObj.energy * weight / 100; - out.protein = ingredientObj.protein * weight / 100; - out.carbohydrates = ingredientObj.carbohydrates * weight / 100; - out.carbohydratesSugar = ingredientObj.carbohydratesSugar * weight / 100; - out.fat = ingredientObj.fat * weight / 100; - out.fatSaturated = ingredientObj.fatSaturated * weight / 100; - out.fibres = ingredientObj.fibres * weight / 100; - out.sodium = ingredientObj.sodium * weight / 100; + out.energy = ingredient.energy * weight / 100; + out.protein = ingredient.protein * weight / 100; + out.carbohydrates = ingredient.carbohydrates * weight / 100; + out.carbohydratesSugar = ingredient.carbohydratesSugar * weight / 100; + out.fat = ingredient.fat * weight / 100; + out.fatSaturated = ingredient.fatSaturated * weight / 100; + out.fibres = ingredient.fibres * weight / 100; + out.sodium = ingredient.sodium * weight / 100; return out; } diff --git a/lib/models/nutrition/nutritional_plan.dart b/lib/models/nutrition/nutritional_plan.dart index e5493fe5..7acae977 100644 --- a/lib/models/nutrition/nutritional_plan.dart +++ b/lib/models/nutrition/nutritional_plan.dart @@ -58,7 +58,7 @@ class NutritionalPlan { List meals = []; @JsonKey(includeFromJson: false, includeToJson: false, defaultValue: []) - List logs = []; + List diaryEntries = []; NutritionalPlan({ this.id, @@ -70,10 +70,10 @@ class NutritionalPlan { this.goalCarbohydrates, this.goalFat, List? meals, - List? logs, + List? diaryEntries, }) { this.meals = meals ?? []; - this.logs = logs ?? []; + this.diaryEntries = diaryEntries ?? []; } NutritionalPlan.empty() { @@ -97,7 +97,7 @@ class NutritionalPlan { var out = NutritionalValues(); for (final meal in meals) { - out += meal.nutritionalValues; + out += meal.plannedNutritionalValues; } return out; @@ -114,7 +114,7 @@ class NutritionalPlan { final currentDate = DateTime.now(); final sevenDaysAgo = currentDate.subtract(const Duration(days: 7)); - final entries = logs.where((obj) { + final entries = diaryEntries.where((obj) { final DateTime objDate = obj.datetime; return objDate.isAfter(sevenDaysAgo) && objDate.isBefore(currentDate); }).toList(); @@ -151,7 +151,7 @@ class NutritionalPlan { Map get logEntriesValues { final out = {}; - for (final log in logs) { + for (final log in diaryEntries) { final date = DateTime(log.datetime.year, log.datetime.month, log.datetime.day); if (!out.containsKey(date)) { @@ -175,7 +175,7 @@ class NutritionalPlan { /// Returns the nutritional logs for the given date List getLogsForDate(DateTime date) { final List out = []; - for (final log in logs) { + for (final log in diaryEntries) { final dateKey = DateTime(date.year, date.month, date.day); final logKey = DateTime(log.datetime.year, log.datetime.month, log.datetime.day); @@ -203,4 +203,14 @@ class NutritionalPlan { } return out; } + + Meal pseudoMealOthers(String name) { + return Meal( + id: -1, + plan: id, + name: name, + time: null, + diaryEntries: diaryEntries.where((e) => e.mealId == null).toList(), + ); + } } diff --git a/lib/models/nutrition/nutritional_plan.g.dart b/lib/models/nutrition/nutritional_plan.g.dart index bff43f9a..b0d499e8 100644 --- a/lib/models/nutrition/nutritional_plan.g.dart +++ b/lib/models/nutrition/nutritional_plan.g.dart @@ -22,13 +22,13 @@ NutritionalPlan _$NutritionalPlanFromJson(Map json) { ); return NutritionalPlan( id: json['id'] as int?, + description: json['description'] as String, + creationDate: DateTime.parse(json['creation_date'] as String), onlyLogging: json['only_logging'] as bool? ?? false, goalEnergy: json['goal_energy'] as num?, goalProtein: json['goal_protein'] as num?, goalCarbohydrates: json['goal_carbohydrates'] as num?, goalFat: json['goal_fat'] as num?, - description: json['description'] as String, - creationDate: DateTime.parse(json['creation_date'] as String), ); } diff --git a/lib/providers/nutrition.dart b/lib/providers/nutrition.dart index 2fd2cb2a..3b182746 100644 --- a/lib/providers/nutrition.dart +++ b/lib/providers/nutrition.dart @@ -106,9 +106,7 @@ class NutritionPlansProvider with ChangeNotifier { /// Fetches and sets all plans fully, i.e. with all corresponding child objects Future fetchAndSetAllPlansFull() async { final data = await baseProvider.fetchPaginated(baseProvider.makeUrl(_nutritionalPlansPath)); - for (final entry in data) { - await fetchAndSetPlanFull(entry['id']); - } + await Future.wait(data.map((e) => fetchAndSetPlanFull(e['id'])).toList()); } /// Fetches and sets the given nutritional plan @@ -153,7 +151,7 @@ class NutritionPlansProvider with ChangeNotifier { final image = IngredientImage.fromJson(mealItemData['image']); ingredient.image = image; } - mealItem.ingredientObj = ingredient; + mealItem.ingredient = ingredient; mealItems.add(mealItem); } meal.mealItems = mealItems; @@ -163,6 +161,9 @@ class NutritionPlansProvider with ChangeNotifier { // Logs await fetchAndSetLogs(plan); + for (final meal in meals) { + meal.diaryEntries = plan.diaryEntries.where((e) => e.mealId == meal.id).toList(); + } // ... and done notifyListeners(); @@ -255,7 +256,7 @@ class NutritionPlansProvider with ChangeNotifier { final data = await baseProvider.post(mealItem.toJson(), baseProvider.makeUrl(_mealItemPath)); mealItem = MealItem.fromJson(data); - mealItem.ingredientObj = await fetchIngredient(mealItem.ingredientId); + mealItem.ingredient = await fetchIngredient(mealItem.ingredientId); meal.mealItems.add(mealItem); notifyListeners(); @@ -385,7 +386,7 @@ class NutritionPlansProvider with ChangeNotifier { baseProvider.makeUrl(_nutritionDiaryPath), ); log.id = data['id']; - plan.logs.add(log); + plan.diaryEntries.add(log); } notifyListeners(); } @@ -393,12 +394,12 @@ class NutritionPlansProvider with ChangeNotifier { /// Log custom ingredient to nutrition diary Future logIngredientToDiary(MealItem mealItem, int planId, [DateTime? dateTime]) async { final plan = findById(planId); - mealItem.ingredientObj = await fetchIngredient(mealItem.ingredientId); + mealItem.ingredient = await fetchIngredient(mealItem.ingredientId); final Log log = Log.fromMealItem(mealItem, plan.id!, null, dateTime); final data = await baseProvider.post(log.toJson(), baseProvider.makeUrl(_nutritionDiaryPath)); log.id = data['id']; - plan.logs.add(log); + plan.diaryEntries.add(log); notifyListeners(); } @@ -407,7 +408,7 @@ class NutritionPlansProvider with ChangeNotifier { await baseProvider.deleteRequest(_nutritionDiaryPath, logId); final plan = findById(planId); - plan.logs.removeWhere((element) => element.id == logId); + plan.diaryEntries.removeWhere((element) => element.id == logId); notifyListeners(); } @@ -420,12 +421,12 @@ class NutritionPlansProvider with ChangeNotifier { ), ); - plan.logs = []; + plan.diaryEntries = []; for (final logData in data) { final log = Log.fromJson(logData); final ingredient = await fetchIngredient(log.ingredientId); log.ingredientObj = ingredient; - plan.logs.add(log); + plan.diaryEntries.add(log); } notifyListeners(); } diff --git a/lib/widgets/dashboard/widgets.dart b/lib/widgets/dashboard/widgets.dart index e80465d4..5c2e7a60 100644 --- a/lib/widgets/dashboard/widgets.dart +++ b/lib/widgets/dashboard/widgets.dart @@ -85,16 +85,16 @@ class _DashboardNutritionWidgetState extends State { mainAxisSize: MainAxisSize.min, children: [ MutedText( - '${AppLocalizations.of(context).energyShort} ${meal.nutritionalValues.energy.toStringAsFixed(0)}${AppLocalizations.of(context).kcal}'), + '${AppLocalizations.of(context).energyShort} ${meal.plannedNutritionalValues.energy.toStringAsFixed(0)}${AppLocalizations.of(context).kcal}'), const MutedText(' / '), MutedText( - '${AppLocalizations.of(context).proteinShort} ${meal.nutritionalValues.protein.toStringAsFixed(0)}${AppLocalizations.of(context).g}'), + '${AppLocalizations.of(context).proteinShort} ${meal.plannedNutritionalValues.protein.toStringAsFixed(0)}${AppLocalizations.of(context).g}'), const MutedText(' / '), MutedText( - '${AppLocalizations.of(context).carbohydratesShort} ${meal.nutritionalValues.carbohydrates.toStringAsFixed(0)}${AppLocalizations.of(context).g}'), + '${AppLocalizations.of(context).carbohydratesShort} ${meal.plannedNutritionalValues.carbohydrates.toStringAsFixed(0)}${AppLocalizations.of(context).g}'), const MutedText(' / '), MutedText( - '${AppLocalizations.of(context).fatShort} ${meal.nutritionalValues.fat.toStringAsFixed(0)}${AppLocalizations.of(context).g} '), + '${AppLocalizations.of(context).fatShort} ${meal.plannedNutritionalValues.fat.toStringAsFixed(0)}${AppLocalizations.of(context).g} '), ], ), IconButton( @@ -127,7 +127,7 @@ class _DashboardNutritionWidgetState extends State { children: [ Flexible( child: Text( - item.ingredientObj.name, + item.ingredient.name, overflow: TextOverflow.ellipsis, ), ), diff --git a/lib/widgets/nutrition/charts.dart b/lib/widgets/nutrition/charts.dart index b7f9b257..5a6b0c09 100644 --- a/lib/widgets/nutrition/charts.dart +++ b/lib/widgets/nutrition/charts.dart @@ -201,14 +201,11 @@ class NutritionalDiaryChartWidgetFlState extends State createState() => MealDiaryBarChartWidgetState(); +} + +class MealDiaryBarChartWidgetState extends State { + Widget bottomTitles(double value, TitleMeta meta) { + String text; + switch (value.toInt()) { + case 0: + text = AppLocalizations.of(context).protein; + break; + case 1: + text = AppLocalizations.of(context).carbohydrates; + break; + case 2: + text = AppLocalizations.of(context).fat; + break; + case 3: + text = AppLocalizations.of(context).energy; + break; + default: + text = ''; + break; + } + return SideTitleWidget( + axisSide: meta.axisSide, + child: Text( + text, + style: const TextStyle(fontSize: 10), + ), + ); + } + + Widget leftTitles(double value, TitleMeta meta) => SideTitleWidget( + axisSide: meta.axisSide, + child: Text( + AppLocalizations.of(context).percentValue(value.toStringAsFixed(0)), + style: const TextStyle(fontSize: 10), + ), + ); + + @override + Widget build(BuildContext context) { + return AspectRatio( + aspectRatio: 2.5, + child: Padding( + padding: const EdgeInsets.only(top: 16), + child: LayoutBuilder( + builder: (context, constraints) { + final barsSpace = 1.0 * constraints.maxWidth / 400; + final barsWidth = 10.0 * constraints.maxWidth / 400; + return BarChart( + BarChartData( + alignment: BarChartAlignment.center, + barTouchData: BarTouchData( + enabled: false, + ), + titlesData: FlTitlesData( + show: true, + bottomTitles: AxisTitles( + sideTitles: SideTitles( + showTitles: true, + reservedSize: 48, + getTitlesWidget: bottomTitles, + ), + ), + leftTitles: AxisTitles( + sideTitles: SideTitles( + showTitles: true, + reservedSize: 40, + getTitlesWidget: leftTitles, + ), + ), + topTitles: const AxisTitles( + sideTitles: SideTitles(showTitles: false), + ), + rightTitles: const AxisTitles( + sideTitles: SideTitles(showTitles: false), + ), + ), + gridData: FlGridData( + show: true, + checkToShowHorizontalLine: (value) => value % 10 == 0, + getDrawingHorizontalLine: (value) => const FlLine( + color: Colors.black, + strokeWidth: 1, + ), + drawVerticalLine: false, + ), + borderData: FlBorderData( + show: false, + ), + groupsSpace: 30, + // groupsSpace: barsSpace, + barGroups: [ + BarChartGroupData( + x: 3, + barsSpace: barsSpace, + barRods: [ + BarChartRodData( + toY: widget._logged.energy / widget._planned.energy * 100, + color: LIST_OF_COLORS3.first, + width: barsWidth, + ), + ], + ), + BarChartGroupData( + x: 0, + barsSpace: barsSpace, + barRods: [ + BarChartRodData( + toY: widget._logged.protein / widget._planned.protein * 100, + color: LIST_OF_COLORS3.first, + width: barsWidth, + ), + ], + ), + BarChartGroupData( + x: 1, + barsSpace: barsSpace, + barRods: [ + BarChartRodData( + toY: widget._logged.carbohydrates / widget._planned.carbohydrates * 100, + color: LIST_OF_COLORS3.first, + width: barsWidth, + ), + ], + ), + BarChartGroupData( + x: 2, + barsSpace: barsSpace, + barRods: [ + BarChartRodData( + toY: widget._logged.fat / widget._planned.fat * 100, + color: LIST_OF_COLORS3.first, + width: barsWidth, + ), + ], + ), + ], + // barGroups: getData(barsWidth, barsSpace), + ), + ); + }, + ), + ), + ); + } +} + class FlNutritionalDiaryChartWidget extends StatefulWidget { final NutritionalPlan _nutritionalPlan; @@ -492,7 +650,7 @@ class FlNutritionalDiaryChartWidgetState extends State[ TextSpan( - text: '${(rod.toY - 1).toStringAsFixed(0)} kcal', + text: AppLocalizations.of(context).kcalValue((rod.toY - 1).toStringAsFixed(0)), style: const TextStyle( color: Colors.white, fontWeight: FontWeight.w500, diff --git a/lib/widgets/nutrition/forms.dart b/lib/widgets/nutrition/forms.dart index 1a4242eb..534c9cf4 100644 --- a/lib/widgets/nutrition/forms.dart +++ b/lib/widgets/nutrition/forms.dart @@ -203,14 +203,14 @@ class MealItemForm extends StatelessWidget { return Card( child: ListTile( onTap: () { - _ingredientController.text = _listMealItems[index].ingredientObj.name; + _ingredientController.text = _listMealItems[index].ingredient.name; _ingredientIdController.text = - _listMealItems[index].ingredientObj.id.toString(); + _listMealItems[index].ingredient.id.toString(); _amountController.text = _listMealItems[index].amount.toStringAsFixed(0); _mealItem.ingredientId = _listMealItems[index].ingredientId; _mealItem.amount = _listMealItems[index].amount; }, - title: Text(_listMealItems[index].ingredientObj.name), + title: Text(_listMealItems[index].ingredient.name), subtitle: Text('${_listMealItems[index].amount.toStringAsFixed(0)}$unit'), trailing: const Icon(Icons.copy), ), @@ -242,7 +242,7 @@ class IngredientLogForm extends StatelessWidget { @override Widget build(BuildContext context) { - final diaryEntries = _plan.logs; + final diaryEntries = _plan.diaryEntries; final String unit = AppLocalizations.of(context).g; return Container( @@ -338,7 +338,7 @@ class IngredientLogForm extends StatelessWidget { _mealItem.ingredientId = diaryEntries[index].ingredientId; _mealItem.amount = diaryEntries[index].amount; }, - title: Text(_plan.logs[index].ingredientObj.name), + title: Text(_plan.diaryEntries[index].ingredientObj.name), subtitle: Text('${diaryEntries[index].amount.toStringAsFixed(0)}$unit'), trailing: const Icon(Icons.copy), ), diff --git a/lib/widgets/nutrition/helpers.dart b/lib/widgets/nutrition/helpers.dart index 9aede4fa..e135216c 100644 --- a/lib/widgets/nutrition/helpers.dart +++ b/lib/widgets/nutrition/helpers.dart @@ -28,22 +28,17 @@ List getMutedNutritionalValues(NutritionalValues values, BuildContext co '${values.energy.toStringAsFixed(0)}' '${AppLocalizations.of(context).kcal}', ), - Text( - '${AppLocalizations.of(context).protein}: ' - '${values.protein.toStringAsFixed(0)}' - '${AppLocalizations.of(context).g}', - ), + Text('${AppLocalizations.of(context).protein}: ' + '${AppLocalizations.of(context).gValue(values.protein.toStringAsFixed(0))}'), Text( '${AppLocalizations.of(context).carbohydrates}: ' - '${values.carbohydrates.toStringAsFixed(0)} ' - '${AppLocalizations.of(context).g} ' - '(${values.carbohydratesSugar.toStringAsFixed(0)} ${AppLocalizations.of(context).sugars})', + '${AppLocalizations.of(context).gValue(values.carbohydrates.toStringAsFixed(0))} ' + '(${AppLocalizations.of(context).gValue(values.carbohydratesSugar.toStringAsFixed(0))} ${AppLocalizations.of(context).sugars})', ), Text( '${AppLocalizations.of(context).fat}: ' - '${values.fat.toStringAsFixed(0)}' - '${AppLocalizations.of(context).g} ' - '(${values.fatSaturated.toStringAsFixed(0)} ${AppLocalizations.of(context).saturatedFat})', + '${AppLocalizations.of(context).gValue(values.fat.toStringAsFixed(0))} ' + '(${AppLocalizations.of(context).gValue(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 a9d71c13..78dc541b 100644 --- a/lib/widgets/nutrition/meal.dart +++ b/lib/widgets/nutrition/meal.dart @@ -26,6 +26,7 @@ import 'package:wger/models/nutrition/meal_item.dart'; import 'package:wger/providers/nutrition.dart'; import 'package:wger/screens/form_screen.dart'; import 'package:wger/widgets/core/core.dart'; +import 'package:wger/widgets/nutrition/charts.dart'; import 'package:wger/widgets/nutrition/forms.dart'; import 'package:wger/widgets/nutrition/helpers.dart'; @@ -43,7 +44,7 @@ class MealWidget extends StatefulWidget { } class _MealWidgetState extends State { - bool _showingDetails = false; + bool _showDetails = false; bool _editing = false; void _toggleEditing() { @@ -54,7 +55,7 @@ class _MealWidgetState extends State { void _toggleDetails() { setState(() { - _showingDetails = !_showingDetails; + _showDetails = !_showDetails; }); } @@ -67,11 +68,12 @@ class _MealWidgetState extends State { crossAxisAlignment: CrossAxisAlignment.start, children: [ MealHeader( - editing: _editing, - toggleEditing: _toggleEditing, - showingDetails: _showingDetails, - toggleDetails: _toggleDetails, - meal: widget._meal), + editing: _editing, + toggleEditing: _toggleEditing, + showingDetails: _showDetails, + toggleDetails: _toggleDetails, + meal: widget._meal, + ), if (_editing) Padding( padding: const EdgeInsets.symmetric(vertical: 8), @@ -128,8 +130,19 @@ class _MealWidgetState extends State { ], )), const Divider(), - ...widget._meal.mealItems - .map((item) => MealItemWidget(item, _showingDetails, _editing)), + ...widget._meal.mealItems.map((item) => MealItemWidget(item, _showDetails, _editing)), + Center( + child: Text( + AppLocalizations.of(context).loggedToday, + style: Theme.of(context).textTheme.titleMedium, + ), + ), + if (widget._meal.plannedNutritionalValues.energy != 0 && + widget._meal.loggedNutritionalValuesToday.energy != 0) + MealDiaryBarChartWidget( + planned: widget._meal.plannedNutritionalValues, + logged: widget._meal.loggedNutritionalValuesToday, + ), ], ), ), @@ -157,12 +170,12 @@ class MealItemWidget extends StatelessWidget { final values = _item.nutritionalValues; return ListTile( - leading: _item.ingredientObj.image != null + leading: _item.ingredient.image != null ? GestureDetector( - child: CircleAvatar(backgroundImage: NetworkImage(_item.ingredientObj.image!.image)), + child: CircleAvatar(backgroundImage: NetworkImage(_item.ingredient.image!.image)), onTap: () async { - if (_item.ingredientObj.image!.objectUrl != '') { - return launchURL(_item.ingredientObj.image!.objectUrl, context); + if (_item.ingredient.image!.objectUrl != '') { + return launchURL(_item.ingredient.image!.objectUrl, context); } else { return; } @@ -170,7 +183,7 @@ class MealItemWidget extends StatelessWidget { ) : const CircleIconAvatar(Icon(Icons.image, color: Colors.grey)), title: Text( - '${_item.amount.toStringAsFixed(0)}$unit ${_item.ingredientObj.name}', + '${_item.amount.toStringAsFixed(0)}$unit ${_item.ingredient.name}', overflow: TextOverflow.ellipsis, ), subtitle: Column( @@ -238,10 +251,11 @@ class MealHeader extends StatelessWidget { _meal.name, style: Theme.of(context).textTheme.titleMedium, ), - Text( - _meal.time!.format(context), - style: Theme.of(context).textTheme.headlineSmall, - ) + if (_meal.time != null) + Text( + _meal.time!.format(context), + style: Theme.of(context).textTheme.headlineSmall, + ) ], ) : Text( diff --git a/lib/widgets/nutrition/nutritional_plan_detail.dart b/lib/widgets/nutrition/nutritional_plan_detail.dart index ac6611ac..ecf172b7 100644 --- a/lib/widgets/nutrition/nutritional_plan_detail.dart +++ b/lib/widgets/nutrition/nutritional_plan_detail.dart @@ -52,7 +52,14 @@ class NutritionalPlanDetailWidget extends StatelessWidget { delegate: SliverChildListDelegate( [ const SizedBox(height: 10), - ..._nutritionalPlan.meals.map((meal) => MealWidget(meal, _nutritionalPlan.allMealItems)), + ..._nutritionalPlan.meals.map((meal) => MealWidget( + meal, + _nutritionalPlan.allMealItems, + )), + MealWidget( + _nutritionalPlan.pseudoMealOthers('Other logs'), + _nutritionalPlan.allMealItems, + ), Padding( padding: const EdgeInsets.all(8.0), child: ElevatedButton( @@ -211,7 +218,7 @@ class NutritionalPlanDetailWidget extends StatelessWidget { ), const Padding(padding: EdgeInsets.all(8.0)), Text( - '${AppLocalizations.of(context).planned} / ${AppLocalizations.of(context).logged} / ${AppLocalizations.of(context).weekAverage}', + AppLocalizations.of(context).logged, textAlign: TextAlign.center, style: Theme.of(context).textTheme.titleLarge, ), @@ -252,7 +259,7 @@ class NutritionalPlanDetailWidget extends StatelessWidget { Text( AppLocalizations.of(context).nutritionalDiary, textAlign: TextAlign.center, - style: Theme.of(context).textTheme.titleLarge, + style: Theme.of(context).textTheme.titleSmall, ), Container( padding: const EdgeInsets.all(15), diff --git a/test/nutrition/nutritional_plan_model_test.dart b/test/nutrition/nutritional_plan_model_test.dart index 8eb86cc5..5dd27bd1 100644 --- a/test/nutrition/nutritional_plan_model_test.dart +++ b/test/nutrition/nutritional_plan_model_test.dart @@ -38,7 +38,7 @@ void main() { test('Test the nutritionalValues method for meals', () { final meal = plan.meals.first; final values = NutritionalValues.values(518.75, 5.75, 17.5, 3.5, 29.0, 13.75, 49.5, 0.5); - expect(meal.nutritionalValues, values); + expect(meal.plannedNutritionalValues, values); }); test('Test that the getter returns all meal items for a plan', () { diff --git a/test_data/nutritional_plans.dart b/test_data/nutritional_plans.dart index 79441d19..d729beb1 100644 --- a/test_data/nutritional_plans.dart +++ b/test_data/nutritional_plans.dart @@ -71,19 +71,19 @@ NutritionalPlan getNutritionalPlan() { ingredientId: 1, amount: 100, ); - mealItem1.ingredientObj = ingredient1; + mealItem1.ingredient = ingredient1; final mealItem2 = MealItem( ingredientId: 2, amount: 75, ); - mealItem2.ingredientObj = ingredient2; + mealItem2.ingredient = ingredient2; final mealItem3 = MealItem( ingredientId: 3, amount: 300, ); - mealItem3.ingredientObj = ingredient3; + mealItem3.ingredient = ingredient3; final meal1 = Meal( id: 1, @@ -109,9 +109,9 @@ NutritionalPlan getNutritionalPlan() { plan.meals = [meal1, meal2]; // Add logs - plan.logs.add(Log.fromMealItem(mealItem1, 1, 1, DateTime(2021, 6, 1))); - plan.logs.add(Log.fromMealItem(mealItem2, 1, 1, DateTime(2021, 6, 1))); - plan.logs.add(Log.fromMealItem(mealItem3, 1, 1, DateTime(2021, 6, 10))); + plan.diaryEntries.add(Log.fromMealItem(mealItem1, 1, 1, DateTime(2021, 6, 1))); + plan.diaryEntries.add(Log.fromMealItem(mealItem2, 1, 1, DateTime(2021, 6, 1))); + plan.diaryEntries.add(Log.fromMealItem(mealItem3, 1, 1, DateTime(2021, 6, 10))); return plan; }