diff --git a/packages/smooth_app/lib/knowledge_panel/knowledge_panels/knowledge_panel_action_card.dart b/packages/smooth_app/lib/knowledge_panel/knowledge_panels/knowledge_panel_action_card.dart index 81ac267eeed..4d54cd1c426 100644 --- a/packages/smooth_app/lib/knowledge_panel/knowledge_panels/knowledge_panel_action_card.dart +++ b/packages/smooth_app/lib/knowledge_panel/knowledge_panels/knowledge_panel_action_card.dart @@ -6,7 +6,7 @@ import 'package:smooth_app/pages/product/add_nutrition_button.dart'; import 'package:smooth_app/pages/product/add_ocr_button.dart'; import 'package:smooth_app/pages/product/add_packaging_button.dart'; import 'package:smooth_app/pages/product/add_simple_input_button.dart'; -import 'package:smooth_app/pages/product/ocr_ingredients_helper.dart'; +import 'package:smooth_app/pages/product/product_field_editor.dart'; import 'package:smooth_app/pages/product/simple_input_page_helpers.dart'; import 'package:smooth_app/services/smooth_services.dart'; @@ -63,9 +63,9 @@ class KnowledgePanelActionCard extends StatelessWidget { ); } if (_isIngredient(action)) { - return AddOCRButton( + return AddOcrButton( product: product, - helper: OcrIngredientsHelper(), + editor: ProductFieldOcrIngredientEditor(), ); } if (action == KnowledgePanelActionElement.ACTION_ADD_NUTRITION_FACTS) { diff --git a/packages/smooth_app/lib/knowledge_panel/knowledge_panels_builder.dart b/packages/smooth_app/lib/knowledge_panel/knowledge_panels_builder.dart index 64763951da3..830cfac6aad 100644 --- a/packages/smooth_app/lib/knowledge_panel/knowledge_panels_builder.dart +++ b/packages/smooth_app/lib/knowledge_panel/knowledge_panels_builder.dart @@ -7,7 +7,7 @@ import 'package:smooth_app/knowledge_panel/knowledge_panels/knowledge_panel_elem import 'package:smooth_app/pages/preferences/user_preferences_dev_mode.dart'; import 'package:smooth_app/pages/product/add_nutrition_button.dart'; import 'package:smooth_app/pages/product/add_ocr_button.dart'; -import 'package:smooth_app/pages/product/ocr_ingredients_helper.dart'; +import 'package:smooth_app/pages/product/product_field_editor.dart'; import 'package:smooth_app/services/smooth_services.dart'; // TODO(monsieurtanuki): rename without "Widget", like the file: KnowledgePanelsBuilder? @@ -71,9 +71,9 @@ class KnowledgePanelWidget { // When the flag is removed, this should be the following: // if (product.statesTags?.contains('en:ingredients-to-be-completed') ?? false) { children.add( - AddOCRButton( + AddOcrButton( product: product, - helper: OcrIngredientsHelper(), + editor: ProductFieldOcrIngredientEditor(), ), ); } diff --git a/packages/smooth_app/lib/l10n/app_en.arb b/packages/smooth_app/lib/l10n/app_en.arb index 9bd42811548..dedc6915c4e 100644 --- a/packages/smooth_app/lib/l10n/app_en.arb +++ b/packages/smooth_app/lib/l10n/app_en.arb @@ -544,6 +544,7 @@ "new_product_subtitle_nutriscore": "Get it by filling the food category and nutritional values", "new_product_title_ecoscore": "Compute the Eco-Score", "new_product_subtitle_ecoscore": "Get it by filling at least a category", + "new_product_additional_ecoscore": "Make Eco-Score computation more precise with origins, packaging & more", "new_product_title_pictures": "Let's take some pictures!", "new_product_title_misc": "And some basic data…", "nutritional_facts_photo_uploaded": "Nutrition facts photo uploaded", diff --git a/packages/smooth_app/lib/l10n/app_fr.arb b/packages/smooth_app/lib/l10n/app_fr.arb index bd73a83d592..50df74abfd0 100644 --- a/packages/smooth_app/lib/l10n/app_fr.arb +++ b/packages/smooth_app/lib/l10n/app_fr.arb @@ -540,6 +540,7 @@ "new_product_subtitle_nutriscore": "Merci de renseigner la catégorie de l'aliment et les données nutritionnelles", "new_product_title_ecoscore": "Calculez l'Eco-Score", "new_product_subtitle_ecoscore": "Merci de renseigner au moins la catégorie de l'aliment", + "new_product_additional_ecoscore": "Améliorez le calcul de l'Eco-Score grâce aux origines, au packaging & autres", "new_product_title_pictures": "Prenez quelques photos", "new_product_title_misc": "Quelques données générales…", "nutritional_facts_photo_uploaded": "Photo des informations nutritionnelles téléchargée", diff --git a/packages/smooth_app/lib/pages/product/add_new_product_page.dart b/packages/smooth_app/lib/pages/product/add_new_product_page.dart index f41a6b79652..f7d2d5eeeb7 100644 --- a/packages/smooth_app/lib/pages/product/add_new_product_page.dart +++ b/packages/smooth_app/lib/pages/product/add_new_product_page.dart @@ -13,9 +13,9 @@ import 'package:smooth_app/generic_lib/svg_icon_chip.dart'; import 'package:smooth_app/generic_lib/widgets/smooth_card.dart'; import 'package:smooth_app/helpers/image_field_extension.dart'; import 'package:smooth_app/pages/image_crop_page.dart'; -import 'package:smooth_app/pages/product/add_basic_details_page.dart'; import 'package:smooth_app/pages/product/common/product_dialog_helper.dart'; import 'package:smooth_app/pages/product/nutrition_page_loaded.dart'; +import 'package:smooth_app/pages/product/product_field_editor.dart'; import 'package:smooth_app/pages/product/simple_input_page_helpers.dart'; import 'package:smooth_app/query/product_query.dart'; import 'package:smooth_app/widgets/smooth_scaffold.dart'; @@ -30,10 +30,6 @@ TextStyle? _getSubtitleStyle(final BuildContext context) => null; double _getScoreIconHeight(final BuildContext context) => MediaQuery.of(context).size.height * .2; -/// Returns true if the [field] is valid (= not empty). -bool _isProductFieldValid(final String? field) => - field != null && field.trim().isNotEmpty; - /// "Create a product we couldn't find on the server" page. class AddNewProductPage extends StatefulWidget { const AddNewProductPage({ @@ -60,20 +56,35 @@ class _AddNewProductPageState extends State { final ProductList _history = ProductList.history(); - bool get _nutritionFactsAdded => _product.nutriments?.isEmpty() == false; - - bool get _categoriesAdded => - _product.categoriesTagsInLanguages?.isEmpty == false; + final ProductFieldEditor _packagingEditor = ProductFieldPackagingEditor(); + final ProductFieldEditor _ingredientsEditor = + ProductFieldOcrIngredientEditor(); + final ProductFieldEditor _originEditor = + ProductFieldSimpleEditor(SimpleInputPageOriginHelper()); + final ProductFieldEditor _categoryEditor = + ProductFieldSimpleEditor(SimpleInputPageCategoryHelper()); + final ProductFieldEditor _labelEditor = + ProductFieldSimpleEditor(SimpleInputPageLabelHelper()); + final ProductFieldEditor _detailsEditor = ProductFieldDetailsEditor(); + late final List _editors; - bool get _basicDetailsAdded => - _isProductFieldValid(_product.productName) || - _isProductFieldValid(_product.brands); + bool get _nutritionFactsAdded => _product.nutriments?.isEmpty() == false; bool _alreadyPushedToHistory = false; + bool _ecoscoreExpanded = false; + @override void initState() { super.initState(); + _editors = [ + _packagingEditor, + _ingredientsEditor, + _originEditor, + _categoryEditor, + _labelEditor, + _detailsEditor, + ]; _initialProduct = Product(barcode: barcode); _localDatabase = context.read(); _localDatabase.upToDate.showInterest(barcode); @@ -128,11 +139,7 @@ class _AddNewProductPageState extends State { if (_alreadyPushedToHistory) { return; } - if (_basicDetailsAdded || - _nutritionFactsAdded || - _categoriesAdded || - _uploadedImages.isNotEmpty || - _otherUploadedImages.isNotEmpty) { + if (_isPopulated) { _product.productName = _product.productName?.trim(); _product.brands = _product.brands?.trim(); await _daoProductList.push(_history, barcode); @@ -140,6 +147,18 @@ class _AddNewProductPageState extends State { } } + /// Returns true if at least one field is populated. + bool get _isPopulated { + for (final ProductFieldEditor editor in _editors) { + if (editor.isPopulated(_product)) { + return true; + } + } + return _nutritionFactsAdded || + _uploadedImages.isNotEmpty || + _otherUploadedImages.isNotEmpty; + } + Widget _buildCard( final List children, ) => @@ -195,6 +214,17 @@ class _AddNewProductPageState extends State { height: _getScoreIconHeight(context), ), ), + ListTile( + title: Text(appLocalizations.new_product_additional_ecoscore), + trailing: Icon( + _ecoscoreExpanded ? Icons.expand_less : Icons.expand_more, + ), + onTap: () => setState(() => _ecoscoreExpanded = !_ecoscoreExpanded), + ), + if (_ecoscoreExpanded) _buildEditorButton(context, _originEditor), + if (_ecoscoreExpanded) _buildEditorButton(context, _labelEditor), + if (_ecoscoreExpanded) _buildEditorButton(context, _packagingEditor), + if (_ecoscoreExpanded) _buildEditorButton(context, _ingredientsEditor), ]; } @@ -254,39 +284,45 @@ class _AddNewProductPageState extends State { done: imageFile != null, ); - // we let the user change the values - Widget _buildNutritionInputButton(final BuildContext context) { - final AppLocalizations appLocalizations = AppLocalizations.of(context); - return _MyButton( - appLocalizations.nutritional_facts_input_button_label, - Icons.filter_2, - // deactivated when the categories were not set beforehand - !_categoriesAdded - ? null - : () async => NutritionPageLoaded.showNutritionPage( - product: _product, - isLoggedInMandatory: false, - widget: this, - ), - done: _nutritionFactsAdded, - ); - } + Widget _buildNutritionInputButton(final BuildContext context) => _MyButton( + AppLocalizations.of(context).nutritional_facts_input_button_label, + Icons.filter_2, + // deactivated when the categories were not set beforehand + !_categoryEditor.isPopulated(_product) + ? null + : () async => NutritionPageLoaded.showNutritionPage( + product: _product, + isLoggedInMandatory: false, + widget: this, + ), + done: _nutritionFactsAdded, + ); - Widget _buildCategoriesButton(final BuildContext context) { - final SimpleInputPageCategoryHelper helper = - SimpleInputPageCategoryHelper(); + Widget _buildEditorButton( + final BuildContext context, + final ProductFieldEditor editor, { + final IconData? forceIconData, + }) { + final bool done = editor.isPopulated(_product); return _MyButton( - helper.getAddButtonLabel(AppLocalizations.of(context)), - Icons.filter_1, - () async => helper.showEditPage( + editor.getLabel(AppLocalizations.of(context)), + forceIconData ?? (done ? _doneIcon : _todoIcon), + () async => editor.edit( context: context, product: _product, isLoggedInMandatory: false, ), - done: _categoriesAdded, + done: done, ); } + Widget _buildCategoriesButton(final BuildContext context) => + _buildEditorButton( + context, + _categoryEditor, + forceIconData: Icons.filter_1, + ); + List _getMiscRows(final BuildContext context) { final AppLocalizations appLocalizations = AppLocalizations.of(context); return [ @@ -294,20 +330,7 @@ class _AddNewProductPageState extends State { appLocalizations.new_product_title_misc, style: _getTitleStyle(context), ), - _MyButton( - appLocalizations.completed_basic_details_btn_text, - _basicDetailsAdded ? _doneIcon : _todoIcon, - () async => Navigator.push( - context, - MaterialPageRoute( - builder: (BuildContext context) => AddBasicDetailsPage( - _product, - isLoggedInMandatory: false, - ), - ), - ), - done: _basicDetailsAdded, - ), + _buildEditorButton(context, _detailsEditor), ]; } } diff --git a/packages/smooth_app/lib/pages/product/add_ocr_button.dart b/packages/smooth_app/lib/pages/product/add_ocr_button.dart index 0d30a407844..36e82c5d090 100644 --- a/packages/smooth_app/lib/pages/product/add_ocr_button.dart +++ b/packages/smooth_app/lib/pages/product/add_ocr_button.dart @@ -2,38 +2,21 @@ import 'package:flutter/material.dart'; import 'package:flutter_gen/gen_l10n/app_localizations.dart'; import 'package:openfoodfacts/openfoodfacts.dart'; import 'package:smooth_app/helpers/product_cards_helper.dart'; -import 'package:smooth_app/pages/product/common/product_refresher.dart'; -import 'package:smooth_app/pages/product/edit_ingredients_page.dart'; -import 'package:smooth_app/pages/product/ocr_helper.dart'; +import 'package:smooth_app/pages/product/product_field_editor.dart'; /// "Add OCR image" button for user contribution. -class AddOCRButton extends StatelessWidget { - const AddOCRButton({ +class AddOcrButton extends StatelessWidget { + const AddOcrButton({ required this.product, - required this.helper, + required this.editor, }); final Product product; - final OcrHelper helper; + final ProductFieldOcrEditor editor; @override Widget build(BuildContext context) => addPanelButton( - helper.getAddButtonLabel(AppLocalizations.of(context)), - onPressed: () async { - // ignore: use_build_context_synchronously - if (!await ProductRefresher().checkIfLoggedIn(context)) { - return; - } - // ignore: use_build_context_synchronously - await Navigator.push( - context, - MaterialPageRoute( - builder: (BuildContext context) => EditOcrPage( - product: product, - helper: helper, - ), - ), - ); - }, + editor.getLabel(AppLocalizations.of(context)), + onPressed: () async => editor.edit(context: context, product: product), ); } diff --git a/packages/smooth_app/lib/pages/product/add_packaging_button.dart b/packages/smooth_app/lib/pages/product/add_packaging_button.dart index 570eef82e8d..b37c4a77aa3 100644 --- a/packages/smooth_app/lib/pages/product/add_packaging_button.dart +++ b/packages/smooth_app/lib/pages/product/add_packaging_button.dart @@ -2,36 +2,25 @@ import 'package:flutter/material.dart'; import 'package:flutter_gen/gen_l10n/app_localizations.dart'; import 'package:openfoodfacts/openfoodfacts.dart'; import 'package:smooth_app/helpers/product_cards_helper.dart'; -import 'package:smooth_app/pages/product/common/product_refresher.dart'; -import 'package:smooth_app/pages/product/edit_new_packagings.dart'; -import 'package:smooth_app/pages/product/ocr_packaging_helper.dart'; +import 'package:smooth_app/pages/product/product_field_editor.dart'; /// "Add (new) packaging" button for user contribution. class AddPackagingButton extends StatelessWidget { - const AddPackagingButton({ + AddPackagingButton({ required this.product, }); final Product product; + final ProductFieldEditor _editor = ProductFieldPackagingEditor(); + @override Widget build(BuildContext context) => addPanelButton( - OcrPackagingHelper().getAddButtonLabel(AppLocalizations.of(context)), - onPressed: () async { - // ignore: use_build_context_synchronously - if (!await ProductRefresher().checkIfLoggedIn(context)) { - return; - } - // ignore: use_build_context_synchronously - await Navigator.push( - context, - MaterialPageRoute( - builder: (BuildContext context) => EditNewPackagings( - product: product, - ), - fullscreenDialog: true, - ), - ); - }, + _editor.getLabel(AppLocalizations.of(context)), + onPressed: () async => _editor.edit( + context: context, + product: product, + isLoggedInMandatory: true, + ), ); } diff --git a/packages/smooth_app/lib/pages/product/add_simple_input_button.dart b/packages/smooth_app/lib/pages/product/add_simple_input_button.dart index 03cb7240f6f..4d861d37edd 100644 --- a/packages/smooth_app/lib/pages/product/add_simple_input_button.dart +++ b/packages/smooth_app/lib/pages/product/add_simple_input_button.dart @@ -2,6 +2,7 @@ import 'package:flutter/material.dart'; import 'package:flutter_gen/gen_l10n/app_localizations.dart'; import 'package:openfoodfacts/openfoodfacts.dart'; import 'package:smooth_app/helpers/product_cards_helper.dart'; +import 'package:smooth_app/pages/product/product_field_editor.dart'; import 'package:smooth_app/pages/product/simple_input_page_helpers.dart'; /// "Add simple input" button for user contribution. @@ -10,24 +11,19 @@ class AddSimpleInputButton extends StatelessWidget { required this.product, required this.helper, this.isLoggedInMandatory = true, - this.forcedTitle, - this.forcedIconData, }); final Product product; final AbstractSimpleInputPageHelper helper; final bool isLoggedInMandatory; - final String? forcedTitle; - final IconData? forcedIconData; @override Widget build(BuildContext context) => addPanelButton( - forcedTitle ?? helper.getAddButtonLabel(AppLocalizations.of(context)), - onPressed: () async => helper.showEditPage( + helper.getAddButtonLabel(AppLocalizations.of(context)), + onPressed: () async => ProductFieldSimpleEditor(helper).edit( isLoggedInMandatory: isLoggedInMandatory, context: context, product: product, ), - iconData: forcedIconData, ); } diff --git a/packages/smooth_app/lib/pages/product/edit_ingredients_page.dart b/packages/smooth_app/lib/pages/product/edit_ocr_page.dart similarity index 99% rename from packages/smooth_app/lib/pages/product/edit_ingredients_page.dart rename to packages/smooth_app/lib/pages/product/edit_ocr_page.dart index fece74572ac..b29d4b530f3 100644 --- a/packages/smooth_app/lib/pages/product/edit_ingredients_page.dart +++ b/packages/smooth_app/lib/pages/product/edit_ocr_page.dart @@ -22,7 +22,6 @@ import 'package:smooth_app/pages/product/product_image_server_button.dart'; import 'package:smooth_app/widgets/smooth_app_bar.dart'; import 'package:smooth_app/widgets/smooth_scaffold.dart'; -// TODO(monsieurtanuki): rename file as `edit_ocr_page.dart` /// Editing with OCR a product field and the corresponding image. /// /// Typical use-cases: ingredients and packaging. diff --git a/packages/smooth_app/lib/pages/product/edit_product_page.dart b/packages/smooth_app/lib/pages/product/edit_product_page.dart index ee79b9c699e..8e67232c812 100644 --- a/packages/smooth_app/lib/pages/product/edit_product_page.dart +++ b/packages/smooth_app/lib/pages/product/edit_product_page.dart @@ -18,14 +18,10 @@ import 'package:smooth_app/generic_lib/widgets/smooth_list_tile_card.dart'; import 'package:smooth_app/helpers/analytics_helper.dart'; import 'package:smooth_app/helpers/app_helper.dart'; import 'package:smooth_app/helpers/product_cards_helper.dart'; -import 'package:smooth_app/pages/product/add_basic_details_page.dart'; import 'package:smooth_app/pages/product/add_other_details_page.dart'; import 'package:smooth_app/pages/product/common/product_refresher.dart'; -import 'package:smooth_app/pages/product/edit_ingredients_page.dart'; -import 'package:smooth_app/pages/product/edit_new_packagings.dart'; import 'package:smooth_app/pages/product/nutrition_page_loaded.dart'; -import 'package:smooth_app/pages/product/ocr_ingredients_helper.dart'; -import 'package:smooth_app/pages/product/ocr_packaging_helper.dart'; +import 'package:smooth_app/pages/product/product_field_editor.dart'; import 'package:smooth_app/pages/product/product_image_gallery_view.dart'; import 'package:smooth_app/pages/product/simple_input_page.dart'; import 'package:smooth_app/pages/product/simple_input_page_helpers.dart'; @@ -132,22 +128,10 @@ class _EditProductPageState extends State { title: appLocalizations.edit_product_form_item_details_title, subtitle: appLocalizations.edit_product_form_item_details_subtitle, - onTap: () async { - if (!await ProductRefresher().checkIfLoggedIn(context)) { - return; - } - - AnalyticsHelper.trackProductEdit( - AnalyticsEditEvents.basicDetails, _barcode); - - await Navigator.push( - context, - MaterialPageRoute( - builder: (_) => AddBasicDetailsPage(_product), - fullscreenDialog: true, - ), - ); - }, + onTap: () async => ProductFieldDetailsEditor().edit( + context: context, + product: _product, + ), ), _ListTitleItem( leading: const Icon(Icons.add_a_photo_rounded), @@ -185,24 +169,10 @@ class _EditProductPageState extends State { const _SvgIcon('assets/cacheTintable/ingredients.svg'), title: appLocalizations.edit_product_form_item_ingredients_title, - onTap: () async { - if (!await ProductRefresher().checkIfLoggedIn(context)) { - return; - } - AnalyticsHelper.trackProductEdit( - AnalyticsEditEvents.ingredients_and_Origins, _barcode); - - await Navigator.push( - context, - MaterialPageRoute( - builder: (BuildContext context) => EditOcrPage( - product: _product, - helper: OcrIngredientsHelper(), - ), - fullscreenDialog: true, - ), - ); - }, + onTap: () async => ProductFieldOcrIngredientEditor().edit( + context: context, + product: _product, + ), ), _getSimpleListTileItem(SimpleInputPageCategoryHelper()), _ListTitleItem( @@ -225,48 +195,19 @@ class _EditProductPageState extends State { _ListTitleItem( leading: const _SvgIcon('assets/cacheTintable/packaging.svg'), title: appLocalizations.edit_packagings_title, - onTap: () async { - if (!await ProductRefresher().checkIfLoggedIn(context)) { - return; - } - AnalyticsHelper.trackProductEdit( - AnalyticsEditEvents.packagingComponents, _barcode); - - await Navigator.push( - context, - MaterialPageRoute( - builder: (BuildContext context) => EditNewPackagings( - product: _product, - ), - fullscreenDialog: true, - ), - ); - }, + onTap: () async => ProductFieldPackagingEditor().edit( + context: context, + product: _product, + ), ), _ListTitleItem( leading: const Icon(Icons.recycling), title: appLocalizations.edit_product_form_item_packaging_title, - onTap: () async { - if (!await ProductRefresher().checkIfLoggedIn(context)) { - return; - } - AnalyticsHelper.trackProductEdit( - AnalyticsEditEvents.recyclingInstructionsPhotos, - _barcode, - ); - - await Navigator.push( - context, - MaterialPageRoute( - builder: (BuildContext context) => EditOcrPage( - product: _product, - helper: OcrPackagingHelper(), - ), - fullscreenDialog: true, - ), - ); - }, + onTap: () async => ProductFieldOcrPackagingEditor().edit( + context: context, + product: _product, + ), ), _getSimpleListTileItem(SimpleInputPageStoreHelper()), _getSimpleListTileItem(SimpleInputPageOriginHelper()), @@ -307,7 +248,7 @@ class _EditProductPageState extends State { leading: helper.getIcon(), title: helper.getTitle(appLocalizations), subtitle: helper.getSubtitle(appLocalizations), - onTap: () async => helper.showEditPage( + onTap: () async => ProductFieldSimpleEditor(helper).edit( context: context, product: _product, ), diff --git a/packages/smooth_app/lib/pages/product/product_field_editor.dart b/packages/smooth_app/lib/pages/product/product_field_editor.dart new file mode 100644 index 00000000000..78650b7c3dc --- /dev/null +++ b/packages/smooth_app/lib/pages/product/product_field_editor.dart @@ -0,0 +1,234 @@ +import 'package:flutter/material.dart'; +import 'package:flutter_gen/gen_l10n/app_localizations.dart'; +import 'package:openfoodfacts/openfoodfacts.dart'; +import 'package:smooth_app/helpers/analytics_helper.dart'; +import 'package:smooth_app/pages/product/add_basic_details_page.dart'; +import 'package:smooth_app/pages/product/common/product_refresher.dart'; +import 'package:smooth_app/pages/product/edit_new_packagings.dart'; +import 'package:smooth_app/pages/product/edit_ocr_page.dart'; +import 'package:smooth_app/pages/product/ocr_helper.dart'; +import 'package:smooth_app/pages/product/ocr_ingredients_helper.dart'; +import 'package:smooth_app/pages/product/ocr_packaging_helper.dart'; +import 'package:smooth_app/pages/product/simple_input_page.dart'; +import 'package:smooth_app/pages/product/simple_input_page_helpers.dart'; + +// TODO(monsieurtanuki): refactor - move all product field edit files to a new "field" folder +/// Helper class about product fields. +/// +/// The typical use-case is the centralized "open edit page" method. +abstract class ProductFieldEditor { + /// Returns true if the field is populated for that product. + bool isPopulated(final Product product); + + /// Returns a standard "add/edit" button label. + String getLabel(final AppLocalizations appLocalizations); + + /// Opens a page to edit that field for that product. + Future edit({ + required final BuildContext context, + required final Product product, + final bool isLoggedInMandatory = true, + }); + + /// Returns true if no log-in required or if logged in + @protected + Future passedLoggedIn({ + required final BuildContext context, + required final bool isLoggedInMandatory, + }) async { + if (!isLoggedInMandatory) { + return true; + } + + return ProductRefresher().checkIfLoggedIn(context); + } +} + +class ProductFieldSimpleEditor extends ProductFieldEditor { + ProductFieldSimpleEditor(this.helper); + + final AbstractSimpleInputPageHelper helper; + + @override + bool isPopulated(final Product product) => helper.isPopulated(product); + + @override + String getLabel(final AppLocalizations appLocalizations) => + helper.getAddButtonLabel(appLocalizations); + + @override + Future edit({ + required final BuildContext context, + required final Product product, + final bool isLoggedInMandatory = true, + }) async { + if (isLoggedInMandatory) { + // ignore: use_build_context_synchronously + if (!await ProductRefresher().checkIfLoggedIn(context)) { + return; + } + } + + AnalyticsHelper.trackProductEdit( + helper.getAnalyticsEditEvent(), + product.barcode!, + ); + + // ignore: use_build_context_synchronously + await Navigator.push( + context, + MaterialPageRoute( + builder: (BuildContext context) => SimpleInputPage( + helper: helper, + product: product, + ), + fullscreenDialog: true, + ), + ); + } +} + +class ProductFieldDetailsEditor extends ProductFieldEditor { + /// Returns true if the [field] is valid (= not empty). + bool _isProductFieldValid(final String? field) => + field != null && field.trim().isNotEmpty; + + @override + bool isPopulated(final Product product) => + _isProductFieldValid(product.productName) || + _isProductFieldValid(product.brands); + + @override + String getLabel(final AppLocalizations appLocalizations) => + appLocalizations.completed_basic_details_btn_text; + + @override + Future edit({ + required final BuildContext context, + required final Product product, + final bool isLoggedInMandatory = true, + }) async { + // ignore: use_build_context_synchronously + if (!await passedLoggedIn( + context: context, + isLoggedInMandatory: isLoggedInMandatory, + )) { + return; + } + + AnalyticsHelper.trackProductEdit( + AnalyticsEditEvents.basicDetails, + product.barcode!, + ); + + // ignore: use_build_context_synchronously + await Navigator.push( + context, + MaterialPageRoute( + builder: (BuildContext context) => AddBasicDetailsPage( + product, + isLoggedInMandatory: isLoggedInMandatory, + ), + ), + ); + } +} + +class ProductFieldPackagingEditor extends ProductFieldEditor { + @override + bool isPopulated(final Product product) => + product.packagings?.isEmpty == false; + + @override + String getLabel(final AppLocalizations appLocalizations) => + OcrPackagingHelper().getAddButtonLabel(appLocalizations); + + @override + Future edit({ + required final BuildContext context, + required final Product product, + final bool isLoggedInMandatory = true, + }) async { + // ignore: use_build_context_synchronously + if (!await passedLoggedIn( + context: context, + isLoggedInMandatory: isLoggedInMandatory, + )) { + return; + } + + AnalyticsHelper.trackProductEdit( + AnalyticsEditEvents.packagingComponents, + product.barcode!, + ); + + // ignore: use_build_context_synchronously + await Navigator.push( + context, + MaterialPageRoute( + builder: (BuildContext context) => EditNewPackagings( + product: product, + ), + fullscreenDialog: true, + ), + ); + } +} + +abstract class ProductFieldOcrEditor extends ProductFieldEditor { + ProductFieldOcrEditor(this.helper); + + final OcrHelper helper; + + @override + String getLabel(final AppLocalizations appLocalizations) => + helper.getAddButtonLabel(appLocalizations); + + @override + Future edit({ + required final BuildContext context, + required final Product product, + final bool isLoggedInMandatory = true, + }) async { + // ignore: use_build_context_synchronously + if (!await passedLoggedIn( + context: context, + isLoggedInMandatory: isLoggedInMandatory, + )) { + return; + } + + AnalyticsHelper.trackProductEdit( + helper.getEditEventAnalyticsTag(), + product.barcode!, + ); + + // ignore: use_build_context_synchronously + await Navigator.push( + context, + MaterialPageRoute( + builder: (BuildContext context) => EditOcrPage( + product: product, + helper: helper, + ), + fullscreenDialog: true, + ), + ); + } +} + +class ProductFieldOcrIngredientEditor extends ProductFieldOcrEditor { + ProductFieldOcrIngredientEditor() : super(OcrIngredientsHelper()); + + @override + bool isPopulated(final Product product) => + product.ingredientsTextInLanguages?.isEmpty == false; +} + +class ProductFieldOcrPackagingEditor extends ProductFieldOcrEditor { + ProductFieldOcrPackagingEditor() : super(OcrPackagingHelper()); + + @override + bool isPopulated(final Product product) => + product.packagingTextInLanguages?.isEmpty == false; +} diff --git a/packages/smooth_app/lib/pages/product/simple_input_page_helpers.dart b/packages/smooth_app/lib/pages/product/simple_input_page_helpers.dart index 5d1145ab675..c18b0e4647d 100644 --- a/packages/smooth_app/lib/pages/product/simple_input_page_helpers.dart +++ b/packages/smooth_app/lib/pages/product/simple_input_page_helpers.dart @@ -3,8 +3,6 @@ import 'package:flutter_gen/gen_l10n/app_localizations.dart'; import 'package:openfoodfacts/openfoodfacts.dart'; import 'package:smooth_app/background/background_task_details.dart'; import 'package:smooth_app/helpers/analytics_helper.dart'; -import 'package:smooth_app/pages/product/common/product_refresher.dart'; -import 'package:smooth_app/pages/product/simple_input_page.dart'; import 'package:smooth_app/query/product_query.dart'; /// Abstract helper for Simple Input Page. @@ -25,7 +23,7 @@ abstract class AbstractSimpleInputPageHelper extends ChangeNotifier { /// Starts from scratch with a new (or refreshed) [Product]. void reInit(final Product product) { this.product = product; - _terms = List.from(initTerms()); + _terms = List.from(initTerms(this.product)); _changed = false; notifyListeners(); } @@ -37,11 +35,14 @@ abstract class AbstractSimpleInputPageHelper extends ChangeNotifier { /// WARNING: this list must be copied; if not you may alter the product. /// cf. https://github.com/openfoodfacts/smooth-app/issues/3529 @protected - List initTerms(); + List initTerms(final Product product); /// Returns the current terms to be displayed. List get terms => _terms; + /// Returns true if the field is populated. + bool isPopulated(final Product product) => initTerms(product).isNotEmpty; + /// Returns true if the term was not in the list and then was added. bool addTerm(String term) { term = term.trim(); @@ -70,37 +71,6 @@ abstract class AbstractSimpleInputPageHelper extends ChangeNotifier { return false; } - /// Shows the related "edit" page. - Future showEditPage({ - required final BuildContext context, - required final Product product, - final bool isLoggedInMandatory = true, - }) async { - if (isLoggedInMandatory) { - // ignore: use_build_context_synchronously - if (!await ProductRefresher().checkIfLoggedIn(context)) { - return; - } - } - - AnalyticsHelper.trackProductEdit( - getAnalyticsEditEvent(), - product.barcode!, - ); - - // ignore: use_build_context_synchronously - await Navigator.push( - context, - MaterialPageRoute( - builder: (BuildContext context) => SimpleInputPage( - helper: this, - product: product, - ), - fullscreenDialog: true, - ), - ); - } - /// Returns the title on the main "edit product" page. String getTitle(final AppLocalizations appLocalizations); @@ -178,7 +148,7 @@ abstract class AbstractSimpleInputPageHelper extends ChangeNotifier { /// Implementation for "Stores" of an [AbstractSimpleInputPageHelper]. class SimpleInputPageStoreHelper extends AbstractSimpleInputPageHelper { @override - List initTerms() => splitString(product.stores); + List initTerms(final Product product) => splitString(product.stores); @override void changeProduct(final Product changedProduct) => @@ -212,7 +182,7 @@ class SimpleInputPageStoreHelper extends AbstractSimpleInputPageHelper { /// Implementation for "Origins" of an [AbstractSimpleInputPageHelper]. class SimpleInputPageOriginHelper extends AbstractSimpleInputPageHelper { @override - List initTerms() => splitString(product.origins); + List initTerms(final Product product) => splitString(product.origins); @override void changeProduct(final Product changedProduct) => @@ -252,7 +222,8 @@ class SimpleInputPageOriginHelper extends AbstractSimpleInputPageHelper { /// Implementation for "Emb Code" of an [AbstractSimpleInputPageHelper]. class SimpleInputPageEmbCodeHelper extends AbstractSimpleInputPageHelper { @override - List initTerms() => splitString(product.embCodes); + List initTerms(final Product product) => + splitString(product.embCodes); @override void changeProduct(final Product changedProduct) => @@ -291,7 +262,7 @@ class SimpleInputPageEmbCodeHelper extends AbstractSimpleInputPageHelper { /// Implementation for "Labels" of an [AbstractSimpleInputPageHelper]. class SimpleInputPageLabelHelper extends AbstractSimpleInputPageHelper { @override - List initTerms() => + List initTerms(final Product product) => product.labelsTagsInLanguages?[getLanguage()] ?? []; @override @@ -336,7 +307,7 @@ class SimpleInputPageLabelHelper extends AbstractSimpleInputPageHelper { /// Implementation for "Categories" of an [AbstractSimpleInputPageHelper]. class SimpleInputPageCategoryHelper extends AbstractSimpleInputPageHelper { @override - List initTerms() => + List initTerms(final Product product) => product.categoriesTagsInLanguages?[getLanguage()] ?? []; @override @@ -385,7 +356,7 @@ class SimpleInputPageCategoryHelper extends AbstractSimpleInputPageHelper { /// Implementation for "Countries" of an [AbstractSimpleInputPageHelper]. class SimpleInputPageCountryHelper extends AbstractSimpleInputPageHelper { @override - List initTerms() => + List initTerms(final Product product) => product.countriesTagsInLanguages?[getLanguage()] ?? []; @override diff --git a/packages/smooth_app/lib/pages/product/summary_card.dart b/packages/smooth_app/lib/pages/product/summary_card.dart index abc2b331f85..d6438cb354c 100644 --- a/packages/smooth_app/lib/pages/product/summary_card.dart +++ b/packages/smooth_app/lib/pages/product/summary_card.dart @@ -18,10 +18,10 @@ import 'package:smooth_app/knowledge_panel/knowledge_panels/knowledge_panel_page import 'package:smooth_app/knowledge_panel/knowledge_panels_builder.dart'; import 'package:smooth_app/pages/navigator/app_navigator.dart'; import 'package:smooth_app/pages/preferences/user_preferences_page.dart'; -import 'package:smooth_app/pages/product/add_basic_details_page.dart'; import 'package:smooth_app/pages/product/add_simple_input_button.dart'; import 'package:smooth_app/pages/product/common/product_query_page_helper.dart'; import 'package:smooth_app/pages/product/hideable_container.dart'; +import 'package:smooth_app/pages/product/product_field_editor.dart'; import 'package:smooth_app/pages/product/product_questions_widget.dart'; import 'package:smooth_app/pages/product/simple_input_page_helpers.dart'; import 'package:smooth_app/query/category_product_query.dart'; @@ -326,16 +326,14 @@ class _SummaryCardState extends State { .contains(ProductState.PRODUCT_NAME_COMPLETED.toBeCompletedTag) || statesTags .contains(ProductState.QUANTITY_COMPLETED.toBeCompletedTag)) { + final ProductFieldEditor editor = ProductFieldDetailsEditor(); summaryCardButtons.add( addPanelButton( - localizations.completed_basic_details_btn_text, + editor.getLabel(localizations), onPressed: () async => widget.isProductEditable - ? Navigator.push( - context, - MaterialPageRoute( - builder: (BuildContext context) => - AddBasicDetailsPage(_product), - ), + ? editor.edit( + context: context, + product: _product, ) : null, ),