Skip to content

Commit

Permalink
Nutrition page: owner fields in a banner + pick quantity from the pro…
Browse files Browse the repository at this point in the history
…duct (openfoodfacts#5922)

Co-authored-by: Pierre Slamich <[email protected]>
  • Loading branch information
g123k and teolemon authored Nov 26, 2024
1 parent 004daee commit 4cf7e0a
Show file tree
Hide file tree
Showing 13 changed files with 450 additions and 126 deletions.
Binary file modified packages/smooth_app/assets/fonts/SmoothIcons.ttf
Binary file not shown.
14 changes: 14 additions & 0 deletions packages/smooth_app/assets/fonts/icons/config.json
Original file line number Diff line number Diff line change
Expand Up @@ -1677,6 +1677,20 @@
"search": [
"image_error"
]
},
{
"uid": "d9b51ca84fa9ddc8ad93d480ffa74b81",
"css": "milk_download",
"code": 59511,
"src": "custom_icons",
"selected": true,
"svg": {
"path": "M538.5 0C554.7 0 567.8 13.1 567.8 29.3L567.8 61.4 654 233.8 657.5 241.4C662.6 254.2 665.4 267.9 665.4 281.8L665.4 502.3C645 505 625.4 510.1 606.8 517.2L606.8 281.8C606.8 274.3 605.1 266.8 601.7 260L538.5 133.7 475.4 260C472 266.8 470.2 274.2 470.2 281.8L470.2 638.2C450.9 674.6 440 716 440 760 440 773.6 441 787 443.1 800L148.3 800C110.9 800 80 769.1 80 731.7L80 281.8C80 265.2 83.9 248.8 91.3 233.8L177.6 61.4 177.6 29.3C177.6 13.1 190.7 0 206.8 0L538.5 0ZM202.2 509.6C190.2 509.9 179.5 517.5 175.4 528.9 171.3 540.2 174.6 552.9 183.7 560.9 234.5 607.9 312.5 607.9 363.3 560.9 372.3 552.8 375.4 540 371.1 528.7 366.8 517.4 356.1 509.9 344 509.8 336.4 509.8 329 512.7 323.5 517.9 293.5 545.7 253.4 545.7 223.4 517.9 217.8 512.4 210.1 509.4 202.2 509.6ZM206.8 409.8C190.7 409.8 177.6 422.9 177.6 439 177.6 455.2 190.7 468.3 206.8 468.3 223 468.3 236.1 455.2 236.1 439 236.1 422.9 223 409.8 206.8 409.8ZM343.4 409.8C327.2 409.8 314.1 422.9 314.1 439 314.1 455.2 327.2 468.3 343.4 468.3 359.6 468.3 372.7 455.2 372.7 439 372.7 422.9 359.6 409.8 343.4 409.8ZM491.2 97.6L224.9 97.6 156.6 234.1 422.9 234.1 491.2 97.6ZM480 760C480 638.5 578.5 540 700 540 821.5 540 920 638.5 920 760 920 881.5 821.5 980 700 980 578.5 980 480 881.5 480 760ZM713 864.6L786.3 791.3C789.9 787.7 791.7 783 791.7 778.3 791.7 773.6 789.9 768.9 786.3 765.4 779.1 758.2 767.5 758.2 760.4 765.4L718.3 807.4 718.3 668.3C718.3 658.2 710.1 650 700 650 689.9 650 681.7 658.2 681.7 668.3L681.7 807.4 639.6 765.4C632.5 758.2 620.9 758.2 613.7 765.4 606.5 772.5 606.5 784.1 613.7 791.3L687 864.6C694.2 871.8 705.8 871.8 713 864.6Z",
"width": 1000
},
"search": [
"milk_download"
]
}
]
}
Binary file modified packages/smooth_app/assets/fonts/icons/icons.sketch
Binary file not shown.
8 changes: 8 additions & 0 deletions packages/smooth_app/assets/fonts/icons/milk_download.svg
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,7 @@ import 'package:smooth_app/themes/smooth_theme.dart';
import 'package:smooth_app/themes/smooth_theme_colors.dart';
import 'package:smooth_app/themes/theme_provider.dart';
import 'package:smooth_app/widgets/smooth_barcode_widget.dart';
import 'package:smooth_app/widgets/smooth_close_button.dart';
import 'package:smooth_app/widgets/smooth_text.dart';

/// A common Widget for carrousel item cards.
Expand Down Expand Up @@ -191,36 +192,12 @@ class _SmoothProductCardHeader extends StatelessWidget {
),
if (onClose != null) ...<Widget>[
const SizedBox(width: MEDIUM_SPACE),
Semantics(
label: closeTooltip,
button: true,
excludeSemantics: true,
child: Tooltip(
message: closeTooltip,
child: Material(
type: MaterialType.transparency,
child: InkWell(
onTap: onClose,
customBorder: const CircleBorder(),
child: Padding(
padding: const EdgeInsetsDirectional.all(11.0),
child: Ink(
width: 28.0,
height: 28.0,
decoration: const BoxDecoration(
shape: BoxShape.circle,
color: Colors.white,
),
padding: const EdgeInsetsDirectional.all(7.0),
child: icons.Close(
size: 14.0,
color: bgColor,
),
),
),
),
),
),
SmoothCloseButton(
onClose: onClose!,
circleColor: bgColor,
crossColor: Colors.white,
padding: const EdgeInsetsDirectional.all(11.0),
tooltip: closeTooltip,
),
],
],
Expand Down
8 changes: 8 additions & 0 deletions packages/smooth_app/lib/l10n/app_en.arb
Original file line number Diff line number Diff line change
Expand Up @@ -749,6 +749,10 @@
"nutrition_page_invalid_number": "Invalid number",
"nutrition_page_update_running": "Updating the product on the server…",
"nutrition_page_update_done": "Product updated!",
"nutrition_page_take_serving_size_from_product_quantity": "Use the product quantity as serving size",
"@nutrition_page_take_serving_size_from_product_quantity": {
"description": "Button label: Use the product quantity as serving size (nutrition page)"
},
"more_photos": "More interesting photos",
"@more_photos": {},
"view_more_photo_button": "View all existing photos for this product",
Expand Down Expand Up @@ -2673,6 +2677,10 @@
"@owner_field_info_message": {
"description": "Title of the 'producer provided' info list-tile"
},
"owner_field_info_close_button": "Close this info",
"@owner_field_info_close_button": {
"description": "The owner info may be shown in a closeable dialog. This is the label of the button (used on a long press event and for the accessibility label)."
},
"edit_packagings_title": "Packaging components",
"@edit_packagings_title": {
"description": "Title of the structured packagings page"
Expand Down
215 changes: 138 additions & 77 deletions packages/smooth_app/lib/pages/product/nutrition_page_loaded.dart
Original file line number Diff line number Diff line change
Expand Up @@ -24,6 +24,7 @@ import 'package:smooth_app/pages/product/owner_field_info.dart';
import 'package:smooth_app/pages/product/simple_input_number_field.dart';
import 'package:smooth_app/pages/text_field_helper.dart';
import 'package:smooth_app/query/product_query.dart';
import 'package:smooth_app/resources/app_icons.dart' as icons;
import 'package:smooth_app/widgets/smooth_scaffold.dart';
import 'package:smooth_app/widgets/smooth_switch.dart';
import 'package:smooth_app/widgets/will_pop_scope.dart';
Expand Down Expand Up @@ -88,6 +89,10 @@ class _NutritionPageLoadedState extends State<NutritionPageLoaded>
with UpToDateMixin {
late final NumberFormat _decimalNumberFormat;
late final NutritionContainer _nutritionContainer;
bool _ownerFieldBannerVisible = false;

/// When the banner is visible, we add a padding to the list
double _ownerFieldBannerHeight = 0.0;

final Map<Nutrient, TextEditingControllerWithHistory> _controllers =
<Nutrient, TextEditingControllerWithHistory>{};
Expand All @@ -98,14 +103,18 @@ class _NutritionPageLoadedState extends State<NutritionPageLoaded>
@override
void initState() {
super.initState();
initUpToDate(widget.product, context.read<LocalDatabase>());
initUpToDate(
widget.product,
context.read<LocalDatabase>(),
);
_nutritionContainer = NutritionContainer(
orderedNutrients: widget.orderedNutrients,
product: upToDateProduct,
);

_decimalNumberFormat =
SimpleInputNumberField.getNumberFormat(decimal: true);
_decimalNumberFormat = SimpleInputNumberField.getNumberFormat(
decimal: true,
);
}

@override
Expand All @@ -126,6 +135,80 @@ class _NutritionPageLoadedState extends State<NutritionPageLoaded>
context.watch<LocalDatabase>();
refreshUpToDate();

final List<Widget> children = _generateListOfWidgets(
appLocalizations,
context,
);

return WillPopScope2(
onWillPop: () async => (await _mayExitPage(saving: false), null),
child: Provider<_NutritionPageLoadedState>.value(
value: this,
child: SmoothScaffold(
fixKeyboard: true,
appBar: buildEditProductAppBar(
context: context,
title: appLocalizations.nutrition_page_title,
product: upToDateProduct,
),
body: Stack(
children: <Widget>[
Positioned.fill(
child: Padding(
padding: const EdgeInsetsDirectional.symmetric(
horizontal: LARGE_SPACE,
),
child: Form(
key: _formKey,
child: Provider<List<FocusNode>>.value(
value: _focusNodes,
child: ListView(
padding: const EdgeInsetsDirectional.symmetric(
vertical: SMALL_SPACE,
),
children: children,
),
),
),
),
),
Positioned(
bottom: 0.0,
left: 0.0,
right: 0.0,
child: AnimatedOwnerFieldBanner(
visible: _ownerFieldBannerVisible,
shadow: true,
onHeightChanged: (double height) {
_ownerFieldBannerHeight = height;
if (_ownerFieldBannerVisible) {
setState(() {});
}
},
onDismissClicked: () {
setState(() => _ownerFieldBannerVisible = false);
},
),
),
],
),
bottomNavigationBar: ProductBottomButtonsBar(
onSave: () async => _exitPage(
await _mayExitPage(saving: true),
),
onCancel: () async => _exitPage(
await _mayExitPage(saving: false),
),
),
),
),
);
}

List<Widget> _generateListOfWidgets(
AppLocalizations appLocalizations,
BuildContext context,
) {
final List<Widget> children = <Widget>[];

// List of focus nodes for all text fields except the serving one.
Expand All @@ -146,9 +229,6 @@ class _NutritionPageLoadedState extends State<NutritionPageLoaded>
),
),
);
if (_hasOwnerField(displayableNutrients)) {
children.add(const OwnerFieldInfo());
}

children.add(_getServingField(appLocalizations));
children.add(_getServingSwitch(appLocalizations));
Expand Down Expand Up @@ -195,70 +275,14 @@ class _NutritionPageLoadedState extends State<NutritionPageLoaded>
refreshParent: () => setState(() {}),
),
);
} else {
_focusNodes.clear();
}

return WillPopScope2(
onWillPop: () async => (await _mayExitPage(saving: false), null),
child: SmoothScaffold(
fixKeyboard: true,
appBar: buildEditProductAppBar(
context: context,
title: appLocalizations.nutrition_page_title,
product: upToDateProduct,
),
body: Padding(
padding: const EdgeInsetsDirectional.symmetric(
horizontal: LARGE_SPACE,
),
child: Form(
key: _formKey,
child: Provider<List<FocusNode>>.value(
value: _focusNodes,
child: ListView(
padding: const EdgeInsetsDirectional.symmetric(
vertical: SMALL_SPACE,
),
children: children,
),
),
),
),
bottomNavigationBar: ProductBottomButtonsBar(
onSave: () async => _exitPage(
await _mayExitPage(saving: true),
),
onCancel: () async => _exitPage(
await _mayExitPage(saving: false),
),
),
),
);
}

bool _hasOwnerField(
final Iterable<OrderedNutrient> displayableNutrients,
) {
if (upToDateProduct.getOwnerFieldTimestamp(
OwnerField.productField(
ProductField.SERVING_SIZE,
ProductQuery.getLanguage(),
),
) !=
null) {
return true;
}
for (final OrderedNutrient orderedNutrient in displayableNutrients) {
final Nutrient nutrient = _getNutrient(orderedNutrient);
if (upToDateProduct.getOwnerFieldTimestamp(
OwnerField.nutrient(nutrient),
) !=
null) {
return true;
if (_ownerFieldBannerVisible) {
children.add(SizedBox(height: _ownerFieldBannerHeight));
}
} else {
_focusNodes.clear();
}
return false;
return children;
}

Widget _getServingField(final AppLocalizations appLocalizations) {
Expand All @@ -281,15 +305,7 @@ class _NutritionPageLoadedState extends State<NutritionPageLoaded>
decoration: InputDecoration(
enabledBorder: const UnderlineInputBorder(),
labelText: appLocalizations.nutrition_page_serving_size,
suffixIcon: widget.product.getOwnerFieldTimestamp(
OwnerField.productField(
ProductField.SERVING_SIZE,
ProductQuery.getLanguage(),
),
) ==
null
? null
: const OwnerFieldIcon(),
suffixIcon: _getServingFieldLeading(appLocalizations),
),
textInputAction: TextInputAction.next,
onFieldSubmitted: (_) {
Expand All @@ -310,6 +326,38 @@ class _NutritionPageLoadedState extends State<NutritionPageLoaded>
);
}

Widget? _getServingFieldLeading(final AppLocalizations appLocalizations) {
if (widget.product.getOwnerFieldTimestamp(OwnerField.productField(
ProductField.SERVING_SIZE,
ProductQuery.getLanguage(),
)) !=
null) {
return IconButton(
onPressed: () {
context.read<_NutritionPageLoadedState>().toggleOwnerFieldBanner();
},
icon: const OwnerFieldIcon(),
);
}

if (_servingController?.initialValue?.isEmpty == true &&
_servingController?.text.isEmpty == true &&
widget.product.quantity != null) {
return IconButton(
onPressed: () {
_servingController!.text = widget.product.quantity!;
},
icon: const icons.Milk.download(),
visualDensity: VisualDensity.compact,
enableFeedback: true,
tooltip: appLocalizations
.nutrition_page_take_serving_size_from_product_quantity,
);
}

return null;
}

Widget _getServingSwitch(final AppLocalizations appLocalizations) =>
IntrinsicHeight(
child: Row(
Expand Down Expand Up @@ -512,6 +560,12 @@ class _NutritionPageLoadedState extends State<NutritionPageLoaded>
);
return true;
}

void toggleOwnerFieldBanner() {
setState(() {
_ownerFieldBannerVisible = !_ownerFieldBannerVisible;
});
}
}

class _NutrientRow extends StatelessWidget {
Expand Down Expand Up @@ -604,7 +658,14 @@ class _NutrientValueCell extends StatelessWidget {
product.getOwnerFieldTimestamp(OwnerField.nutrient(nutrient)) ==
null
? null
: const OwnerFieldIcon(),
: IconButton(
onPressed: () {
context
.read<_NutritionPageLoadedState>()
.toggleOwnerFieldBanner();
},
icon: const OwnerFieldIcon(),
),
),
keyboardType: const TextInputType.numberWithOptions(
signed: false,
Expand Down
Loading

0 comments on commit 4cf7e0a

Please sign in to comment.