diff --git a/packages/smooth_app/lib/l10n/app_en.arb b/packages/smooth_app/lib/l10n/app_en.arb index 428221608a7..c43584a769f 100644 --- a/packages/smooth_app/lib/l10n/app_en.arb +++ b/packages/smooth_app/lib/l10n/app_en.arb @@ -13,6 +13,7 @@ "description": "A label on a button that says 'Next', pressing the button takes the user to the next screen." }, "save": "Save", + "save_confirmation": "Are you sure you want to save?", "skip": "Skip", "cancel": "Cancel", "@cancel": {}, @@ -38,6 +39,7 @@ "@label_web": {}, "learnMore": "Learn more", "@learnMore": {}, + "general_confirmation": "Are you sure?", "incompatible": "Incompatible", "@incompatible": { "description": "Short label for product list view: the product is incompatible with your preferences" @@ -57,7 +59,7 @@ "description": "Looking for: ${BARCODE}" }, "@Introduction screen": {}, - "welcomeToOpenFoodFacts": "Welcome to Open Food Facts", + "welcomeToOpenFoodFacts": "Welcome to Open\u00A0Food\u00A0Facts", "@welcomeToOpenFoodFacts": {}, "whatIsOff": "Open Food Facts is a global non-profit powered by local communities.", "@whatIsOff": { @@ -352,6 +354,7 @@ }, "nutrition": "Nutrition", "@nutrition": {}, + "nutrition_page_close_confirmation": "Are you sure you want to close without saving?", "nutrition_facts_photo": "Nutrition facts photo", "@nutrition_facts_photo": { "description": "Button label: For adding a picture of the nutrition facts of a product" @@ -587,7 +590,7 @@ "count": {} } }, - "compare_products_mode": "Original", + "compare_products_mode": "Compare", "@compare_products_mode": { "description": "Button to switch to 'compare products mode'" }, diff --git a/packages/smooth_app/lib/pages/product/nutrition_page_loaded.dart b/packages/smooth_app/lib/pages/product/nutrition_page_loaded.dart index ecf27b0f183..a696a702840 100644 --- a/packages/smooth_app/lib/pages/product/nutrition_page_loaded.dart +++ b/packages/smooth_app/lib/pages/product/nutrition_page_loaded.dart @@ -10,6 +10,7 @@ import 'package:provider/provider.dart'; import 'package:smooth_app/database/local_database.dart'; import 'package:smooth_app/database/product_query.dart'; import 'package:smooth_app/generic_lib/design_constants.dart'; +import 'package:smooth_app/generic_lib/widgets/smooth_card.dart'; import 'package:smooth_app/pages/product/common/product_refresher.dart'; import 'package:smooth_app/pages/product/nutrition_container.dart'; @@ -68,35 +69,45 @@ class _NutritionPageLoadedState extends State { @override Widget build(BuildContext context) { - final AppLocalizations appLocalizations = AppLocalizations.of(context)!; + final AppLocalizations localizations = AppLocalizations.of(context)!; final LocalDatabase localDatabase = context.read(); final List children = []; - children.add(_switchNoNutrition(appLocalizations)); + children.add(_switchNoNutrition(localizations)); if (!_unspecified) { - children.add(_getServingField(appLocalizations)); - children.add(_getServingSwitch(appLocalizations)); + children.add(_getServingField(localizations)); + children.add(_getServingSwitch(localizations)); for (final OrderedNutrient orderedNutrient in _nutritionContainer.getDisplayableNutrients()) { children.add( - _getNutrientRow(appLocalizations, orderedNutrient), + _getNutrientRow(localizations, orderedNutrient), ); } - children.add(_addNutrientButton(appLocalizations)); + children.add(_addNutrientButton(localizations)); } - children.add(_addCancelSaveButtons( - appLocalizations, - localDatabase, - )); - return Scaffold( - appBar: AppBar(title: Text(appLocalizations.nutrition_page_title)), - body: Padding( - padding: const EdgeInsets.all(8.0), - child: Form( - key: _formKey, - child: ListView(children: children), + return WillPopScope( + child: Scaffold( + appBar: AppBar( + title: Text(localizations.nutrition_page_title), + actions: [ + IconButton( + onPressed: () => _validateAndSave(localizations, localDatabase), + icon: const Icon(Icons.check), + ) + ], + ), + body: Padding( + padding: const EdgeInsets.symmetric( + horizontal: LARGE_SPACE, + vertical: SMALL_SPACE, + ), + child: Form( + key: _formKey, + child: ListView(children: children), + ), ), ), + onWillPop: () => _showCancelPopup(localizations), ); } @@ -216,7 +227,7 @@ class _NutritionPageLoadedState extends State { } Widget _getServingSwitch(final AppLocalizations appLocalizations) => Row( - mainAxisAlignment: MainAxisAlignment.start, + mainAxisAlignment: MainAxisAlignment.center, crossAxisAlignment: CrossAxisAlignment.center, children: [ Text(appLocalizations.nutrition_page_per_100g), @@ -229,11 +240,15 @@ class _NutritionPageLoadedState extends State { ], ); - Widget _switchNoNutrition(final AppLocalizations appLocalizations) => - Container( + Widget _switchNoNutrition(final AppLocalizations localizations) => SmoothCard( color: Theme.of(context).colorScheme.primary, + padding: const EdgeInsets.symmetric( + horizontal: MEDIUM_SPACE, + vertical: SMALL_SPACE, + ), + margin: EdgeInsets.zero, child: Row( - mainAxisAlignment: MainAxisAlignment.spaceBetween, + mainAxisAlignment: MainAxisAlignment.spaceEvenly, crossAxisAlignment: CrossAxisAlignment.center, children: [ Switch( @@ -241,14 +256,11 @@ class _NutritionPageLoadedState extends State { onChanged: (final bool value) => setState(() => _unspecified = !_unspecified), ), - SizedBox( - width: getColumnSizeFromContext(context, 0.6), - child: Text( - appLocalizations.nutrition_page_unspecified, - style: const TextStyle(color: Colors.white), - maxLines: 2, - overflow: TextOverflow.ellipsis, - ), + Text( + localizations.nutrition_page_unspecified, + style: Theme.of(context).primaryTextTheme.bodyText1, + maxLines: 2, + overflow: TextOverflow.ellipsis, ), ], ), @@ -260,8 +272,10 @@ class _NutritionPageLoadedState extends State { final List leftovers = List.from( _nutritionContainer.getLeftoverNutrients(), ); + leftovers.sort((final OrderedNutrient a, final OrderedNutrient b) => a.name!.compareTo(b.name!)); + final OrderedNutrient? selected = await showDialog( context: context, builder: (BuildContext context) { @@ -294,33 +308,77 @@ class _NutritionPageLoadedState extends State { setState(() => _nutritionContainer.add(selected)); } }, + style: ButtonStyle( + shape: MaterialStateProperty.all( + const RoundedRectangleBorder( + borderRadius: ROUNDED_BORDER_RADIUS, + side: BorderSide.none, + ), + ), + ), icon: const Icon(Icons.add), label: Text(appLocalizations.nutrition_page_add_nutrient), ); - Widget _addCancelSaveButtons( - final AppLocalizations appLocalizations, - final LocalDatabase localDatabase, - ) => - Row( - mainAxisAlignment: MainAxisAlignment.spaceBetween, - crossAxisAlignment: CrossAxisAlignment.center, - children: [ - ElevatedButton( - onPressed: () => Navigator.pop(context), - child: Text(appLocalizations.cancel), + Future _showCancelPopup(AppLocalizations localizations) async => + await showDialog( + context: context, + builder: (BuildContext context) => AlertDialog( + shape: const RoundedRectangleBorder( + borderRadius: ROUNDED_BORDER_RADIUS, ), - ElevatedButton( - onPressed: () async { - if (!_formKey.currentState!.validate()) { - return; - } - await _save(localDatabase); - }, - child: Text(appLocalizations.save), - ), - ], - ); + title: Text(localizations.general_confirmation), + content: Text(localizations.nutrition_page_close_confirmation), + actions: [ + TextButton( + child: Text(localizations.cancel.toUpperCase()), + onPressed: () => Navigator.pop(context, false), + ), + TextButton( + child: Text(localizations.close.toUpperCase()), + onPressed: () => Navigator.pop(context, true), + ), + ], + ), + ) ?? + false; + + Future _validateAndSave(final AppLocalizations localizations, + final LocalDatabase localDatabase) async { + if (!_formKey.currentState!.validate()) { + return; + } + + await _showSavePopup(localizations, localDatabase); + } + + Future _showSavePopup( + AppLocalizations localizations, LocalDatabase localDatabase) async { + final bool shouldSave = await showDialog( + context: context, + builder: (BuildContext context) => AlertDialog( + title: Text(localizations.general_confirmation), + content: Text(localizations.save_confirmation), + shape: const RoundedRectangleBorder( + borderRadius: ROUNDED_BORDER_RADIUS, + ), + actions: [ + TextButton( + child: Text(localizations.cancel.toUpperCase()), + onPressed: () => Navigator.pop(context, false), + ), + TextButton( + child: Text(localizations.save.toUpperCase()), + onPressed: () => Navigator.pop(context, true), + ), + ], + )) ?? + false; + + if (shouldSave) { + _save(localDatabase); + } + } Future _save(final LocalDatabase localDatabase) async { for (final String key in _controllers.keys) {