From f05e0fc5463228f79a3a65d49e66f3c891288dc8 Mon Sep 17 00:00:00 2001 From: monsieurtanuki Date: Tue, 7 Jun 2022 12:35:41 +0200 Subject: [PATCH] feat: #2169 - new simple input page for "Labels" Impacted files: * `app_en.arb`: added 5 translations * `app_fr.arb`: added 5 translations * `edit_product_page.dart`: added an action for "edit labels"; refactored * `simple_input_page.dart`: renamed "label" as "term" to avoid confusion; translated; refactored * `simple_input_page_helpers.dart`: implementation for "labels"; renamed "label" as "term"; translated * `smooth_alert_dialog.dart`: minor refactoring --- .../dialogs/smooth_alert_dialog.dart | 6 +- packages/smooth_app/lib/l10n/app_en.arb | 17 +++ packages/smooth_app/lib/l10n/app_fr.arb | 17 +++ .../lib/pages/product/edit_product_page.dart | 44 +++--- .../lib/pages/product/simple_input_page.dart | 27 ++-- .../product/simple_input_page_helpers.dart | 130 ++++++++++++------ 6 files changed, 161 insertions(+), 80 deletions(-) diff --git a/packages/smooth_app/lib/generic_lib/dialogs/smooth_alert_dialog.dart b/packages/smooth_app/lib/generic_lib/dialogs/smooth_alert_dialog.dart index a208e05fc34..e2c280e0ebe 100644 --- a/packages/smooth_app/lib/generic_lib/dialogs/smooth_alert_dialog.dart +++ b/packages/smooth_app/lib/generic_lib/dialogs/smooth_alert_dialog.dart @@ -24,8 +24,8 @@ class SmoothAlertDialog extends StatelessWidget { this.positiveAction, this.neutralAction, this.negativeAction, - }) : close = false, - maxHeight = null, + this.close = false, + }) : maxHeight = null, _simpleMode = true; /// Advanced alert dialog with fancy effects. @@ -153,7 +153,7 @@ class SmoothAlertDialog extends StatelessWidget { Icons.close, size: 29.0, ), - onTap: () => Navigator.of(context, rootNavigator: true).pop('dialog'), + onTap: () => Navigator.of(context, rootNavigator: true).pop(), ), ); } else { diff --git a/packages/smooth_app/lib/l10n/app_en.arb b/packages/smooth_app/lib/l10n/app_en.arb index f3b4e8b1a09..81963b2b885 100644 --- a/packages/smooth_app/lib/l10n/app_en.arb +++ b/packages/smooth_app/lib/l10n/app_en.arb @@ -18,6 +18,10 @@ "skip": "Skip", "cancel": "Cancel", "@cancel": {}, + "ignore": "Ignore", + "@ignore": { + "description": "'Ignore' button. Typical use case in combination with 'OK' and 'Cancel' buttons." + }, "close": "Close", "@close": {}, "no": "No", @@ -879,6 +883,19 @@ "@edit_product_form_item_labels_subtitle": { "description": "Product edition - Labels - SubTitle" }, + "edit_product_form_item_labels_hint": "label", + "@edit_product_form_item_labels_hint": { + "description": "Product edition - Labels - input textfield hint" + }, + "edit_product_form_item_stores_title": "Stores", + "@edit_product_form_item_stores_title": { + "description": "Product edition - Stores - Title" + }, + "edit_product_form_item_stores_hint": "label", + "@edit_product_form_item_stores_hint": { + "description": "Product edition - Stores - input textfield hint" + }, + "edit_product_form_item_exit_confirmation": "Do you want to save your changes before leaving this page?", "edit_product_form_item_ingredients_title": "Ingredients & Origins", "@edit_product_form_item_ingredients_title": { "description": "Product edition - Ingredients - Title" diff --git a/packages/smooth_app/lib/l10n/app_fr.arb b/packages/smooth_app/lib/l10n/app_fr.arb index f774f34d3ee..90986e35aac 100644 --- a/packages/smooth_app/lib/l10n/app_fr.arb +++ b/packages/smooth_app/lib/l10n/app_fr.arb @@ -18,6 +18,10 @@ "skip": "Ignorer", "cancel": "Annuler", "@cancel": {}, + "ignore": "Ignorer", + "@ignore": { + "description": "'Ignore' button. Typical use case in combination with 'OK' and 'Cancel' buttons." + }, "close": "Fermer", "@close": {}, "no": "Non", @@ -871,6 +875,19 @@ "@edit_product_form_item_labels_subtitle": { "description": "Product edition - Labels - SubTitle" }, + "edit_product_form_item_labels_hint": "label", + "@edit_product_form_item_labels_hint": { + "description": "Product edition - Labels - input textfield hint" + }, + "edit_product_form_item_stores_title": "Magasins", + "@edit_product_form_item_stores_title": { + "description": "Product edition - Stores - Title" + }, + "edit_product_form_item_stores_hint": "magasin", + "@edit_product_form_item_stores_hint": { + "description": "Product edition - Stores - input textfield hint" + }, + "edit_product_form_item_exit_confirmation": "Voulez-vous sauver vos modifications avant de quitter cette page ?", "edit_product_form_item_ingredients_title": "Ingrédients & Origines", "@edit_product_form_item_ingredients_title": { "description": "Product edition - Ingredients - Title" 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 bdd54da30b7..41877b59da4 100644 --- a/packages/smooth_app/lib/pages/product/edit_product_page.dart +++ b/packages/smooth_app/lib/pages/product/edit_product_page.dart @@ -103,9 +103,8 @@ class _EditProductPageState extends State { } }, ), - _ListTitleItem( - title: appLocalizations.edit_product_form_item_labels_title, - subtitle: appLocalizations.edit_product_form_item_labels_subtitle, + _getSimpleListTileItem( + SimpleInputPageLabelHelper(_product, appLocalizations), ), _ListTitleItem( title: appLocalizations.edit_product_form_item_ingredients_title, @@ -126,25 +125,8 @@ class _EditProductPageState extends State { _ListTitleItem( title: appLocalizations.edit_product_form_item_packaging_title, ), - _ListTitleItem( - title: 'Stores', // TODO(monsieurtanuki): translate - onTap: () async { - final Product? refreshed = await Navigator.push( - context, - MaterialPageRoute( - builder: (BuildContext context) => SimpleInputPage( - SimpleInputPageStoreHelper( - _product, - appLocalizations, - ), - ), - ), - ); - if (refreshed != null) { - _product = refreshed; - } - return; - }, + _getSimpleListTileItem( + SimpleInputPageStoreHelper(_product, appLocalizations), ), _ListTitleItem( title: @@ -179,6 +161,24 @@ class _EditProductPageState extends State { ), ); } + + Widget _getSimpleListTileItem(final AbstractSimpleInputPageHelper helper) => + _ListTitleItem( + title: helper.getTitle(), + subtitle: helper.getSubtitle(), + onTap: () async { + final Product? refreshed = await Navigator.push( + context, + MaterialPageRoute( + builder: (BuildContext context) => SimpleInputPage(helper), + ), + ); + if (refreshed != null) { + _product = refreshed; + } + setState(() {}); + }, + ); } class _ListTitleItem extends StatelessWidget { diff --git a/packages/smooth_app/lib/pages/product/simple_input_page.dart b/packages/smooth_app/lib/pages/product/simple_input_page.dart index 0ca3bb4b070..6512ec556d4 100644 --- a/packages/smooth_app/lib/pages/product/simple_input_page.dart +++ b/packages/smooth_app/lib/pages/product/simple_input_page.dart @@ -10,7 +10,7 @@ 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/simple_input_page_helpers.dart'; -/// Simple input page: we have a list of labels, we add, we remove, we save. +/// Simple input page: we have a list of terms, we add, we remove, we save. class SimpleInputPage extends StatefulWidget { const SimpleInputPage(this.helper) : super(); @@ -41,21 +41,18 @@ class _SimpleInputPageState extends State { final bool? pleaseSave = await showDialog( context: context, builder: (final BuildContext context) => SmoothAlertDialog( + close: true, body: - const Text('You are about to leave this page without saving.'), + Text(appLocalizations.edit_product_form_item_exit_confirmation), title: widget.helper.getTitle(), negativeAction: SmoothActionButton( - text: 'Ignore', + text: appLocalizations.ignore, onPressed: () => Navigator.pop(context, false), ), positiveAction: SmoothActionButton( - text: 'Save', + text: appLocalizations.save, onPressed: () => Navigator.pop(context, true), ), - neutralAction: SmoothActionButton( - text: 'Cancel', - onPressed: () => Navigator.pop(context, null), - ), ), ); if (pleaseSave == null) { @@ -96,13 +93,13 @@ class _SimpleInputPageState extends State { widget.helper.getTitle(), style: themeData.textTheme.headline1, ), - const SizedBox(height: LARGE_SPACE), - Text(widget.helper.getAddTitle()), + if (widget.helper.getSubtitle() != null) + Text(widget.helper.getSubtitle()!), Row( children: [ ElevatedButton( onPressed: () { - if (widget.helper.addLabel(_controller.text)) { + if (widget.helper.addTerm(_controller.text)) { setState(() => _controller.text = ''); } }, @@ -137,14 +134,14 @@ class _SimpleInputPageState extends State { spacing: LARGE_SPACE, runSpacing: VERY_SMALL_SPACE, children: List.generate( - widget.helper.getLabels().length, + widget.helper.terms.length, (final int index) { - final String label = widget.helper.getLabels()[index]; + final String term = widget.helper.terms[index]; return ElevatedButton.icon( icon: const Icon(Icons.clear), - label: Text(label), + label: Text(term), onPressed: () async { - if (widget.helper.removeLabel(label)) { + if (widget.helper.removeTerm(term)) { setState(() {}); } }, 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 f708f086945..faab13c871a 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 @@ -1,56 +1,59 @@ import 'package:flutter/material.dart'; import 'package:flutter_gen/gen_l10n/app_localizations.dart'; import 'package:openfoodfacts/openfoodfacts.dart'; +import 'package:smooth_app/database/product_query.dart'; /// Abstract helper for Simple Input Page. /// -/// * we retrieve the initial list of labels. -/// * we add a label to the list. -/// * we remove a label from the list. +/// * we retrieve the initial list of terms. +/// * we add a term to the list. +/// * we remove a term from the list. abstract class AbstractSimpleInputPageHelper { AbstractSimpleInputPageHelper( this.product, this.appLocalizations, ) { - _labels = initLabels(); + _language = ProductQuery.getLanguage()!; + _terms = initTerms(); } final Product product; final AppLocalizations appLocalizations; + late final OpenFoodFactsLanguage _language; - /// Labels as they were initially then edited by the user. - late List _labels; + /// Terms as they were initially then edited by the user. + late List _terms; - /// "Have the labels been changed?" + /// "Have the terms been changed?" bool _changed = false; - /// Returns the labels as they were initially in the product. + /// Returns the terms as they were initially in the product. @protected - List initLabels(); + List initTerms(); - /// Returns the current labels to be displayed. - List getLabels() => _labels; + /// Returns the current terms to be displayed. + List get terms => _terms; - /// Returns true if the label was not in the list and then was added. - bool addLabel(String label) { - label = label.trim(); - if (label.isEmpty) { + /// Returns true if the term was not in the list and then was added. + bool addTerm(String term) { + term = term.trim(); + if (term.isEmpty) { return false; } - if (_labels.contains(label)) { + if (_terms.contains(term)) { return false; } - _labels.add(label); + _terms.add(term); _changed = true; return true; } - /// Returns true if the label was in the list and then was removed. + /// Returns true if the term was in the list and then was removed. /// /// The things we build the interface, very unlikely to return false, /// as we remove existing items. - bool removeLabel(final String label) { - if (_labels.remove(label)) { + bool removeTerm(final String term) { + if (_terms.remove(term)) { _changed = true; return true; } @@ -60,8 +63,8 @@ abstract class AbstractSimpleInputPageHelper { /// Returns the title on the main "edit product" page. String getTitle(); - /// Returns the title of the "add" paragraph. - String getAddTitle(); + /// Returns the subtitle on the main "edit product" page. + String? getSubtitle() => null; /// Returns the hint of the "add" text field. String getAddHint(); @@ -79,26 +82,46 @@ abstract class AbstractSimpleInputPageHelper { changeProduct(changedProduct); return changedProduct; } +} - List _splitString(final String? input) { +/// Implementation for "Stores" of an [AbstractSimpleInputPageHelper]. +class SimpleInputPageStoreHelper extends AbstractSimpleInputPageHelper { + SimpleInputPageStoreHelper( + final Product product, + final AppLocalizations appLocalizations, + ) : super( + product, + appLocalizations, + ); + + @override + List initTerms() => _splitString(product.stores); + + @override + void changeProduct(final Product changedProduct) => + changedProduct.stores = terms.join(', '); + + @override + String getTitle() => appLocalizations.edit_product_form_item_stores_title; + + @override + String getAddHint() => appLocalizations.edit_product_form_item_stores_hint; + + List _splitString(String? input) { if (input == null) { return []; } - final List result = input.split(','); - for (int i = 0; i < result.length; i++) { - final int pos = result[i].indexOf(':'); - if (pos == 2) { - // we get rid of the language, e.g. 'fr:Sac' - result[i] = result[i].substring(pos + 1); - } + input = input.trim(); + if (input.isEmpty) { + return []; } - return result; + return input.split(','); } } -/// Implementation for "Stores" of an [AbstractSimpleInputPageHelper]. -class SimpleInputPageStoreHelper extends AbstractSimpleInputPageHelper { - SimpleInputPageStoreHelper( +/// Implementation for "Labels" of an [AbstractSimpleInputPageHelper]. +class SimpleInputPageLabelHelper extends AbstractSimpleInputPageHelper { + SimpleInputPageLabelHelper( final Product product, final AppLocalizations appLocalizations, ) : super( @@ -106,19 +129,46 @@ class SimpleInputPageStoreHelper extends AbstractSimpleInputPageHelper { appLocalizations, ); + final Map _termToTags = {}; + @override - List initLabels() => _splitString(product.stores); + List initTerms() { + if (product.labelsTags != null && product.labelsTagsInLanguages != null) { + final List? translations = + product.labelsTagsInLanguages![_language]; + if (translations != null && + translations.length == product.labelsTags!.length) { + for (int i = 0; i < translations.length; i++) { + _termToTags[translations[i]] = product.labelsTags![i]; + } + return List.from(translations); + } + } + return []; + } @override - void changeProduct(final Product changedProduct) => - changedProduct.stores = _labels.join(','); + void changeProduct(final Product changedProduct) { + final StringBuffer result = StringBuffer(); + for (int i = 0; i < terms.length; i++) { + final String term = terms[i]; + String? tag = _termToTags[term]; + tag ??= '${_language.code}:$term'; + if (i > 0) { + result.write(', '); + } + result.write(tag); + } + changedProduct.labels = result.toString(); + } @override - String getTitle() => 'Stores'; // TODO(monsieurtanuki): translate + String getTitle() => appLocalizations.edit_product_form_item_labels_title; @override - String getAddTitle() => 'Add a store'; // TODO(monsieurtanuki): translate + String getSubtitle() => + appLocalizations.edit_product_form_item_labels_subtitle; @override - String getAddHint() => 'store'; // TODO(monsieurtanuki): translate + String getAddHint() => appLocalizations.edit_product_form_item_labels_hint; }