From cbd8a99c1ec091715352b342e8e2f65f15f686f7 Mon Sep 17 00:00:00 2001 From: Valimp Date: Thu, 14 Nov 2024 14:54:36 +0100 Subject: [PATCH 01/11] feat: Change UI of new product type cards --- .../smooth_app/assets/misc/logo_obf_half.svg | 14 + .../smooth_app/assets/misc/logo_off_half.svg | 18 + .../smooth_app/assets/misc/logo_opf_half.svg | 20 + .../smooth_app/assets/misc/logo_opff_half.svg | 23 + .../lib/helpers/analytics_helper.dart | 78 +-- .../lib/helpers/product_cards_helper.dart | 25 +- packages/smooth_app/lib/l10n/app_en.arb | 16 + .../lib/pages/navigator/app_navigator.dart | 2 +- .../pages/product/add_basic_details_page.dart | 2 +- .../add_new_product_page.dart | 19 +- .../add_new_product/add_new_product_type.dart | 231 +++++++++ .../pages/product/add_other_details_page.dart | 2 +- .../product/attribute_first_row_helper.dart | 2 +- .../pages/product/edit_new_packagings.dart | 2 +- .../pages/product/edit_ocr/edit_ocr_page.dart | 2 +- .../lib/pages/product/edit_product_page.dart | 8 +- .../product_image_gallery_view.dart | 2 +- .../pages/product/nutrition_page_loaded.dart | 2 +- .../pages/product/product_field_editor.dart | 8 +- .../product/product_incomplete_card.dart | 10 +- .../product_page/new_product_footer.dart | 469 ++++++++++++++++++ .../product/product_questions_widget.dart | 3 +- .../lib/pages/product/simple_input_page.dart | 4 +- .../lib/pages/product/summary_card.dart | 4 +- .../lib/query/barcode_product_query.dart | 4 +- 25 files changed, 861 insertions(+), 109 deletions(-) create mode 100644 packages/smooth_app/assets/misc/logo_obf_half.svg create mode 100644 packages/smooth_app/assets/misc/logo_off_half.svg create mode 100644 packages/smooth_app/assets/misc/logo_opf_half.svg create mode 100644 packages/smooth_app/assets/misc/logo_opff_half.svg rename packages/smooth_app/lib/pages/product/{ => add_new_product}/add_new_product_page.dart (98%) create mode 100644 packages/smooth_app/lib/pages/product/add_new_product/add_new_product_type.dart create mode 100644 packages/smooth_app/lib/pages/product/product_page/new_product_footer.dart diff --git a/packages/smooth_app/assets/misc/logo_obf_half.svg b/packages/smooth_app/assets/misc/logo_obf_half.svg new file mode 100644 index 000000000000..cf489b462d0e --- /dev/null +++ b/packages/smooth_app/assets/misc/logo_obf_half.svg @@ -0,0 +1,14 @@ + + + Group 3 + + + + + + + + + + + \ No newline at end of file diff --git a/packages/smooth_app/assets/misc/logo_off_half.svg b/packages/smooth_app/assets/misc/logo_off_half.svg new file mode 100644 index 000000000000..949a582801f2 --- /dev/null +++ b/packages/smooth_app/assets/misc/logo_off_half.svg @@ -0,0 +1,18 @@ + + + Group 4 + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/packages/smooth_app/assets/misc/logo_opf_half.svg b/packages/smooth_app/assets/misc/logo_opf_half.svg new file mode 100644 index 000000000000..4b748a28d9bc --- /dev/null +++ b/packages/smooth_app/assets/misc/logo_opf_half.svg @@ -0,0 +1,20 @@ + + + Group 2 + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/packages/smooth_app/assets/misc/logo_opff_half.svg b/packages/smooth_app/assets/misc/logo_opff_half.svg new file mode 100644 index 000000000000..8eb7df1bcee2 --- /dev/null +++ b/packages/smooth_app/assets/misc/logo_opff_half.svg @@ -0,0 +1,23 @@ + + + Group 5 + + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/packages/smooth_app/lib/helpers/analytics_helper.dart b/packages/smooth_app/lib/helpers/analytics_helper.dart index f299a979b4c9..56b885cecd5c 100644 --- a/packages/smooth_app/lib/helpers/analytics_helper.dart +++ b/packages/smooth_app/lib/helpers/analytics_helper.dart @@ -3,11 +3,9 @@ import 'dart:async'; import 'package:flutter/foundation.dart'; import 'package:matomo_tracker/matomo_tracker.dart'; import 'package:openfoodfacts/openfoodfacts.dart'; -import 'package:package_info_plus/package_info_plus.dart'; import 'package:sentry_flutter/sentry_flutter.dart'; import 'package:smooth_app/data_models/preferences/user_preferences.dart'; import 'package:smooth_app/helpers/global_vars.dart'; -import 'package:smooth_app/query/product_query.dart'; /// Category for Matomo Events enum AnalyticsCategory { @@ -277,12 +275,9 @@ class AnalyticsHelper { return event; } - static late PackageInfo _packageInfo; - static Future initMatomo( final bool screenshotMode, ) async { - _packageInfo = await PackageInfo.fromPlatform(); if (screenshotMode) { _setCrashReports(false); _setAnalyticsReports(false); @@ -328,11 +323,13 @@ class AnalyticsHelper { int? eventValue, String? barcode, }) => - trackCustomEvent( - msg.name, - msg.category.tag, - eventValue: eventValue, - barcode: barcode, + MatomoTracker.instance.trackEvent( + eventInfo: EventInfo( + name: msg.name, + category: msg.category.tag, + action: msg.name, + value: eventValue ?? _formatBarcode(barcode), + ), ); // Used by code which is outside of the core:smooth_app code @@ -342,51 +339,26 @@ class AnalyticsHelper { String category, { int? eventValue, String? barcode, - String? action, - ProductType? productType, - }) { - final Map dimensions = { - 'dimension1': ProductQuery.getLanguage().offTag, - 'dimension2': ProductQuery.getCountry().offTag, - 'dimension3': ProductQuery.isLoggedIn() ? 'Y' : 'N', - 'dimension4': _packageInfo.version, - 'dimension5': productType?.offTag ?? '', - }; - MatomoTracker.instance.trackEvent( - eventInfo: EventInfo( - name: msg, - category: category, - action: action ?? msg, - value: eventValue ?? _formatBarcode(barcode), - ), - dimensions: dimensions, - ); - } - - static void trackProductEdit( - AnalyticsEditEvents editEventName, - Product product, [ - bool saved = false, - ]) => - trackCustomEvent( - saved ? '${editEventName.name}-saved' : editEventName.name, - AnalyticsCategory.productEdit.tag, - action: editEventName.name, - barcode: product.barcode, - productType: product.productType ?? ProductType.food, + }) => + MatomoTracker.instance.trackEvent( + eventInfo: EventInfo( + name: msg, + category: category, + action: msg, + value: eventValue ?? _formatBarcode(barcode), + ), ); - static void trackProductEvent( - AnalyticsEvent msg, { - int? eventValue, - required Product product, - }) => - trackCustomEvent( - msg.name, - msg.category.tag, - eventValue: eventValue, - barcode: product.barcode, - productType: product.productType ?? ProductType.food, + static void trackProductEdit( + AnalyticsEditEvents editEventName, String barcode, + [bool saved = false]) => + MatomoTracker.instance.trackEvent( + eventInfo: EventInfo( + name: saved ? '${editEventName.name}-saved' : editEventName.name, + category: AnalyticsCategory.productEdit.tag, + action: editEventName.name, + value: _formatBarcode(barcode), + ), ); static void trackSearch({ diff --git a/packages/smooth_app/lib/helpers/product_cards_helper.dart b/packages/smooth_app/lib/helpers/product_cards_helper.dart index 496892713608..2284527b791b 100644 --- a/packages/smooth_app/lib/helpers/product_cards_helper.dart +++ b/packages/smooth_app/lib/helpers/product_cards_helper.dart @@ -416,9 +416,10 @@ List getRawProductImages( final Product product, final ImageSize imageSize, ) { + final List result = []; final List? rawImages = product.getRawImages(); if (rawImages == null) { - return []; + return result; } final Map map = {}; for (final ProductImage productImage in rawImages) { @@ -444,22 +445,10 @@ List getRawProductImages( } map[imageId] = productImage; } - final List result = List.of(map.values); - result.sort( - ( - final ProductImage a, - final ProductImage b, - ) { - final int result = (a.uploaded?.millisecondsSinceEpoch ?? 0).compareTo( - b.uploaded?.millisecondsSinceEpoch ?? 0, - ); - if (result != 0) { - return result; - } - return (int.tryParse(a.imgid ?? '0') ?? 0).compareTo( - int.tryParse(b.imgid ?? '0') ?? 0, - ); - }, - ); + final List sortedIds = List.of(map.keys); + sortedIds.sort(); + for (final int id in sortedIds) { + result.add(map[id]!); + } return result; } diff --git a/packages/smooth_app/lib/l10n/app_en.arb b/packages/smooth_app/lib/l10n/app_en.arb index 80ba6a19c9c6..518570bc06d1 100644 --- a/packages/smooth_app/lib/l10n/app_en.arb +++ b/packages/smooth_app/lib/l10n/app_en.arb @@ -3271,5 +3271,21 @@ "carousel_loading_text": "We are searching for it in our database of more than **3 million products!**", "@carousel_loading_text": { "description": "Please keep the ** syntax to make the text bold" + }, + "product_type_subtitle_food": "Vegetebles, fruits, frozen food...", + "@product_type_subtitle_food" : { + "description": "Exemple of products for food category" + }, + "product_type_subtitle_beauty": "Makeup, soap, toothpaste...", + "@product_type_subtitle_beauty" : { + "description": "Exemple of products for beauty category" + }, + "product_type_subtitle_pet_food": "Food for dogs, cats...", + "@product_type_subtitle_pet_food" : { + "description": "Exemple of products for pet food category" + }, + "product_type_subtitle_product": "Smartphone, furniture...", + "@product_type_subtitle_product" : { + "description": "Exemple of products for other categories" } } diff --git a/packages/smooth_app/lib/pages/navigator/app_navigator.dart b/packages/smooth_app/lib/pages/navigator/app_navigator.dart index 47d606a2bcda..4c13003a6cf2 100644 --- a/packages/smooth_app/lib/pages/navigator/app_navigator.dart +++ b/packages/smooth_app/lib/pages/navigator/app_navigator.dart @@ -13,7 +13,7 @@ import 'package:smooth_app/pages/navigator/error_page.dart'; import 'package:smooth_app/pages/navigator/external_page.dart'; import 'package:smooth_app/pages/onboarding/onboarding_flow_navigator.dart'; import 'package:smooth_app/pages/preferences/user_preferences_page.dart'; -import 'package:smooth_app/pages/product/add_new_product_page.dart'; +import 'package:smooth_app/pages/product/add_new_product/add_new_product_page.dart'; import 'package:smooth_app/pages/product/edit_product_page.dart'; import 'package:smooth_app/pages/product/product_loader_page.dart'; import 'package:smooth_app/pages/product/product_page/new_product_header.dart'; diff --git a/packages/smooth_app/lib/pages/product/add_basic_details_page.dart b/packages/smooth_app/lib/pages/product/add_basic_details_page.dart index 008d12e3d3a5..9c5944015e9d 100644 --- a/packages/smooth_app/lib/pages/product/add_basic_details_page.dart +++ b/packages/smooth_app/lib/pages/product/add_basic_details_page.dart @@ -316,7 +316,7 @@ class _AddBasicDetailsPageState extends State { AnalyticsHelper.trackProductEdit( AnalyticsEditEvents.basicDetails, - _product, + _product.barcode!, true, ); await BackgroundTaskDetails.addTask( diff --git a/packages/smooth_app/lib/pages/product/add_new_product_page.dart b/packages/smooth_app/lib/pages/product/add_new_product/add_new_product_page.dart similarity index 98% rename from packages/smooth_app/lib/pages/product/add_new_product_page.dart rename to packages/smooth_app/lib/pages/product/add_new_product/add_new_product_page.dart index 3a6491a32c46..6dedd9683979 100644 --- a/packages/smooth_app/lib/pages/product/add_new_product_page.dart +++ b/packages/smooth_app/lib/pages/product/add_new_product/add_new_product_page.dart @@ -19,6 +19,7 @@ import 'package:smooth_app/helpers/product_cards_helper.dart'; import 'package:smooth_app/pages/crop_parameters.dart'; import 'package:smooth_app/pages/image_crop_page.dart'; import 'package:smooth_app/pages/preferences/user_preferences_widgets.dart'; +import 'package:smooth_app/pages/product/add_new_product/add_new_product_type.dart'; import 'package:smooth_app/pages/product/add_new_product_helper.dart'; import 'package:smooth_app/pages/product/common/product_dialog_helper.dart'; import 'package:smooth_app/pages/product/nutrition_page_loaded.dart'; @@ -169,9 +170,9 @@ class _AddNewProductPageState extends State ), ]; _daoProductList = DaoProductList(localDatabase); - AnalyticsHelper.trackProductEvent( + AnalyticsHelper.trackEvent( widget.events[EditProductAction.openPage]!, - product: upToDateProduct, + barcode: barcode, ); _pageController.addListener(() => setState(() {})); } @@ -205,9 +206,9 @@ class _AddNewProductPageState extends State ), ); if (leaveThePage == true) { - AnalyticsHelper.trackProductEvent( + AnalyticsHelper.trackEvent( widget.events[EditProductAction.leaveEmpty]!, - product: upToDateProduct, + barcode: barcode, ); } return leaveThePage ?? false; @@ -576,18 +577,18 @@ class _AddNewProductPageState extends State for (final ProductType productType in ProductType.values) { rows.add( - RadioListTile( - title: Text(productType.getLabel(appLocalizations)), - onChanged: (ProductType? value) { + AddNewProductType( + productType: productType, + checked: productType == _inputProductType, + onChanged: (ProductType value) { if (value != null) { setState(() => _inputProductType = value); } }, - value: productType, - groupValue: _inputProductType, ), ); } + return rows; } diff --git a/packages/smooth_app/lib/pages/product/add_new_product/add_new_product_type.dart b/packages/smooth_app/lib/pages/product/add_new_product/add_new_product_type.dart new file mode 100644 index 000000000000..1c5686b645f8 --- /dev/null +++ b/packages/smooth_app/lib/pages/product/add_new_product/add_new_product_type.dart @@ -0,0 +1,231 @@ +import 'package:flutter/material.dart'; +import 'package:flutter_gen/gen_l10n/app_localizations.dart'; +import 'package:flutter_svg/flutter_svg.dart'; +import 'package:openfoodfacts/openfoodfacts.dart'; +import 'package:smooth_app/generic_lib/design_constants.dart'; +import 'package:smooth_app/resources/app_icons.dart' as icons; +import 'package:smooth_app/themes/smooth_theme_colors.dart'; +import 'package:smooth_app/themes/theme_provider.dart'; + +class AddNewProductType extends StatefulWidget { + const AddNewProductType({ + super.key, + required this.productType, + required this.checked, + required this.onChanged, + }); + + final ProductType productType; + final bool checked; + final void Function(ProductType) onChanged; + + @override + State createState() => _AddNewProductType(); +} + +class _AddNewProductType extends State + with TickerProviderStateMixin { + AnimationController? _controller; + // ignore: use_late_for_private_fields_and_variables + Animation? _colorAnimation; + // ignore: use_late_for_private_fields_and_variables + Animation? _opacityAnimation; + bool? _currentTheme; + + @override + void didUpdateWidget(AddNewProductType oldWidget) { + super.didUpdateWidget(oldWidget); + + if (oldWidget.checked != widget.checked) { + if (widget.checked) { + _controller?.forward(); + } else { + _controller?.reverse(); + } + } + } + + // To reset animations when theme changes + @override + void didChangeDependencies() { + super.didChangeDependencies(); + + if (_currentTheme != context.lightTheme()) { + _controller = null; + } + } + + @override + Widget build(BuildContext context) { + final AppLocalizations appLocalizations = AppLocalizations.of(context); + final bool lightTheme = context.lightTheme(); + _initAnimationsIfNecessary(lightTheme); + + final SmoothColorsThemeExtension theme = + Theme.of(context).extension()!; + + return Semantics( + label: + '${_title(appLocalizations, context)} ${_subtitle(appLocalizations, context)}', + checked: widget.checked, + excludeSemantics: true, + child: Container( + width: double.infinity, + decoration: BoxDecoration( + color: _colorAnimation!.value, + borderRadius: ANGULAR_BORDER_RADIUS, + border: Border.all( + color: lightTheme ? theme.primarySemiDark : theme.primaryNormal, + width: 2.0, + ), + ), + margin: const EdgeInsetsDirectional.only( + start: VERY_LARGE_SPACE, + end: VERY_LARGE_SPACE, + top: VERY_LARGE_SPACE, + ), + child: InkWell( + onTap: () => widget.onChanged(widget.productType), + child: Stack( + children: [ + PositionedDirectional( + bottom: 0.0, + end: 0.0, + child: ClipRRect( + borderRadius: BorderRadius.only( + bottomRight: Radius.circular(ANGULAR_RADIUS.x - 2.0), + ), + child: SvgPicture.asset( + _illustration, + width: 50.0, + ), + ), + ), + Padding( + padding: const EdgeInsetsDirectional.only( + top: LARGE_SPACE, + start: MEDIUM_SPACE, + ), + child: Row( + crossAxisAlignment: CrossAxisAlignment.start, + children: [ + Container( + width: 20.0, + height: 20.0, + decoration: BoxDecoration( + border: Border.all( + color: lightTheme + ? theme.primarySemiDark + : theme.primaryMedium, + ), + shape: BoxShape.circle, + ), + child: Opacity( + opacity: _opacityAnimation!.value, + child: const icons.Check( + size: 9.0, + ), + ), + ), + const SizedBox(width: MEDIUM_SPACE), + Expanded( + child: Padding( + padding: const EdgeInsetsDirectional.only( + bottom: LARGE_SPACE, + ), + child: Column( + crossAxisAlignment: CrossAxisAlignment.start, + children: [ + Text( + _title(appLocalizations, context), + overflow: TextOverflow.ellipsis, + maxLines: 1, + style: TextStyle( + fontWeight: FontWeight.bold, + color: lightTheme ? Colors.black : Colors.white, + ), + ), + const SizedBox(height: 0.0), + Text( + _subtitle(appLocalizations, context), + overflow: TextOverflow.ellipsis, + style: TextStyle( + color: lightTheme + ? Colors.black54 + : Colors.white54, + ), + ), + ], + ), + ), + ) + ], + ), + ), + ], + ), + ), + ), + ); + } + + void _initAnimationsIfNecessary(bool lightTheme) { + if (_controller != null) { + return; + } + + _currentTheme = lightTheme; + + _controller = AnimationController( + vsync: this, duration: const Duration(milliseconds: 300)) + ..addListener(() => setState(() {})); + + final ThemeData themeData = Theme.of(context); + + _colorAnimation = ColorTween( + begin: themeData.scaffoldBackgroundColor.withOpacity(0.0), + end: lightTheme + ? themeData.extension()!.primaryMedium + : themeData.extension()!.primarySemiDark, + ).animate( + CurvedAnimation(parent: _controller!, curve: Curves.fastOutSlowIn)); + + _opacityAnimation = Tween(begin: 0.0, end: 1.0).animate( + CurvedAnimation(parent: _controller!, curve: Curves.fastOutSlowIn)); + } + + String _title(AppLocalizations appLocalizations, final BuildContext context) { + return switch (widget.productType) { + ProductType.food => AppLocalizations.of(context).product_type_label_food, + ProductType.beauty => + AppLocalizations.of(context).product_type_label_beauty, + ProductType.petFood => + AppLocalizations.of(context).product_type_label_pet_food, + ProductType.product => + AppLocalizations.of(context).product_type_label_product, + }; + } + + String _subtitle( + AppLocalizations appLocalizations, final BuildContext context) { + return switch (widget.productType) { + ProductType.food => + AppLocalizations.of(context).product_type_subtitle_food, + ProductType.beauty => + AppLocalizations.of(context).product_type_subtitle_beauty, + ProductType.petFood => + AppLocalizations.of(context).product_type_subtitle_pet_food, + ProductType.product => + AppLocalizations.of(context).product_type_subtitle_product, + }; + } + + String get _illustration { + return switch (widget.productType) { + ProductType.food => 'assets/misc/logo_off_half.svg', + ProductType.beauty => 'assets/misc/logo_obf_half.svg', + ProductType.petFood => 'assets/misc/logo_opff_half.svg', + ProductType.product => 'assets/misc/logo_opf_half.svg', + }; + } +} diff --git a/packages/smooth_app/lib/pages/product/add_other_details_page.dart b/packages/smooth_app/lib/pages/product/add_other_details_page.dart index 1e3a2dbcaf83..5ff14cbb0047 100644 --- a/packages/smooth_app/lib/pages/product/add_other_details_page.dart +++ b/packages/smooth_app/lib/pages/product/add_other_details_page.dart @@ -134,7 +134,7 @@ class _AddOtherDetailsPageState extends State { AnalyticsHelper.trackProductEdit( AnalyticsEditEvents.otherDetails, - widget.product, + widget.product.barcode!, true, ); await BackgroundTaskDetails.addTask( diff --git a/packages/smooth_app/lib/pages/product/attribute_first_row_helper.dart b/packages/smooth_app/lib/pages/product/attribute_first_row_helper.dart index 43fcd25da83c..d4ea4d7b6c03 100644 --- a/packages/smooth_app/lib/pages/product/attribute_first_row_helper.dart +++ b/packages/smooth_app/lib/pages/product/attribute_first_row_helper.dart @@ -127,7 +127,7 @@ class AttributeFirstRowNutritionHelper extends AttributeFirstRowHelper { AnalyticsHelper.trackProductEdit( AnalyticsEditEvents.nutrition_Facts, - product, + product.barcode!, ); if (!context.mounted) { diff --git a/packages/smooth_app/lib/pages/product/edit_new_packagings.dart b/packages/smooth_app/lib/pages/product/edit_new_packagings.dart index 0985e7dfab59..430121914852 100644 --- a/packages/smooth_app/lib/pages/product/edit_new_packagings.dart +++ b/packages/smooth_app/lib/pages/product/edit_new_packagings.dart @@ -295,7 +295,7 @@ class _EditNewPackagingsState extends State AnalyticsHelper.trackProductEdit( AnalyticsEditEvents.packagingComponents, - upToDateProduct, + barcode, true, ); diff --git a/packages/smooth_app/lib/pages/product/edit_ocr/edit_ocr_page.dart b/packages/smooth_app/lib/pages/product/edit_ocr/edit_ocr_page.dart index ed1b03e43518..e5aebe01f2f9 100644 --- a/packages/smooth_app/lib/pages/product/edit_ocr/edit_ocr_page.dart +++ b/packages/smooth_app/lib/pages/product/edit_ocr/edit_ocr_page.dart @@ -120,7 +120,7 @@ class _EditOcrPageState extends State with UpToDateMixin { } AnalyticsHelper.trackProductEdit( _helper.getEditEventAnalyticsTag(), - upToDateProduct, + barcode, true, ); await BackgroundTaskDetails.addTask( diff --git a/packages/smooth_app/lib/pages/product/edit_product_page.dart b/packages/smooth_app/lib/pages/product/edit_product_page.dart index 01a7fc4a752d..53c8b82fe618 100644 --- a/packages/smooth_app/lib/pages/product/edit_product_page.dart +++ b/packages/smooth_app/lib/pages/product/edit_product_page.dart @@ -163,7 +163,7 @@ class _EditProductPageState extends State with UpToDateMixin { onTap: () async { AnalyticsHelper.trackProductEdit( AnalyticsEditEvents.photos, - upToDateProduct, + barcode, ); await Navigator.push( @@ -222,7 +222,7 @@ class _EditProductPageState extends State with UpToDateMixin { } AnalyticsHelper.trackProductEdit( AnalyticsEditEvents.nutrition_Facts, - upToDateProduct, + barcode, ); if (!context.mounted) { return; @@ -274,7 +274,7 @@ class _EditProductPageState extends State with UpToDateMixin { } AnalyticsHelper.trackProductEdit( AnalyticsEditEvents.otherDetails, - upToDateProduct, + barcode, ); await Navigator.push( context, @@ -346,7 +346,7 @@ class _EditProductPageState extends State with UpToDateMixin { } AnalyticsHelper.trackProductEdit( AnalyticsEditEvents.powerEditScreen, - upToDateProduct, + barcode, ); await Navigator.push( context, diff --git a/packages/smooth_app/lib/pages/product/gallery_view/product_image_gallery_view.dart b/packages/smooth_app/lib/pages/product/gallery_view/product_image_gallery_view.dart index 42cba79d23a0..5b2729d3d56a 100644 --- a/packages/smooth_app/lib/pages/product/gallery_view/product_image_gallery_view.dart +++ b/packages/smooth_app/lib/pages/product/gallery_view/product_image_gallery_view.dart @@ -48,7 +48,7 @@ class ProductImageGalleryView extends StatefulWidget { }) async { AnalyticsHelper.trackProductEdit( AnalyticsEditEvents.photos, - product, + product.barcode!, true, ); 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 a34997d4eb3a..70635a3dba10 100644 --- a/packages/smooth_app/lib/pages/product/nutrition_page_loaded.dart +++ b/packages/smooth_app/lib/pages/product/nutrition_page_loaded.dart @@ -501,7 +501,7 @@ class _NutritionPageLoadedState extends State AnalyticsHelper.trackProductEdit( AnalyticsEditEvents.nutrition_Facts, - upToDateProduct, + barcode, true, ); await BackgroundTaskDetails.addTask( diff --git a/packages/smooth_app/lib/pages/product/product_field_editor.dart b/packages/smooth_app/lib/pages/product/product_field_editor.dart index 7e53d2c16f46..55b7c4717e4b 100644 --- a/packages/smooth_app/lib/pages/product/product_field_editor.dart +++ b/packages/smooth_app/lib/pages/product/product_field_editor.dart @@ -59,7 +59,7 @@ class ProductFieldSimpleEditor extends ProductFieldEditor { AnalyticsHelper.trackProductEdit( helper.getAnalyticsEditEvent(), - product, + product.barcode!, ); if (!context.mounted) { @@ -107,7 +107,7 @@ class ProductFieldDetailsEditor extends ProductFieldEditor { AnalyticsHelper.trackProductEdit( AnalyticsEditEvents.basicDetails, - product, + product.barcode!, ); if (!context.mounted) { @@ -149,7 +149,7 @@ class ProductFieldPackagingEditor extends ProductFieldEditor { AnalyticsHelper.trackProductEdit( AnalyticsEditEvents.packagingComponents, - product, + product.barcode!, ); if (!context.mounted) { @@ -213,7 +213,7 @@ abstract class ProductFieldOcrEditor extends ProductFieldEditor { AnalyticsHelper.trackProductEdit( helper.getEditEventAnalyticsTag(), - product, + product.barcode!, ); if (!context.mounted) { diff --git a/packages/smooth_app/lib/pages/product/product_incomplete_card.dart b/packages/smooth_app/lib/pages/product/product_incomplete_card.dart index af583b871957..42a3b85b8168 100644 --- a/packages/smooth_app/lib/pages/product/product_incomplete_card.dart +++ b/packages/smooth_app/lib/pages/product/product_incomplete_card.dart @@ -3,7 +3,7 @@ import 'package:flutter_gen/gen_l10n/app_localizations.dart'; import 'package:openfoodfacts/openfoodfacts.dart'; import 'package:smooth_app/generic_lib/design_constants.dart'; import 'package:smooth_app/helpers/analytics_helper.dart'; -import 'package:smooth_app/pages/product/add_new_product_page.dart'; +import 'package:smooth_app/pages/product/add_new_product/add_new_product_page.dart'; import 'package:smooth_app/pages/product/product_field_editor.dart'; import 'package:smooth_app/pages/product/simple_input_page_helpers.dart'; import 'package:smooth_app/query/product_query.dart'; @@ -28,16 +28,16 @@ class ProductIncompleteCard extends StatelessWidget { } bool checkScores = true; if (_isNutriscoreNotApplicable(product)) { - AnalyticsHelper.trackProductEvent( + AnalyticsHelper.trackEvent( AnalyticsEvent.notShowFastTrackProductEditCardNutriscore, - product: product, + barcode: product.barcode, ); checkScores = false; } if (_isEcoscoreNotApplicable(product)) { - AnalyticsHelper.trackProductEvent( + AnalyticsHelper.trackEvent( AnalyticsEvent.notShowFastTrackProductEditCardEcoscore, - product: product, + barcode: product.barcode, ); checkScores = false; } diff --git a/packages/smooth_app/lib/pages/product/product_page/new_product_footer.dart b/packages/smooth_app/lib/pages/product/product_page/new_product_footer.dart new file mode 100644 index 000000000000..0b7357be13da --- /dev/null +++ b/packages/smooth_app/lib/pages/product/product_page/new_product_footer.dart @@ -0,0 +1,469 @@ +import 'dart:io'; + +import 'package:flutter/material.dart'; +import 'package:flutter_gen/gen_l10n/app_localizations.dart'; +import 'package:openfoodfacts/openfoodfacts.dart'; +import 'package:provider/provider.dart'; +import 'package:share_plus/share_plus.dart'; +import 'package:smooth_app/data_models/preferences/user_preferences.dart'; +import 'package:smooth_app/database/local_database.dart'; +import 'package:smooth_app/generic_lib/bottom_sheets/smooth_bottom_sheet.dart'; +import 'package:smooth_app/generic_lib/design_constants.dart'; +import 'package:smooth_app/generic_lib/widgets/smooth_snackbar.dart'; +import 'package:smooth_app/helpers/analytics_helper.dart'; +import 'package:smooth_app/helpers/provider_helper.dart'; +import 'package:smooth_app/pages/prices/price_meta_product.dart'; +import 'package:smooth_app/pages/prices/product_price_add_page.dart'; +import 'package:smooth_app/pages/product/common/product_query_page_helper.dart'; +import 'package:smooth_app/pages/product/edit_product_page.dart'; +import 'package:smooth_app/pages/product/product_list_helper.dart'; +import 'package:smooth_app/pages/product/product_page/new_product_page.dart'; +import 'package:smooth_app/query/category_product_query.dart'; +import 'package:smooth_app/query/product_query.dart'; +import 'package:smooth_app/resources/app_icons.dart' as icons; +import 'package:smooth_app/themes/smooth_theme_colors.dart'; +import 'package:smooth_app/themes/theme_provider.dart'; + +class ProductFooter extends StatelessWidget { + const ProductFooter({super.key}); + + static const double kHeight = 48.0; + + @override + Widget build(BuildContext context) { + return DecoratedBox( + decoration: BoxDecoration( + color: Theme.of(context).scaffoldBackgroundColor, + boxShadow: [ + BoxShadow( + color: Theme.of(context) + .shadowColor + .withOpacity(context.lightTheme() ? 0.25 : 0.6), + blurRadius: 10.0, + ), + ], + ), + child: const _ProductFooterButtonsBar(), + ); + } +} + +class _ProductFooterButtonsBar extends StatelessWidget { + const _ProductFooterButtonsBar(); + + @override + Widget build(BuildContext context) { + final SmoothColorsThemeExtension themeExtension = + Theme.of(context).extension()!; + + double bottomPadding = MediaQuery.viewPaddingOf(context).bottom; + // Add an extra padding (for Android) + if (Platform.isAndroid) { + bottomPadding += MEDIUM_SPACE; + } + + return SizedBox( + height: ProductFooter.kHeight + LARGE_SPACE + bottomPadding, + child: OutlinedButtonTheme( + data: OutlinedButtonThemeData( + style: OutlinedButton.styleFrom( + shape: RoundedRectangleBorder( + borderRadius: BorderRadius.circular(20.0), + ), + side: BorderSide(color: themeExtension.greyLight), + padding: const EdgeInsetsDirectional.symmetric( + horizontal: 19.0, + ), + ), + ), + child: ListView( + padding: EdgeInsetsDirectional.only( + start: SMALL_SPACE, + end: SMALL_SPACE, + top: LARGE_SPACE, + bottom: bottomPadding, + ), + scrollDirection: Axis.horizontal, + children: const [ + SizedBox(width: 10.0), + _ProductAddPriceButton(), + SizedBox(width: 10.0), + _ProductEditButton(), + SizedBox(width: 10.0), + _ProductCompareButton(), + SizedBox(width: 10.0), + _ProductAddToListButton(), + SizedBox(width: 10.0), + _ProductShareButton(), + ], + ), + ), + ); + } +} + +class _ProductAddToListButton extends StatelessWidget { + const _ProductAddToListButton(); + + @override + Widget build(BuildContext context) { + final AppLocalizations appLocalizations = AppLocalizations.of(context); + + return _ProductFooterOutlinedButton( + label: appLocalizations.user_list_button_add_product, + icon: const icons.AddToList(), + onTap: () => _editList(context, context.read()), + ); + } + + Future _editList(BuildContext context, Product product) async { + final AppLocalizations appLocalizations = AppLocalizations.of(context); + + showSmoothDraggableModalSheet( + context: context, + header: SmoothModalSheetHeader( + prefix: const SmoothModalSheetHeaderPrefixIndicator(), + title: appLocalizations.user_list_title, + suffix: const SmoothModalSheetHeaderCloseButton(), + ), + bodyBuilder: (BuildContext context) => AddProductToListContainer( + barcode: product.barcode!, + ), + ); + + return true; + } +} + +class _ProductAddPriceButton extends StatelessWidget { + const _ProductAddPriceButton(); + + @override + Widget build(BuildContext context) { + final AppLocalizations appLocalizations = AppLocalizations.of(context); + + return ConsumerFilter( + buildWhen: (UserPreferences? previous, UserPreferences current) => + previous?.userCurrencyCode != current.userCurrencyCode, + builder: (BuildContext context, UserPreferences userPreferences, _) { + final Currency currency = Currency.values.firstWhere( + (Currency currency) => + currency.name == userPreferences.userCurrencyCode, + orElse: () => Currency.USD, + ); + + return _ProductFooterFilledButton( + label: appLocalizations.prices_add_a_price, + icon: icons.AddPrice(currency), + onTap: () => _addAPrice(context, context.read()), + ); + }, + ); + } + + Future _addAPrice(BuildContext context, Product product) { + return ProductPriceAddPage.showProductPage( + context: context, + product: PriceMetaProduct.product(context.read()), + proofType: ProofType.priceTag, + ); + } +} + +class _ProductEditButton extends StatelessWidget { + const _ProductEditButton(); + + @override + Widget build(BuildContext context) { + final AppLocalizations appLocalizations = AppLocalizations.of(context); + + return _ProductFooterOutlinedButton( + label: appLocalizations.edit_product_label_short, + semanticsLabel: appLocalizations.edit_product_label, + icon: const icons.Edit(), + onTap: () => _editProduct(context, context.read()), + ); + } + + Future _editProduct(BuildContext context, Product product) async { + ProductPageState.of(context).stopRobotoffQuestion(); + + AnalyticsHelper.trackEvent( + AnalyticsEvent.openProductEditPage, + barcode: product.barcode, + ); + + await Navigator.push( + context, + MaterialPageRoute( + builder: (BuildContext context) => EditProductPage(product), + ), + ); + + if (context.mounted) { + ProductPageState.of(context).startRobotoffQuestion(); + } + } +} + +class _ProductCompareButton extends StatelessWidget { + const _ProductCompareButton(); + + @override + Widget build(BuildContext context) { + final AppLocalizations appLocalizations = AppLocalizations.of(context); + final Product product = context.read(); + + const Set blackListedCategories = { + 'fr:vegan', + }; + String? categoryTag; + String? categoryLabel; + final List? labels = + product.categoriesTagsInLanguages?[ProductQuery.getLanguage()]; + final List? tags = product.categoriesTags; + if (tags != null && + labels != null && + tags.isNotEmpty && + tags.length == labels.length) { + categoryTag = product.comparedToCategory; + if (categoryTag != null) { + for (int i = 0; i < tags.length; i++) { + if (categoryTag == tags[i]) { + categoryLabel = labels[i]; + break; + } + } + } + if (categoryLabel == null || + blackListedCategories.contains(categoryTag)) { + // fallback algorithm + int index = tags.length - 1; + // cf. https://github.com/openfoodfacts/openfoodfacts-dart/pull/474 + // looking for the most detailed non blacklisted category + categoryTag = tags[index]; + categoryLabel = labels[index]; + while (blackListedCategories.contains(categoryTag) && index > 0) { + index--; + categoryTag = tags[index]; + categoryLabel = labels[index]; + } + } + } + + final bool enabled = categoryTag != null && categoryLabel != null; + + return _ProductFooterOutlinedButton( + label: appLocalizations.product_search_same_category_short, + semanticsLabel: appLocalizations.product_search_same_category, + icon: const icons.Compare(), + enabled: enabled, + onTap: () => enabled + ? _compareProduct( + context: context, + product: product, + categoryLabel: categoryLabel!, + categoryTag: categoryTag!, + ) + : _showFeatureDisabledDialog(context), + ); + } + + Future _compareProduct({ + required BuildContext context, + required Product product, + required String categoryLabel, + required String categoryTag, + }) { + return ProductQueryPageHelper.openBestChoice( + name: categoryLabel, + localDatabase: context.read(), + productQuery: CategoryProductQuery( + categoryTag, + productType: product.productType ?? ProductType.food, + ), + context: context, + searchResult: false, + ); + } + + void _showFeatureDisabledDialog(BuildContext context) { + final AppLocalizations appLocalizations = AppLocalizations.of(context); + final ThemeData themeData = Theme.of(context); + + ScaffoldMessenger.of(context).showSnackBar( + SmoothFloatingSnackbar( + content: Row( + children: [ + const ExcludeSemantics( + child: icons.Warning( + color: Colors.white, + ), + ), + const SizedBox(width: LARGE_SPACE), + Expanded( + child: + Text(appLocalizations.product_search_same_category_error)), + ], + ), + backgroundColor: themeData.extension()!.red, + action: SnackBarAction( + label: appLocalizations.okay, + onPressed: () { + ScaffoldMessenger.of(context).hideCurrentSnackBar(); + }, + ), + ), + ); + } +} + +class _ProductShareButton extends StatelessWidget { + const _ProductShareButton(); + + @override + Widget build(BuildContext context) { + final AppLocalizations appLocalizations = AppLocalizations.of(context); + + return _ProductFooterOutlinedButton( + label: appLocalizations.share, + icon: icons.Share(), + onTap: () => _shareProduct(context, context.read()), + ); + } + + Future _shareProduct(BuildContext context, Product product) async { + final ProductType productType = product.productType ?? ProductType.food; + AnalyticsHelper.trackEvent( + AnalyticsEvent.shareProduct, + barcode: product.barcode, + ); + final AppLocalizations appLocalizations = AppLocalizations.of(context); + // We need to provide a sharePositionOrigin to make the plugin work on ipad + final RenderBox? box = context.findRenderObject() as RenderBox?; + final String url = 'https://' + '${ProductQuery.getCountry().offTag}.${productType.getDomain()}.org' + '/product/${product.barcode}'; + Share.share( + productType.getShareProductLabel(appLocalizations, url), + sharePositionOrigin: + box == null ? null : box.localToGlobal(Offset.zero) & box.size, + ); + } +} + +class _ProductFooterFilledButton extends StatelessWidget { + const _ProductFooterFilledButton({ + required this.label, + required this.icon, + required this.onTap, + // ignore: unused_element + this.semanticsLabel, + }); + + final String label; + final String? semanticsLabel; + final icons.AppIcon icon; + final VoidCallback onTap; + + @override + Widget build(BuildContext context) { + final SmoothColorsThemeExtension themeExtension = + Theme.of(context).extension()!; + final ProductPageCompatibility compatibility = + context.watch(); + + return Semantics( + excludeSemantics: true, + button: true, + label: semanticsLabel, + child: OutlinedButton( + onPressed: onTap, + style: OutlinedButton.styleFrom( + foregroundColor: Colors.white, + backgroundColor: compatibility.color ?? + (context.lightTheme() + ? themeExtension.primaryBlack + : themeExtension.primarySemiDark), + side: BorderSide.none, + ), + child: Row( + crossAxisAlignment: CrossAxisAlignment.center, + children: [ + IconTheme( + data: const IconThemeData( + color: Colors.white, + size: 18.0, + ), + child: icon, + ), + const SizedBox(width: 8.0), + Text( + label, + style: const TextStyle( + fontWeight: FontWeight.bold, + ), + ), + ], + ), + ), + ); + } +} + +class _ProductFooterOutlinedButton extends StatelessWidget { + const _ProductFooterOutlinedButton({ + required this.label, + required this.icon, + required this.onTap, + this.enabled = true, + this.semanticsLabel, + }); + + final String label; + final String? semanticsLabel; + final icons.AppIcon icon; + final VoidCallback onTap; + final bool enabled; + + @override + Widget build(BuildContext context) { + final SmoothColorsThemeExtension themeExtension = + Theme.of(context).extension()!; + final Color contentColor = + context.lightTheme() ? themeExtension.primaryBlack : Colors.white; + final Color mainColor = + enabled ? contentColor : contentColor.withOpacity(0.5); + + return Semantics( + label: semanticsLabel, + excludeSemantics: true, + button: true, + child: OutlinedButton( + onPressed: onTap, + style: OutlinedButton.styleFrom( + foregroundColor: mainColor, + backgroundColor: Colors.transparent, + ), + child: Row( + children: [ + IconTheme( + data: IconThemeData( + color: mainColor, + size: 18.0, + ), + child: icon, + ), + const SizedBox(width: 8.0), + Text( + label, + style: const TextStyle( + fontWeight: FontWeight.bold, + ), + ), + ], + ), + ), + ); + } +} diff --git a/packages/smooth_app/lib/pages/product/product_questions_widget.dart b/packages/smooth_app/lib/pages/product/product_questions_widget.dart index 3d30abee5ad3..fe0a55fc846e 100644 --- a/packages/smooth_app/lib/pages/product/product_questions_widget.dart +++ b/packages/smooth_app/lib/pages/product/product_questions_widget.dart @@ -122,13 +122,12 @@ class _ProductQuestionsWidgetState extends State } } - void _trackEvent(AnalyticsEvent event) => AnalyticsHelper.trackProductEvent( + void _trackEvent(AnalyticsEvent event) => AnalyticsHelper.trackEvent( event, eventValue: switch (widget.layout) { ProductQuestionsLayout.button => 0, ProductQuestionsLayout.banner => 1, }, - product: widget.product, ); Future?> _loadProductQuestions() async { diff --git a/packages/smooth_app/lib/pages/product/simple_input_page.dart b/packages/smooth_app/lib/pages/product/simple_input_page.dart index 1ed7108e5170..c554f820adfa 100644 --- a/packages/smooth_app/lib/pages/product/simple_input_page.dart +++ b/packages/smooth_app/lib/pages/product/simple_input_page.dart @@ -194,13 +194,13 @@ class _SimpleInputPageState extends State { if (widget.helpers.length > 1) { AnalyticsHelper.trackProductEdit( AnalyticsEditEvents.powerEditScreen, - widget.product, + widget.product.barcode!, true, ); } else { AnalyticsHelper.trackProductEdit( widget.helpers[0].getAnalyticsEditEvent(), - widget.product, + widget.product.barcode!, true, ); } diff --git a/packages/smooth_app/lib/pages/product/summary_card.dart b/packages/smooth_app/lib/pages/product/summary_card.dart index 6d3a333c866d..a69c1afd2bda 100644 --- a/packages/smooth_app/lib/pages/product/summary_card.dart +++ b/packages/smooth_app/lib/pages/product/summary_card.dart @@ -111,9 +111,9 @@ class _SummaryCardState extends State with UpToDateMixin { initUpToDate(widget._product, context.read()); _questionsLayout = getUserQuestionsLayout(context.read()); if (ProductIncompleteCard.isProductIncomplete(upToDateProduct)) { - AnalyticsHelper.trackProductEvent( + AnalyticsHelper.trackEvent( AnalyticsEvent.showFastTrackProductEditCard, - product: widget._product, + barcode: barcode, ); } } diff --git a/packages/smooth_app/lib/query/barcode_product_query.dart b/packages/smooth_app/lib/query/barcode_product_query.dart index 88904ed1c255..c17a804aa34a 100644 --- a/packages/smooth_app/lib/query/barcode_product_query.dart +++ b/packages/smooth_app/lib/query/barcode_product_query.dart @@ -27,9 +27,9 @@ class BarcodeProductQuery { ProductQuery.setUserAgentComment(''); if (fetchedProduct.product != null) { if (fetchedProduct.product!.obsolete == true) { - AnalyticsHelper.trackProductEvent( + AnalyticsHelper.trackEvent( AnalyticsEvent.obsoleteProduct, - product: fetchedProduct.product!, + barcode: barcode, ); } return fetchedProduct; From 6f0d6dd5ef268845f737265714b3b59f90134f3f Mon Sep 17 00:00:00 2001 From: Valimp Date: Thu, 14 Nov 2024 16:19:04 +0100 Subject: [PATCH 02/11] fix: revert files from previous commit --- .../lib/helpers/analytics_helper.dart | 78 +++++++++++++------ .../pages/product/add_basic_details_page.dart | 2 +- .../pages/product/add_other_details_page.dart | 2 +- .../product/attribute_first_row_helper.dart | 2 +- .../pages/product/edit_new_packagings.dart | 2 +- .../pages/product/edit_ocr/edit_ocr_page.dart | 2 +- .../lib/pages/product/edit_product_page.dart | 8 +- .../product_image_gallery_view.dart | 2 +- .../pages/product/nutrition_page_loaded.dart | 2 +- .../pages/product/product_field_editor.dart | 8 +- .../product/product_incomplete_card.dart | 8 +- .../product_page/new_product_footer.dart | 8 +- .../product/product_questions_widget.dart | 3 +- .../lib/pages/product/simple_input_page.dart | 4 +- .../lib/pages/product/summary_card.dart | 4 +- .../lib/query/barcode_product_query.dart | 4 +- 16 files changed, 84 insertions(+), 55 deletions(-) diff --git a/packages/smooth_app/lib/helpers/analytics_helper.dart b/packages/smooth_app/lib/helpers/analytics_helper.dart index 56b885cecd5c..f299a979b4c9 100644 --- a/packages/smooth_app/lib/helpers/analytics_helper.dart +++ b/packages/smooth_app/lib/helpers/analytics_helper.dart @@ -3,9 +3,11 @@ import 'dart:async'; import 'package:flutter/foundation.dart'; import 'package:matomo_tracker/matomo_tracker.dart'; import 'package:openfoodfacts/openfoodfacts.dart'; +import 'package:package_info_plus/package_info_plus.dart'; import 'package:sentry_flutter/sentry_flutter.dart'; import 'package:smooth_app/data_models/preferences/user_preferences.dart'; import 'package:smooth_app/helpers/global_vars.dart'; +import 'package:smooth_app/query/product_query.dart'; /// Category for Matomo Events enum AnalyticsCategory { @@ -275,9 +277,12 @@ class AnalyticsHelper { return event; } + static late PackageInfo _packageInfo; + static Future initMatomo( final bool screenshotMode, ) async { + _packageInfo = await PackageInfo.fromPlatform(); if (screenshotMode) { _setCrashReports(false); _setAnalyticsReports(false); @@ -323,13 +328,11 @@ class AnalyticsHelper { int? eventValue, String? barcode, }) => - MatomoTracker.instance.trackEvent( - eventInfo: EventInfo( - name: msg.name, - category: msg.category.tag, - action: msg.name, - value: eventValue ?? _formatBarcode(barcode), - ), + trackCustomEvent( + msg.name, + msg.category.tag, + eventValue: eventValue, + barcode: barcode, ); // Used by code which is outside of the core:smooth_app code @@ -339,26 +342,51 @@ class AnalyticsHelper { String category, { int? eventValue, String? barcode, - }) => - MatomoTracker.instance.trackEvent( - eventInfo: EventInfo( - name: msg, - category: category, - action: msg, - value: eventValue ?? _formatBarcode(barcode), - ), - ); + String? action, + ProductType? productType, + }) { + final Map dimensions = { + 'dimension1': ProductQuery.getLanguage().offTag, + 'dimension2': ProductQuery.getCountry().offTag, + 'dimension3': ProductQuery.isLoggedIn() ? 'Y' : 'N', + 'dimension4': _packageInfo.version, + 'dimension5': productType?.offTag ?? '', + }; + MatomoTracker.instance.trackEvent( + eventInfo: EventInfo( + name: msg, + category: category, + action: action ?? msg, + value: eventValue ?? _formatBarcode(barcode), + ), + dimensions: dimensions, + ); + } static void trackProductEdit( - AnalyticsEditEvents editEventName, String barcode, - [bool saved = false]) => - MatomoTracker.instance.trackEvent( - eventInfo: EventInfo( - name: saved ? '${editEventName.name}-saved' : editEventName.name, - category: AnalyticsCategory.productEdit.tag, - action: editEventName.name, - value: _formatBarcode(barcode), - ), + AnalyticsEditEvents editEventName, + Product product, [ + bool saved = false, + ]) => + trackCustomEvent( + saved ? '${editEventName.name}-saved' : editEventName.name, + AnalyticsCategory.productEdit.tag, + action: editEventName.name, + barcode: product.barcode, + productType: product.productType ?? ProductType.food, + ); + + static void trackProductEvent( + AnalyticsEvent msg, { + int? eventValue, + required Product product, + }) => + trackCustomEvent( + msg.name, + msg.category.tag, + eventValue: eventValue, + barcode: product.barcode, + productType: product.productType ?? ProductType.food, ); static void trackSearch({ diff --git a/packages/smooth_app/lib/pages/product/add_basic_details_page.dart b/packages/smooth_app/lib/pages/product/add_basic_details_page.dart index 9c5944015e9d..008d12e3d3a5 100644 --- a/packages/smooth_app/lib/pages/product/add_basic_details_page.dart +++ b/packages/smooth_app/lib/pages/product/add_basic_details_page.dart @@ -316,7 +316,7 @@ class _AddBasicDetailsPageState extends State { AnalyticsHelper.trackProductEdit( AnalyticsEditEvents.basicDetails, - _product.barcode!, + _product, true, ); await BackgroundTaskDetails.addTask( diff --git a/packages/smooth_app/lib/pages/product/add_other_details_page.dart b/packages/smooth_app/lib/pages/product/add_other_details_page.dart index 5ff14cbb0047..1e3a2dbcaf83 100644 --- a/packages/smooth_app/lib/pages/product/add_other_details_page.dart +++ b/packages/smooth_app/lib/pages/product/add_other_details_page.dart @@ -134,7 +134,7 @@ class _AddOtherDetailsPageState extends State { AnalyticsHelper.trackProductEdit( AnalyticsEditEvents.otherDetails, - widget.product.barcode!, + widget.product, true, ); await BackgroundTaskDetails.addTask( diff --git a/packages/smooth_app/lib/pages/product/attribute_first_row_helper.dart b/packages/smooth_app/lib/pages/product/attribute_first_row_helper.dart index d4ea4d7b6c03..43fcd25da83c 100644 --- a/packages/smooth_app/lib/pages/product/attribute_first_row_helper.dart +++ b/packages/smooth_app/lib/pages/product/attribute_first_row_helper.dart @@ -127,7 +127,7 @@ class AttributeFirstRowNutritionHelper extends AttributeFirstRowHelper { AnalyticsHelper.trackProductEdit( AnalyticsEditEvents.nutrition_Facts, - product.barcode!, + product, ); if (!context.mounted) { diff --git a/packages/smooth_app/lib/pages/product/edit_new_packagings.dart b/packages/smooth_app/lib/pages/product/edit_new_packagings.dart index 430121914852..0985e7dfab59 100644 --- a/packages/smooth_app/lib/pages/product/edit_new_packagings.dart +++ b/packages/smooth_app/lib/pages/product/edit_new_packagings.dart @@ -295,7 +295,7 @@ class _EditNewPackagingsState extends State AnalyticsHelper.trackProductEdit( AnalyticsEditEvents.packagingComponents, - barcode, + upToDateProduct, true, ); diff --git a/packages/smooth_app/lib/pages/product/edit_ocr/edit_ocr_page.dart b/packages/smooth_app/lib/pages/product/edit_ocr/edit_ocr_page.dart index e5aebe01f2f9..ed1b03e43518 100644 --- a/packages/smooth_app/lib/pages/product/edit_ocr/edit_ocr_page.dart +++ b/packages/smooth_app/lib/pages/product/edit_ocr/edit_ocr_page.dart @@ -120,7 +120,7 @@ class _EditOcrPageState extends State with UpToDateMixin { } AnalyticsHelper.trackProductEdit( _helper.getEditEventAnalyticsTag(), - barcode, + upToDateProduct, true, ); await BackgroundTaskDetails.addTask( diff --git a/packages/smooth_app/lib/pages/product/edit_product_page.dart b/packages/smooth_app/lib/pages/product/edit_product_page.dart index 53c8b82fe618..01a7fc4a752d 100644 --- a/packages/smooth_app/lib/pages/product/edit_product_page.dart +++ b/packages/smooth_app/lib/pages/product/edit_product_page.dart @@ -163,7 +163,7 @@ class _EditProductPageState extends State with UpToDateMixin { onTap: () async { AnalyticsHelper.trackProductEdit( AnalyticsEditEvents.photos, - barcode, + upToDateProduct, ); await Navigator.push( @@ -222,7 +222,7 @@ class _EditProductPageState extends State with UpToDateMixin { } AnalyticsHelper.trackProductEdit( AnalyticsEditEvents.nutrition_Facts, - barcode, + upToDateProduct, ); if (!context.mounted) { return; @@ -274,7 +274,7 @@ class _EditProductPageState extends State with UpToDateMixin { } AnalyticsHelper.trackProductEdit( AnalyticsEditEvents.otherDetails, - barcode, + upToDateProduct, ); await Navigator.push( context, @@ -346,7 +346,7 @@ class _EditProductPageState extends State with UpToDateMixin { } AnalyticsHelper.trackProductEdit( AnalyticsEditEvents.powerEditScreen, - barcode, + upToDateProduct, ); await Navigator.push( context, diff --git a/packages/smooth_app/lib/pages/product/gallery_view/product_image_gallery_view.dart b/packages/smooth_app/lib/pages/product/gallery_view/product_image_gallery_view.dart index 5b2729d3d56a..42cba79d23a0 100644 --- a/packages/smooth_app/lib/pages/product/gallery_view/product_image_gallery_view.dart +++ b/packages/smooth_app/lib/pages/product/gallery_view/product_image_gallery_view.dart @@ -48,7 +48,7 @@ class ProductImageGalleryView extends StatefulWidget { }) async { AnalyticsHelper.trackProductEdit( AnalyticsEditEvents.photos, - product.barcode!, + product, true, ); 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 70635a3dba10..a34997d4eb3a 100644 --- a/packages/smooth_app/lib/pages/product/nutrition_page_loaded.dart +++ b/packages/smooth_app/lib/pages/product/nutrition_page_loaded.dart @@ -501,7 +501,7 @@ class _NutritionPageLoadedState extends State AnalyticsHelper.trackProductEdit( AnalyticsEditEvents.nutrition_Facts, - barcode, + upToDateProduct, true, ); await BackgroundTaskDetails.addTask( diff --git a/packages/smooth_app/lib/pages/product/product_field_editor.dart b/packages/smooth_app/lib/pages/product/product_field_editor.dart index 55b7c4717e4b..7e53d2c16f46 100644 --- a/packages/smooth_app/lib/pages/product/product_field_editor.dart +++ b/packages/smooth_app/lib/pages/product/product_field_editor.dart @@ -59,7 +59,7 @@ class ProductFieldSimpleEditor extends ProductFieldEditor { AnalyticsHelper.trackProductEdit( helper.getAnalyticsEditEvent(), - product.barcode!, + product, ); if (!context.mounted) { @@ -107,7 +107,7 @@ class ProductFieldDetailsEditor extends ProductFieldEditor { AnalyticsHelper.trackProductEdit( AnalyticsEditEvents.basicDetails, - product.barcode!, + product, ); if (!context.mounted) { @@ -149,7 +149,7 @@ class ProductFieldPackagingEditor extends ProductFieldEditor { AnalyticsHelper.trackProductEdit( AnalyticsEditEvents.packagingComponents, - product.barcode!, + product, ); if (!context.mounted) { @@ -213,7 +213,7 @@ abstract class ProductFieldOcrEditor extends ProductFieldEditor { AnalyticsHelper.trackProductEdit( helper.getEditEventAnalyticsTag(), - product.barcode!, + product, ); if (!context.mounted) { diff --git a/packages/smooth_app/lib/pages/product/product_incomplete_card.dart b/packages/smooth_app/lib/pages/product/product_incomplete_card.dart index 42a3b85b8168..6b81cc0e1efb 100644 --- a/packages/smooth_app/lib/pages/product/product_incomplete_card.dart +++ b/packages/smooth_app/lib/pages/product/product_incomplete_card.dart @@ -28,16 +28,16 @@ class ProductIncompleteCard extends StatelessWidget { } bool checkScores = true; if (_isNutriscoreNotApplicable(product)) { - AnalyticsHelper.trackEvent( + AnalyticsHelper.trackProductEvent( AnalyticsEvent.notShowFastTrackProductEditCardNutriscore, - barcode: product.barcode, + product: product, ); checkScores = false; } if (_isEcoscoreNotApplicable(product)) { - AnalyticsHelper.trackEvent( + AnalyticsHelper.trackProductEvent( AnalyticsEvent.notShowFastTrackProductEditCardEcoscore, - barcode: product.barcode, + product: product, ); checkScores = false; } diff --git a/packages/smooth_app/lib/pages/product/product_page/new_product_footer.dart b/packages/smooth_app/lib/pages/product/product_page/new_product_footer.dart index 0b7357be13da..2e55bcac5748 100644 --- a/packages/smooth_app/lib/pages/product/product_page/new_product_footer.dart +++ b/packages/smooth_app/lib/pages/product/product_page/new_product_footer.dart @@ -188,9 +188,9 @@ class _ProductEditButton extends StatelessWidget { Future _editProduct(BuildContext context, Product product) async { ProductPageState.of(context).stopRobotoffQuestion(); - AnalyticsHelper.trackEvent( + AnalyticsHelper.trackProductEvent( AnalyticsEvent.openProductEditPage, - barcode: product.barcode, + product: product, ); await Navigator.push( @@ -334,9 +334,9 @@ class _ProductShareButton extends StatelessWidget { Future _shareProduct(BuildContext context, Product product) async { final ProductType productType = product.productType ?? ProductType.food; - AnalyticsHelper.trackEvent( + AnalyticsHelper.trackProductEvent( AnalyticsEvent.shareProduct, - barcode: product.barcode, + product: product, ); final AppLocalizations appLocalizations = AppLocalizations.of(context); // We need to provide a sharePositionOrigin to make the plugin work on ipad diff --git a/packages/smooth_app/lib/pages/product/product_questions_widget.dart b/packages/smooth_app/lib/pages/product/product_questions_widget.dart index fe0a55fc846e..3d30abee5ad3 100644 --- a/packages/smooth_app/lib/pages/product/product_questions_widget.dart +++ b/packages/smooth_app/lib/pages/product/product_questions_widget.dart @@ -122,12 +122,13 @@ class _ProductQuestionsWidgetState extends State } } - void _trackEvent(AnalyticsEvent event) => AnalyticsHelper.trackEvent( + void _trackEvent(AnalyticsEvent event) => AnalyticsHelper.trackProductEvent( event, eventValue: switch (widget.layout) { ProductQuestionsLayout.button => 0, ProductQuestionsLayout.banner => 1, }, + product: widget.product, ); Future?> _loadProductQuestions() async { diff --git a/packages/smooth_app/lib/pages/product/simple_input_page.dart b/packages/smooth_app/lib/pages/product/simple_input_page.dart index c554f820adfa..1ed7108e5170 100644 --- a/packages/smooth_app/lib/pages/product/simple_input_page.dart +++ b/packages/smooth_app/lib/pages/product/simple_input_page.dart @@ -194,13 +194,13 @@ class _SimpleInputPageState extends State { if (widget.helpers.length > 1) { AnalyticsHelper.trackProductEdit( AnalyticsEditEvents.powerEditScreen, - widget.product.barcode!, + widget.product, true, ); } else { AnalyticsHelper.trackProductEdit( widget.helpers[0].getAnalyticsEditEvent(), - widget.product.barcode!, + widget.product, true, ); } diff --git a/packages/smooth_app/lib/pages/product/summary_card.dart b/packages/smooth_app/lib/pages/product/summary_card.dart index a69c1afd2bda..6d3a333c866d 100644 --- a/packages/smooth_app/lib/pages/product/summary_card.dart +++ b/packages/smooth_app/lib/pages/product/summary_card.dart @@ -111,9 +111,9 @@ class _SummaryCardState extends State with UpToDateMixin { initUpToDate(widget._product, context.read()); _questionsLayout = getUserQuestionsLayout(context.read()); if (ProductIncompleteCard.isProductIncomplete(upToDateProduct)) { - AnalyticsHelper.trackEvent( + AnalyticsHelper.trackProductEvent( AnalyticsEvent.showFastTrackProductEditCard, - barcode: barcode, + product: widget._product, ); } } diff --git a/packages/smooth_app/lib/query/barcode_product_query.dart b/packages/smooth_app/lib/query/barcode_product_query.dart index c17a804aa34a..88904ed1c255 100644 --- a/packages/smooth_app/lib/query/barcode_product_query.dart +++ b/packages/smooth_app/lib/query/barcode_product_query.dart @@ -27,9 +27,9 @@ class BarcodeProductQuery { ProductQuery.setUserAgentComment(''); if (fetchedProduct.product != null) { if (fetchedProduct.product!.obsolete == true) { - AnalyticsHelper.trackEvent( + AnalyticsHelper.trackProductEvent( AnalyticsEvent.obsoleteProduct, - barcode: barcode, + product: fetchedProduct.product!, ); } return fetchedProduct; From 133ed8d853cefe43f53e25db546d3fab9c3f2dc0 Mon Sep 17 00:00:00 2001 From: Valimp Date: Thu, 14 Nov 2024 16:25:15 +0100 Subject: [PATCH 03/11] fix: revert product cards helper --- .../lib/helpers/product_cards_helper.dart | 25 +++++++++++++------ 1 file changed, 18 insertions(+), 7 deletions(-) diff --git a/packages/smooth_app/lib/helpers/product_cards_helper.dart b/packages/smooth_app/lib/helpers/product_cards_helper.dart index 2284527b791b..496892713608 100644 --- a/packages/smooth_app/lib/helpers/product_cards_helper.dart +++ b/packages/smooth_app/lib/helpers/product_cards_helper.dart @@ -416,10 +416,9 @@ List getRawProductImages( final Product product, final ImageSize imageSize, ) { - final List result = []; final List? rawImages = product.getRawImages(); if (rawImages == null) { - return result; + return []; } final Map map = {}; for (final ProductImage productImage in rawImages) { @@ -445,10 +444,22 @@ List getRawProductImages( } map[imageId] = productImage; } - final List sortedIds = List.of(map.keys); - sortedIds.sort(); - for (final int id in sortedIds) { - result.add(map[id]!); - } + final List result = List.of(map.values); + result.sort( + ( + final ProductImage a, + final ProductImage b, + ) { + final int result = (a.uploaded?.millisecondsSinceEpoch ?? 0).compareTo( + b.uploaded?.millisecondsSinceEpoch ?? 0, + ); + if (result != 0) { + return result; + } + return (int.tryParse(a.imgid ?? '0') ?? 0).compareTo( + int.tryParse(b.imgid ?? '0') ?? 0, + ); + }, + ); return result; } From 45cd3b2c5e48dca746f85580ffe3d82a724ab6e9 Mon Sep 17 00:00:00 2001 From: Valimp Date: Thu, 14 Nov 2024 16:44:35 +0100 Subject: [PATCH 04/11] fix: resolve conflicts --- .../add_new_product/add_new_product_page.dart | 12 +++++------- 1 file changed, 5 insertions(+), 7 deletions(-) diff --git a/packages/smooth_app/lib/pages/product/add_new_product/add_new_product_page.dart b/packages/smooth_app/lib/pages/product/add_new_product/add_new_product_page.dart index 6dedd9683979..d300d573ab7a 100644 --- a/packages/smooth_app/lib/pages/product/add_new_product/add_new_product_page.dart +++ b/packages/smooth_app/lib/pages/product/add_new_product/add_new_product_page.dart @@ -170,9 +170,9 @@ class _AddNewProductPageState extends State ), ]; _daoProductList = DaoProductList(localDatabase); - AnalyticsHelper.trackEvent( + AnalyticsHelper.trackProductEvent( widget.events[EditProductAction.openPage]!, - barcode: barcode, + product: upToDateProduct, ); _pageController.addListener(() => setState(() {})); } @@ -206,9 +206,9 @@ class _AddNewProductPageState extends State ), ); if (leaveThePage == true) { - AnalyticsHelper.trackEvent( + AnalyticsHelper.trackProductEvent( widget.events[EditProductAction.leaveEmpty]!, - barcode: barcode, + product: upToDateProduct, ); } return leaveThePage ?? false; @@ -581,9 +581,7 @@ class _AddNewProductPageState extends State productType: productType, checked: productType == _inputProductType, onChanged: (ProductType value) { - if (value != null) { - setState(() => _inputProductType = value); - } + setState(() => _inputProductType = value); }, ), ); From e5250ebda214b62dadcd3fa7be152bb12e644356 Mon Sep 17 00:00:00 2001 From: Valimp Date: Thu, 14 Nov 2024 16:49:36 +0100 Subject: [PATCH 05/11] fix: remove blank lines --- .../lib/pages/product/add_new_product/add_new_product_page.dart | 2 -- 1 file changed, 2 deletions(-) diff --git a/packages/smooth_app/lib/pages/product/add_new_product/add_new_product_page.dart b/packages/smooth_app/lib/pages/product/add_new_product/add_new_product_page.dart index d300d573ab7a..3009518176cf 100644 --- a/packages/smooth_app/lib/pages/product/add_new_product/add_new_product_page.dart +++ b/packages/smooth_app/lib/pages/product/add_new_product/add_new_product_page.dart @@ -97,7 +97,6 @@ class _AddNewProductPageState extends State (widget.displayPictures ? 1 : 0); double get _progress => (_pageNumber + 1) / _totalPages; - bool get _isLastPage => (_pageNumber + 1) == _totalPages; ProductType? _inputProductType; late ColorScheme _colorScheme; @@ -586,7 +585,6 @@ class _AddNewProductPageState extends State ), ); } - return rows; } From c9e82b1e0c33385532eda1ce8b675ea3d3040808 Mon Sep 17 00:00:00 2001 From: Valimp Date: Mon, 18 Nov 2024 15:45:55 +0100 Subject: [PATCH 06/11] fix: change class name and make extensions --- .../add_new_product/add_new_product_page.dart | 2 +- .../add_new_product/add_new_product_type.dart | 65 +++++++++---------- 2 files changed, 31 insertions(+), 36 deletions(-) diff --git a/packages/smooth_app/lib/pages/product/add_new_product/add_new_product_page.dart b/packages/smooth_app/lib/pages/product/add_new_product/add_new_product_page.dart index 3009518176cf..fc07acaf8069 100644 --- a/packages/smooth_app/lib/pages/product/add_new_product/add_new_product_page.dart +++ b/packages/smooth_app/lib/pages/product/add_new_product/add_new_product_page.dart @@ -576,7 +576,7 @@ class _AddNewProductPageState extends State for (final ProductType productType in ProductType.values) { rows.add( - AddNewProductType( + ProductTypeRadioListTile( productType: productType, checked: productType == _inputProductType, onChanged: (ProductType value) { diff --git a/packages/smooth_app/lib/pages/product/add_new_product/add_new_product_type.dart b/packages/smooth_app/lib/pages/product/add_new_product/add_new_product_type.dart index 1c5686b645f8..1be0767e0a25 100644 --- a/packages/smooth_app/lib/pages/product/add_new_product/add_new_product_type.dart +++ b/packages/smooth_app/lib/pages/product/add_new_product/add_new_product_type.dart @@ -7,8 +7,8 @@ import 'package:smooth_app/resources/app_icons.dart' as icons; import 'package:smooth_app/themes/smooth_theme_colors.dart'; import 'package:smooth_app/themes/theme_provider.dart'; -class AddNewProductType extends StatefulWidget { - const AddNewProductType({ +class ProductTypeRadioListTile extends StatefulWidget { + const ProductTypeRadioListTile({ super.key, required this.productType, required this.checked, @@ -20,20 +20,18 @@ class AddNewProductType extends StatefulWidget { final void Function(ProductType) onChanged; @override - State createState() => _AddNewProductType(); + State createState() => _AddNewProductType(); } -class _AddNewProductType extends State +class _AddNewProductType extends State with TickerProviderStateMixin { AnimationController? _controller; - // ignore: use_late_for_private_fields_and_variables - Animation? _colorAnimation; - // ignore: use_late_for_private_fields_and_variables - Animation? _opacityAnimation; + late Animation _colorAnimation; + late Animation _opacityAnimation; bool? _currentTheme; @override - void didUpdateWidget(AddNewProductType oldWidget) { + void didUpdateWidget(ProductTypeRadioListTile oldWidget) { super.didUpdateWidget(oldWidget); if (oldWidget.checked != widget.checked) { @@ -66,7 +64,7 @@ class _AddNewProductType extends State return Semantics( label: - '${_title(appLocalizations, context)} ${_subtitle(appLocalizations, context)}', + '${widget.productType.getTitle(AppLocalizations.of(context))} ${widget.productType.getSubtitle(AppLocalizations.of(context))}', checked: widget.checked, excludeSemantics: true, child: Container( @@ -96,7 +94,8 @@ class _AddNewProductType extends State bottomRight: Radius.circular(ANGULAR_RADIUS.x - 2.0), ), child: SvgPicture.asset( - _illustration, + widget.productType + .getIllustration(AppLocalizations.of(context)), width: 50.0, ), ), @@ -137,7 +136,8 @@ class _AddNewProductType extends State crossAxisAlignment: CrossAxisAlignment.start, children: [ Text( - _title(appLocalizations, context), + widget.productType + .getTitle(AppLocalizations.of(context)), overflow: TextOverflow.ellipsis, maxLines: 1, style: TextStyle( @@ -147,7 +147,8 @@ class _AddNewProductType extends State ), const SizedBox(height: 0.0), Text( - _subtitle(appLocalizations, context), + widget.productType + .getSubtitle(AppLocalizations.of(context)), overflow: TextOverflow.ellipsis, style: TextStyle( color: lightTheme @@ -193,35 +194,29 @@ class _AddNewProductType extends State _opacityAnimation = Tween(begin: 0.0, end: 1.0).animate( CurvedAnimation(parent: _controller!, curve: Curves.fastOutSlowIn)); } +} - String _title(AppLocalizations appLocalizations, final BuildContext context) { - return switch (widget.productType) { - ProductType.food => AppLocalizations.of(context).product_type_label_food, - ProductType.beauty => - AppLocalizations.of(context).product_type_label_beauty, - ProductType.petFood => - AppLocalizations.of(context).product_type_label_pet_food, - ProductType.product => - AppLocalizations.of(context).product_type_label_product, +extension ProductTypeExtension on ProductType { + String getTitle(AppLocalizations appLocalizations) { + return switch (this) { + ProductType.food => appLocalizations.product_type_label_food, + ProductType.beauty => appLocalizations.product_type_label_beauty, + ProductType.petFood => appLocalizations.product_type_label_pet_food, + ProductType.product => appLocalizations.product_type_label_product, }; } - String _subtitle( - AppLocalizations appLocalizations, final BuildContext context) { - return switch (widget.productType) { - ProductType.food => - AppLocalizations.of(context).product_type_subtitle_food, - ProductType.beauty => - AppLocalizations.of(context).product_type_subtitle_beauty, - ProductType.petFood => - AppLocalizations.of(context).product_type_subtitle_pet_food, - ProductType.product => - AppLocalizations.of(context).product_type_subtitle_product, + String getSubtitle(AppLocalizations appLocalizations) { + return switch (this) { + ProductType.food => appLocalizations.product_type_subtitle_food, + ProductType.beauty => appLocalizations.product_type_subtitle_beauty, + ProductType.petFood => appLocalizations.product_type_subtitle_pet_food, + ProductType.product => appLocalizations.product_type_subtitle_product, }; } - String get _illustration { - return switch (widget.productType) { + String getIllustration(AppLocalizations appLocalizations) { + return switch (this) { ProductType.food => 'assets/misc/logo_off_half.svg', ProductType.beauty => 'assets/misc/logo_obf_half.svg', ProductType.petFood => 'assets/misc/logo_opff_half.svg', From 007a8723158de48c7a7381fe59c6b392629886ee Mon Sep 17 00:00:00 2001 From: Valimp Date: Tue, 19 Nov 2024 14:28:39 +0100 Subject: [PATCH 07/11] fix: use appLocalization variable --- .../add_new_product/add_new_product_type.dart | 15 ++++++--------- 1 file changed, 6 insertions(+), 9 deletions(-) diff --git a/packages/smooth_app/lib/pages/product/add_new_product/add_new_product_type.dart b/packages/smooth_app/lib/pages/product/add_new_product/add_new_product_type.dart index 1be0767e0a25..75f13452e947 100644 --- a/packages/smooth_app/lib/pages/product/add_new_product/add_new_product_type.dart +++ b/packages/smooth_app/lib/pages/product/add_new_product/add_new_product_type.dart @@ -64,13 +64,13 @@ class _AddNewProductType extends State return Semantics( label: - '${widget.productType.getTitle(AppLocalizations.of(context))} ${widget.productType.getSubtitle(AppLocalizations.of(context))}', + '${widget.productType.getTitle(appLocalizations)} ${widget.productType.getSubtitle(appLocalizations)}', checked: widget.checked, excludeSemantics: true, child: Container( width: double.infinity, decoration: BoxDecoration( - color: _colorAnimation!.value, + color: _colorAnimation.value, borderRadius: ANGULAR_BORDER_RADIUS, border: Border.all( color: lightTheme ? theme.primarySemiDark : theme.primaryNormal, @@ -94,8 +94,7 @@ class _AddNewProductType extends State bottomRight: Radius.circular(ANGULAR_RADIUS.x - 2.0), ), child: SvgPicture.asset( - widget.productType - .getIllustration(AppLocalizations.of(context)), + widget.productType.getIllustration(appLocalizations), width: 50.0, ), ), @@ -120,7 +119,7 @@ class _AddNewProductType extends State shape: BoxShape.circle, ), child: Opacity( - opacity: _opacityAnimation!.value, + opacity: _opacityAnimation.value, child: const icons.Check( size: 9.0, ), @@ -136,8 +135,7 @@ class _AddNewProductType extends State crossAxisAlignment: CrossAxisAlignment.start, children: [ Text( - widget.productType - .getTitle(AppLocalizations.of(context)), + widget.productType.getTitle(appLocalizations), overflow: TextOverflow.ellipsis, maxLines: 1, style: TextStyle( @@ -147,8 +145,7 @@ class _AddNewProductType extends State ), const SizedBox(height: 0.0), Text( - widget.productType - .getSubtitle(AppLocalizations.of(context)), + widget.productType.getSubtitle(appLocalizations), overflow: TextOverflow.ellipsis, style: TextStyle( color: lightTheme From 1131776fc7fe62444b075ba84fe49115f64077d3 Mon Sep 17 00:00:00 2001 From: Valimp Date: Tue, 19 Nov 2024 14:29:19 +0100 Subject: [PATCH 08/11] fix: super.key convention --- .../lib/pages/product/add_new_product/add_new_product_type.dart | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/packages/smooth_app/lib/pages/product/add_new_product/add_new_product_type.dart b/packages/smooth_app/lib/pages/product/add_new_product/add_new_product_type.dart index 75f13452e947..1949aead3dfd 100644 --- a/packages/smooth_app/lib/pages/product/add_new_product/add_new_product_type.dart +++ b/packages/smooth_app/lib/pages/product/add_new_product/add_new_product_type.dart @@ -9,10 +9,10 @@ import 'package:smooth_app/themes/theme_provider.dart'; class ProductTypeRadioListTile extends StatefulWidget { const ProductTypeRadioListTile({ - super.key, required this.productType, required this.checked, required this.onChanged, + super.key, }); final ProductType productType; From 0167f51b4f7f028c1880caa2b0c35c00751e1106 Mon Sep 17 00:00:00 2001 From: Valimp Date: Wed, 20 Nov 2024 15:19:28 +0100 Subject: [PATCH 09/11] fix: syntax fixes --- packages/smooth_app/lib/l10n/app_en.arb | 16 ++++++++-------- .../add_new_product/add_new_product_type.dart | 17 ++++++++++------- 2 files changed, 18 insertions(+), 15 deletions(-) diff --git a/packages/smooth_app/lib/l10n/app_en.arb b/packages/smooth_app/lib/l10n/app_en.arb index 518570bc06d1..6df16d3708c8 100644 --- a/packages/smooth_app/lib/l10n/app_en.arb +++ b/packages/smooth_app/lib/l10n/app_en.arb @@ -3272,20 +3272,20 @@ "@carousel_loading_text": { "description": "Please keep the ** syntax to make the text bold" }, - "product_type_subtitle_food": "Vegetebles, fruits, frozen food...", + "product_type_subtitle_food": "Vegetables, fruits, frozen food…", "@product_type_subtitle_food" : { - "description": "Exemple of products for food category" + "description": "Example of products for food category" }, - "product_type_subtitle_beauty": "Makeup, soap, toothpaste...", + "product_type_subtitle_beauty": "Makeup, soaps, toothpastes…", "@product_type_subtitle_beauty" : { - "description": "Exemple of products for beauty category" + "description": "Example of products for beauty category" }, - "product_type_subtitle_pet_food": "Food for dogs, cats...", + "product_type_subtitle_pet_food": "Food for dogs, cats…", "@product_type_subtitle_pet_food" : { - "description": "Exemple of products for pet food category" + "description": "Example of products for pet food category" }, - "product_type_subtitle_product": "Smartphone, furniture...", + "product_type_subtitle_product": "Smartphones, furniture…", "@product_type_subtitle_product" : { - "description": "Exemple of products for other categories" + "description": "Example of products for other categories" } } diff --git a/packages/smooth_app/lib/pages/product/add_new_product/add_new_product_type.dart b/packages/smooth_app/lib/pages/product/add_new_product/add_new_product_type.dart index 1949aead3dfd..8b47b1f8d0a3 100644 --- a/packages/smooth_app/lib/pages/product/add_new_product/add_new_product_type.dart +++ b/packages/smooth_app/lib/pages/product/add_new_product/add_new_product_type.dart @@ -94,7 +94,7 @@ class _AddNewProductType extends State bottomRight: Radius.circular(ANGULAR_RADIUS.x - 2.0), ), child: SvgPicture.asset( - widget.productType.getIllustration(appLocalizations), + widget.productType.getIllustration(), width: 50.0, ), ), @@ -143,7 +143,7 @@ class _AddNewProductType extends State color: lightTheme ? Colors.black : Colors.white, ), ), - const SizedBox(height: 0.0), + const SizedBox(height: SMALL_SPACE), Text( widget.productType.getSubtitle(appLocalizations), overflow: TextOverflow.ellipsis, @@ -175,8 +175,9 @@ class _AddNewProductType extends State _currentTheme = lightTheme; _controller = AnimationController( - vsync: this, duration: const Duration(milliseconds: 300)) - ..addListener(() => setState(() {})); + vsync: this, + duration: const Duration(milliseconds: 300), + )..addListener(() => setState(() {})); final ThemeData themeData = Theme.of(context); @@ -186,10 +187,12 @@ class _AddNewProductType extends State ? themeData.extension()!.primaryMedium : themeData.extension()!.primarySemiDark, ).animate( - CurvedAnimation(parent: _controller!, curve: Curves.fastOutSlowIn)); + CurvedAnimation(parent: _controller!, curve: Curves.fastOutSlowIn), + ); _opacityAnimation = Tween(begin: 0.0, end: 1.0).animate( - CurvedAnimation(parent: _controller!, curve: Curves.fastOutSlowIn)); + CurvedAnimation(parent: _controller!, curve: Curves.fastOutSlowIn), + ); } } @@ -212,7 +215,7 @@ extension ProductTypeExtension on ProductType { }; } - String getIllustration(AppLocalizations appLocalizations) { + String getIllustration() { return switch (this) { ProductType.food => 'assets/misc/logo_off_half.svg', ProductType.beauty => 'assets/misc/logo_obf_half.svg', From 39601a0738bcc111f80a72563479d80bc3dab287 Mon Sep 17 00:00:00 2001 From: Valimp Date: Fri, 22 Nov 2024 15:02:45 +0100 Subject: [PATCH 10/11] fix: merge ProductType extensions on a single file --- .../lib/pages/offline_data_page.dart | 2 +- .../add_new_product/add_new_product_page.dart | 3 +- ...dart => product_type_radio_list_tile.dart} | 34 +-------- .../product/product_incomplete_card.dart | 2 +- .../product_page/new_product_footer.dart | 1 + .../product/product_type_extensions.dart | 75 +++++++++++++++++++ .../pages/search/search_product_helper.dart | 2 +- .../smooth_app/lib/query/product_query.dart | 48 +----------- 8 files changed, 85 insertions(+), 82 deletions(-) rename packages/smooth_app/lib/pages/product/add_new_product/{add_new_product_type.dart => product_type_radio_list_tile.dart} (83%) create mode 100644 packages/smooth_app/lib/pages/product/product_type_extensions.dart diff --git a/packages/smooth_app/lib/pages/offline_data_page.dart b/packages/smooth_app/lib/pages/offline_data_page.dart index 382e502fbd57..18c648aefed6 100644 --- a/packages/smooth_app/lib/pages/offline_data_page.dart +++ b/packages/smooth_app/lib/pages/offline_data_page.dart @@ -11,7 +11,7 @@ import 'package:smooth_app/database/local_database.dart'; import 'package:smooth_app/generic_lib/design_constants.dart'; import 'package:smooth_app/generic_lib/duration_constants.dart'; import 'package:smooth_app/helpers/app_helper.dart'; -import 'package:smooth_app/query/product_query.dart'; +import 'package:smooth_app/pages/product/product_type_extensions.dart'; import 'package:smooth_app/widgets/smooth_app_bar.dart'; import 'package:smooth_app/widgets/smooth_scaffold.dart'; diff --git a/packages/smooth_app/lib/pages/product/add_new_product/add_new_product_page.dart b/packages/smooth_app/lib/pages/product/add_new_product/add_new_product_page.dart index fc07acaf8069..25ccaa47ccbd 100644 --- a/packages/smooth_app/lib/pages/product/add_new_product/add_new_product_page.dart +++ b/packages/smooth_app/lib/pages/product/add_new_product/add_new_product_page.dart @@ -19,12 +19,13 @@ import 'package:smooth_app/helpers/product_cards_helper.dart'; import 'package:smooth_app/pages/crop_parameters.dart'; import 'package:smooth_app/pages/image_crop_page.dart'; import 'package:smooth_app/pages/preferences/user_preferences_widgets.dart'; -import 'package:smooth_app/pages/product/add_new_product/add_new_product_type.dart'; +import 'package:smooth_app/pages/product/add_new_product/product_type_radio_list_tile.dart'; import 'package:smooth_app/pages/product/add_new_product_helper.dart'; import 'package:smooth_app/pages/product/common/product_dialog_helper.dart'; import 'package:smooth_app/pages/product/nutrition_page_loaded.dart'; import 'package:smooth_app/pages/product/product_field_editor.dart'; import 'package:smooth_app/pages/product/product_image_swipeable_view.dart'; +import 'package:smooth_app/pages/product/product_type_extensions.dart'; import 'package:smooth_app/pages/product/simple_input_page_helpers.dart'; import 'package:smooth_app/query/product_query.dart'; import 'package:smooth_app/widgets/smooth_scaffold.dart'; diff --git a/packages/smooth_app/lib/pages/product/add_new_product/add_new_product_type.dart b/packages/smooth_app/lib/pages/product/add_new_product/product_type_radio_list_tile.dart similarity index 83% rename from packages/smooth_app/lib/pages/product/add_new_product/add_new_product_type.dart rename to packages/smooth_app/lib/pages/product/add_new_product/product_type_radio_list_tile.dart index 8b47b1f8d0a3..aa373190cea7 100644 --- a/packages/smooth_app/lib/pages/product/add_new_product/add_new_product_type.dart +++ b/packages/smooth_app/lib/pages/product/add_new_product/product_type_radio_list_tile.dart @@ -3,6 +3,7 @@ import 'package:flutter_gen/gen_l10n/app_localizations.dart'; import 'package:flutter_svg/flutter_svg.dart'; import 'package:openfoodfacts/openfoodfacts.dart'; import 'package:smooth_app/generic_lib/design_constants.dart'; +import 'package:smooth_app/pages/product/product_type_extensions.dart'; import 'package:smooth_app/resources/app_icons.dart' as icons; import 'package:smooth_app/themes/smooth_theme_colors.dart'; import 'package:smooth_app/themes/theme_provider.dart'; @@ -20,10 +21,10 @@ class ProductTypeRadioListTile extends StatefulWidget { final void Function(ProductType) onChanged; @override - State createState() => _AddNewProductType(); + State createState() => _ProductTypeRadioListTile(); } -class _AddNewProductType extends State +class _ProductTypeRadioListTile extends State with TickerProviderStateMixin { AnimationController? _controller; late Animation _colorAnimation; @@ -195,32 +196,3 @@ class _AddNewProductType extends State ); } } - -extension ProductTypeExtension on ProductType { - String getTitle(AppLocalizations appLocalizations) { - return switch (this) { - ProductType.food => appLocalizations.product_type_label_food, - ProductType.beauty => appLocalizations.product_type_label_beauty, - ProductType.petFood => appLocalizations.product_type_label_pet_food, - ProductType.product => appLocalizations.product_type_label_product, - }; - } - - String getSubtitle(AppLocalizations appLocalizations) { - return switch (this) { - ProductType.food => appLocalizations.product_type_subtitle_food, - ProductType.beauty => appLocalizations.product_type_subtitle_beauty, - ProductType.petFood => appLocalizations.product_type_subtitle_pet_food, - ProductType.product => appLocalizations.product_type_subtitle_product, - }; - } - - String getIllustration() { - return switch (this) { - ProductType.food => 'assets/misc/logo_off_half.svg', - ProductType.beauty => 'assets/misc/logo_obf_half.svg', - ProductType.petFood => 'assets/misc/logo_opff_half.svg', - ProductType.product => 'assets/misc/logo_opf_half.svg', - }; - } -} diff --git a/packages/smooth_app/lib/pages/product/product_incomplete_card.dart b/packages/smooth_app/lib/pages/product/product_incomplete_card.dart index 6b81cc0e1efb..0dad587f222f 100644 --- a/packages/smooth_app/lib/pages/product/product_incomplete_card.dart +++ b/packages/smooth_app/lib/pages/product/product_incomplete_card.dart @@ -5,8 +5,8 @@ import 'package:smooth_app/generic_lib/design_constants.dart'; import 'package:smooth_app/helpers/analytics_helper.dart'; import 'package:smooth_app/pages/product/add_new_product/add_new_product_page.dart'; import 'package:smooth_app/pages/product/product_field_editor.dart'; +import 'package:smooth_app/pages/product/product_type_extensions.dart'; import 'package:smooth_app/pages/product/simple_input_page_helpers.dart'; -import 'package:smooth_app/query/product_query.dart'; /// "Incomplete product!" card to be displayed in product summary, if relevant. /// diff --git a/packages/smooth_app/lib/pages/product/product_page/new_product_footer.dart b/packages/smooth_app/lib/pages/product/product_page/new_product_footer.dart index 2e55bcac5748..e84116364467 100644 --- a/packages/smooth_app/lib/pages/product/product_page/new_product_footer.dart +++ b/packages/smooth_app/lib/pages/product/product_page/new_product_footer.dart @@ -18,6 +18,7 @@ import 'package:smooth_app/pages/product/common/product_query_page_helper.dart'; import 'package:smooth_app/pages/product/edit_product_page.dart'; import 'package:smooth_app/pages/product/product_list_helper.dart'; import 'package:smooth_app/pages/product/product_page/new_product_page.dart'; +import 'package:smooth_app/pages/product/product_type_extensions.dart'; import 'package:smooth_app/query/category_product_query.dart'; import 'package:smooth_app/query/product_query.dart'; import 'package:smooth_app/resources/app_icons.dart' as icons; diff --git a/packages/smooth_app/lib/pages/product/product_type_extensions.dart b/packages/smooth_app/lib/pages/product/product_type_extensions.dart new file mode 100644 index 000000000000..43cf39defcc5 --- /dev/null +++ b/packages/smooth_app/lib/pages/product/product_type_extensions.dart @@ -0,0 +1,75 @@ +import 'package:flutter_gen/gen_l10n/app_localizations.dart'; +import 'package:openfoodfacts/openfoodfacts.dart'; + +extension ProductTypeExtension on ProductType { + String getTitle(AppLocalizations appLocalizations) { + return switch (this) { + ProductType.food => appLocalizations.product_type_label_food, + ProductType.beauty => appLocalizations.product_type_label_beauty, + ProductType.petFood => appLocalizations.product_type_label_pet_food, + ProductType.product => appLocalizations.product_type_label_product, + }; + } + + String getSubtitle(AppLocalizations appLocalizations) { + return switch (this) { + ProductType.food => appLocalizations.product_type_subtitle_food, + ProductType.beauty => appLocalizations.product_type_subtitle_beauty, + ProductType.petFood => appLocalizations.product_type_subtitle_pet_food, + ProductType.product => appLocalizations.product_type_subtitle_product, + }; + } + + String getIllustration() { + return switch (this) { + ProductType.food => 'assets/misc/logo_off_half.svg', + ProductType.beauty => 'assets/misc/logo_obf_half.svg', + ProductType.petFood => 'assets/misc/logo_opff_half.svg', + ProductType.product => 'assets/misc/logo_opf_half.svg', + }; + } + + String getDomain() => switch (this) { + ProductType.food => 'openfoodfacts', + ProductType.beauty => 'openbeautyfacts', + ProductType.petFood => 'openpetfoodfacts', + ProductType.product => 'openproductsfacts', + }; + + String getLabel(final AppLocalizations appLocalizations) => switch (this) { + ProductType.food => appLocalizations.product_type_label_food, + ProductType.beauty => appLocalizations.product_type_label_beauty, + ProductType.petFood => appLocalizations.product_type_label_pet_food, + ProductType.product => appLocalizations.product_type_label_product, + }; + + String getRoadToScoreLabel(final AppLocalizations appLocalizations) => + switch (this) { + ProductType.food => appLocalizations.hey_incomplete_product_message, + ProductType.beauty => + appLocalizations.hey_incomplete_product_message_beauty, + ProductType.petFood => + appLocalizations.hey_incomplete_product_message_pet_food, + ProductType.product => + appLocalizations.hey_incomplete_product_message_product, + }; + + String getShareProductLabel( + final AppLocalizations appLocalizations, + final String url, + ) => + switch (this) { + ProductType.food => appLocalizations.share_product_text( + url, + ), + ProductType.beauty => appLocalizations.share_product_text_beauty( + url, + ), + ProductType.petFood => appLocalizations.share_product_text_pet_food( + url, + ), + ProductType.product => appLocalizations.share_product_text_product( + url, + ), + }; +} diff --git a/packages/smooth_app/lib/pages/search/search_product_helper.dart b/packages/smooth_app/lib/pages/search/search_product_helper.dart index d2cca1d1fcbb..cf22f9139b4e 100644 --- a/packages/smooth_app/lib/pages/search/search_product_helper.dart +++ b/packages/smooth_app/lib/pages/search/search_product_helper.dart @@ -13,8 +13,8 @@ import 'package:smooth_app/pages/navigator/app_navigator.dart'; import 'package:smooth_app/pages/product/common/product_dialog_helper.dart'; import 'package:smooth_app/pages/product/common/product_query_page_helper.dart'; import 'package:smooth_app/pages/product/common/search_helper.dart'; +import 'package:smooth_app/pages/product/product_type_extensions.dart'; import 'package:smooth_app/query/keywords_product_query.dart'; -import 'package:smooth_app/query/product_query.dart'; /// Search helper dedicated to product search. class SearchProductHelper extends SearchHelper { diff --git a/packages/smooth_app/lib/query/product_query.dart b/packages/smooth_app/lib/query/product_query.dart index 437549dcf01f..57c776c4f909 100644 --- a/packages/smooth_app/lib/query/product_query.dart +++ b/packages/smooth_app/lib/query/product_query.dart @@ -1,7 +1,6 @@ import 'dart:ui'; import 'package:flutter/material.dart'; -import 'package:flutter_gen/gen_l10n/app_localizations.dart'; import 'package:openfoodfacts/openfoodfacts.dart'; import 'package:sentry_flutter/sentry_flutter.dart'; import 'package:smooth_app/data_models/preferences/user_preferences.dart'; @@ -9,6 +8,7 @@ import 'package:smooth_app/database/dao_string.dart'; import 'package:smooth_app/database/local_database.dart'; import 'package:smooth_app/helpers/analytics_helper.dart'; import 'package:smooth_app/pages/preferences/user_preferences_dev_mode.dart'; +import 'package:smooth_app/pages/product/product_type_extensions.dart'; import 'package:uuid/uuid.dart'; // ignore: avoid_classes_with_only_static_members @@ -295,49 +295,3 @@ abstract class ProductQuery { ProductField.OWNER_FIELDS, ]; } - -extension ProductTypeExtension on ProductType { - String getDomain() => switch (this) { - ProductType.food => 'openfoodfacts', - ProductType.beauty => 'openbeautyfacts', - ProductType.petFood => 'openpetfoodfacts', - ProductType.product => 'openproductsfacts', - }; - - String getLabel(final AppLocalizations appLocalizations) => switch (this) { - ProductType.food => appLocalizations.product_type_label_food, - ProductType.beauty => appLocalizations.product_type_label_beauty, - ProductType.petFood => appLocalizations.product_type_label_pet_food, - ProductType.product => appLocalizations.product_type_label_product, - }; - - String getRoadToScoreLabel(final AppLocalizations appLocalizations) => - switch (this) { - ProductType.food => appLocalizations.hey_incomplete_product_message, - ProductType.beauty => - appLocalizations.hey_incomplete_product_message_beauty, - ProductType.petFood => - appLocalizations.hey_incomplete_product_message_pet_food, - ProductType.product => - appLocalizations.hey_incomplete_product_message_product, - }; - - String getShareProductLabel( - final AppLocalizations appLocalizations, - final String url, - ) => - switch (this) { - ProductType.food => appLocalizations.share_product_text( - url, - ), - ProductType.beauty => appLocalizations.share_product_text_beauty( - url, - ), - ProductType.petFood => appLocalizations.share_product_text_pet_food( - url, - ), - ProductType.product => appLocalizations.share_product_text_product( - url, - ), - }; -} From 6e02557437844e1c733ada7f97e74f4c73094bce Mon Sep 17 00:00:00 2001 From: Valimp Date: Fri, 22 Nov 2024 16:24:40 +0100 Subject: [PATCH 11/11] fix: --- .../product_page/new_product_footer.dart | 470 ------------------ 1 file changed, 470 deletions(-) delete mode 100644 packages/smooth_app/lib/pages/product/product_page/new_product_footer.dart diff --git a/packages/smooth_app/lib/pages/product/product_page/new_product_footer.dart b/packages/smooth_app/lib/pages/product/product_page/new_product_footer.dart deleted file mode 100644 index e84116364467..000000000000 --- a/packages/smooth_app/lib/pages/product/product_page/new_product_footer.dart +++ /dev/null @@ -1,470 +0,0 @@ -import 'dart:io'; - -import 'package:flutter/material.dart'; -import 'package:flutter_gen/gen_l10n/app_localizations.dart'; -import 'package:openfoodfacts/openfoodfacts.dart'; -import 'package:provider/provider.dart'; -import 'package:share_plus/share_plus.dart'; -import 'package:smooth_app/data_models/preferences/user_preferences.dart'; -import 'package:smooth_app/database/local_database.dart'; -import 'package:smooth_app/generic_lib/bottom_sheets/smooth_bottom_sheet.dart'; -import 'package:smooth_app/generic_lib/design_constants.dart'; -import 'package:smooth_app/generic_lib/widgets/smooth_snackbar.dart'; -import 'package:smooth_app/helpers/analytics_helper.dart'; -import 'package:smooth_app/helpers/provider_helper.dart'; -import 'package:smooth_app/pages/prices/price_meta_product.dart'; -import 'package:smooth_app/pages/prices/product_price_add_page.dart'; -import 'package:smooth_app/pages/product/common/product_query_page_helper.dart'; -import 'package:smooth_app/pages/product/edit_product_page.dart'; -import 'package:smooth_app/pages/product/product_list_helper.dart'; -import 'package:smooth_app/pages/product/product_page/new_product_page.dart'; -import 'package:smooth_app/pages/product/product_type_extensions.dart'; -import 'package:smooth_app/query/category_product_query.dart'; -import 'package:smooth_app/query/product_query.dart'; -import 'package:smooth_app/resources/app_icons.dart' as icons; -import 'package:smooth_app/themes/smooth_theme_colors.dart'; -import 'package:smooth_app/themes/theme_provider.dart'; - -class ProductFooter extends StatelessWidget { - const ProductFooter({super.key}); - - static const double kHeight = 48.0; - - @override - Widget build(BuildContext context) { - return DecoratedBox( - decoration: BoxDecoration( - color: Theme.of(context).scaffoldBackgroundColor, - boxShadow: [ - BoxShadow( - color: Theme.of(context) - .shadowColor - .withOpacity(context.lightTheme() ? 0.25 : 0.6), - blurRadius: 10.0, - ), - ], - ), - child: const _ProductFooterButtonsBar(), - ); - } -} - -class _ProductFooterButtonsBar extends StatelessWidget { - const _ProductFooterButtonsBar(); - - @override - Widget build(BuildContext context) { - final SmoothColorsThemeExtension themeExtension = - Theme.of(context).extension()!; - - double bottomPadding = MediaQuery.viewPaddingOf(context).bottom; - // Add an extra padding (for Android) - if (Platform.isAndroid) { - bottomPadding += MEDIUM_SPACE; - } - - return SizedBox( - height: ProductFooter.kHeight + LARGE_SPACE + bottomPadding, - child: OutlinedButtonTheme( - data: OutlinedButtonThemeData( - style: OutlinedButton.styleFrom( - shape: RoundedRectangleBorder( - borderRadius: BorderRadius.circular(20.0), - ), - side: BorderSide(color: themeExtension.greyLight), - padding: const EdgeInsetsDirectional.symmetric( - horizontal: 19.0, - ), - ), - ), - child: ListView( - padding: EdgeInsetsDirectional.only( - start: SMALL_SPACE, - end: SMALL_SPACE, - top: LARGE_SPACE, - bottom: bottomPadding, - ), - scrollDirection: Axis.horizontal, - children: const [ - SizedBox(width: 10.0), - _ProductAddPriceButton(), - SizedBox(width: 10.0), - _ProductEditButton(), - SizedBox(width: 10.0), - _ProductCompareButton(), - SizedBox(width: 10.0), - _ProductAddToListButton(), - SizedBox(width: 10.0), - _ProductShareButton(), - ], - ), - ), - ); - } -} - -class _ProductAddToListButton extends StatelessWidget { - const _ProductAddToListButton(); - - @override - Widget build(BuildContext context) { - final AppLocalizations appLocalizations = AppLocalizations.of(context); - - return _ProductFooterOutlinedButton( - label: appLocalizations.user_list_button_add_product, - icon: const icons.AddToList(), - onTap: () => _editList(context, context.read()), - ); - } - - Future _editList(BuildContext context, Product product) async { - final AppLocalizations appLocalizations = AppLocalizations.of(context); - - showSmoothDraggableModalSheet( - context: context, - header: SmoothModalSheetHeader( - prefix: const SmoothModalSheetHeaderPrefixIndicator(), - title: appLocalizations.user_list_title, - suffix: const SmoothModalSheetHeaderCloseButton(), - ), - bodyBuilder: (BuildContext context) => AddProductToListContainer( - barcode: product.barcode!, - ), - ); - - return true; - } -} - -class _ProductAddPriceButton extends StatelessWidget { - const _ProductAddPriceButton(); - - @override - Widget build(BuildContext context) { - final AppLocalizations appLocalizations = AppLocalizations.of(context); - - return ConsumerFilter( - buildWhen: (UserPreferences? previous, UserPreferences current) => - previous?.userCurrencyCode != current.userCurrencyCode, - builder: (BuildContext context, UserPreferences userPreferences, _) { - final Currency currency = Currency.values.firstWhere( - (Currency currency) => - currency.name == userPreferences.userCurrencyCode, - orElse: () => Currency.USD, - ); - - return _ProductFooterFilledButton( - label: appLocalizations.prices_add_a_price, - icon: icons.AddPrice(currency), - onTap: () => _addAPrice(context, context.read()), - ); - }, - ); - } - - Future _addAPrice(BuildContext context, Product product) { - return ProductPriceAddPage.showProductPage( - context: context, - product: PriceMetaProduct.product(context.read()), - proofType: ProofType.priceTag, - ); - } -} - -class _ProductEditButton extends StatelessWidget { - const _ProductEditButton(); - - @override - Widget build(BuildContext context) { - final AppLocalizations appLocalizations = AppLocalizations.of(context); - - return _ProductFooterOutlinedButton( - label: appLocalizations.edit_product_label_short, - semanticsLabel: appLocalizations.edit_product_label, - icon: const icons.Edit(), - onTap: () => _editProduct(context, context.read()), - ); - } - - Future _editProduct(BuildContext context, Product product) async { - ProductPageState.of(context).stopRobotoffQuestion(); - - AnalyticsHelper.trackProductEvent( - AnalyticsEvent.openProductEditPage, - product: product, - ); - - await Navigator.push( - context, - MaterialPageRoute( - builder: (BuildContext context) => EditProductPage(product), - ), - ); - - if (context.mounted) { - ProductPageState.of(context).startRobotoffQuestion(); - } - } -} - -class _ProductCompareButton extends StatelessWidget { - const _ProductCompareButton(); - - @override - Widget build(BuildContext context) { - final AppLocalizations appLocalizations = AppLocalizations.of(context); - final Product product = context.read(); - - const Set blackListedCategories = { - 'fr:vegan', - }; - String? categoryTag; - String? categoryLabel; - final List? labels = - product.categoriesTagsInLanguages?[ProductQuery.getLanguage()]; - final List? tags = product.categoriesTags; - if (tags != null && - labels != null && - tags.isNotEmpty && - tags.length == labels.length) { - categoryTag = product.comparedToCategory; - if (categoryTag != null) { - for (int i = 0; i < tags.length; i++) { - if (categoryTag == tags[i]) { - categoryLabel = labels[i]; - break; - } - } - } - if (categoryLabel == null || - blackListedCategories.contains(categoryTag)) { - // fallback algorithm - int index = tags.length - 1; - // cf. https://github.com/openfoodfacts/openfoodfacts-dart/pull/474 - // looking for the most detailed non blacklisted category - categoryTag = tags[index]; - categoryLabel = labels[index]; - while (blackListedCategories.contains(categoryTag) && index > 0) { - index--; - categoryTag = tags[index]; - categoryLabel = labels[index]; - } - } - } - - final bool enabled = categoryTag != null && categoryLabel != null; - - return _ProductFooterOutlinedButton( - label: appLocalizations.product_search_same_category_short, - semanticsLabel: appLocalizations.product_search_same_category, - icon: const icons.Compare(), - enabled: enabled, - onTap: () => enabled - ? _compareProduct( - context: context, - product: product, - categoryLabel: categoryLabel!, - categoryTag: categoryTag!, - ) - : _showFeatureDisabledDialog(context), - ); - } - - Future _compareProduct({ - required BuildContext context, - required Product product, - required String categoryLabel, - required String categoryTag, - }) { - return ProductQueryPageHelper.openBestChoice( - name: categoryLabel, - localDatabase: context.read(), - productQuery: CategoryProductQuery( - categoryTag, - productType: product.productType ?? ProductType.food, - ), - context: context, - searchResult: false, - ); - } - - void _showFeatureDisabledDialog(BuildContext context) { - final AppLocalizations appLocalizations = AppLocalizations.of(context); - final ThemeData themeData = Theme.of(context); - - ScaffoldMessenger.of(context).showSnackBar( - SmoothFloatingSnackbar( - content: Row( - children: [ - const ExcludeSemantics( - child: icons.Warning( - color: Colors.white, - ), - ), - const SizedBox(width: LARGE_SPACE), - Expanded( - child: - Text(appLocalizations.product_search_same_category_error)), - ], - ), - backgroundColor: themeData.extension()!.red, - action: SnackBarAction( - label: appLocalizations.okay, - onPressed: () { - ScaffoldMessenger.of(context).hideCurrentSnackBar(); - }, - ), - ), - ); - } -} - -class _ProductShareButton extends StatelessWidget { - const _ProductShareButton(); - - @override - Widget build(BuildContext context) { - final AppLocalizations appLocalizations = AppLocalizations.of(context); - - return _ProductFooterOutlinedButton( - label: appLocalizations.share, - icon: icons.Share(), - onTap: () => _shareProduct(context, context.read()), - ); - } - - Future _shareProduct(BuildContext context, Product product) async { - final ProductType productType = product.productType ?? ProductType.food; - AnalyticsHelper.trackProductEvent( - AnalyticsEvent.shareProduct, - product: product, - ); - final AppLocalizations appLocalizations = AppLocalizations.of(context); - // We need to provide a sharePositionOrigin to make the plugin work on ipad - final RenderBox? box = context.findRenderObject() as RenderBox?; - final String url = 'https://' - '${ProductQuery.getCountry().offTag}.${productType.getDomain()}.org' - '/product/${product.barcode}'; - Share.share( - productType.getShareProductLabel(appLocalizations, url), - sharePositionOrigin: - box == null ? null : box.localToGlobal(Offset.zero) & box.size, - ); - } -} - -class _ProductFooterFilledButton extends StatelessWidget { - const _ProductFooterFilledButton({ - required this.label, - required this.icon, - required this.onTap, - // ignore: unused_element - this.semanticsLabel, - }); - - final String label; - final String? semanticsLabel; - final icons.AppIcon icon; - final VoidCallback onTap; - - @override - Widget build(BuildContext context) { - final SmoothColorsThemeExtension themeExtension = - Theme.of(context).extension()!; - final ProductPageCompatibility compatibility = - context.watch(); - - return Semantics( - excludeSemantics: true, - button: true, - label: semanticsLabel, - child: OutlinedButton( - onPressed: onTap, - style: OutlinedButton.styleFrom( - foregroundColor: Colors.white, - backgroundColor: compatibility.color ?? - (context.lightTheme() - ? themeExtension.primaryBlack - : themeExtension.primarySemiDark), - side: BorderSide.none, - ), - child: Row( - crossAxisAlignment: CrossAxisAlignment.center, - children: [ - IconTheme( - data: const IconThemeData( - color: Colors.white, - size: 18.0, - ), - child: icon, - ), - const SizedBox(width: 8.0), - Text( - label, - style: const TextStyle( - fontWeight: FontWeight.bold, - ), - ), - ], - ), - ), - ); - } -} - -class _ProductFooterOutlinedButton extends StatelessWidget { - const _ProductFooterOutlinedButton({ - required this.label, - required this.icon, - required this.onTap, - this.enabled = true, - this.semanticsLabel, - }); - - final String label; - final String? semanticsLabel; - final icons.AppIcon icon; - final VoidCallback onTap; - final bool enabled; - - @override - Widget build(BuildContext context) { - final SmoothColorsThemeExtension themeExtension = - Theme.of(context).extension()!; - final Color contentColor = - context.lightTheme() ? themeExtension.primaryBlack : Colors.white; - final Color mainColor = - enabled ? contentColor : contentColor.withOpacity(0.5); - - return Semantics( - label: semanticsLabel, - excludeSemantics: true, - button: true, - child: OutlinedButton( - onPressed: onTap, - style: OutlinedButton.styleFrom( - foregroundColor: mainColor, - backgroundColor: Colors.transparent, - ), - child: Row( - children: [ - IconTheme( - data: IconThemeData( - color: mainColor, - size: 18.0, - ), - child: icon, - ), - const SizedBox(width: 8.0), - Text( - label, - style: const TextStyle( - fontWeight: FontWeight.bold, - ), - ), - ], - ), - ), - ); - } -}