Add chart for percentage of logged nutritional values for meals

This commit is contained in:
Roland Geider
2024-02-23 19:19:13 +01:00
parent 4b6a0a8875
commit 9b0985fbf3
18 changed files with 333 additions and 107 deletions

View File

@@ -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: [

View File

@@ -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",

View File

@@ -12,7 +12,7 @@ part of 'exercise_api.dart';
T _$identity<T>(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<String, dynamic> json) {
return _ExerciseBaseData.fromJson(json);

View File

@@ -12,7 +12,7 @@ part of 'ingredient_api.dart';
T _$identity<T>(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<String, dynamic> json) {
return _IngredientApiSearchDetails.fromJson(json);

View File

@@ -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;

View File

@@ -16,10 +16,11 @@
* along with this program. If not, see <http://www.gnu.org/licenses/>.
*/
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<MealItem> mealItems = [];
@JsonKey(includeFromJson: false, includeToJson: false, defaultValue: [])
List<Log> diaryEntries = [];
Meal({
this.id,
int? plan,
TimeOfDay? time,
this.time,
String? name,
List<MealItem>? mealItems,
List<Log>? 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<String, dynamic> json) => _$MealFromJson(json);
Map<String, dynamic> 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<String, dynamic> json) => _$MealFromJson(json);
Map<String, dynamic> toJson() => _$MealToJson(this);
}

View File

@@ -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;
}

View File

@@ -58,7 +58,7 @@ class NutritionalPlan {
List<Meal> meals = [];
@JsonKey(includeFromJson: false, includeToJson: false, defaultValue: [])
List<Log> logs = [];
List<Log> diaryEntries = [];
NutritionalPlan({
this.id,
@@ -70,10 +70,10 @@ class NutritionalPlan {
this.goalCarbohydrates,
this.goalFat,
List<Meal>? meals,
List<Log>? logs,
List<Log>? 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<DateTime, NutritionalValues> get logEntriesValues {
final out = <DateTime, NutritionalValues>{};
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<Log> getLogsForDate(DateTime date) {
final List<Log> 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(),
);
}
}

View File

@@ -22,13 +22,13 @@ NutritionalPlan _$NutritionalPlanFromJson(Map<String, dynamic> 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),
);
}

View File

@@ -106,9 +106,7 @@ class NutritionPlansProvider with ChangeNotifier {
/// Fetches and sets all plans fully, i.e. with all corresponding child objects
Future<void> 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<void> 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();
}

View File

@@ -85,16 +85,16 @@ class _DashboardNutritionWidgetState extends State<DashboardNutritionWidget> {
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<DashboardNutritionWidget> {
children: [
Flexible(
child: Text(
item.ingredientObj.name,
item.ingredient.name,
overflow: TextOverflow.ellipsis,
),
),

View File

@@ -201,14 +201,11 @@ class NutritionalDiaryChartWidgetFlState extends State<NutritionalDiaryChartWidg
if (value == meta.max) {
return Container();
}
const style = TextStyle(
fontSize: 10,
);
return SideTitleWidget(
axisSide: meta.axisSide,
child: Text(
meta.formattedValue,
style: style,
AppLocalizations.of(context).gValue(meta.formattedValue),
style: const TextStyle(fontSize: 10),
),
);
}
@@ -391,6 +388,167 @@ class NutritionalDiaryChartWidgetFlState extends State<NutritionalDiaryChartWidg
}
}
class MealDiaryBarChartWidget extends StatefulWidget {
const MealDiaryBarChartWidget({
super.key,
required NutritionalValues logged,
required NutritionalValues planned,
}) : _logged = logged,
_planned = planned;
final NutritionalValues _logged;
final NutritionalValues _planned;
@override
State<StatefulWidget> createState() => MealDiaryBarChartWidgetState();
}
class MealDiaryBarChartWidgetState extends State<MealDiaryBarChartWidget> {
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<FlNutritionalDiaryChartWi
return SideTitleWidget(
axisSide: meta.axisSide,
child: Text(
'${meta.formattedValue} kcal',
AppLocalizations.of(context).kcalValue(meta.formattedValue),
style: style,
),
);
@@ -516,7 +674,7 @@ class FlNutritionalDiaryChartWidgetState extends State<FlNutritionalDiaryChartWi
),
children: <TextSpan>[
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,

View File

@@ -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),
),

View File

@@ -28,22 +28,17 @@ List<Widget> 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;

View File

@@ -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<MealWidget> {
bool _showingDetails = false;
bool _showDetails = false;
bool _editing = false;
void _toggleEditing() {
@@ -54,7 +55,7 @@ class _MealWidgetState extends State<MealWidget> {
void _toggleDetails() {
setState(() {
_showingDetails = !_showingDetails;
_showDetails = !_showDetails;
});
}
@@ -67,11 +68,12 @@ class _MealWidgetState extends State<MealWidget> {
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<MealWidget> {
],
)),
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(

View File

@@ -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),

View File

@@ -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', () {

View File

@@ -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;
}