From 0c9552efecc7b0683da85116e54819a36f5c2f6a Mon Sep 17 00:00:00 2001 From: monsieurtanuki Date: Fri, 18 Feb 2022 17:26:08 +0100 Subject: [PATCH] refactor: #954 - split the nutrition page code for better focus (#1142) New file: * `nutrition_container.dart`: Nutrition data, for nutrient order and conversions. * `tmp_to_off_nutriments.dart`: Nutrient ids supported by `Nutriments`, to be moved to off-dart Impacted file: * `nutrition_page_loaded.dart`: moved the nutrient and conversion code to new class `NutritionContainer` --- .../pages/product/nutrition_container.dart | 318 ++++++++++++++++++ .../pages/product/nutrition_page_loaded.dart | 306 ++++------------- .../pages/product/tmp_to_off_nutriments.dart | 69 ++++ 3 files changed, 460 insertions(+), 233 deletions(-) create mode 100644 packages/smooth_app/lib/pages/product/nutrition_container.dart create mode 100644 packages/smooth_app/lib/pages/product/tmp_to_off_nutriments.dart diff --git a/packages/smooth_app/lib/pages/product/nutrition_container.dart b/packages/smooth_app/lib/pages/product/nutrition_container.dart new file mode 100644 index 00000000000..6ac9b28edbe --- /dev/null +++ b/packages/smooth_app/lib/pages/product/nutrition_container.dart @@ -0,0 +1,318 @@ +import 'package:openfoodfacts/interface/JsonObject.dart'; +import 'package:openfoodfacts/model/Nutriments.dart'; +import 'package:openfoodfacts/model/OrderedNutrient.dart'; +import 'package:openfoodfacts/model/OrderedNutrients.dart'; +import 'package:openfoodfacts/model/Product.dart'; +import 'package:openfoodfacts/utils/UnitHelper.dart'; +import 'package:smooth_app/pages/product/tmp_to_off_nutriments.dart'; + +/// Nutrition data, for nutrient order and conversions. +class NutritionContainer { + NutritionContainer({ + required final OrderedNutrients orderedNutrients, + required final Product product, + }) { + _loadNutrients(orderedNutrients.nutrients); + final Map? json = product.nutriments?.toJson(); + if (json != null) { + _loadUnits(json); + _loadValues(json); + } + _servingSize = product.servingSize; + _barcode = product.barcode!; + } + + static const String _energyId = 'energy'; + + /// special case: present id [OrderedNutrient] but not in [Nutriments] map. + static const String _energyKJId = 'energy-kj'; + static const String _energyKCalId = 'energy-kcal'; + static const String fakeNutrientIdServingSize = '_servingSize'; + + static const Map _nextWeightUnits = { + Unit.G: Unit.MILLI_G, + Unit.MILLI_G: Unit.MICRO_G, + Unit.MICRO_G: Unit.G, + }; + + // For the moment we only care about "weight or not weight?" + // Could be refined with values taken from https://static.openfoodfacts.org/data/taxonomies/nutrients.json + // Fun fact: most of them are not supported (yet) by [Nutriments]. + static const Map _defaultNotWeightUnits = { + _energyId: Unit.KJ, + _energyKCalId: Unit.KCAL, + 'alcohol': Unit.PERCENT, + 'cocoa': Unit.PERCENT, + 'collagen-meat-protein-ratio': Unit.PERCENT, + 'fruits-vegetables-nuts': Unit.PERCENT, + 'fruits-vegetables-nuts-dried': Unit.PERCENT, + 'fruits-vegetables-nuts-estimate': Unit.PERCENT, + }; + + /// All the nutrients (country-related). + final List _nutrients = []; + + /// Nutrient values for 100g and serving. + final Map _values = {}; + + /// Nutrient units. + final Map _units = {}; + + /// Nutrient Ids added by the end-user + final Set _added = {}; + + String? _servingSize; + + String? get servingSize => _servingSize; + + late final String _barcode; + + /// Returns the not interesting nutrients, for a "Please add me!" list. + Iterable getLeftoverNutrients() => _nutrients.where( + (final OrderedNutrient element) => _isNotRelevant(element), + ); + + /// Returns the interesting nutrients that need to be displayed. + Iterable getDisplayableNutrients() => _nutrients.where( + (final OrderedNutrient element) => !_isNotRelevant(element), + ); + + /// Returns true if the [OrderedNutrient] is not relevant. + bool _isNotRelevant(final OrderedNutrient orderedNutrient) { + final String nutrientId = orderedNutrient.id; + final double? value100g = getValue(getValueKey(nutrientId, false)); + final double? valueServing = getValue(getValueKey(nutrientId, true)); + return value100g == null && + valueServing == null && + (!orderedNutrient.important) && + (!_added.contains(nutrientId)); + } + + /// Returns a [Product] with only nutrients data. + Product getProduct() => Product( + barcode: _barcode, + nutriments: _getNutriments(), + servingSize: _servingSize, + ); + + /// Converts all the data to a [Nutriments]. + Nutriments _getNutriments() { + /// Converts a (weight) value to grams (before sending a value to the BE) + double? _convertWeightToG(final double? value, final Unit unit) { + if (value == null) { + return null; + } + if (unit == Unit.MILLI_G) { + return value / 1E3; + } + if (unit == Unit.MICRO_G) { + return value / 1E6; + } + return value; + } + + final Map map = {}; + for (final OrderedNutrient orderedNutrient in getDisplayableNutrients()) { + final String nutrientId = orderedNutrient.id; + final String key100g = getValueKey(nutrientId, false); + final String keyServing = getValueKey(nutrientId, true); + final double? value100g = getValue(key100g); + final double? valueServing = getValue(keyServing); + if (value100g == null && valueServing == null) { + continue; + } + final Unit unit = getUnit(nutrientId); + if (value100g != null) { + map[key100g] = _convertWeightToG(value100g, unit); + } + if (valueServing != null) { + //map[keyServing] = _convertWeightToG(valueServing, unit); + } + map[_getNutrimentsUnitKey(nutrientId)] = UnitHelper.unitToString(unit); + } + + return Nutriments.fromJson(map); + } + + /// Returns the stored product nutrient's value. + double? getValue(final String valueKey) => _values[valueKey]; + + /// Stores the text from the end-user input. + void setControllerText(final String controllerKey, final String text) { + if (controllerKey == fakeNutrientIdServingSize) { + _servingSize = text.trim().isEmpty ? null : text; + return; + } + + double? value; + if (text.isNotEmpty) { + try { + value = double.parse(text.replaceAll(',', '.')); + } catch (e) { + // + } + } + if (value == null) { + _values.remove(controllerKey); + } else { + _values[controllerKey] = value; + } + } + + /// Typical use-case: should we make the [Unit] button clickable? + static bool isEditableWeight(final OrderedNutrient orderedNutrient) => + _getDefaultUnit(orderedNutrient.id) == null; + + /// Typical use-case: [Unit] button action. + void setNextWeightUnit(final OrderedNutrient orderedNutrient) { + final Unit unit = getUnit(orderedNutrient.id); + _setUnit(orderedNutrient.id, _nextWeightUnits[unit] ?? unit); + } + + /// Returns the nutrient [Unit], after possible alterations. + Unit getUnit(String nutrientId) { + nutrientId = _fixNutrientId(nutrientId); + switch (nutrientId) { + case _energyId: + case _energyKJId: + return Unit.KJ; + case _energyKCalId: + return Unit.KCAL; + default: + return _units[nutrientId] ?? _getDefaultUnit(nutrientId) ?? Unit.G; + } + } + + /// Stores the nutrient [Unit]. + void _setUnit(final String nutrientId, final Unit unit) => + _units[_fixNutrientId(nutrientId)] = unit; + + static Unit? _getDefaultUnit(final String nutrientId) => + _defaultNotWeightUnits[_fixNutrientId(nutrientId)]; + + /// To be used when an [OrderedNutrient] is added to the input list + void add(final OrderedNutrient orderedNutrient) => + _added.add(orderedNutrient.id); + + /// Returns the [Nutriments] map key for the nutrient value. + /// + /// * [perServing] true: per serving. + /// * [perServing] false: per 100g. + static String getValueKey( + String nutrientId, + final bool perServing, + ) { + nutrientId = _fixNutrientId(nutrientId); + // 'energy-kcal' is directly for serving (no 'energy-kcal_serving') + if (nutrientId == _energyKCalId && perServing) { + return _energyKCalId; + } + return '$nutrientId${perServing ? '_serving' : '_100g'}'; + } + + /// Returns a vertical list of nutrients from a tree structure. + /// + /// Typical use-case: to be used from BE's tree nutrients in order to get + /// a simple one-dimension list, easier to display and parse. + /// For some countries, there's energy or energyKJ, or both + /// cf. https://github.com/openfoodfacts/openfoodfacts-server/blob/main/lib/ProductOpener/Food.pm + /// Regarding our list of nutrients here, we need one and only one of them. + void _loadNutrients( + final List nutrients, + ) { + bool alreadyEnergyKJ = false; + + // inner method, in order to use alreadyEnergyKJ without a private variable. + void _populateOrderedNutrientList(final List list) { + for (final OrderedNutrient nutrient in list) { + if (nutrient.id != _energyKJId && + !TmpToOffNutriments.supportedNutrientIds.contains(nutrient.id)) { + continue; + } + final bool nowEnergy = + nutrient.id == _energyId || nutrient.id == _energyKJId; + bool addNutrient = true; + if (nowEnergy) { + if (alreadyEnergyKJ) { + addNutrient = false; + } + alreadyEnergyKJ = true; + } + if (addNutrient) { + _nutrients.add(nutrient); + } + if (nutrient.subNutrients != null) { + _populateOrderedNutrientList(nutrient.subNutrients!); + } + } + } + + _populateOrderedNutrientList(nutrients); + + if (!alreadyEnergyKJ) { + throw Exception('no energy or energyKJ found: very suspicious!'); + } + } + + /// Returns the unit key according to [Nutriments] json map. + static String _getNutrimentsUnitKey(final String nutrientId) => + '${_fixNutrientId(nutrientId)}_unit'; + + static String _fixNutrientId(final String nutrientId) => + nutrientId == _energyKJId ? _energyId : nutrientId; + + /// Loads product nutrient units into a map. + /// + /// Needs nutrients to be loaded first. + void _loadUnits(final Map json) { + for (final OrderedNutrient orderedNutrient in _nutrients) { + final String nutrientId = orderedNutrient.id; + final String unitKey = _getNutrimentsUnitKey(nutrientId); + final dynamic value = json[unitKey]; + if (value == null || value is! String) { + continue; + } + final Unit? unit = UnitHelper.stringToUnit(value); + if (unit != null) { + _setUnit(nutrientId, unit); + } + } + } + + /// Loads product nutrients into a map. + /// + /// Needs nutrients and units to be loaded first. + void _loadValues(final Map json) { + /// Converts a double (weight) value from grams. + /// + /// Typical use-case: after receiving a value from the BE. + double? _convertWeightFromG(final double? value, final Unit unit) { + if (value == null) { + return null; + } + if (unit == Unit.MILLI_G) { + return value * 1E3; + } + if (unit == Unit.MICRO_G) { + return value * 1E6; + } + return value; + } + + for (final OrderedNutrient orderedNutrient in _nutrients) { + final String nutrientId = orderedNutrient.id; + final Unit unit = getUnit(nutrientId); + for (int i = 0; i < 2; i++) { + final bool perServing = i == 0; + final String valueKey = getValueKey(nutrientId, perServing); + final double? value = _convertWeightFromG( + JsonObject.parseDouble(json[valueKey]), + unit, + ); + if (value != null) { + _values[valueKey] = value; + } + } + } + } +} 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 21c9c9904dd..0c9d483c9ed 100644 --- a/packages/smooth_app/lib/pages/product/nutrition_page_loaded.dart +++ b/packages/smooth_app/lib/pages/product/nutrition_page_loaded.dart @@ -2,8 +2,6 @@ import 'package:flutter/material.dart'; import 'package:flutter/services.dart'; import 'package:flutter_gen/gen_l10n/app_localizations.dart'; import 'package:intl/intl.dart'; -import 'package:openfoodfacts/interface/JsonObject.dart'; -import 'package:openfoodfacts/model/Nutriments.dart'; import 'package:openfoodfacts/model/OrderedNutrient.dart'; import 'package:openfoodfacts/model/OrderedNutrients.dart'; import 'package:openfoodfacts/openfoodfacts.dart'; @@ -15,6 +13,7 @@ import 'package:smooth_app/database/product_query.dart'; import 'package:smooth_app/generic_lib/buttons/smooth_action_button.dart'; import 'package:smooth_app/generic_lib/dialogs/smooth_alert_dialog.dart'; import 'package:smooth_app/helpers/ui_helpers.dart'; +import 'package:smooth_app/pages/product/nutrition_container.dart'; import 'package:smooth_app/widgets/loading_dialog.dart'; /// Actual nutrition page, with data already loaded. @@ -29,47 +28,29 @@ class NutritionPageLoaded extends StatefulWidget { } class _NutritionPageLoadedState extends State { - final List _displayableList = []; - late Map _values; - late RegExp _decimalRegExp; - late NumberFormat _numberFormat; + late final RegExp _decimalRegExp; + late final NumberFormat _numberFormat; + late final NutritionContainer _nutritionContainer; bool _unspecified = false; // TODO(monsieurtanuki): fetch that data from API? + // If true then serving, if false then 100g. bool _servingOr100g = false; static const double _columnSize1 = 250; // TODO(monsieurtanuki): proper size static const double _columnSize2 = 100; // TODO(monsieurtanuki): anyway, should fit the largest text, probably 'mcg/µg' - static const String _fakeNutrientIdServingSize = '_servingSize'; - static const String _energyId = 'energy'; - static const String _energyKJId = 'energy-kj'; - static const String _energyKCalId = 'energy-kcal'; - final Map _controllers = {}; - final Map _units = {}; final GlobalKey _formKey = GlobalKey(); - static const Map _nextWeightUnits = { - Unit.G: Unit.MILLI_G, - Unit.MILLI_G: Unit.MICRO_G, - Unit.MICRO_G: Unit.G, - }; - static const Map _unitLabels = { - Unit.G: 'g', - Unit.MILLI_G: 'mg', - Unit.MICRO_G: 'mcg/µg', - Unit.KJ: 'kJ', - Unit.KCAL: 'kcal', - Unit.PERCENT: '%', - }; - @override void initState() { super.initState(); - _populateOrderedNutrientList(widget.orderedNutrients.nutrients); - _values = widget.product.nutriments!.toJson(); + _nutritionContainer = NutritionContainer( + orderedNutrients: widget.orderedNutrients, + product: widget.product, + ); _numberFormat = NumberFormat('####0.#####', ProductQuery.getLocaleString()); _decimalRegExp = _numberFormat.format(1.2).contains('.') ? RegExp(r'[0-9\.]') // TODO(monsieurtanuki): check if . or \. @@ -93,14 +74,14 @@ class _NutritionPageLoadedState extends State { if (!_unspecified) { children.add(_getServingField(appLocalizations)); children.add(_getServingSwitch(appLocalizations)); - for (final OrderedNutrient orderedNutrient in _displayableList) { - final Widget? item = _getNutrientWidget( - appLocalizations, - orderedNutrient, + for (final OrderedNutrient orderedNutrient + in _nutritionContainer.getDisplayableNutrients()) { + children.add( + _getNutrientRow( + appLocalizations, + orderedNutrient, + ), ); - if (item != null) { - children.add(item); - } } children.add(_addNutrientButton(appLocalizations)); } @@ -121,85 +102,46 @@ class _NutritionPageLoadedState extends State { ); } - void _populateOrderedNutrientList(final List? list) { - if (list == null) { - return; - } - for (final OrderedNutrient nutrient in list) { - _displayableList.add(nutrient); - _populateOrderedNutrientList(nutrient.subNutrients); - } - } - - Widget? _getNutrientWidget( + Widget _getNutrientRow( final AppLocalizations appLocalizations, final OrderedNutrient orderedNutrient, - ) { - final String id = orderedNutrient.id; - if (id == _energyId) { - // we keep only kj and kcal - return null; - } - final double? value100g = _getValue(id, false); - final double? valueServing = _getValue(id, true); - if (value100g == null && - valueServing == null && - !orderedNutrient.important) { - return null; - } - return Row( - mainAxisAlignment: MainAxisAlignment.spaceBetween, - crossAxisAlignment: CrossAxisAlignment.center, - children: [ - SizedBox( - width: _columnSize1, - child: _getNutrientCell( - appLocalizations, - orderedNutrient, - _servingOr100g, + ) => + Row( + mainAxisAlignment: MainAxisAlignment.spaceBetween, + crossAxisAlignment: CrossAxisAlignment.center, + children: [ + SizedBox( + width: _columnSize1, + child: _getNutrientCell( + appLocalizations, + orderedNutrient, + _servingOr100g, + ), ), - ), - SizedBox( - width: _columnSize2, - child: _getUnitCell(id), - ), - ], - ); - } + SizedBox( + width: _columnSize2, + child: _getUnitCell(orderedNutrient), + ), + ], + ); Widget _getNutrientCell( final AppLocalizations appLocalizations, final OrderedNutrient orderedNutrient, final bool perServing, ) { - final String id = orderedNutrient.id; - final String tag = _getNutrientServingTag(id, perServing); + final String valueKey = NutritionContainer.getValueKey( + orderedNutrient.id, + perServing, + ); final TextEditingController controller; - if (_controllers[tag] != null) { - controller = _controllers[tag]!; + if (_controllers[valueKey] != null) { + controller = _controllers[valueKey]!; } else { - double? value = _getValue(id, perServing); - final Unit? defaultNotWeightUnit = _getDefaultNotWeightUnit(id); - final Unit unit = _getUnit(id) ?? defaultNotWeightUnit ?? Unit.G; - if (value == null) { - if (id == _energyKJId || id == _energyKCalId) { - final double? valueEnergy = _getValue(_energyId, perServing); - final Unit? unitEnergy = _getUnit(_energyId); - if (id == _energyKJId) { - if (unitEnergy == Unit.KJ) { - value = valueEnergy; - } - } else if (id == _energyKCalId) { - if (unitEnergy == Unit.KCAL) { - value = valueEnergy; - } - } - } - } - value = _convertValueFromG(value, unit); + final double? value = _nutritionContainer.getValue(valueKey); controller = TextEditingController(); controller.text = value == null ? '' : _numberFormat.format(value); - _controllers[tag] = controller; + _controllers[valueKey] = controller; } return TextFormField( controller: controller, @@ -230,24 +172,24 @@ class _NutritionPageLoadedState extends State { ); } - Widget _getUnitCell(final String nutrientId) { - final Unit? defaultNotWeightUnit = _getDefaultNotWeightUnit(nutrientId); - final bool isWeight = defaultNotWeightUnit == null; - final Unit unit; - final String tag = _getNutrientIdFromServingTag(nutrientId); - if (_units[tag] != null) { - unit = _units[tag]!; - } else { - unit = _getUnit(nutrientId) ?? defaultNotWeightUnit ?? Unit.G; - _units[tag] = unit; - } + static const Map _unitLabels = { + Unit.G: 'g', + Unit.MILLI_G: 'mg', + Unit.MICRO_G: 'mcg/µg', + Unit.KJ: 'kJ', + Unit.KCAL: 'kcal', + Unit.PERCENT: '%', + }; + + static String _getUnitLabel(final Unit unit) => + _unitLabels[unit] ?? UnitHelper.unitToString(unit)!; + + Widget _getUnitCell(final OrderedNutrient orderedNutrient) { + final Unit unit = _nutritionContainer.getUnit(orderedNutrient.id); return ElevatedButton( - onPressed: isWeight + onPressed: NutritionContainer.isEditableWeight(orderedNutrient) ? () => setState( - () => _setUnit( - nutrientId, - _units[nutrientId] = _nextWeightUnits[unit]!, - ), + () => _nutritionContainer.setNextWeightUnit(orderedNutrient), ) : null, child: Text( @@ -259,8 +201,8 @@ class _NutritionPageLoadedState extends State { Widget _getServingField(final AppLocalizations appLocalizations) { final TextEditingController controller = TextEditingController(); - controller.text = widget.product.servingSize ?? ''; - _controllers[_fakeNutrientIdServingSize] = controller; + controller.text = _nutritionContainer.servingSize ?? ''; + _controllers[NutritionContainer.fakeNutrientIdServingSize] = controller; return Padding( padding: const EdgeInsets.only(bottom: VERY_LARGE_SPACE), child: TextFormField( @@ -289,80 +231,6 @@ class _NutritionPageLoadedState extends State { ], ); - Unit? _getUnit(final String nutrientId) => UnitHelper.stringToUnit( - _values[_getUnitValueTag(nutrientId)] as String?, - ); - - double? _getValue(final String nutrientId, final bool perServing) => - JsonObject.parseDouble( - _values[_getNutrientServingTag(nutrientId, perServing)]); - - void _initValues(final String nutrientId) => - _values[_getNutrientServingTag(nutrientId, true)] = - _values[_getNutrientServingTag(nutrientId, false)] = 0; - - void _setUnit(final String nutrientId, final Unit unit) => - _values[_getUnitValueTag(nutrientId)] = UnitHelper.unitToString(unit); - - String _getUnitValueTag(final String nutrientId) => '${nutrientId}_unit'; - - // note: 'energy-kcal' is directly for serving (no 'energy-kcal_serving') - String _getNutrientServingTag( - final String nutrientId, - final bool perServing, - ) => - nutrientId == _energyKCalId && perServing - ? _energyKCalId - : '$nutrientId${perServing ? '_serving' : '_100g'}'; - - String _getNutrientIdFromServingTag(final String key) => - key.replaceAll('_100g', '').replaceAll('_serving', ''); - - String _getUnitLabel(final Unit unit) => - _unitLabels[unit] ?? UnitHelper.unitToString(unit)!; - - // For the moment we only care about "weight or not weight?" - static const Map _defaultNotWeightUnits = { - _energyKJId: Unit.KJ, - _energyKCalId: Unit.KCAL, - 'alcohol': Unit.PERCENT, - 'cocoa': Unit.PERCENT, - 'collagen-meat-protein-ratio': Unit.PERCENT, - 'fruits-vegetables-nuts': Unit.PERCENT, - 'fruits-vegetables-nuts-dried': Unit.PERCENT, - 'fruits-vegetables-nuts-estimate': Unit.PERCENT, - }; - - // TODO(monsieurtanuki): could be refined with values taken from https://static.openfoodfacts.org/data/taxonomies/nutrients.json - Unit? _getDefaultNotWeightUnit(final String nutrientId) => - _defaultNotWeightUnits[nutrientId]; - - double? _convertValueFromG(final double? value, final Unit unit) { - if (value == null) { - return null; - } - if (unit == Unit.MILLI_G) { - return value * 1E3; - } - if (unit == Unit.MICRO_G) { - return value * 1E6; - } - return value; - } - - double? _convertValueToG(final double? value, final Unit unit) { - if (value == null) { - return null; - } - if (unit == Unit.MILLI_G) { - return value / 1E3; - } - if (unit == Unit.MICRO_G) { - return value / 1E6; - } - return value; - } - Widget _switchNoNutrition(final AppLocalizations appLocalizations) => Container( color: Theme.of(context).colorScheme.primary, @@ -391,25 +259,16 @@ class _NutritionPageLoadedState extends State { Widget _addNutrientButton(final AppLocalizations appLocalizations) => ElevatedButton.icon( onPressed: () async { - final List availables = []; - for (final OrderedNutrient orderedNutrient in _displayableList) { - final String id = orderedNutrient.id; - final double? value100g = _getValue(id, false); - final double? valueServing = _getValue(id, true); - final bool addAble = value100g == null && - valueServing == null && - !orderedNutrient.important; - if (addAble) { - availables.add(orderedNutrient); - } - } - availables.sort((final OrderedNutrient a, final OrderedNutrient b) => + 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) { final List children = []; - for (final OrderedNutrient nutrient in availables) { + for (final OrderedNutrient nutrient in leftovers) { children.add( ListTile( title: Text(nutrient.name!), @@ -434,7 +293,7 @@ class _NutritionPageLoadedState extends State { ); }); if (selected != null) { - setState(() => _initValues(selected.id)); + setState(() => _nutritionContainer.add(selected)); } }, icon: const Icon(Icons.add), @@ -466,33 +325,14 @@ class _NutritionPageLoadedState extends State { ); Future _save(final LocalDatabase localDatabase) async { - final Map map = {}; - String? servingSize; for (final String key in _controllers.keys) { final TextEditingController controller = _controllers[key]!; - final String text = controller.text; - if (key == _fakeNutrientIdServingSize) { - servingSize = text; - } else { - if (text.isNotEmpty) { - final String nutrientId = _getNutrientIdFromServingTag(key); - final Unit unit = _units[nutrientId]!; - map[_getUnitValueTag(nutrientId)] = UnitHelper.unitToString(unit); - map[key] = _convertValueToG( - _numberFormat.parse(text).toDouble(), unit); // careful with comma - } - } + _nutritionContainer.setControllerText(key, controller.text); } - final Nutriments nutriments = Nutriments.fromJson(map); // minimal product: we only want to save the nutrients - final Product inputProduct = Product( - barcode: widget.product.barcode, - nutriments: nutriments, - servingSize: servingSize, - ); - + final Product minimalProduct = _nutritionContainer.getProduct(); final bool? savedAndRefreshed = await LoadingDialog.run( - future: _saveAndRefresh(inputProduct, localDatabase), + future: _saveAndRefresh(minimalProduct, localDatabase), context: context, title: AppLocalizations.of(context)!.nutrition_page_update_running, ); @@ -520,19 +360,19 @@ class _NutritionPageLoadedState extends State { /// Saves a product on the BE and refreshes the local database Future _saveAndRefresh( - final Product inputProduct, + final Product minimalProduct, final LocalDatabase localDatabase, ) async { try { final Status status = await OpenFoodAPIClient.saveProduct( ProductQuery.getUser(), - inputProduct, + minimalProduct, ); if (status.error != null) { return false; } final ProductQueryConfiguration configuration = ProductQueryConfiguration( - inputProduct.barcode!, + minimalProduct.barcode!, fields: ProductQuery.fields, language: ProductQuery.getLanguage(), country: ProductQuery.getCountry(), diff --git a/packages/smooth_app/lib/pages/product/tmp_to_off_nutriments.dart b/packages/smooth_app/lib/pages/product/tmp_to_off_nutriments.dart new file mode 100644 index 00000000000..9c8af6d40bf --- /dev/null +++ b/packages/smooth_app/lib/pages/product/tmp_to_off_nutriments.dart @@ -0,0 +1,69 @@ +// TODO(monsieurtanuki): move to off-dart +class TmpToOffNutriments { + /// Nutrient ids supported by [Nutriments]. + /// + /// To be used when another source of nutrients (e.g. [OrderedNutrient]) + /// has a broader list of nutrients - that we simply could not handle with + /// [Nutriments]. + static const Set supportedNutrientIds = { + 'salt', + 'fiber', + 'sugars', + 'fat', + 'saturated-fat', + 'proteins', + 'nova-group', + 'energy', + 'energy-kcal', + 'carbohydrates', + 'caffeine', + 'calcium', + 'iron', + 'vitamin-c', + 'magnesium', + 'phosphorus', + 'potassium', + 'sodium', + 'zinc', + 'copper', + 'selenium', + 'vitamin-a', + 'vitamin-e', + 'vitamin-d', + 'vitamin-b1', + 'vitamin-b2', + 'vitamin-pp', + 'vitamin-b6', + 'vitamin-b12', + 'vitamin-b9', + 'vitamin-k', + 'cholesterol', + 'butyric-acid', + 'caproic-acid', + 'caprylic-acid', + 'capric-acid', + 'lauric-acid', + 'myristic-acid', + 'palmitic-acid', + 'stearic-acid', + 'oleic-acid', + 'linoleic-acid', + 'docosahexaenoic-acid', + 'eicosapentaenoic-acid', + 'erucic-acid', + 'monounsaturated', + 'polyunsaturated', + 'alcohol', + 'pantothenic-acid', + 'biotin', + 'chloride', + 'chromium', + 'fluoride', + 'iodine', + 'manganese', + 'molybdenum', + 'omega-3-fat', + 'omega-6-fat', + 'trans-fat', + }; +}