Skip to content

Commit

Permalink
fix: 3436 - no more "serving size" jump (#3921)
Browse files Browse the repository at this point in the history
Impacted file:
* `nutrition_page_loaded.dart`: refactored with "real" `Widget`s
  • Loading branch information
monsieurtanuki authored Apr 29, 2023
1 parent 4634f5b commit 9ed1cdc
Showing 1 changed file with 178 additions and 140 deletions.
318 changes: 178 additions & 140 deletions packages/smooth_app/lib/pages/product/nutrition_page_loaded.dart
Original file line number Diff line number Diff line change
Expand Up @@ -89,14 +89,6 @@ class _NutritionPageLoadedState extends State<NutritionPageLoaded> {
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<Nutrient, TextEditingControllerWithInitialValue> _controllers =
<Nutrient, TextEditingControllerWithInitialValue>{};
TextEditingControllerWithInitialValue? _servingController;
Expand Down Expand Up @@ -150,9 +142,25 @@ class _NutritionPageLoadedState extends State<NutritionPageLoaded> {
);

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(
Expand Down Expand Up @@ -222,126 +230,6 @@ class _NutritionPageLoadedState extends State<NutritionPageLoaded> {
);
}

Widget _getNutrientRow(
final AppLocalizations appLocalizations,
final OrderedNutrient orderedNutrient,
int position,
) =>
Row(
mainAxisAlignment: MainAxisAlignment.spaceBetween,
crossAxisAlignment: CrossAxisAlignment.center,
children: <Widget>[
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<FocusNode> focusNodes = Provider.of<List<FocusNode>>(
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: <TextInputFormatter>[
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<Unit, String> _unitLabels = <Unit, String>{
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;

Expand Down Expand Up @@ -442,7 +330,7 @@ class _NutritionPageLoadedState extends State<NutritionPageLoaded> {
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(
Expand Down Expand Up @@ -563,15 +451,165 @@ class _NutritionPageLoadedState extends State<NutritionPageLoaded> {
);
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: <Widget>[
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<FocusNode> focusNodes = Provider.of<List<FocusNode>>(
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: <TextInputFormatter>[
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<Unit, String> _unitLabels = <Unit, String>{
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}"');
}

0 comments on commit 9ed1cdc

Please sign in to comment.