Skip to content

Commit ecad7bf

Browse files
authored
Merge pull request #604 from wger-project/show-kcal-macros-total-for-meal
kcal/macros total for meals + fix alignments
2 parents 6b2f49e + 1c6d96e commit ecad7bf

13 files changed

+238
-195
lines changed

lib/l10n/app_en.arb

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -122,6 +122,7 @@
122122
"@searchExercise": {
123123
"description": "Label on set form. Selected exercises are added to the set"
124124
},
125+
"noIngredientsDefined": "No ingredients defined yet",
125126
"noMatchingExerciseFound": "No matching exercises found",
126127
"@noMatchingExerciseFound": {
127128
"description": "Message returned if no exercises match the searched string"

lib/screens/log_meal_screen.dart

Lines changed: 6 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -22,7 +22,7 @@ import 'package:provider/provider.dart';
2222
import 'package:wger/models/nutrition/meal.dart';
2323
import 'package:wger/providers/nutrition.dart';
2424
import 'package:wger/widgets/nutrition/meal.dart';
25-
import 'package:wger/widgets/nutrition/widgets.dart';
25+
import 'package:wger/widgets/nutrition/nutrition_tiles.dart';
2626

2727
class LogMealArguments {
2828
final Meal meal;
@@ -51,7 +51,7 @@ class _LogMealScreenState extends State<LogMealScreen> {
5151

5252
return Scaffold(
5353
appBar: AppBar(
54-
title: Text('Log meal to diary'),
54+
title: Text(AppLocalizations.of(context).logMeal),
5555
),
5656
body: Consumer<NutritionPlansProvider>(
5757
builder: (context, nutritionProvider, child) => SingleChildScrollView(
@@ -64,13 +64,13 @@ class _LogMealScreenState extends State<LogMealScreen> {
6464
children: [
6565
Text(meal.name, style: Theme.of(context).textTheme.headlineSmall),
6666
if (meal.mealItems.isEmpty)
67-
const ListTile(title: Text('No ingredients defined yet'))
67+
ListTile(title: Text(AppLocalizations.of(context).noIngredientsDefined))
6868
else
6969
Column(
7070
children: [
71-
const NutritionDiaryheader(),
72-
...meal.mealItems
73-
.map((item) => MealItemWidget(item, viewMode.withAllDetails, false)),
71+
const DiaryheaderTile(),
72+
...meal.mealItems.map(
73+
(item) => MealItemEditableFullTile(item, viewMode.withAllDetails, false)),
7474
const SizedBox(height: 32),
7575
Text(
7676
'Portion: ${portionPct.round()} %',

lib/theme/theme.dart

Lines changed: 0 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -16,7 +16,6 @@
1616
* along with this program. If not, see <http://www.gnu.org/licenses/>.
1717
*/
1818

19-
import 'dart:ui';
2019

2120
import 'package:flex_color_scheme/flex_color_scheme.dart';
2221
import 'package:flutter/material.dart';

lib/widgets/core/core.dart

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -21,10 +21,12 @@ import 'package:flutter/material.dart';
2121
class MutedText extends StatelessWidget {
2222
final String _text;
2323
final TextAlign textAlign;
24+
final TextOverflow? overflow;
2425

2526
const MutedText(
2627
this._text, {
2728
this.textAlign = TextAlign.left,
29+
this.overflow,
2830
});
2931

3032
@override
@@ -33,6 +35,7 @@ class MutedText extends StatelessWidget {
3335
_text,
3436
style: TextStyle(color: Theme.of(context).colorScheme.outline),
3537
textAlign: textAlign,
38+
overflow: overflow,
3639
);
3740
}
3841
}

lib/widgets/nutrition/forms.dart

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -31,6 +31,7 @@ import 'package:wger/models/nutrition/nutritional_plan.dart';
3131
import 'package:wger/providers/nutrition.dart';
3232
import 'package:wger/screens/nutritional_plan_screen.dart';
3333
import 'package:wger/widgets/nutrition/helpers.dart';
34+
import 'package:wger/widgets/nutrition/nutrition_tiles.dart';
3435
import 'package:wger/widgets/nutrition/widgets.dart';
3536

3637
class MealForm extends StatelessWidget {
@@ -341,7 +342,7 @@ class IngredientFormState extends State<IngredientForm> {
341342
builder: (BuildContext context, AsyncSnapshot<Ingredient> snapshot) {
342343
if (snapshot.hasData) {
343344
_mealItem.ingredient = snapshot.data!;
344-
return MealItemTile(
345+
return MealItemValuesTile(
345346
ingredient: _mealItem.ingredient,
346347
nutritionalValues: _mealItem.nutritionalValues,
347348
);

lib/widgets/nutrition/helpers.dart

Lines changed: 47 additions & 17 deletions
Original file line numberDiff line numberDiff line change
@@ -23,25 +23,55 @@ import 'package:wger/models/nutrition/meal.dart';
2323
import 'package:wger/models/nutrition/nutritional_values.dart';
2424
import 'package:wger/widgets/core/core.dart';
2525

26-
List<Widget> getMutedNutritionalValues(NutritionalValues values, BuildContext context) => [
27-
MutedText(
28-
AppLocalizations.of(context).kcalValue(values.energy.toStringAsFixed(0)),
29-
textAlign: TextAlign.right,
30-
),
31-
MutedText(
32-
AppLocalizations.of(context).gValue(values.protein.toStringAsFixed(0)),
33-
textAlign: TextAlign.right,
34-
),
35-
MutedText(
36-
AppLocalizations.of(context).gValue(values.carbohydrates.toStringAsFixed(0)),
37-
textAlign: TextAlign.right,
38-
),
39-
MutedText(
40-
AppLocalizations.of(context).gValue(values.fat.toStringAsFixed(0)),
41-
textAlign: TextAlign.right,
42-
),
26+
List<String> getNutritionColumnNames(BuildContext context) => [
27+
AppLocalizations.of(context).energy,
28+
AppLocalizations.of(context).protein,
29+
AppLocalizations.of(context).carbohydrates,
30+
AppLocalizations.of(context).fat,
4331
];
4432

33+
List<String> getNutritionalValues(NutritionalValues values, BuildContext context) => [
34+
AppLocalizations.of(context).kcalValue(values.energy.toStringAsFixed(0)),
35+
AppLocalizations.of(context).gValue(values.protein.toStringAsFixed(0)),
36+
AppLocalizations.of(context).gValue(values.carbohydrates.toStringAsFixed(0)),
37+
AppLocalizations.of(context).gValue(values.fat.toStringAsFixed(0)),
38+
];
39+
40+
List<int> getNutritionColumnFlexes(BuildContext context) {
41+
return getNutritionColumnNames(context).map((e) {
42+
final l = e.characters.length;
43+
// if the word is really small (e.g. "fat"),
44+
// we still want to have a minimum value to keep some spacing,
45+
// especially because column values might become like "123 g"
46+
return (l <= 3) ? 4 : l;
47+
}).toList();
48+
}
49+
50+
List<Widget> muted(List<String> children) => children
51+
.map((e) => MutedText(
52+
e,
53+
textAlign: TextAlign.right,
54+
overflow: TextOverflow.ellipsis,
55+
))
56+
.toList();
57+
58+
// return a row of elements in the standard macros spacing
59+
Row getNutritionRow(BuildContext context, List<Widget> children) {
60+
return Row(
61+
mainAxisSize: MainAxisSize.max,
62+
mainAxisAlignment: MainAxisAlignment.spaceBetween,
63+
children: children.indexed
64+
.map(
65+
(e) => Flexible(
66+
fit: FlexFit.tight,
67+
flex: getNutritionColumnFlexes(context)[e.$1],
68+
child: e.$2,
69+
),
70+
)
71+
.toList(),
72+
);
73+
}
74+
4575
String getShortNutritionValues(NutritionalValues values, BuildContext context) {
4676
final loc = AppLocalizations.of(context);
4777
final e = '${loc.energyShort} ${loc.kcalValue(values.energy.toStringAsFixed(0))}';

lib/widgets/nutrition/meal.dart

Lines changed: 38 additions & 48 deletions
Original file line numberDiff line numberDiff line change
@@ -21,7 +21,6 @@ import 'package:flutter_gen/gen_l10n/app_localizations.dart';
2121
import 'package:flutter_svg_icons/flutter_svg_icons.dart';
2222
import 'package:provider/provider.dart';
2323
import 'package:wger/helpers/consts.dart';
24-
import 'package:wger/models/nutrition/log.dart';
2524
import 'package:wger/models/nutrition/meal.dart';
2625
import 'package:wger/models/nutrition/meal_item.dart';
2726
import 'package:wger/providers/nutrition.dart';
@@ -30,6 +29,8 @@ import 'package:wger/screens/log_meal_screen.dart';
3029
import 'package:wger/widgets/nutrition/charts.dart';
3130
import 'package:wger/widgets/nutrition/forms.dart';
3231
import 'package:wger/widgets/nutrition/helpers.dart';
32+
import 'package:wger/widgets/nutrition/nutrition_tile.dart';
33+
import 'package:wger/widgets/nutrition/nutrition_tiles.dart';
3334
import 'package:wger/widgets/nutrition/widgets.dart';
3435

3536
enum viewMode {
@@ -157,15 +158,33 @@ class _MealWidgetState extends State<MealWidget> {
157158
)),
158159
if (_viewMode == viewMode.withIngredients || _viewMode == viewMode.withAllDetails)
159160
const Divider(),
160-
if (_viewMode == viewMode.withAllDetails) const NutritionDiaryheader(),
161+
if (_viewMode == viewMode.withIngredients || _viewMode == viewMode.withAllDetails)
162+
const DiaryheaderTile(),
161163
if (_viewMode == viewMode.withIngredients || _viewMode == viewMode.withAllDetails)
162164
if (widget._meal.mealItems.isEmpty && widget._meal.isRealMeal)
163-
const ListTile(title: Text('No ingredients defined yet'))
165+
NutritionTile(
166+
title: Text(
167+
AppLocalizations.of(context).noIngredientsDefined,
168+
textAlign: TextAlign.left,
169+
))
164170
else
165-
...widget._meal.mealItems.map((item) => MealItemWidget(item, _viewMode, _editing)),
171+
...widget._meal.mealItems
172+
.map((item) => MealItemEditableFullTile(item, _viewMode, _editing)),
173+
if (_viewMode == viewMode.withIngredients || _viewMode == viewMode.withAllDetails)
174+
const Divider(),
175+
if (_viewMode == viewMode.withIngredients || _viewMode == viewMode.withAllDetails)
176+
NutritionTile(
177+
vPadding: 0,
178+
leading: const Text('total'),
179+
title: getNutritionRow(
180+
context,
181+
muted(getNutritionalValues(widget._meal.plannedNutritionalValues, context)),
182+
),
183+
),
166184
if (_viewMode == viewMode.withAllDetails)
167185
Column(
168186
children: [
187+
const Divider(),
169188
Center(
170189
child: Text(
171190
AppLocalizations.of(context).loggedToday,
@@ -181,7 +200,7 @@ class _MealWidgetState extends State<MealWidget> {
181200
padding: const EdgeInsets.all(8.0),
182201
child: Column(
183202
children: [
184-
NutritionDiaryEntry(diaryEntry: item),
203+
DiaryEntryTile(diaryEntry: item),
185204
],
186205
),
187206
)),
@@ -194,12 +213,13 @@ class _MealWidgetState extends State<MealWidget> {
194213
}
195214
}
196215

197-
class MealItemWidget extends StatelessWidget {
216+
/// An editable NutritionTile showing the avatar, name, nutritional values
217+
class MealItemEditableFullTile extends StatelessWidget {
198218
final bool _editing;
199219
final viewMode _viewMode;
200220
final MealItem _item;
201221

202-
const MealItemWidget(this._item, this._viewMode, this._editing);
222+
const MealItemEditableFullTile(this._item, this._viewMode, this._editing);
203223

204224
@override
205225
Widget build(BuildContext context) {
@@ -213,22 +233,24 @@ class MealItemWidget extends StatelessWidget {
213233
final String unit = AppLocalizations.of(context).g;
214234
final values = _item.nutritionalValues;
215235

216-
return ListTile(
236+
return NutritionTile(
217237
leading: IngredientAvatar(ingredient: _item.ingredient),
218-
title: Text(
219-
'${_item.amount.toStringAsFixed(0)}$unit ${_item.ingredient.name}',
220-
overflow: TextOverflow.ellipsis,
221-
),
222-
subtitle: Row(
223-
mainAxisAlignment: MainAxisAlignment.spaceBetween,
238+
title: Row(
224239
mainAxisSize: MainAxisSize.max,
225240
children: [
226-
if (_viewMode == viewMode.withAllDetails) ...getMutedNutritionalValues(values, context)
241+
Text(
242+
'${_item.amount.toStringAsFixed(0)}$unit ${_item.ingredient.name}',
243+
overflow: TextOverflow.ellipsis,
244+
textAlign: TextAlign.left,
245+
),
227246
],
228247
),
248+
subtitle: (_viewMode != viewMode.withAllDetails && !_editing)
249+
? null
250+
: getNutritionRow(context, muted(getNutritionalValues(values, context))),
229251
trailing: _editing
230252
? IconButton(
231-
icon: const Icon(Icons.delete),
253+
icon: const Icon(Icons.delete, size: ICON_SIZE_SMALL),
232254
tooltip: AppLocalizations.of(context).delete,
233255
iconSize: ICON_SIZE_SMALL,
234256
onPressed: () {
@@ -250,38 +272,6 @@ class MealItemWidget extends StatelessWidget {
250272
}
251273
}
252274

253-
class LogDiaryItemWidget extends StatelessWidget {
254-
final Log _item;
255-
256-
const LogDiaryItemWidget(this._item);
257-
258-
@override
259-
Widget build(BuildContext context) {
260-
// TODO(x): add real support for weight units
261-
/*
262-
String unit = _item.weightUnitId == null
263-
? AppLocalizations.of(context).g
264-
: _item.weightUnitObj!.weightUnit.name;
265-
266-
*/
267-
final String unit = AppLocalizations.of(context).g;
268-
final values = _item.nutritionalValues;
269-
270-
return ListTile(
271-
leading: IngredientAvatar(ingredient: _item.ingredient),
272-
title: Text(
273-
'${_item.amount.toStringAsFixed(0)}$unit ${_item.ingredient.name}',
274-
overflow: TextOverflow.ellipsis,
275-
),
276-
subtitle: Row(
277-
mainAxisAlignment: MainAxisAlignment.spaceBetween,
278-
mainAxisSize: MainAxisSize.max,
279-
children: [...getMutedNutritionalValues(values, context)],
280-
),
281-
);
282-
}
283-
}
284-
285275
class MealHeader extends StatelessWidget {
286276
final Meal _meal;
287277
final bool _editing;
Lines changed: 49 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,49 @@
1+
import 'package:flutter/material.dart';
2+
3+
/// NutritionTile is similar to a non-interactive ListTile,
4+
/// but uses a fixed, easy to understand layout.
5+
class NutritionTile extends StatelessWidget {
6+
final Widget? leading; // always constrained to 40px wide
7+
final double vPadding;
8+
final Widget? title;
9+
final Widget? subtitle;
10+
final Widget? trailing; // always constrained to 20px wide
11+
12+
const NutritionTile({
13+
this.leading,
14+
this.title,
15+
this.subtitle,
16+
this.trailing,
17+
this.vPadding = 8,
18+
});
19+
20+
@override
21+
Widget build(BuildContext context) {
22+
return Padding(
23+
padding: EdgeInsets.symmetric(horizontal: 8.0, vertical: vPadding),
24+
child: Row(
25+
mainAxisSize: MainAxisSize.max,
26+
children: [
27+
ConstrainedBox(
28+
constraints: const BoxConstraints(minWidth: 40, maxWidth: 40),
29+
child: leading ?? const SizedBox(width: 40),
30+
),
31+
const SizedBox(width: 8),
32+
Expanded(
33+
child: Column(
34+
children: [
35+
if (title != null) title!,
36+
if (subtitle != null) subtitle!,
37+
],
38+
),
39+
),
40+
const SizedBox(width: 8),
41+
ConstrainedBox(
42+
constraints: const BoxConstraints(minWidth: 20, maxWidth: 20),
43+
child: trailing ?? const SizedBox(width: 20),
44+
),
45+
],
46+
),
47+
);
48+
}
49+
}

0 commit comments

Comments
 (0)