Skip to content

Commit

Permalink
feat: 4058 - expand/collapse for additional ecoscore fields (#4071)
Browse files Browse the repository at this point in the history
* feat: 4058 - expand/collapse for additional ecoscore fields

New file:
* `product_field_editor.dart`: Helper class about product fields.

Impacted files:
* `add_new_product_page.dart`: added origins, ingredients, labels and packaging in option with expand/collapse; refactored using new classes around `ProductFieldEditor`
* `add_ocr_button.dart`: refactored using new class `ProductFieldOcrEditor`
* `add_packaging_button.dart`: refactored using new class `ProductFieldPackagingEditor
* `app_en.arb`: added one label for "additional ecoscore fields"
* `app_fr.arb`: added one label for "additional ecoscore fields"
* `edit_ocr_page.dart`: renamed from `edit_ingredients_page.dart`
* `edit_product_page.dart`: refactored using new classes `ProductFieldDetailsEditor`, `ProductFieldOcrIngredientEditor`, `ProductFieldPackagingEditor` and `ProductFieldOcrPackagingEditor`
* `knowledge_panel_action_card.dart`: minor refactoring
* `knowledge_panels_builder.dart`: minor refactoring
* `simple_input_page_helpers.dart`: added method `isPopulated`; minor refactoring
* `summary_card.dart`: refactored using new class `ProductFieldDetailsEditor`

* feat: 4058 - format

* feat: 4058 - refactoring

Impacted files:
* `add_simple_input_button.dart`: removed useless parameters; now uses `ProductFieldSimpleEditor`
* `edit_product_page.dart`: now uses `ProductFieldSimpleEditor`
* `product_field_editor.dart`: moved `edit` code from `simple_input_page_helpers.dart`
* `simple_input_page_helpers.dart`: moved code to `product_field_editor.dart`
  • Loading branch information
monsieurtanuki authored Jun 4, 2023
1 parent fbb6fd3 commit 30be338
Show file tree
Hide file tree
Showing 13 changed files with 376 additions and 240 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -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';

Expand Down Expand Up @@ -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) {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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?
Expand Down Expand Up @@ -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(),
),
);
}
Expand Down
1 change: 1 addition & 0 deletions packages/smooth_app/lib/l10n/app_en.arb
Original file line number Diff line number Diff line change
Expand Up @@ -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",
Expand Down
1 change: 1 addition & 0 deletions packages/smooth_app/lib/l10n/app_fr.arb
Original file line number Diff line number Diff line change
Expand Up @@ -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",
Expand Down
133 changes: 78 additions & 55 deletions packages/smooth_app/lib/pages/product/add_new_product_page.dart
Original file line number Diff line number Diff line change
Expand Up @@ -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';
Expand All @@ -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({
Expand All @@ -60,20 +56,35 @@ class _AddNewProductPageState extends State<AddNewProductPage> {

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<ProductFieldEditor> _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 = <ProductFieldEditor>[
_packagingEditor,
_ingredientsEditor,
_originEditor,
_categoryEditor,
_labelEditor,
_detailsEditor,
];
_initialProduct = Product(barcode: barcode);
_localDatabase = context.read<LocalDatabase>();
_localDatabase.upToDate.showInterest(barcode);
Expand Down Expand Up @@ -128,18 +139,26 @@ class _AddNewProductPageState extends State<AddNewProductPage> {
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);
_alreadyPushedToHistory = true;
}
}

/// 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<Widget> children,
) =>
Expand Down Expand Up @@ -195,6 +214,17 @@ class _AddNewProductPageState extends State<AddNewProductPage> {
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),
];
}

Expand Down Expand Up @@ -254,60 +284,53 @@ class _AddNewProductPageState extends State<AddNewProductPage> {
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<Widget> _getMiscRows(final BuildContext context) {
final AppLocalizations appLocalizations = AppLocalizations.of(context);
return <Widget>[
Text(
appLocalizations.new_product_title_misc,
style: _getTitleStyle(context),
),
_MyButton(
appLocalizations.completed_basic_details_btn_text,
_basicDetailsAdded ? _doneIcon : _todoIcon,
() async => Navigator.push<void>(
context,
MaterialPageRoute<void>(
builder: (BuildContext context) => AddBasicDetailsPage(
_product,
isLoggedInMandatory: false,
),
),
),
done: _basicDetailsAdded,
),
_buildEditorButton(context, _detailsEditor),
];
}
}
Expand Down
31 changes: 7 additions & 24 deletions packages/smooth_app/lib/pages/product/add_ocr_button.dart
Original file line number Diff line number Diff line change
Expand Up @@ -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<void>(
context,
MaterialPageRoute<void>(
builder: (BuildContext context) => EditOcrPage(
product: product,
helper: helper,
),
),
);
},
editor.getLabel(AppLocalizations.of(context)),
onPressed: () async => editor.edit(context: context, product: product),
);
}
31 changes: 10 additions & 21 deletions packages/smooth_app/lib/pages/product/add_packaging_button.dart
Original file line number Diff line number Diff line change
Expand Up @@ -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<void>(
context,
MaterialPageRoute<void>(
builder: (BuildContext context) => EditNewPackagings(
product: product,
),
fullscreenDialog: true,
),
);
},
_editor.getLabel(AppLocalizations.of(context)),
onPressed: () async => _editor.edit(
context: context,
product: product,
isLoggedInMandatory: true,
),
);
}
Original file line number Diff line number Diff line change
Expand Up @@ -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.
Expand All @@ -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,
);
}
Loading

0 comments on commit 30be338

Please sign in to comment.