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 1e77ca24c97..0a68bdf94c1 100644 --- a/packages/smooth_app/lib/pages/product/nutrition_page_loaded.dart +++ b/packages/smooth_app/lib/pages/product/nutrition_page_loaded.dart @@ -89,14 +89,6 @@ class _NutritionPageLoadedState extends State { late final NumberFormat _decimalNumberFormat; late final NutritionContainer _nutritionContainer; - double getColumnSizeFromContext( - BuildContext context, - double adjustmentFactor, - ) { - final double columnSize = MediaQuery.of(context).size.width; - return columnSize * adjustmentFactor; - } - final Map _controllers = {}; TextEditingControllerWithInitialValue? _servingController; @@ -150,9 +142,25 @@ class _NutritionPageLoadedState extends State { ); for (int i = 0; i != displayableNutrients.length; i++) { + final OrderedNutrient orderedNutrient = + displayableNutrients.elementAt(i); + + final Nutrient nutrient = _getNutrient(orderedNutrient); + if (_controllers[nutrient] == null) { + final double? value = _nutritionContainer.getValue(nutrient); + _controllers[nutrient] = TextEditingControllerWithInitialValue( + text: value == null ? '' : _decimalNumberFormat.format(value), + ); + } + children.add( - _getNutrientRow( - appLocalizations, displayableNutrients.elementAt(i), i), + _NutrientRow( + _nutritionContainer, + _decimalNumberFormat, + _controllers[nutrient]!, + orderedNutrient, + i, + ), ); } children.add( @@ -222,126 +230,6 @@ class _NutritionPageLoadedState extends State { ); } - Widget _getNutrientRow( - final AppLocalizations appLocalizations, - final OrderedNutrient orderedNutrient, - int position, - ) => - Row( - mainAxisAlignment: MainAxisAlignment.spaceBetween, - crossAxisAlignment: CrossAxisAlignment.center, - children: [ - Expanded( - child: _getNutrientCell( - appLocalizations, - orderedNutrient, - position, - ), - ), - SizedBox( - width: getColumnSizeFromContext(context, 0.3), - child: _getUnitCell(orderedNutrient), - ), - ], - ); - - Widget _getNutrientCell( - final AppLocalizations appLocalizations, - final OrderedNutrient orderedNutrient, - final int position, - ) { - final Nutrient nutrient = _getNutrient(orderedNutrient); - - if (_controllers[nutrient] == null) { - final double? value = _nutritionContainer.getValue(nutrient); - _controllers[nutrient] = TextEditingControllerWithInitialValue( - text: value == null ? '' : _decimalNumberFormat.format(value), - ); - } - final TextEditingControllerWithInitialValue controller = - _controllers[nutrient]!; - - return Builder( - builder: (BuildContext context) { - final List focusNodes = Provider.of>( - context, - listen: false, - ); - - final bool isLast = position == focusNodes.length - 1; - - return TextFormField( - controller: controller, - focusNode: focusNodes[position], - decoration: InputDecoration( - enabledBorder: const UnderlineInputBorder(), - labelText: orderedNutrient.name, - ), - keyboardType: const TextInputType.numberWithOptions( - signed: false, - decimal: true, - ), - textInputAction: isLast ? TextInputAction.send : TextInputAction.next, - onFieldSubmitted: (_) async { - if (!isLast) { - // Move to next field - focusNodes[position + 1].requestFocus(); - } else { - // Save page content - _exitPage( - await _mayExitPage(saving: true), - ); - } - }, - inputFormatters: [ - FilteringTextInputFormatter.allow( - SimpleInputNumberField.getNumberRegExp(decimal: true), - ), - DecimalSeparatorRewriter(_decimalNumberFormat), - ], - validator: (String? value) { - if (value == null || value.trim().isEmpty) { - return null; - } - try { - _decimalNumberFormat.parse(value); - return null; - } catch (e) { - return appLocalizations.nutrition_page_invalid_number; - } - }, - ); - }, - ); - } - - 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(_getNutrient(orderedNutrient)); - return ElevatedButton( - onPressed: _nutritionContainer.isEditableWeight(unit) - ? () => setState( - () => _nutritionContainer.setNextWeightUnit(orderedNutrient)) - : null, - child: Text( - _getUnitLabel(unit), - style: const TextStyle(fontWeight: FontWeight.bold), - ), - ); - } - Widget _getServingField(final AppLocalizations appLocalizations) { final String value = _nutritionContainer.servingSize; @@ -442,7 +330,7 @@ class _NutritionPageLoadedState extends State { Theme.of(context).colorScheme.onPrimary), ), SizedBox( - width: getColumnSizeFromContext(context, 0.6), + width: _getColumnSize(context, 0.6), child: AutoSizeText( localizations.nutrition_page_unspecified, style: Theme.of(context).primaryTextTheme.bodyMedium?.copyWith( @@ -563,15 +451,165 @@ class _NutritionPageLoadedState extends State { ); return true; } +} - // cf. https://github.com/openfoodfacts/smooth-app/issues/3387 - Nutrient _getNutrient(final OrderedNutrient orderedNutrient) { - if (orderedNutrient.nutrient != null) { - return orderedNutrient.nutrient!; - } - if (orderedNutrient.id == 'energy') { - return Nutrient.energyKJ; - } - throw Exception('unknown nutrient for "${orderedNutrient.id}"'); +class _NutrientRow extends StatelessWidget { + const _NutrientRow( + this.nutritionContainer, + this.decimalNumberFormat, + this.controller, + this.orderedNutrient, + this.position, + ); + + final NutritionContainer nutritionContainer; + final NumberFormat decimalNumberFormat; + final TextEditingControllerWithInitialValue controller; + final OrderedNutrient orderedNutrient; + final int position; + + @override + Widget build(BuildContext context) => Row( + mainAxisAlignment: MainAxisAlignment.spaceBetween, + crossAxisAlignment: CrossAxisAlignment.center, + children: [ + Expanded( + child: _NutrientValueCell( + decimalNumberFormat, + controller, + orderedNutrient, + position, + ), + ), + SizedBox( + width: _getColumnSize(context, 0.3), + child: _NutrientUnitCell( + nutritionContainer, + orderedNutrient, + ), + ), + ], + ); +} + +class _NutrientValueCell extends StatelessWidget { + const _NutrientValueCell( + this.decimalNumberFormat, + this.controller, + this.orderedNutrient, + this.position, + ); + + final NumberFormat decimalNumberFormat; + final TextEditingControllerWithInitialValue controller; + final OrderedNutrient orderedNutrient; + final int position; + + @override + Widget build(BuildContext context) { + final List focusNodes = Provider.of>( + context, + listen: false, + ); + + final bool isLast = position == focusNodes.length - 1; + + return TextFormField( + controller: controller, + focusNode: focusNodes[position], + decoration: InputDecoration( + enabledBorder: const UnderlineInputBorder(), + labelText: orderedNutrient.name, + ), + keyboardType: const TextInputType.numberWithOptions( + signed: false, + decimal: true, + ), + textInputAction: isLast ? null : TextInputAction.next, + onFieldSubmitted: (_) async { + if (!isLast) { + focusNodes[position + 1].requestFocus(); + } + }, + inputFormatters: [ + FilteringTextInputFormatter.allow( + SimpleInputNumberField.getNumberRegExp(decimal: true), + ), + DecimalSeparatorRewriter(decimalNumberFormat), + ], + validator: (String? value) { + if (value == null || value.trim().isEmpty) { + return null; + } + try { + decimalNumberFormat.parse(value); + return null; + } catch (e) { + return AppLocalizations.of(context).nutrition_page_invalid_number; + } + }, + ); + } +} + +class _NutrientUnitCell extends StatefulWidget { + const _NutrientUnitCell( + this.nutritionContainer, + this.orderedNutrient, + ); + + final NutritionContainer nutritionContainer; + final OrderedNutrient orderedNutrient; + + @override + State<_NutrientUnitCell> createState() => _NutrientUnitCellState(); +} + +class _NutrientUnitCellState extends State<_NutrientUnitCell> { + @override + Widget build(BuildContext context) { + final Unit unit = + widget.nutritionContainer.getUnit(_getNutrient(widget.orderedNutrient)); + return ElevatedButton( + onPressed: widget.nutritionContainer.isEditableWeight(unit) + ? () => setState( + () => widget.nutritionContainer + .setNextWeightUnit(widget.orderedNutrient), + ) + : null, + child: Text( + _getUnitLabel(unit), + style: const TextStyle(fontWeight: FontWeight.bold), + ), + ); + } + + 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)!; +} + +double _getColumnSize( + final BuildContext context, + final double adjustmentFactor, +) => + MediaQuery.of(context).size.width * adjustmentFactor; + +// cf. https://github.com/openfoodfacts/smooth-app/issues/3387 +Nutrient _getNutrient(final OrderedNutrient orderedNutrient) { + if (orderedNutrient.nutrient != null) { + return orderedNutrient.nutrient!; + } + if (orderedNutrient.id == 'energy') { + return Nutrient.energyKJ; } + throw Exception('unknown nutrient for "${orderedNutrient.id}"'); }