Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
27 commits
Select commit Hold shift + click to select a range
088f31b
cleanup
Dieterbe Apr 21, 2024
580b053
use improved barchart widget on nutrition dashboard widget
Dieterbe Apr 27, 2024
db83b13
remove aspectratio and padding from chart widget.
Dieterbe Apr 27, 2024
3b70cb3
nutrition dashboard widget: use 3 layers of detail
Dieterbe Apr 27, 2024
b7df869
display caloric balance on nutrition dashboard widget
Dieterbe Apr 27, 2024
54e2aa4
suggestion: replace chart and horizontal table with vertical table
Dieterbe Apr 27, 2024
d2c8e4a
remove FlNutritionalDiaryChartWidget, it is no longer used
Dieterbe Apr 28, 2024
8c5eded
new "progress towards goals" widget for nutrional plans
Dieterbe Apr 28, 2024
b756e5e
test fix
Dieterbe Apr 30, 2024
820dee2
move nutritional diary table into separate file, clean up a bit
Dieterbe May 3, 2024
00b5cc2
tweak design
Dieterbe May 3, 2024
36697a6
proper nullable "nutritional goals" with inference
Dieterbe May 3, 2024
84f43bb
trying to fix tests by using freezed
Dieterbe May 4, 2024
bffaa9a
Revert freezed attempt. it needs a constructor without args... :/
Dieterbe May 4, 2024
faa417d
fix test
Dieterbe May 4, 2024
1ae3eaf
more tests
Dieterbe May 4, 2024
2347657
fixes
Dieterbe May 4, 2024
2a9ac23
move MacronutrientsTable into its own file
Dieterbe May 5, 2024
9a67a07
fix: use goal derived from goals or meals
Dieterbe May 5, 2024
e9d7a39
nutrition plan details: use 3 levels of detail
Dieterbe May 7, 2024
578d56a
fix tests, introduce golden tests
Dieterbe May 7, 2024
7fffa94
cleanup
Dieterbe May 7, 2024
cfd47bc
fix a random bug
Dieterbe May 8, 2024
9ad9695
fix
Dieterbe May 8, 2024
25aa08f
allow picking date when logging ingredient
Dieterbe May 8, 2024
d875d81
style the table better
Dieterbe May 10, 2024
ef78bc5
Remove BaseNutritionalValues, this wasn't used anymore
rolandgeider May 12, 2024
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 2 additions & 0 deletions .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -30,6 +30,8 @@
.pub-cache/
.pub/
/build/
**/failures/*.png


# Web related
lib/generated_plugin_registrant.dart
Expand Down
2 changes: 2 additions & 0 deletions lib/helpers/colors.dart
Original file line number Diff line number Diff line change
Expand Up @@ -25,6 +25,8 @@ const LIST_OF_COLORS3 = [
Color(0xFFFFA600),
];

const COLOR_SURPLUS = Color.fromARGB(255, 231, 71, 71);

Iterable<Color> generateChartColors(int nrOfItems) sync* {
final List<Color> colors;

Expand Down
8 changes: 8 additions & 0 deletions lib/l10n/app_en.arb
Original file line number Diff line number Diff line change
Expand Up @@ -352,6 +352,14 @@
"@weekAverage": {
"description": "Header for the column of '7 day average' nutritional values, i.e. what was logged last week"
},
"surplus": "surplus",
"@surplus": {
"description": "Caloric surplus (either planned or unplanned)"
},
"deficit": "deficit",
"@deficit": {
"description": "Caloric deficit (either planned or unplanned)"
},
"difference": "Difference",
"@difference": {},
"percentEnergy": "Percent of energy",
Expand Down
1 change: 0 additions & 1 deletion lib/models/nutrition/meal.dart
Original file line number Diff line number Diff line change
Expand Up @@ -64,7 +64,6 @@ class Meal {

this.mealItems = mealItems ?? [];
this.diaryEntries = diaryEntries ?? [];
time = time ?? TimeOfDay.fromDateTime(DateTime.now());
this.name = name ?? '';
}

Expand Down
161 changes: 161 additions & 0 deletions lib/models/nutrition/nutritional_goals.dart
Original file line number Diff line number Diff line change
@@ -0,0 +1,161 @@
/*
* This file is part of wger Workout Manager <https://github.com/wger-project>.
* 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.
*
* This program 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 <http://www.gnu.org/licenses/>.
*/

import 'package:wger/helpers/consts.dart';
import 'package:wger/models/nutrition/nutritional_values.dart';

class NutritionalGoals {
double? energy = 0;
double? protein = 0;
double? carbohydrates = 0;
double? carbohydratesSugar = 0;
double? fat = 0;
double? fatSaturated = 0;
double? fibres = 0;
double? sodium = 0;

NutritionalGoals({
this.energy,
this.protein,
this.carbohydrates,
this.carbohydratesSugar,
this.fat,
this.fatSaturated,
this.fibres,
this.sodium,
}) {
// infer values where we can
if (energy == null) {
if (protein != null && carbohydrates != null && fat != null) {
energy =
protein! * ENERGY_PROTEIN + carbohydrates! * ENERGY_CARBOHYDRATES + fat! * ENERGY_FAT;
}
return;
}
// TODO: input validation when the user modifies/creates the plan, to assure energy is high enough
if (protein == null && carbohydrates != null && fat != null) {
protein =
(energy! - carbohydrates! * ENERGY_CARBOHYDRATES - fat! * ENERGY_FAT) / ENERGY_PROTEIN;
assert(protein! > 0);
} else if (carbohydrates == null && protein != null && fat != null) {
carbohydrates =
(energy! - protein! * ENERGY_PROTEIN - fat! * ENERGY_FAT) / ENERGY_CARBOHYDRATES;
assert(carbohydrates! > 0);
} else if (fat == null && protein != null && carbohydrates != null) {
fat = (energy! - protein! * ENERGY_PROTEIN - carbohydrates! * ENERGY_CARBOHYDRATES) /
ENERGY_FAT;
assert(fat! > 0);
}
}

NutritionalGoals operator /(double v) {
return NutritionalGoals(
energy: energy != null ? energy! / v : null,
protein: protein != null ? protein! / v : null,
carbohydrates: carbohydrates != null ? carbohydrates! / v : null,
carbohydratesSugar: carbohydratesSugar != null ? carbohydratesSugar! / v : null,
fat: fat != null ? fat! / v : null,
fatSaturated: fatSaturated != null ? fatSaturated! / v : null,
fibres: fibres != null ? fibres! / v : null,
sodium: sodium != null ? sodium! / v : null,
);
}

bool isComplete() {
return energy != null && protein != null && carbohydrates != null && fat != null;
}

/// Convert goals into values.
/// This turns unset goals into values of 0.
/// Only use this if you know what you're doing, e.g. if isComplete() is true
NutritionalValues toValues() {
return NutritionalValues.values(
energy ?? 0,
protein ?? 0,
carbohydrates ?? 0,
carbohydratesSugar ?? 0,
fat ?? 0,
fatSaturated ?? 0,
fibres ?? 0,
sodium ?? 0,
);
}

/// Calculates the percentage each macro nutrient adds to the total energy
NutritionalGoals energyPercentage() {
final goals = NutritionalGoals();
// when you create a new plan or meal, somehow goals like energy is set to 0
// whereas strictly speaking it should be null. However,
// we know the intention so treat 0 as null here.
if (energy == null || energy == 0) {
return goals;
}

if (protein != null) {
goals.protein = (100 * protein! * ENERGY_PROTEIN) / energy!;
}
if (carbohydrates != null) {
goals.carbohydrates = (100 * carbohydrates! * ENERGY_CARBOHYDRATES) / energy!;
}
if (fat != null) {
goals.fat = (100 * fat! * ENERGY_FAT) / energy!;
}
return goals;
}

double? prop(String name) {
return switch (name) {
'energy' => energy,
'protein' => protein,
'carbohydrates' => carbohydrates,
'carbohydratesSugar' => carbohydratesSugar,
'fat' => fat,
'fatSaturated' => fatSaturated,
'fibres' => fibres,
'sodium' => sodium,
_ => 0,
};
}

@override
String toString() {
return 'e: $energy, p: $protein, c: $carbohydrates, cS: $carbohydratesSugar, f: $fat, fS: $fatSaturated, fi: $fibres, s: $sodium';
}

@override
//ignore: avoid_equals_and_hash_code_on_mutable_classes
int get hashCode => Object.hash(
energy, protein, carbohydrates, carbohydratesSugar, fat, fatSaturated, fibres, sodium);

@override
// ignore: avoid_equals_and_hash_code_on_mutable_classes
bool operator ==(Object other) {
if (identical(this, other)) {
return true;
}
return other is NutritionalGoals &&
other.energy == energy &&
other.protein == protein &&
other.carbohydrates == carbohydrates &&
other.carbohydratesSugar == carbohydratesSugar &&
other.fat == fat &&
other.fatSaturated == fatSaturated &&
other.fibres == fibres &&
other.sodium == sodium;
}
}
57 changes: 24 additions & 33 deletions lib/models/nutrition/nutritional_plan.dart
Original file line number Diff line number Diff line change
Expand Up @@ -24,6 +24,7 @@ import 'package:wger/helpers/json.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_goals.dart';
import 'package:wger/models/nutrition/nutritional_values.dart';

part 'nutritional_plan.g.dart';
Expand Down Expand Up @@ -107,19 +108,32 @@ class NutritionalPlan {
/// note that (some of) this is already done on the server. It might be better
/// to read it from there, but on the other hand we might want to do more locally
/// so that a mostly offline mode is possible.
NutritionalValues get plannedNutritionalValues {
NutritionalGoals get nutritionalGoals {
// If there are set goals, they take preference over any meals
if (hasAnyGoals) {
final out = NutritionalValues();

out.energy = goalEnergy != null ? goalEnergy!.toDouble() : 0;
out.fat = goalFat != null ? goalFat!.toDouble() : 0;
out.carbohydrates = goalCarbohydrates != null ? goalCarbohydrates!.toDouble() : 0;
out.protein = goalProtein != null ? goalProtein!.toDouble() : 0;
return out;
return NutritionalGoals(
energy: goalEnergy?.toDouble(),
fat: goalFat?.toDouble(),
protein: goalProtein?.toDouble(),
carbohydrates: goalCarbohydrates?.toDouble(),
);
}

return meals.fold(NutritionalValues(), (a, b) => a + b.plannedNutritionalValues);
// if there are no set goals and no defined meals, the goals are still undefined
if (meals.isEmpty) {
return NutritionalGoals();
}
// otherwise, add up all the nutritional values of the meals and use that as goals
final sumValues = meals.fold(NutritionalValues(), (a, b) => a + b.plannedNutritionalValues);
return NutritionalGoals(
energy: sumValues.energy,
fat: sumValues.fat,
protein: sumValues.protein,
carbohydrates: sumValues.carbohydrates,
carbohydratesSugar: sumValues.carbohydratesSugar,
fatSaturated: sumValues.fatSaturated,
fibres: sumValues.fibres,
sodium: sumValues.sodium,
);
}

NutritionalValues get loggedNutritionalValuesToday {
Expand All @@ -138,28 +152,6 @@ class NutritionalPlan {
.fold(NutritionalValues(), (a, b) => a + b.nutritionalValues);
}

/// 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<DateTime, NutritionalValues> get logEntriesValues {
final out = <DateTime, NutritionalValues>{};
for (final log in diaryEntries) {
Expand Down Expand Up @@ -220,7 +212,6 @@ class NutritionalPlan {
id: PSEUDO_MEAL_ID,
plan: id,
name: name,
time: null,
diaryEntries: diaryEntries.where((e) => e.mealId == null).toList(),
);
}
Expand Down
8 changes: 0 additions & 8 deletions lib/models/nutrition/nutritional_values.dart
Original file line number Diff line number Diff line change
Expand Up @@ -106,11 +106,3 @@ class NutritionalValues {
int get hashCode => Object.hash(
energy, protein, carbohydrates, carbohydratesSugar, fat, fatSaturated, fibres, sodium);
}

class BaseNutritionalValues {
double protein = 0;
double carbohydrates = 0;
double fat = 0;

BaseNutritionalValues(this.protein, this.carbohydrates, this.fat);
}
Loading