Skip to content

Commit

Permalink
feat: 3897 - ingredients and packaging with mulitlingual OCR image+te…
Browse files Browse the repository at this point in the history
…xt (#3911)

New file:
* `multilingual_helper.dart`: Helper for multilingual inputs (e.g. product name).

Deleted file:
* `ocr_widget.dart`: moved code to `edit_ingredients_page.dart`, for better state refresh

Impacted files:
* `add_basic_details_page.dart`: refactored with new class `MultilingualHelper`
* `edit_ingredients_page.dart`: added multilingual management; moved code from `ocr_widget.dart`
* `ocr_helper.dart`: refactored with multilingual management
* `ocr_ingredients_helper.dart`: refactored with multilingual management
* `ocr_packaging_helper.dart`: refactored with multilingual management
* `product_query.dart`: added multilingual ingredients field; removed duplicate field
  • Loading branch information
monsieurtanuki authored Apr 27, 2023
1 parent 4e9349c commit e5bf1cb
Show file tree
Hide file tree
Showing 8 changed files with 438 additions and 343 deletions.
133 changes: 25 additions & 108 deletions packages/smooth_app/lib/pages/product/add_basic_details_page.dart
Original file line number Diff line number Diff line change
Expand Up @@ -5,24 +5,20 @@ import 'package:smooth_app/background/background_task_details.dart';
import 'package:smooth_app/cards/product_cards/product_image_carousel.dart';
import 'package:smooth_app/generic_lib/design_constants.dart';
import 'package:smooth_app/generic_lib/dialogs/smooth_alert_dialog.dart';
import 'package:smooth_app/generic_lib/widgets/language_selector.dart';
import 'package:smooth_app/generic_lib/widgets/smooth_text_form_field.dart';
import 'package:smooth_app/helpers/analytics_helper.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/may_exit_page_helper.dart';
import 'package:smooth_app/pages/product/multilingual_helper.dart';
import 'package:smooth_app/pages/text_field_helper.dart';
import 'package:smooth_app/query/product_query.dart';
import 'package:smooth_app/widgets/smooth_app_bar.dart';
import 'package:smooth_app/widgets/smooth_scaffold.dart';

/// Input of a product's basic details, like name, quantity and brands.
///
/// There are 2 versions of the product name input:
/// 1. the old one - only one name per product
/// 2. the multilingual one
/// Typically, the old version will be used with "old" data, that have not
/// downloaded yet the "recent" [ProductField.NAME_ALL_LANGUAGES] field.
/// The product name input is either monolingual or multilingual, depending on
/// the product data version.
class AddBasicDetailsPage extends StatefulWidget {
const AddBasicDetailsPage(
this.product, {
Expand All @@ -45,49 +41,25 @@ class _AddBasicDetailsPageState extends State<AddBasicDetailsPage> {
final GlobalKey<FormState> _formKey = GlobalKey<FormState>();
late final Product _product;

/// Current language; only relevant/valid if _names is not empty.
late OpenFoodFactsLanguage _currentLanguage;

/// Current product name translations.
final Map<OpenFoodFactsLanguage, String> _names =
<OpenFoodFactsLanguage, String>{};
late final MultilingualHelper _multilingualHelper;

@override
void initState() {
super.initState();
_product = widget.product;
_weightController = TextEditingControllerWithInitialValue(
text: _getCleanName(_product.quantity ?? ''),
text: MultilingualHelper.getCleanText(_product.quantity ?? ''),
);
_brandNameController = TextEditingControllerWithInitialValue(
text: _formatProductBrands(_product.brands),
);
// checking if we use the multilingual version...
if (_product.productNameInLanguages != null) {
for (final OpenFoodFactsLanguage language
in _product.productNameInLanguages!.keys) {
final String name =
_getCleanName(_product.productNameInLanguages![language]);
if (name.isNotEmpty) {
_names[language] = name;
}
}
if (_names.isNotEmpty) {
final OpenFoodFactsLanguage language = ProductQuery.getLanguage();
if (_names.containsKey(language)) {
// best choice
_currentLanguage = language;
} else {
// fallback
_currentLanguage = _names.keys.first;
}
_productNameController.text = _names[_currentLanguage] ?? '';
}
}
// Fallback: we may have old data where there are no translations.
if (_names.isEmpty) {
_productNameController.text = _product.productName ?? '';
}
_multilingualHelper = MultilingualHelper(
controller: _productNameController,
);
_multilingualHelper.init(
multilingualTexts: _product.productNameInLanguages,
monolingualText: _product.productName,
);
}

@override
Expand All @@ -98,10 +70,9 @@ class _AddBasicDetailsPageState extends State<AddBasicDetailsPage> {
super.dispose();
}

String _formatProductBrands(String? text) =>
_getCleanName(text == null ? '' : formatProductBrands(text));

String _getCleanName(final String? name) => (name ?? '').trim();
String _formatProductBrands(String? text) => MultilingualHelper.getCleanText(
text == null ? '' : formatProductBrands(text),
);

@override
Widget build(BuildContext context) {
Expand Down Expand Up @@ -140,7 +111,7 @@ class _AddBasicDetailsPageState extends State<AddBasicDetailsPage> {
),
),
SizedBox(height: _heightSpace),
if (_names.isEmpty)
if (_multilingualHelper.isMonolingual())
SmoothTextFormField(
controller: _productNameController,
type: TextFieldTypes.PLAIN_TEXT,
Expand All @@ -150,27 +121,7 @@ class _AddBasicDetailsPageState extends State<AddBasicDetailsPage> {
Card(
child: Column(
children: <Widget>[
LanguageSelector(
setLanguage: (
final OpenFoodFactsLanguage? newLanguage,
) async {
if (newLanguage == null) {
return;
}
if (_currentLanguage == newLanguage) {
return;
}
_saveCurrentName();
setState(() {
_currentLanguage = newLanguage;
_names[_currentLanguage] ??= '';
_productNameController.text =
_names[_currentLanguage]!;
});
},
selectedLanguages: _names.keys,
displayedLanguage: _currentLanguage,
),
_multilingualHelper.getLanguageSelector(setState),
Padding(
padding: const EdgeInsets.all(8.0),
child: SmoothTextFormField(
Expand Down Expand Up @@ -231,10 +182,6 @@ class _AddBasicDetailsPageState extends State<AddBasicDetailsPage> {
}
}

/// Saves the current input for the current language.
void _saveCurrentName() =>
_names[_currentLanguage] = _productNameController.text;

/// Returns `true` if we should really exit the page.
///
/// Parameter [saving] tells about the context: are we leaving the page,
Expand Down Expand Up @@ -297,50 +244,20 @@ class _AddBasicDetailsPageState extends State<AddBasicDetailsPage> {
result ??= getBasicProduct();
result.brands = _formatProductBrands(_brandNameController.text);
}
if (_names.isEmpty) {
if (_getCleanName(_productNameController.text) !=
_getCleanName(_product.productName)) {
if (_multilingualHelper.isMonolingual()) {
final String? changed = _multilingualHelper.getChangedMonolingualText();
if (changed != null) {
result ??= getBasicProduct();
result.productName = _productNameController.text;
result.productName = changed;
}
} else {
_saveCurrentName();
final Map<OpenFoodFactsLanguage, String>? newNames =
_getNewNamesIfChanged();
if (newNames != null) {
final Map<OpenFoodFactsLanguage, String>? changed =
_multilingualHelper.getChangedMultilingualText();
if (changed != null) {
result ??= getBasicProduct();
result.productNameInLanguages = newNames;
result.productNameInLanguages = changed;
}
}
return result;
}

/// Returns all the new names, if any change happened.
Map<OpenFoodFactsLanguage, String>? _getNewNamesIfChanged() {
bool changed = false;
final Map<OpenFoodFactsLanguage, String> result =
<OpenFoodFactsLanguage, String>{};
final Map<OpenFoodFactsLanguage, String> oldNames =
_product.productNameInLanguages!;

void setNewName(final OpenFoodFactsLanguage language) {
final String newName = _getCleanName(_names[language]);
final String oldName = _getCleanName(oldNames[language]);
if (newName != oldName) {
changed = true;
}
// For the record: if name is empty, will remove the translation (sometimes).
result[language] = newName;
}

// setting new names, comparing them to old names for change flag.
_names.keys.forEach(setNewName);
// double-checking old names: have some old names been removed?
oldNames.keys.forEach(setNewName);

if (!changed) {
return null;
}
return result;
}
}
Loading

0 comments on commit e5bf1cb

Please sign in to comment.