Merge branch 'master' into simplify-tables

This commit is contained in:
Dieter Plaetinck
2024-06-16 12:26:35 +03:00
31 changed files with 458 additions and 225 deletions

View File

@@ -397,7 +397,7 @@
"description": "Text for registration button"
},
"fiber": "الألياف",
"@fibres": {},
"@fiber": {},
"aboutDescription": "شكرًا لاستخدامك وقر! وقر مشروع تعاوني مفتوح المصدر، تم إنشاؤه بواسطة عشّاق اللياقة البدنية من جميع أنحاء العالم.",
"@aboutDescription": {
"description": "Text in the about dialog"

View File

@@ -473,7 +473,7 @@
"description": "Message shown when the user has no logged weight entries"
},
"fiber": "Fibra",
"@fibres": {},
"@fiber": {},
"sodium": "Sodi",
"@sodium": {},
"delete": "Esborra",

View File

@@ -113,7 +113,7 @@
"@save": {},
"addIngredient": "Přidat přísadu",
"@addIngredient": {},
"logIngredient": "Uložit do výživového deníku",
"logIngredient": "Zaznamenat ingredienci do nutričního diáře",
"@logIngredient": {},
"searchIngredient": "Hledat přísadu",
"@searchIngredient": {
@@ -376,7 +376,7 @@
"@supersetWith": {
"description": "Text used between exercise cards when adding a new set. Translate as something like 'in a superset with'"
},
"logMeal": "Zaznamenat toto jídlo",
"logMeal": "Zaznamenat do nutričního diáře",
"@logMeal": {},
"muscles": "Svaly",
"@muscles": {
@@ -901,5 +901,9 @@
"goalMacro": "Makro cíle",
"@goalMacro": {
"description": "The goal for macronutrients"
}
},
"ingredientLogged": "Ingredience zaznamenána do diáře",
"@ingredientLogged": {},
"selectMealToLog": "Vyberte jídlo k záznamu do diáře",
"@selectMealToLog": {}
}

View File

@@ -61,7 +61,7 @@
"sodium": "Natrium",
"@sodium": {},
"fiber": "Ballaststoff",
"@fibres": {},
"@fiber": {},
"saturatedFat": "Gesättigte Fettsäuren",
"@saturatedFat": {},
"fat": "Fett",

View File

@@ -122,6 +122,7 @@
"@searchExercise": {
"description": "Label on set form. Selected exercises are added to the set"
},
"noIngredientsDefined": "No ingredients defined yet",
"noMatchingExerciseFound": "No matching exercises found",
"@noMatchingExerciseFound": {
"description": "Message returned if no exercises match the searched string"

View File

@@ -131,7 +131,7 @@
"sodium": "Sodio",
"@sodium": {},
"fiber": "Fibra",
"@fibres": {},
"@fiber": {},
"saturatedFat": "Grasas saturadas",
"@saturatedFat": {},
"fat": "Grasas",

View File

@@ -70,7 +70,7 @@
"sodium": "Sodium",
"@sodium": {},
"fiber": "fiber",
"@fibres": {},
"@fiber": {},
"saturatedFat": "Graisses saturées",
"@saturatedFat": {},
"fat": "Graisses",

View File

@@ -276,7 +276,7 @@
"saturatedFat": "שומן רווי",
"@saturatedFat": {},
"fiber": "סיבים",
"@fibres": {},
"@fiber": {},
"sodium": "נתרן",
"@sodium": {},
"amount": "כמות",

View File

@@ -24,154 +24,305 @@
"description": "Error message when the user enters an invalid URL, e.g. in the login form"
},
"usernameValidChars": "एक उपयोगकर्ता नाम में केवल अक्षर, अंक और वर्ण @, +, ., - और _ हो सकते हैं।",
"@usernameValidChars": {},
"passwordsDontMatch": "पासवर्ड मेल नहीं खाता",
"@passwordsDontMatch": {},
"passwordTooShort": "पासवर्ड कम से कम 6 अक्षर होना चाहिए",
"@passwordTooShort": {},
"password": "पासवर्ड",
"@password": {},
"confirmPassword": "पासवर्ड की पुष्टि करें",
"@confirmPassword": {},
"invalidEmail": "कृपया एक वैलिड ईमेल दीजिये",
"@invalidEmail": {},
"email": "ईमेल",
"@email": {},
"username": "उपयोगकर्ता नाम",
"@username": {},
"customServerUrl": "कस्टम सर्वर URL wger के लिए",
"@customServerUrl": {},
"customServerHint": "अपने स्वयं के सर्वर का पता दर्ज करें, अन्यथा डिफ़ॉल्ट का उपयोग किया जाएगा",
"@customServerHint": {},
"reset": "रीसेट",
"@reset": {},
"registerInstead": "इसके बजाय रजिस्टर करें",
"@registerInstead": {},
"loginInstead": "इसके बजाय लॉग इन करें",
"@loginInstead": {},
"labelWorkoutPlans": "कसरत की योजनाएं",
"@labelWorkoutPlans": {},
"labelBottomNavWorkout": "कसरत",
"@labelBottomNavWorkout": {},
"labelBottomNavNutrition": "पोषण",
"@labelBottomNavNutrition": {},
"labelWorkoutLogs": "प्रशिक्षण लॉग",
"@labelWorkoutLogs": {},
"labelWorkoutPlan": "कसरत योजना",
"@labelWorkoutPlan": {},
"labelDashboard": "डैशबोर्ड",
"@labelDashboard": {},
"successfullyDeleted": "हटाया गया",
"@successfullyDeleted": {},
"successfullySaved": "सहेजा गया",
"@successfullySaved": {},
"exercise": "व्यायाम",
"@exercise": {},
"searchExercise": "जोड़ने के लिए व्यायाम खोजें",
"@searchExercise": {},
"supersetWith": "सुपरसेट के साथ",
"@supersetWith": {},
"equipment": "उपकरण",
"@equipment": {},
"muscles": "मांसपेशियों",
"@muscles": {},
"musclesSecondary": "माध्यमिक मांसपेशियां",
"@musclesSecondary": {},
"category": "श्रेणी",
"@category": {},
"newWorkout": "नई कसरत योजना",
"@newWorkout": {},
"noWorkoutPlans": "आपकी कोई कसरत योजना नहीं है",
"@noWorkoutPlans": {},
"repetitions": "दोहराव",
"@repetitions": {},
"reps": "रेप्स",
"@reps": {},
"rir": "RiR",
"@rir": {},
"rirNotUsed": "RiR उपयोग नहीं किया गया",
"@rirNotUsed": {},
"weightUnit": "वजन इकाई",
"@weightUnit": {},
"repetitionUnit": "दोहराव इकाई",
"@repetitionUnit": {},
"set": "सेट",
"@set": {},
"dayDescriptionHelp": "इस दिन क्या किया जाता है (जैसे 'पुल डे') या शरीर के किन अंगों को प्रशिक्षित किया जाता है (जैसे 'छाती और कंधे') का विवरण",
"@dayDescriptionHelp": {},
"setNr": "सेट {nr}",
"@setNr": {},
"sameRepetitions": "यदि आप सभी सेटों के लिए समान दोहराव और वजन करते हैं तो आप केवल एक पंक्ति भर सकते हैं। उदाहरण के लिए, 4 सेटों के लिए, दोहराव के लिए केवल 10 दर्ज करें, यह स्वचालित रूप से \"4 x 10\" हो जाता है।",
"@sameRepetitions": {},
"comment": "टिप्पणी",
"@comment": {},
"impression": "प्रभाव जमाना",
"@impression": {},
"notes": "टिप्पणियाँ",
"@notes": {},
"workoutSession": "कसरत सत्र",
"@workoutSession": {},
"newDay": "नया दिन",
"@newDay": {},
"newSet": "नया सेट",
"@newSet": {},
"selectExercises": "यदि आप एक सुपरसेट करना चाहते हैं तो आप कई अभ्यास खोज सकते हैं, उन्हें एक साथ समूहीकृत किया जाएगा",
"@selectExercises": {},
"gymMode": "जिम मोड",
"@gymMode": {},
"plateCalculator": "प्लेट्स",
"@plateCalculator": {},
"plateCalculatorNotDivisible": "उपलब्ध प्लेटों के साथ वजन तक पहुंचना संभव नहीं है",
"@plateCalculatorNotDivisible": {},
"pause": "रोकें",
"@pause": {},
"jumpTo": "यहां जाएं",
"@jumpTo": {},
"todaysWorkout": "आज आपका वर्कआउट",
"@todaysWorkout": {},
"logHelpEntries": "यदि एक ही दिन में एक से अधिक प्रविष्टियाँ समान संख्या में दोहराव के साथ होती हैं, लेकिन अलग-अलग भार होते हैं, तो केवल उच्च भार वाली प्रविष्टि को आरेख में दिखाया जाता है।",
"@logHelpEntries": {},
"logHelpEntriesUnits": "ध्यान दें कि केवल वज़न इकाई (किलो या एलबी) और दोहराव वाली प्रविष्टियां चार्ट की जाती हैं, अन्य संयोजन जैसे समय या विफलता को यहां अनदेखा किया जाता है।",
"@logHelpEntriesUnits": {},
"description": "विवरण",
"@description": {},
"name": "नाम",
"@name": {},
"save": "सेव",
"@save": {},
"addSet": "सेट जोड़ें",
"@addSet": {},
"addMeal": "खाना जोड़ें",
"@addMeal": {},
"mealLogged": "भोजन डायरी में दर्ज",
"@mealLogged": {},
"logMeal": "इस भोजन को दर्ज करें",
"@logMeal": {},
"addIngredient": "सामग्री जोड़ें",
"@addIngredient": {},
"logIngredient": "पोषण डायरी में सहेजें",
"@logIngredient": {},
"searchIngredient": "सामग्री खोजें",
"@searchIngredient": {},
"nutritionalPlan": "पोषण योजना",
"@nutritionalPlan": {},
"nutritionalDiary": "पोषण डायरी",
"@nutritionalDiary": {},
"nutritionalPlans": "पोषण योजनाएं",
"@nutritionalPlans": {},
"noNutritionalPlans": "आपके पास कोई पोषण योजना नहीं है",
"@noNutritionalPlans": {},
"anErrorOccurred": "कुछ गड़बड़ी हुई!",
"@anErrorOccurred": {},
"weight": "वजन",
"@weight": {},
"measurement": "माप",
"@measurement": {},
"measurements": "मापन",
"@measurements": {},
"measurementCategoriesHelpText": "मापन श्रेणी, जैसे 'बाइसेप्स' या 'बॉडी फैट'",
"@measurementCategoriesHelpText": {},
"measurementEntriesHelpText": "'सेमी' या '%' जैसी श्रेणी को मापने के लिए इस्तेमाल की जाने वाली इकाई",
"@measurementEntriesHelpText": {},
"date": "दिनांक",
"@date": {},
"value": "मान",
"@value": {},
"start": "शुरु",
"@start": {},
"time": "समय",
"@time": {},
"timeStart": "शुरु समय",
"@timeStart": {},
"timeEnd": "अंत समय",
"@timeEnd": {},
"timeStartAhead": "प्रारंभ समय समाप्ति समय से आगे का नहीं हो सकता",
"@timeStartAhead": {},
"ingredient": "सामग्री",
"@ingredient": {},
"energy": "ऊर्जा",
"@energy": {},
"energyShort": "E",
"@energyShort": {},
"kcal": "किलो कैलोरी",
"@kcal": {},
"macronutrients": "मैक्रोन्ट्रीय प्रयोग",
"@macronutrients": {},
"planned": "की योजना बनाई",
"@planned": {},
"logged": "की योजना की दर्ज हुई",
"@logged": {},
"weekAverage": "7 दिन का औसत",
"@weekAverage": {},
"difference": "अंतर",
"@difference": {},
"percentEnergy": "ऊर्जा का प्रतिशत",
"@percentEnergy": {},
"gPerBodyKg": "ग्राम प्रति शरीर किलो",
"@gPerBodyKg": {},
"total": "कुल",
"@total": {},
"kJ": "kJ",
"@kJ": {},
"g": "ग्राम",
"@g": {},
"protein": "प्रोटीन",
"@protein": {},
"proteinShort": "P",
"@proteinShort": {},
"carbohydrates": "कार्बोहाइड्रेट्स",
"@carbohydrates": {},
"carbohydratesShort": "C",
"@carbohydratesShort": {},
"sugars": "शुगर्स",
"@sugars": {},
"fat": "चर्बी",
"@fat": {},
"fatShort": "F",
"@fatShort": {},
"saturatedFat": "संतृप्त चर्बी",
"@saturatedFat": {},
"fiber": "रेशा",
"@fiber": {},
"sodium": "सोड़ियम",
"@sodium": {},
"amount": "मात्रा",
"@amount": {},
"unit": "इकाई",
"@unit": {},
"newEntry": "नया प्रविष्टि",
"@newEntry": {},
"noWeightEntries": "आपके पास कोई वजन प्रविष्टि नहीं है",
"@noWeightEntries": {},
"edit": "संपादित करें",
"@edit": {},
"loadingText": "लोड हो रहा है...",
"@loadingText": {},
"delete": "हटाएं",
"@delete": {},
"confirmDelete": "क्या आप वाकई '{toDelete}' को हटाना चाहते हैं?",
"@confirmDelete": {},
"newNutritionalPlan": "नई पोषण योजना",
"@newNutritionalPlan": {},
"toggleDetails": "विवरण टॉगल करें",
"@toggleDetails": {},
"goToDetailPage": "विवरण पेज पर जाएं",
"@goToDetailPage": {},
"aboutDescription": "Wger का उपयोग करने के लिए धन्यवाद! Wger एक सहयोगी ओपन सोर्स प्रोजेक्ट है, जिसे दुनिया भर के फिटनेस उत्साही लोगों द्वारा बनाया गया है।",
"@aboutDescription": {},
"aboutSourceTitle": "सोर्स कोड",
"@aboutSourceTitle": {},
"aboutSourceText": "इस एप्लिकेशन का स्रोत कोड और इसके सर्वर को Github पर प्राप्त करें",
"@aboutSourceText": {},
"aboutBugsTitle": "कोई समस्या या विचार है?",
"@aboutBugsTitle": {},
"aboutBugsText": "अगर किसी चीज़ ने अपेक्षा के अनुरूप व्यवहार नहीं किया या कोई ऐसी विशेषता है जो आपको याद आ रही है तो संपर्क करें।",
"@aboutBugsText": {},
"aboutContactUsTitle": "संपर्क करें",
"@aboutContactUsTitle": {},
"aboutContactUsText": "यदि आप हमारे साथ चैट करना चाहते हैं, तो Discord सर्वर पर आएं और संपर्क करें",
"@aboutContactUsText": {},
"aboutTranslationTitle": "अनुवाद",
"@aboutTranslationTitle": {},
"aboutTranslationText": "इस एप्लिकेशन का वेबलेट पर अनुवाद किया गया है। अगर आप भी मदद करना चाहते हैं, तो लिंक पर क्लिक करें और अनुवाद करना शुरू करें।",
"@aboutTranslationText": {},
"calendar": "कैलेंडर",
"@calendar": {},
"enterRepetitionsOrWeight": "कृपया इनमें से कम से कम एक सेट के लिए दोहराव या वजन भरें",
"@enterRepetitionsOrWeight": {},
"enterValue": "कृपया मान दर्ज करें",
"@enterValue": {},
"selectExercise": "कृपया एक पाठ्यक्रम का चयन करें",
"@selectExercise": {},
"enterCharacters": "कृपया {min} और {max} वर्णों के बीच दर्ज करें",
"@enterCharacters": {},
"nrOfSets": "प्रति व्यायाम सेट: {nrOfSets}",
"@nrOfSets": {},
"setUnitsAndRir": "इकाइयाँ सेट करें और RiR",
"@setUnitsAndRir": {},
"enterValidNumber": "कृपया वैध संख्या दर्ज करें",
"@enterValidNumber": {},
"selectIngredient": "कृपया एक सामग्री का चयन करें",
"@selectIngredient": {},
"recentlyUsedIngredients": "हाल ही में उपयोग की गई सामग्री",
"@recentlyUsedIngredients": {},
"selectImage": "कृपया एक चित्र का चयन करें",
"@selectImage": {},
"optionsLabel": "विकल्प",
"@optionsLabel": {},
"takePicture": "चित्र लें",
"@takePicture": {},
"chooseFromLibrary": "लाइब्रेरी से चुनें",
"@chooseFromLibrary": {},
"gallery": "ग्यालरी",
"@gallery": {},
"addImage": "चित्र जोड़ें",
"@addImage": {},
"dataCopied": "डेटा की प्रतिलिपि बना दिया गया है",
"@dataCopied": {},
"appUpdateTitle": "एप्लिकेशन अपडेट की जरूरत है",
"@appUpdateTitle": {},
"appUpdateContent": "ऐप का यह संस्करण सर्वर के अनुकूल नहीं है, कृपया अपना एप्लिकेशन अपडेट करें।",
"@appUpdateContent": {},
"productFound": "उत्पाद मिला",
"@productFound": {},
"productFoundDescription": "बारकोड इस उत्पाद से मेल खाता है: {productName}। क्या आप जारी रखना चाहते हैं?",
"@productFoundDescription": {},
"productNotFound": "उत्पाद नहीं मिला",
"@productNotFound": {},
"productNotFoundDescription": "स्कैन किए गए बारकोड {barcode} वाला उत्पाद wger डेटाबेस में नहीं मिला",
"@productNotFoundDescription": {},
"scanBarcode": "बारकोड स्कैन करें",
"close": "बंद करें"
"@scanBarcode": {},
"close": "बंद करें",
"@close": {}
}

View File

@@ -317,7 +317,7 @@
},
"addIngredient": "Dodaj sastojak",
"@addIngredient": {},
"logMeal": "Zapiši ovaj obrok",
"logMeal": "Zapiši obrok u dnevnik prehrane",
"@logMeal": {},
"value": "Vrijednost",
"@value": {
@@ -515,7 +515,7 @@
"@recentlyUsedIngredients": {
"description": "A message when a user adds a new ingredient to a meal."
},
"logIngredient": "Spremi u dnevnik prehrane",
"logIngredient": "Zapiši sastojak u dnevnik prehrane",
"@logIngredient": {},
"searchIngredient": "Traži sastojak",
"@searchIngredient": {
@@ -901,5 +901,9 @@
"goalMacro": "Ciljevi za makronutrijente",
"@goalMacro": {
"description": "The goal for macronutrients"
}
},
"selectMealToLog": "Odaberi obrok za zapis u dnevnik",
"@selectMealToLog": {},
"ingredientLogged": "Sastojak je upisan u dnevnik",
"@ingredientLogged": {}
}

View File

@@ -371,7 +371,7 @@
"saturatedFat": "Saturated fat",
"@saturatedFat": {},
"fiber": "Fibre",
"@fibres": {},
"@fiber": {},
"sodium": "Sodium",
"@sodium": {},
"amount": "Amount",

View File

@@ -64,7 +64,7 @@
"sodium": "Sodio",
"@sodium": {},
"fiber": "Fibre",
"@fibres": {},
"@fiber": {},
"saturatedFat": "Grassi saturi",
"@saturatedFat": {},
"fat": "Grassi",

View File

@@ -370,7 +370,7 @@
"saturatedFat": "Saturated fat",
"@saturatedFat": {},
"fiber": "Fibre",
"@fibres": {},
"@fiber": {},
"sodium": "Sodium",
"@sodium": {},
"amount": "Amount",

View File

@@ -415,7 +415,7 @@
"saturatedFat": "Tłuszcz nasycony",
"@saturatedFat": {},
"fiber": "Błonnik",
"@fibres": {},
"@fiber": {},
"sodium": "Sód",
"@sodium": {},
"amount": "Ilość",

View File

@@ -180,7 +180,7 @@
"description": "The first letter or short name of the word 'Carbohydrates', used in overviews"
},
"fiber": "Fibra",
"@fibres": {},
"@fiber": {},
"sodium": "Sódio",
"@sodium": {},
"amount": "Quantidade",

View File

@@ -272,7 +272,7 @@
"difference": "Diferença",
"@difference": {},
"fiber": "Fibra",
"@fibres": {},
"@fiber": {},
"aboutDescription": "Obrigado por usar o Wger! Wger é um projeto colaborativo de código aberto, feito por entusiastas do fitness de todo o mundo.",
"@aboutDescription": {
"description": "Text in the about dialog"

View File

@@ -205,7 +205,7 @@
"@plateCalculator": {
"description": "Label used for the plate calculator in the gym mode"
},
"logIngredient": "Beslenme günlüğüne kaydet",
"logIngredient": "Malzemeyi beslenme günlüğüne kaydet",
"@logIngredient": {},
"comment": "Yorum",
"@comment": {
@@ -257,7 +257,7 @@
"@addMeal": {},
"mealLogged": "Yemek günlüğe kaydedildi",
"@mealLogged": {},
"logMeal": "Bu yemeği kaydet",
"logMeal": "Yemeği beslenme günlüğüne kaydet",
"@logMeal": {},
"addIngredient": "Malzeme ekle",
"@addIngredient": {},
@@ -841,5 +841,9 @@
"deficit": "eksiklik",
"@deficit": {
"description": "Caloric deficit (either planned or unplanned)"
}
},
"ingredientLogged": "Malzeme günlüğe kaydedildi",
"@ingredientLogged": {},
"selectMealToLog": "Günlüğe kaydetmek için bir yemek seçin",
"@selectMealToLog": {}
}

View File

@@ -402,7 +402,7 @@
"saturatedFat": "Насичені жири",
"@saturatedFat": {},
"fiber": "Волокна",
"@fibres": {},
"@fiber": {},
"sodium": "Натрій",
"@sodium": {},
"amount": "Сума",

View File

@@ -22,7 +22,7 @@ import 'package:provider/provider.dart';
import 'package:wger/models/nutrition/meal.dart';
import 'package:wger/providers/nutrition.dart';
import 'package:wger/widgets/nutrition/meal.dart';
import 'package:wger/widgets/nutrition/widgets.dart';
import 'package:wger/widgets/nutrition/nutrition_tiles.dart';
class LogMealArguments {
final Meal meal;
@@ -51,7 +51,7 @@ class _LogMealScreenState extends State<LogMealScreen> {
return Scaffold(
appBar: AppBar(
title: Text('Log meal to diary'),
title: Text(AppLocalizations.of(context).logMeal),
),
body: Consumer<NutritionPlansProvider>(
builder: (context, nutritionProvider, child) => SingleChildScrollView(
@@ -64,13 +64,13 @@ class _LogMealScreenState extends State<LogMealScreen> {
children: [
Text(meal.name, style: Theme.of(context).textTheme.headlineSmall),
if (meal.mealItems.isEmpty)
const ListTile(title: Text('No ingredients defined yet'))
ListTile(title: Text(AppLocalizations.of(context).noIngredientsDefined))
else
Column(
children: [
const NutritionDiaryheader(),
...meal.mealItems
.map((item) => MealItemWidget(item, viewMode.withAllDetails, false)),
const DiaryheaderTile(),
...meal.mealItems.map(
(item) => MealItemEditableFullTile(item, viewMode.withAllDetails, false)),
const SizedBox(height: 32),
Text(
'Portion: ${portionPct.round()} %',

View File

@@ -16,7 +16,6 @@
* along with this program. If not, see <http://www.gnu.org/licenses/>.
*/
import 'dart:ui';
import 'package:flex_color_scheme/flex_color_scheme.dart';
import 'package:flutter/material.dart';

View File

@@ -21,10 +21,12 @@ import 'package:flutter/material.dart';
class MutedText extends StatelessWidget {
final String _text;
final TextAlign textAlign;
final TextOverflow? overflow;
const MutedText(
this._text, {
this.textAlign = TextAlign.left,
this.overflow,
});
@override
@@ -33,6 +35,7 @@ class MutedText extends StatelessWidget {
_text,
style: TextStyle(color: Theme.of(context).colorScheme.outline),
textAlign: textAlign,
overflow: overflow,
);
}
}

View File

@@ -31,6 +31,7 @@ import 'package:wger/models/nutrition/nutritional_plan.dart';
import 'package:wger/providers/nutrition.dart';
import 'package:wger/screens/nutritional_plan_screen.dart';
import 'package:wger/widgets/nutrition/helpers.dart';
import 'package:wger/widgets/nutrition/nutrition_tiles.dart';
import 'package:wger/widgets/nutrition/widgets.dart';
class MealForm extends StatelessWidget {
@@ -341,7 +342,7 @@ class IngredientFormState extends State<IngredientForm> {
builder: (BuildContext context, AsyncSnapshot<Ingredient> snapshot) {
if (snapshot.hasData) {
_mealItem.ingredient = snapshot.data!;
return MealItemTile(
return MealItemValuesTile(
ingredient: _mealItem.ingredient,
nutritionalValues: _mealItem.nutritionalValues,
);

View File

@@ -19,28 +19,59 @@
import 'package:flutter/cupertino.dart';
import 'package:flutter/material.dart';
import 'package:flutter_gen/gen_l10n/app_localizations.dart';
import 'package:wger/models/nutrition/meal.dart';
import 'package:wger/models/nutrition/nutritional_values.dart';
import 'package:wger/widgets/core/core.dart';
List<Widget> getMutedNutritionalValues(NutritionalValues values, BuildContext context) => [
MutedText(
AppLocalizations.of(context).kcalValue(values.energy.toStringAsFixed(0)),
textAlign: TextAlign.right,
),
MutedText(
AppLocalizations.of(context).gValue(values.protein.toStringAsFixed(0)),
textAlign: TextAlign.right,
),
MutedText(
AppLocalizations.of(context).gValue(values.carbohydrates.toStringAsFixed(0)),
textAlign: TextAlign.right,
),
MutedText(
AppLocalizations.of(context).gValue(values.fat.toStringAsFixed(0)),
textAlign: TextAlign.right,
),
List<String> getNutritionColumnNames(BuildContext context) => [
AppLocalizations.of(context).energy,
AppLocalizations.of(context).protein,
AppLocalizations.of(context).carbohydrates,
AppLocalizations.of(context).fat,
];
List<String> getNutritionalValues(NutritionalValues values, BuildContext context) => [
AppLocalizations.of(context).kcalValue(values.energy.toStringAsFixed(0)),
AppLocalizations.of(context).gValue(values.protein.toStringAsFixed(0)),
AppLocalizations.of(context).gValue(values.carbohydrates.toStringAsFixed(0)),
AppLocalizations.of(context).gValue(values.fat.toStringAsFixed(0)),
];
List<int> getNutritionColumnFlexes(BuildContext context) {
return getNutritionColumnNames(context).map((e) {
final l = e.characters.length;
// if the word is really small (e.g. "fat"),
// we still want to have a minimum value to keep some spacing,
// especially because column values might become like "123 g"
return (l <= 3) ? 4 : l;
}).toList();
}
List<Widget> muted(List<String> children) => children
.map((e) => MutedText(
e,
textAlign: TextAlign.right,
overflow: TextOverflow.ellipsis,
))
.toList();
// return a row of elements in the standard macros spacing
Row getNutritionRow(BuildContext context, List<Widget> children) {
return Row(
mainAxisSize: MainAxisSize.max,
mainAxisAlignment: MainAxisAlignment.spaceBetween,
children: children.indexed
.map(
(e) => Flexible(
fit: FlexFit.tight,
flex: getNutritionColumnFlexes(context)[e.$1],
child: e.$2,
),
)
.toList(),
);
}
String getShortNutritionValues(NutritionalValues values, BuildContext context) {
final loc = AppLocalizations.of(context);
final e = '${loc.energyShort} ${loc.kcalValue(values.energy.toStringAsFixed(0))}';
@@ -49,3 +80,18 @@ String getShortNutritionValues(NutritionalValues values, BuildContext context) {
final f = '${loc.fatShort} ${loc.gValue(values.fat.toStringAsFixed(0))}';
return '$e / $p / $c / $f';
}
String getKcalConsumed(Meal meal, BuildContext context) {
final consumed =
meal.diaryEntriesToday.map((e) => e.nutritionalValues.energy).fold(0.0, (a, b) => a + b);
return AppLocalizations.of(context).kcalValue(consumed.toStringAsFixed(0));
}
String getKcalConsumedVsPlanned(Meal meal, BuildContext context) {
final planned = meal.plannedNutritionalValues.energy;
final consumed =
meal.diaryEntriesToday.map((e) => e.nutritionalValues.energy).fold(0.0, (a, b) => a + b);
final loc = AppLocalizations.of(context);
return '${consumed.toStringAsFixed(0)} / ${planned.toStringAsFixed(0)} ${loc.kcal}';
}

View File

@@ -21,7 +21,6 @@ import 'package:flutter_gen/gen_l10n/app_localizations.dart';
import 'package:flutter_svg_icons/flutter_svg_icons.dart';
import 'package:provider/provider.dart';
import 'package:wger/helpers/consts.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/providers/nutrition.dart';
@@ -30,6 +29,8 @@ import 'package:wger/screens/log_meal_screen.dart';
import 'package:wger/widgets/nutrition/charts.dart';
import 'package:wger/widgets/nutrition/forms.dart';
import 'package:wger/widgets/nutrition/helpers.dart';
import 'package:wger/widgets/nutrition/nutrition_tile.dart';
import 'package:wger/widgets/nutrition/nutrition_tiles.dart';
import 'package:wger/widgets/nutrition/widgets.dart';
enum viewMode {
@@ -157,15 +158,33 @@ class _MealWidgetState extends State<MealWidget> {
)),
if (_viewMode == viewMode.withIngredients || _viewMode == viewMode.withAllDetails)
const Divider(),
if (_viewMode == viewMode.withAllDetails) const NutritionDiaryheader(),
if (_viewMode == viewMode.withIngredients || _viewMode == viewMode.withAllDetails)
const DiaryheaderTile(),
if (_viewMode == viewMode.withIngredients || _viewMode == viewMode.withAllDetails)
if (widget._meal.mealItems.isEmpty && widget._meal.isRealMeal)
const ListTile(title: Text('No ingredients defined yet'))
NutritionTile(
title: Text(
AppLocalizations.of(context).noIngredientsDefined,
textAlign: TextAlign.left,
))
else
...widget._meal.mealItems.map((item) => MealItemWidget(item, _viewMode, _editing)),
...widget._meal.mealItems
.map((item) => MealItemEditableFullTile(item, _viewMode, _editing)),
if (_viewMode == viewMode.withIngredients || _viewMode == viewMode.withAllDetails)
const Divider(),
if (_viewMode == viewMode.withIngredients || _viewMode == viewMode.withAllDetails)
NutritionTile(
vPadding: 0,
leading: const Text('total'),
title: getNutritionRow(
context,
muted(getNutritionalValues(widget._meal.plannedNutritionalValues, context)),
),
),
if (_viewMode == viewMode.withAllDetails)
Column(
children: [
const Divider(),
Center(
child: Text(
AppLocalizations.of(context).loggedToday,
@@ -181,7 +200,7 @@ class _MealWidgetState extends State<MealWidget> {
padding: const EdgeInsets.all(8.0),
child: Column(
children: [
NutritionDiaryEntry(diaryEntry: item),
DiaryEntryTile(diaryEntry: item),
],
),
)),
@@ -194,12 +213,13 @@ class _MealWidgetState extends State<MealWidget> {
}
}
class MealItemWidget extends StatelessWidget {
/// An editable NutritionTile showing the avatar, name, nutritional values
class MealItemEditableFullTile extends StatelessWidget {
final bool _editing;
final viewMode _viewMode;
final MealItem _item;
const MealItemWidget(this._item, this._viewMode, this._editing);
const MealItemEditableFullTile(this._item, this._viewMode, this._editing);
@override
Widget build(BuildContext context) {
@@ -213,22 +233,24 @@ class MealItemWidget extends StatelessWidget {
final String unit = AppLocalizations.of(context).g;
final values = _item.nutritionalValues;
return ListTile(
return NutritionTile(
leading: IngredientAvatar(ingredient: _item.ingredient),
title: Text(
'${_item.amount.toStringAsFixed(0)}$unit ${_item.ingredient.name}',
overflow: TextOverflow.ellipsis,
),
subtitle: Row(
mainAxisAlignment: MainAxisAlignment.spaceBetween,
title: Row(
mainAxisSize: MainAxisSize.max,
children: [
if (_viewMode == viewMode.withAllDetails) ...getMutedNutritionalValues(values, context)
Text(
'${_item.amount.toStringAsFixed(0)}$unit ${_item.ingredient.name}',
overflow: TextOverflow.ellipsis,
textAlign: TextAlign.left,
),
],
),
subtitle: (_viewMode != viewMode.withAllDetails && !_editing)
? null
: getNutritionRow(context, muted(getNutritionalValues(values, context))),
trailing: _editing
? IconButton(
icon: const Icon(Icons.delete),
icon: const Icon(Icons.delete, size: ICON_SIZE_SMALL),
tooltip: AppLocalizations.of(context).delete,
iconSize: ICON_SIZE_SMALL,
onPressed: () {
@@ -250,38 +272,6 @@ class MealItemWidget extends StatelessWidget {
}
}
class LogDiaryItemWidget extends StatelessWidget {
final Log _item;
const LogDiaryItemWidget(this._item);
@override
Widget build(BuildContext context) {
// TODO(x): add real support for weight units
/*
String unit = _item.weightUnitId == null
? AppLocalizations.of(context).g
: _item.weightUnitObj!.weightUnit.name;
*/
final String unit = AppLocalizations.of(context).g;
final values = _item.nutritionalValues;
return ListTile(
leading: IngredientAvatar(ingredient: _item.ingredient),
title: Text(
'${_item.amount.toStringAsFixed(0)}$unit ${_item.ingredient.name}',
overflow: TextOverflow.ellipsis,
),
subtitle: Row(
mainAxisAlignment: MainAxisAlignment.spaceBetween,
mainAxisSize: MainAxisSize.max,
children: [...getMutedNutritionalValues(values, context)],
),
);
}
}
class MealHeader extends StatelessWidget {
final Meal _meal;
final bool _editing;
@@ -318,14 +308,25 @@ class MealHeader extends StatelessWidget {
crossAxisAlignment: CrossAxisAlignment.start,
children: [
Text(
((_meal.time != null) ? '${_meal.time!.format(context)} ' : '') + _meal.name,
style: Theme.of(context).textTheme.titleMedium,
_meal.name,
style: Theme.of(context).textTheme.titleLarge,
),
Row(
children: [
if (_meal.time != null)
Text(
_meal.time!.format(context),
style: Theme.of(context).textTheme.titleSmall,
),
if (_meal.time != null) const SizedBox(width: 12),
Text(
_meal.isRealMeal
? getKcalConsumedVsPlanned(_meal, context)
: getKcalConsumed(_meal, context),
style: Theme.of(context).textTheme.titleSmall,
),
],
),
if (_meal.isRealMeal)
Text(
getShortNutritionValues(_meal.plannedNutritionalValues, context),
style: Theme.of(context).textTheme.titleSmall,
),
],
)),
]),

View File

@@ -0,0 +1,49 @@
import 'package:flutter/material.dart';
/// NutritionTile is similar to a non-interactive ListTile,
/// but uses a fixed, easy to understand layout.
class NutritionTile extends StatelessWidget {
final Widget? leading; // always constrained to 40px wide
final double vPadding;
final Widget? title;
final Widget? subtitle;
final Widget? trailing; // always constrained to 20px wide
const NutritionTile({
this.leading,
this.title,
this.subtitle,
this.trailing,
this.vPadding = 8,
});
@override
Widget build(BuildContext context) {
return Padding(
padding: EdgeInsets.symmetric(horizontal: 8.0, vertical: vPadding),
child: Row(
mainAxisSize: MainAxisSize.max,
children: [
ConstrainedBox(
constraints: const BoxConstraints(minWidth: 40, maxWidth: 40),
child: leading ?? const SizedBox(width: 40),
),
const SizedBox(width: 8),
Expanded(
child: Column(
children: [
if (title != null) title!,
if (subtitle != null) subtitle!,
],
),
),
const SizedBox(width: 8),
ConstrainedBox(
constraints: const BoxConstraints(minWidth: 20, maxWidth: 20),
child: trailing ?? const SizedBox(width: 20),
),
],
),
);
}
}

View File

@@ -0,0 +1,85 @@
import 'package:flutter/material.dart';
import 'package:flutter_gen/gen_l10n/app_localizations.dart';
import 'package:intl/intl.dart';
import 'package:provider/provider.dart';
import 'package:wger/helpers/consts.dart';
import 'package:wger/models/nutrition/ingredient.dart';
import 'package:wger/models/nutrition/log.dart';
import 'package:wger/models/nutrition/nutritional_plan.dart';
import 'package:wger/models/nutrition/nutritional_values.dart';
import 'package:wger/providers/nutrition.dart';
import 'package:wger/widgets/nutrition/helpers.dart';
import 'package:wger/widgets/nutrition/nutrition_tile.dart';
import 'package:wger/widgets/nutrition/widgets.dart';
/// a NutritionTitle showing an ingredient, with its
/// avatar and nutritional values
class MealItemValuesTile extends StatelessWidget {
final Ingredient ingredient;
final NutritionalValues nutritionalValues;
const MealItemValuesTile({
super.key,
required this.ingredient,
required this.nutritionalValues,
});
@override
Widget build(BuildContext context) {
return NutritionTile(
leading: IngredientAvatar(ingredient: ingredient),
title: Text(getShortNutritionValues(nutritionalValues, context)),
);
}
}
/// a NutritionTitle showing the header for the diary
class DiaryheaderTile extends StatelessWidget {
final Widget? leading;
const DiaryheaderTile({this.leading});
@override
Widget build(BuildContext context) {
return NutritionTile(title: getNutritionRow(context, muted(getNutritionColumnNames(context))));
}
}
/// a NutritionTitle showing diary entries
class DiaryEntryTile extends StatelessWidget {
const DiaryEntryTile({
super.key,
required this.diaryEntry,
this.nutritionalPlan,
});
final Log diaryEntry;
final NutritionalPlan? nutritionalPlan;
@override
Widget build(BuildContext context) {
return NutritionTile(
leading: Text(
DateFormat.Hm(Localizations.localeOf(context).languageCode).format(diaryEntry.datetime),
style: const TextStyle(fontWeight: FontWeight.bold),
),
title: Text(
'${AppLocalizations.of(context).gValue(diaryEntry.amount.toStringAsFixed(0))} ${diaryEntry.ingredient.name}',
overflow: TextOverflow.ellipsis,
),
subtitle: getNutritionRow(
context, muted(getNutritionalValues(diaryEntry.nutritionalValues, context))),
trailing: (nutritionalPlan == null)
? null
: IconButton(
tooltip: AppLocalizations.of(context).delete,
onPressed: () {
Provider.of<NutritionPlansProvider>(context, listen: false)
.deleteLog(diaryEntry.id!, nutritionalPlan!.id!);
},
icon: const Icon(Icons.delete_outline),
iconSize: ICON_SIZE_SMALL,
),
);
}
}

View File

@@ -21,7 +21,7 @@ import 'package:flutter_gen/gen_l10n/app_localizations.dart';
import 'package:wger/models/nutrition/nutritional_plan.dart';
import 'package:wger/models/nutrition/nutritional_values.dart';
import 'package:wger/widgets/nutrition/charts.dart';
import 'package:wger/widgets/nutrition/widgets.dart';
import 'package:wger/widgets/nutrition/nutrition_tiles.dart';
class NutritionalDiaryDetailWidget extends StatelessWidget {
final NutritionalPlan _nutritionalPlan;
@@ -60,15 +60,10 @@ class NutritionalDiaryDetailWidget extends StatelessWidget {
),
),
const SizedBox(height: 15),
const NutritionDiaryheader(),
...logs.map((e) => Padding(
padding: const EdgeInsets.all(8.0),
child: Column(
children: [
NutritionDiaryEntry(diaryEntry: e, nutritionalPlan: _nutritionalPlan),
],
),
)),
const DiaryheaderTile(),
...logs.map(
(e) => DiaryEntryTile(diaryEntry: e, nutritionalPlan: _nutritionalPlan),
),
],
);
}

View File

@@ -22,7 +22,6 @@ import 'package:flutter_gen/gen_l10n/app_localizations.dart';
import 'package:flutter_typeahead/flutter_typeahead.dart';
import 'package:flutter_zxing/flutter_zxing.dart';
import 'package:font_awesome_flutter/font_awesome_flutter.dart';
import 'package:intl/intl.dart';
import 'package:provider/provider.dart';
import 'package:wger/helpers/consts.dart';
import 'package:wger/helpers/misc.dart';
@@ -30,12 +29,9 @@ import 'package:wger/helpers/platform.dart';
import 'package:wger/helpers/ui.dart';
import 'package:wger/models/exercises/ingredient_api.dart';
import 'package:wger/models/nutrition/ingredient.dart';
import 'package:wger/models/nutrition/log.dart';
import 'package:wger/models/nutrition/nutritional_plan.dart';
import 'package:wger/models/nutrition/nutritional_values.dart';
import 'package:wger/providers/nutrition.dart';
import 'package:wger/widgets/core/core.dart';
import 'package:wger/widgets/nutrition/helpers.dart';
import 'package:wger/widgets/nutrition/nutrition_tiles.dart';
class ScanReader extends StatelessWidget {
@override
@@ -214,7 +210,7 @@ class _IngredientTypeaheadState extends State<IngredientTypeahead> {
mainAxisSize: MainAxisSize.min,
children: [
Text(AppLocalizations.of(context).productFoundDescription(result.name)),
MealItemTile(
MealItemValuesTile(
ingredient: result,
nutritionalValues: result.nutritionalValues,
),
@@ -272,92 +268,6 @@ class _IngredientTypeaheadState extends State<IngredientTypeahead> {
}
}
class NutritionDiaryheader extends StatelessWidget {
final Widget? leading;
const NutritionDiaryheader({this.leading});
@override
Widget build(BuildContext context) {
return ListTile(
leading: leading ??
const CircleIconAvatar(
Icon(Icons.image, color: Colors.transparent),
color: Colors.transparent,
),
subtitle: Row(
mainAxisAlignment: MainAxisAlignment.spaceBetween,
mainAxisSize: MainAxisSize.max,
children: [
AppLocalizations.of(context).energy,
AppLocalizations.of(context).protein,
AppLocalizations.of(context).carbohydrates,
AppLocalizations.of(context).fat
]
.map((e) => MutedText(
e,
textAlign: TextAlign.right,
))
.toList(),
),
);
}
}
class NutritionDiaryEntry extends StatelessWidget {
const NutritionDiaryEntry({
super.key,
required this.diaryEntry,
this.nutritionalPlan,
});
final Log diaryEntry;
final NutritionalPlan? nutritionalPlan;
@override
Widget build(BuildContext context) {
return Row(
crossAxisAlignment: CrossAxisAlignment.start,
mainAxisAlignment: MainAxisAlignment.start,
children: [
Text(
DateFormat.Hm(Localizations.localeOf(context).languageCode).format(diaryEntry.datetime),
style: const TextStyle(fontWeight: FontWeight.bold),
),
const SizedBox(width: 8),
Expanded(
child: Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
Text(
'${AppLocalizations.of(context).gValue(diaryEntry.amount.toStringAsFixed(0))} ${diaryEntry.ingredient.name}',
overflow: TextOverflow.ellipsis,
),
const SizedBox(height: 4),
Row(
mainAxisSize: MainAxisSize.max,
mainAxisAlignment: MainAxisAlignment.spaceAround,
children: [
...getMutedNutritionalValues(diaryEntry.nutritionalValues, context),
],
),
const SizedBox(height: 12),
],
),
),
if (nutritionalPlan != null)
IconButton(
tooltip: AppLocalizations.of(context).delete,
onPressed: () {
Provider.of<NutritionPlansProvider>(context, listen: false)
.deleteLog(diaryEntry.id!, nutritionalPlan!.id!);
},
icon: const Icon(Icons.delete_outline)),
],
);
}
}
class IngredientAvatar extends StatelessWidget {
final Ingredient ingredient;
@@ -377,23 +287,3 @@ class IngredientAvatar extends StatelessWidget {
: const CircleIconAvatar(Icon(Icons.image, color: Colors.grey));
}
}
class MealItemTile extends StatelessWidget {
final Ingredient ingredient;
final NutritionalValues nutritionalValues;
const MealItemTile({
super.key,
required this.ingredient,
required this.nutritionalValues,
});
@override
Widget build(BuildContext context) {
return ListTile(
leading: IngredientAvatar(ingredient: ingredient),
title: Text(getShortNutritionValues(nutritionalValues, context)),
// subtitle: Text(ingredient.id.toString()),
);
}
}

Binary file not shown.

Before

Width:  |  Height:  |  Size: 69 KiB

After

Width:  |  Height:  |  Size: 67 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 63 KiB

After

Width:  |  Height:  |  Size: 64 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 63 KiB

After

Width:  |  Height:  |  Size: 69 KiB