mirror of
https://github.com/wger-project/flutter.git
synced 2026-02-19 07:50:52 +01:00
proper nullable "nutritional goals" with inference
differentiate between a goal being set (but as 0) vs a goal not being set. fixes various correctness issues
This commit is contained in:
142
lib/models/nutrition/nutritional_goals.dart
Normal file
142
lib/models/nutrition/nutritional_goals.dart
Normal file
@@ -0,0 +1,142 @@
|
||||
/*
|
||||
* 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();
|
||||
if (energy == null) {
|
||||
return goals;
|
||||
}
|
||||
assert(energy! > 0);
|
||||
|
||||
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);
|
||||
}
|
||||
@@ -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';
|
||||
@@ -107,19 +108,28 @@ 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);
|
||||
// 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 {
|
||||
@@ -138,28 +148,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) {
|
||||
|
||||
@@ -16,6 +16,8 @@
|
||||
* along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
import 'dart:math';
|
||||
|
||||
import 'package:fl_chart/fl_chart.dart';
|
||||
import 'package:flutter/material.dart';
|
||||
import 'package:flutter_gen/gen_l10n/app_localizations.dart';
|
||||
@@ -50,7 +52,7 @@ class FlNutritionalPlanGoalWidgetState extends State<FlNutritionalPlanGoalWidget
|
||||
// why don't we just handle this inside this function? because it might be
|
||||
// *another* gauge that's in surplus and we want to have consistent widths
|
||||
// between all gauges
|
||||
Widget _DIYGauge(BuildContext context, double normWidth, num? plan, double val) {
|
||||
Widget _DIYGauge(BuildContext context, double normWidth, double? plan, double val) {
|
||||
Container segment(double width, Color color) {
|
||||
return Container(
|
||||
height: 16,
|
||||
@@ -82,59 +84,52 @@ class FlNutritionalPlanGoalWidgetState extends State<FlNutritionalPlanGoalWidget
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
final plan = widget._nutritionalPlan;
|
||||
final goals = plan.nutritionalGoals;
|
||||
final today = plan.loggedNutritionalValuesToday;
|
||||
|
||||
return LayoutBuilder(builder: (context, constraints) {
|
||||
var maxVal = 1.0;
|
||||
// if any of the bars goes over 100%, find the one that goes over the most
|
||||
// that one needs the most horizontal space to show how much it goes over,
|
||||
// and therefore reduces the width of "100%" the most, and this width we want
|
||||
// to be consistent for all other bars
|
||||
if (plan.goalProtein != null && today.protein / plan.goalProtein! > maxVal) {
|
||||
maxVal = today.protein / plan.goalProtein!;
|
||||
}
|
||||
|
||||
if (plan.goalCarbohydrates != null &&
|
||||
today.carbohydrates / plan.goalCarbohydrates! > maxVal) {
|
||||
maxVal = today.carbohydrates / plan.goalCarbohydrates!;
|
||||
}
|
||||
|
||||
if (plan.goalFat != null && today.fat / plan.goalFat! > maxVal) {
|
||||
maxVal = today.fat / plan.goalFat!;
|
||||
}
|
||||
|
||||
if (plan.goalEnergy != null && today.energy / plan.goalEnergy! > maxVal) {
|
||||
maxVal = today.energy / plan.goalEnergy!;
|
||||
}
|
||||
// if any of the bars goes over 100%, find the one that goes over the most
|
||||
// that one needs the most horizontal space to show how much it goes over,
|
||||
// and therefore reduces the width of "100%" the most, and this width we want
|
||||
// to be consistent for all other bars.
|
||||
// if none goes over, 100% means fill all available space
|
||||
final maxVal = [
|
||||
1.0,
|
||||
if (goals.protein != null && goals.protein! > 0) today.protein / goals.protein!,
|
||||
if (goals.carbohydrates != null && goals.carbohydrates! > 0)
|
||||
today.carbohydrates / goals.carbohydrates!,
|
||||
if (goals.fat != null && goals.fat! > 0) today.fat / goals.fat!,
|
||||
if (goals.energy != null && goals.energy! > 0) today.energy / goals.energy!
|
||||
].reduce(max);
|
||||
|
||||
final normWidth = constraints.maxWidth / maxVal;
|
||||
|
||||
String fmtMacro(String name, double today, double? goal, String unit) {
|
||||
return '$name: ${today.toStringAsFixed(0)}${goal == null ? '' : ' /${goal.toStringAsFixed(0)}'} $unit';
|
||||
}
|
||||
|
||||
return Column(
|
||||
crossAxisAlignment: CrossAxisAlignment.start,
|
||||
children: <Widget>[
|
||||
Text(plan.goalProtein == null
|
||||
? '${AppLocalizations.of(context).protein}: ${today.protein.toStringAsFixed(0)} ${AppLocalizations.of(context).g}'
|
||||
: '${AppLocalizations.of(context).protein}: ${today.protein.toStringAsFixed(0)} / ${plan.goalProtein} ${AppLocalizations.of(context).g}'),
|
||||
Text(fmtMacro(AppLocalizations.of(context).protein, today.protein, goals.protein,
|
||||
AppLocalizations.of(context).g)),
|
||||
const SizedBox(height: 2),
|
||||
_DIYGauge(context, normWidth, plan.goalProtein, today.protein),
|
||||
_DIYGauge(context, normWidth, goals.protein, today.protein),
|
||||
const SizedBox(height: 8),
|
||||
Text(plan.goalCarbohydrates == null
|
||||
? '${AppLocalizations.of(context).carbohydrates}: ${today.carbohydrates.toStringAsFixed(0)} ${AppLocalizations.of(context).g}'
|
||||
: '${AppLocalizations.of(context).carbohydrates}: ${today.carbohydrates.toStringAsFixed(0)} / ${plan.goalCarbohydrates} ${AppLocalizations.of(context).g}'),
|
||||
Text(fmtMacro(AppLocalizations.of(context).carbohydrates, today.carbohydrates,
|
||||
goals.carbohydrates, AppLocalizations.of(context).g)),
|
||||
const SizedBox(height: 2),
|
||||
_DIYGauge(context, normWidth, plan.goalCarbohydrates, today.carbohydrates),
|
||||
_DIYGauge(context, normWidth, goals.carbohydrates, today.carbohydrates),
|
||||
const SizedBox(height: 8),
|
||||
Text(plan.goalFat == null
|
||||
? '${AppLocalizations.of(context).fat}: ${today.fat.toStringAsFixed(0)} ${AppLocalizations.of(context).g}'
|
||||
: '${AppLocalizations.of(context).fat}: ${today.fat.toStringAsFixed(0)} / ${plan.goalFat} ${AppLocalizations.of(context).g}'),
|
||||
Text(fmtMacro(AppLocalizations.of(context).fat, today.fat, goals.fat,
|
||||
AppLocalizations.of(context).g)),
|
||||
const SizedBox(height: 2),
|
||||
_DIYGauge(context, normWidth, plan.goalFat, today.fat),
|
||||
_DIYGauge(context, normWidth, goals.fat, today.fat),
|
||||
const SizedBox(height: 8),
|
||||
Text(plan.goalEnergy == null
|
||||
? '${AppLocalizations.of(context).energy}: ${today.energy.toStringAsFixed(0)} ${AppLocalizations.of(context).kcal}'
|
||||
: '${AppLocalizations.of(context).energy}: ${today.energy.toStringAsFixed(0)} / ${plan.goalEnergy} ${AppLocalizations.of(context).kcal}'),
|
||||
Text(fmtMacro(AppLocalizations.of(context).energy, today.energy, goals.energy,
|
||||
AppLocalizations.of(context).kcal)),
|
||||
const SizedBox(height: 2),
|
||||
_DIYGauge(context, normWidth, plan.goalEnergy, today.energy),
|
||||
_DIYGauge(context, normWidth, goals.energy, today.energy),
|
||||
],
|
||||
);
|
||||
});
|
||||
@@ -313,7 +308,7 @@ class NutritionalDiaryChartWidgetFlState extends State<NutritionalDiaryChartWidg
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
final planned = widget._nutritionalPlan.plannedNutritionalValues;
|
||||
final planned = widget._nutritionalPlan.nutritionalGoals;
|
||||
final loggedToday = widget._nutritionalPlan.loggedNutritionalValuesToday;
|
||||
final logged7DayAvg = widget._nutritionalPlan.loggedNutritionalValues7DayAvg;
|
||||
|
||||
@@ -322,9 +317,9 @@ class NutritionalDiaryChartWidgetFlState extends State<NutritionalDiaryChartWidg
|
||||
BarChartGroupData barchartGroup(int x, double barsSpace, double barsWidth, String prop) {
|
||||
final plan = planned.prop(prop);
|
||||
|
||||
BarChartRodData barChartRodData(double plan, double val, Color color) {
|
||||
BarChartRodData barChartRodData(double? plan, double val, Color color) {
|
||||
// paint a simple bar
|
||||
if (plan == 0 || val == plan) {
|
||||
if (plan == null || val == plan) {
|
||||
return BarChartRodData(
|
||||
toY: val,
|
||||
color: color,
|
||||
|
||||
@@ -31,7 +31,7 @@ class NutritionalDiaryDetailWidget extends StatelessWidget {
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
final valuesPlanned = _nutritionalPlan.plannedNutritionalValues;
|
||||
final nutritionalGoals = _nutritionalPlan.nutritionalGoals;
|
||||
final valuesLogged = _nutritionalPlan.getValuesForDate(_date);
|
||||
final logs = _nutritionalPlan.getLogsForDate(_date);
|
||||
|
||||
@@ -52,7 +52,7 @@ class NutritionalDiaryDetailWidget extends StatelessWidget {
|
||||
Padding(
|
||||
padding: const EdgeInsets.all(8.0),
|
||||
child: NutritionDiaryTable(
|
||||
planned: valuesPlanned,
|
||||
planned: nutritionalGoals.toValues(),
|
||||
logged: valuesLogged,
|
||||
),
|
||||
),
|
||||
|
||||
@@ -20,8 +20,8 @@ import 'package:flutter/material.dart';
|
||||
import 'package:flutter_gen/gen_l10n/app_localizations.dart';
|
||||
import 'package:provider/provider.dart';
|
||||
import 'package:wger/helpers/colors.dart';
|
||||
import 'package:wger/models/nutrition/nutritional_goals.dart';
|
||||
import 'package:wger/models/nutrition/nutritional_plan.dart';
|
||||
import 'package:wger/models/nutrition/nutritional_values.dart';
|
||||
import 'package:wger/providers/body_weight.dart';
|
||||
import 'package:wger/screens/form_screen.dart';
|
||||
import 'package:wger/widgets/measurements/charts.dart';
|
||||
@@ -37,12 +37,11 @@ class NutritionalPlanDetailWidget extends StatelessWidget {
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
final plannedNutritionalValues = _nutritionalPlan.plannedNutritionalValues;
|
||||
final nutritionalGoals = _nutritionalPlan.nutritionalGoals;
|
||||
final lastWeightEntry =
|
||||
Provider.of<BodyWeightProvider>(context, listen: false).getNewestEntry();
|
||||
final valuesGperKg = lastWeightEntry != null
|
||||
? _nutritionalPlan.gPerBodyKg(lastWeightEntry.weight, plannedNutritionalValues)
|
||||
: null;
|
||||
final nutritionalGoalsGperKg =
|
||||
lastWeightEntry != null ? nutritionalGoals / lastWeightEntry.weight.toDouble() : null;
|
||||
|
||||
return SliverList(
|
||||
delegate: SliverChildListDelegate(
|
||||
@@ -82,17 +81,18 @@ class NutritionalPlanDetailWidget extends StatelessWidget {
|
||||
},
|
||||
),
|
||||
),
|
||||
Container(
|
||||
padding: const EdgeInsets.all(15),
|
||||
height: 220,
|
||||
child: FlNutritionalPlanPieChartWidget(plannedNutritionalValues), // chart
|
||||
),
|
||||
if (nutritionalGoals.isComplete())
|
||||
Container(
|
||||
padding: const EdgeInsets.all(15),
|
||||
height: 220,
|
||||
child: FlNutritionalPlanPieChartWidget(nutritionalGoals.toValues()),
|
||||
),
|
||||
Padding(
|
||||
padding: const EdgeInsets.symmetric(horizontal: 10),
|
||||
child: MacronutrientsTable(
|
||||
plannedNutritionalValues: plannedNutritionalValues,
|
||||
plannedValuesPercentage: _nutritionalPlan.energyPercentage(plannedNutritionalValues),
|
||||
plannedValuesGperKg: valuesGperKg,
|
||||
nutritionalGoals: nutritionalGoals,
|
||||
plannedValuesPercentage: nutritionalGoals.energyPercentage(),
|
||||
nutritionalGoalsGperKg: nutritionalGoalsGperKg,
|
||||
),
|
||||
),
|
||||
const Padding(padding: EdgeInsets.all(8.0)),
|
||||
@@ -104,7 +104,7 @@ class NutritionalPlanDetailWidget extends StatelessWidget {
|
||||
Container(
|
||||
padding: const EdgeInsets.only(top: 15, left: 15, right: 15),
|
||||
height: 300,
|
||||
child: NutritionalDiaryChartWidgetFl(nutritionalPlan: _nutritionalPlan), // chart
|
||||
child: NutritionalDiaryChartWidgetFl(nutritionalPlan: _nutritionalPlan),
|
||||
),
|
||||
Padding(
|
||||
padding: const EdgeInsets.only(bottom: 40, left: 25, right: 25),
|
||||
@@ -164,15 +164,15 @@ class NutritionalPlanDetailWidget extends StatelessWidget {
|
||||
class MacronutrientsTable extends StatelessWidget {
|
||||
const MacronutrientsTable({
|
||||
super.key,
|
||||
required this.plannedNutritionalValues,
|
||||
required this.nutritionalGoals,
|
||||
required this.plannedValuesPercentage,
|
||||
required this.plannedValuesGperKg,
|
||||
required this.nutritionalGoalsGperKg,
|
||||
});
|
||||
|
||||
static const double tablePadding = 7;
|
||||
final NutritionalValues plannedNutritionalValues;
|
||||
final BaseNutritionalValues plannedValuesPercentage;
|
||||
final BaseNutritionalValues? plannedValuesGperKg;
|
||||
final NutritionalGoals nutritionalGoals;
|
||||
final NutritionalGoals plannedValuesPercentage;
|
||||
final NutritionalGoals? nutritionalGoalsGperKg;
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
@@ -216,8 +216,9 @@ class MacronutrientsTable extends StatelessWidget {
|
||||
child: Text(AppLocalizations.of(context).energy),
|
||||
),
|
||||
Text(
|
||||
plannedNutritionalValues.energy.toStringAsFixed(0) +
|
||||
AppLocalizations.of(context).kcal,
|
||||
nutritionalGoals.energy != null
|
||||
? nutritionalGoals.energy!.toStringAsFixed(0) + AppLocalizations.of(context).kcal
|
||||
: '',
|
||||
),
|
||||
const Text(''),
|
||||
const Text(''),
|
||||
@@ -229,11 +230,15 @@ class MacronutrientsTable extends StatelessWidget {
|
||||
padding: const EdgeInsets.symmetric(vertical: tablePadding),
|
||||
child: Text(AppLocalizations.of(context).protein),
|
||||
),
|
||||
Text(plannedNutritionalValues.protein.toStringAsFixed(0) +
|
||||
AppLocalizations.of(context).g),
|
||||
Text(plannedValuesPercentage.protein.toStringAsFixed(1)),
|
||||
Text(
|
||||
plannedValuesGperKg != null ? plannedValuesGperKg!.protein.toStringAsFixed(1) : ''),
|
||||
Text(nutritionalGoals.protein != null
|
||||
? nutritionalGoals.protein!.toStringAsFixed(0) + AppLocalizations.of(context).g
|
||||
: ''),
|
||||
Text(plannedValuesPercentage.protein != null
|
||||
? plannedValuesPercentage.protein!.toStringAsFixed(1)
|
||||
: ''),
|
||||
Text(nutritionalGoalsGperKg != null && nutritionalGoalsGperKg!.protein != null
|
||||
? nutritionalGoalsGperKg!.protein!.toStringAsFixed(1)
|
||||
: ''),
|
||||
],
|
||||
),
|
||||
TableRow(
|
||||
@@ -242,11 +247,15 @@ class MacronutrientsTable extends StatelessWidget {
|
||||
padding: const EdgeInsets.symmetric(vertical: tablePadding),
|
||||
child: Text(AppLocalizations.of(context).carbohydrates),
|
||||
),
|
||||
Text(plannedNutritionalValues.carbohydrates.toStringAsFixed(0) +
|
||||
AppLocalizations.of(context).g),
|
||||
Text(plannedValuesPercentage.carbohydrates.toStringAsFixed(1)),
|
||||
Text(plannedValuesGperKg != null
|
||||
? plannedValuesGperKg!.carbohydrates.toStringAsFixed(1)
|
||||
Text(nutritionalGoals.carbohydrates != null
|
||||
? nutritionalGoals.carbohydrates!.toStringAsFixed(0) +
|
||||
AppLocalizations.of(context).g
|
||||
: ''),
|
||||
Text(plannedValuesPercentage.carbohydrates != null
|
||||
? plannedValuesPercentage.carbohydrates!.toStringAsFixed(1)
|
||||
: ''),
|
||||
Text(nutritionalGoalsGperKg != null && nutritionalGoalsGperKg!.carbohydrates != null
|
||||
? nutritionalGoalsGperKg!.carbohydrates!.toStringAsFixed(1)
|
||||
: ''),
|
||||
],
|
||||
),
|
||||
@@ -256,10 +265,12 @@ class MacronutrientsTable extends StatelessWidget {
|
||||
padding: const EdgeInsets.symmetric(vertical: tablePadding, horizontal: 12),
|
||||
child: Text(AppLocalizations.of(context).sugars),
|
||||
),
|
||||
Text(plannedNutritionalValues.carbohydratesSugar.toStringAsFixed(0) +
|
||||
AppLocalizations.of(context).g),
|
||||
const Text(''),
|
||||
const Text(''),
|
||||
Text(nutritionalGoals.carbohydratesSugar != null
|
||||
? nutritionalGoals.carbohydratesSugar!.toStringAsFixed(0) +
|
||||
AppLocalizations.of(context).g
|
||||
: ''),
|
||||
],
|
||||
),
|
||||
TableRow(
|
||||
@@ -268,9 +279,15 @@ class MacronutrientsTable extends StatelessWidget {
|
||||
padding: const EdgeInsets.symmetric(vertical: tablePadding),
|
||||
child: Text(AppLocalizations.of(context).fat),
|
||||
),
|
||||
Text(plannedNutritionalValues.fat.toStringAsFixed(0) + AppLocalizations.of(context).g),
|
||||
Text(plannedValuesPercentage.fat.toStringAsFixed(1)),
|
||||
Text(plannedValuesGperKg != null ? plannedValuesGperKg!.fat.toStringAsFixed(1) : ''),
|
||||
Text(nutritionalGoals.fat != null
|
||||
? nutritionalGoals.fat!.toStringAsFixed(0) + AppLocalizations.of(context).g
|
||||
: ''),
|
||||
Text(plannedValuesPercentage.fat != null
|
||||
? plannedValuesPercentage.fat!.toStringAsFixed(1)
|
||||
: ''),
|
||||
Text(nutritionalGoalsGperKg != null && nutritionalGoalsGperKg!.fat != null
|
||||
? nutritionalGoalsGperKg!.fat!.toStringAsFixed(1)
|
||||
: ''),
|
||||
],
|
||||
),
|
||||
TableRow(
|
||||
@@ -279,10 +296,11 @@ class MacronutrientsTable extends StatelessWidget {
|
||||
padding: const EdgeInsets.symmetric(vertical: tablePadding, horizontal: 12),
|
||||
child: Text(AppLocalizations.of(context).saturatedFat),
|
||||
),
|
||||
Text(plannedNutritionalValues.fatSaturated.toStringAsFixed(0) +
|
||||
AppLocalizations.of(context).g),
|
||||
const Text(''),
|
||||
const Text(''),
|
||||
Text(nutritionalGoals.fatSaturated != null
|
||||
? nutritionalGoals.fatSaturated!.toStringAsFixed(0) + AppLocalizations.of(context).g
|
||||
: ''),
|
||||
],
|
||||
),
|
||||
TableRow(
|
||||
@@ -291,10 +309,11 @@ class MacronutrientsTable extends StatelessWidget {
|
||||
padding: const EdgeInsets.symmetric(vertical: tablePadding),
|
||||
child: Text(AppLocalizations.of(context).fibres),
|
||||
),
|
||||
Text(plannedNutritionalValues.fibres.toStringAsFixed(0) +
|
||||
AppLocalizations.of(context).g),
|
||||
const Text(''),
|
||||
const Text(''),
|
||||
Text(nutritionalGoals.fibres != null
|
||||
? nutritionalGoals.fibres!.toStringAsFixed(0) + AppLocalizations.of(context).g
|
||||
: ''),
|
||||
],
|
||||
),
|
||||
TableRow(
|
||||
@@ -303,10 +322,11 @@ class MacronutrientsTable extends StatelessWidget {
|
||||
padding: const EdgeInsets.symmetric(vertical: tablePadding),
|
||||
child: Text(AppLocalizations.of(context).sodium),
|
||||
),
|
||||
Text(plannedNutritionalValues.sodium.toStringAsFixed(0) +
|
||||
AppLocalizations.of(context).g),
|
||||
const Text(''),
|
||||
const Text(''),
|
||||
Text(nutritionalGoals.sodium != null
|
||||
? nutritionalGoals.sodium!.toStringAsFixed(0) + AppLocalizations.of(context).g
|
||||
: ''),
|
||||
],
|
||||
),
|
||||
],
|
||||
|
||||
@@ -17,6 +17,7 @@
|
||||
*/
|
||||
|
||||
import 'package:flutter_test/flutter_test.dart';
|
||||
import 'package:wger/models/nutrition/nutritional_goals.dart';
|
||||
import 'package:wger/models/nutrition/nutritional_plan.dart';
|
||||
import 'package:wger/models/nutrition/nutritional_values.dart';
|
||||
|
||||
@@ -32,7 +33,7 @@ void main() {
|
||||
group('model tests', () {
|
||||
test('Test the nutritionalValues method for nutritional plans', () {
|
||||
final values = NutritionalValues.values(4118.75, 32.75, 347.5, 9.5, 59.0, 37.75, 52.5, 30.5);
|
||||
expect(plan.plannedNutritionalValues, values);
|
||||
expect(plan.nutritionalGoals, values);
|
||||
});
|
||||
|
||||
test('Test the nutritionalValues method for meals', () {
|
||||
|
||||
Reference in New Issue
Block a user