From 2ba1053af2c21b73ae939fb0f10e4fdc3562a593 Mon Sep 17 00:00:00 2001 From: monsieurtanuki Date: Wed, 18 Sep 2024 19:08:33 +0200 Subject: [PATCH] fix: 5576 - first step towards multi product types Impacted files: * `add_basic_details_page.dart`: replaced getter `ProductQuery.uriProductHelper` with method `ProductQuery.getUriProductHelper` * `background_task.dart`: replaced getter `ProductQuery.uriProductHelper` with method `ProductQuery.getUriProductHelper` * `background_task_download_products.dart`: saved new field `Product.productType` * `dao_product.dart`: now we're storing the `Product.productType` field * `edit_new_packagings.dart`: now using new field `Product.productType` * `edit_new_packagings_component.dart`: now using new field `Product.productType` * `edit_product_page.dart`: specific icon for "food" categories; no nutrition for "beauty" and "product" * `forgot_password_page.dart`: replaced getter `ProductQuery.uriProductHelper` with method `ProductQuery.getUriProductHelper` * `lazy_counter.dart`: replaced getter `ProductQuery.uriProductHelper` with method `ProductQuery.getUriProductHelper` * `login_result.dart`: replaced getter `ProductQuery.uriProductHelper` with method `ProductQuery.getUriProductHelper` * `newsfeed_provider.dart`: replaced getter `ProductQuery.uriProductHelper` with method `ProductQuery.getUriProductHelper` * `ocr_ingredients_helper.dart`: replaced getter `ProductQuery.uriProductHelper` with method `ProductQuery.getUriProductHelper` * `ocr_packaging_helper.dart`: replaced getter `ProductQuery.uriProductHelper` with method `ProductQuery.getUriProductHelper` * `onboarding_data_product.dart`: replaced getter `ProductQuery.uriProductHelper` with method `ProductQuery.getUriProductHelper` * `ordered_nutrients_cache.dart`: replaced getter `ProductQuery.uriProductHelper` with method `ProductQuery.getUriProductHelper` * `paged_product_query.dart`: replaced getter `ProductQuery.uriProductHelper` with method `ProductQuery.getUriProductHelper` * `product_cards_helper.dart`: replaced getter `ProductQuery.uriProductHelper` with method `ProductQuery.getUriProductHelper` * `product_image_crop_button.dart`: replaced getter `ProductQuery.uriProductHelper` with method `ProductQuery.getUriProductHelper` * `product_image_gallery_other_view.dart`: now using new field `Product.productType` * `product_image_other_page.dart`: now using new field `Product.productType` * `product_image_server_button.dart`: now using new field `Product.productType` * `product_image_widget.dart`: now using new field `Product.productType` * `product_list_page.dart`: replaced getter `ProductQuery.uriProductHelper` with method `ProductQuery.getUriProductHelper` * `product_query.dart`: now when we look for a barcode (or refresh a product), we use all productTypes if needed * `product_refresher.dart`: replaced getter `ProductQuery.uriProductHelper` with method `ProductQuery.getUriProductHelper` * `pubspec.lock`: wtf * `pubspec.yaml`: upgraded `openfoodfacts` to `3.15.0` for new `Product.productType` field * `sign_up_page.dart`: replaced getter `ProductQuery.uriProductHelper` with method `ProductQuery.getUriProductHelper` * `simple_input_page_helpers.dart`: new class dedicated to non-food categories * `simple_input_text_field.dart`: replaced getter `ProductQuery.uriProductHelper` with method `ProductQuery.getUriProductHelper` * `simple_input_widget.dart`: now using new field `Product.productType` * `temp_product_list_share_helper.dart`: replaced getter `ProductQuery.uriProductHelper` with method `ProductQuery.getUriProductHelper` * `uploaded_image_gallery.dart`: now using new field `Product.productType` * `user_preferences_dev_debug_info.dart`: replaced getter `ProductQuery.uriProductHelper` with method `ProductQuery.getUriProductHelper` --- .../lib/background/background_task.dart | 2 +- .../background_task_download_products.dart | 8 +- .../lib/data_models/login_result.dart | 2 +- .../news_feed/newsfeed_provider.dart | 3 +- .../data_models/onboarding_data_product.dart | 4 +- .../smooth_app/lib/database/dao_product.dart | 31 +++++--- .../lib/helpers/product_cards_helper.dart | 4 +- .../temp_product_list_share_helper.dart | 2 +- .../product_image_gallery_other_view.dart | 1 + .../pages/image/product_image_other_page.dart | 14 +++- .../lib/pages/image/product_image_widget.dart | 6 +- .../pages/image/uploaded_image_gallery.dart | 7 +- .../lib/pages/preferences/lazy_counter.dart | 2 +- .../user_preferences_dev_debug_info.dart | 4 +- .../pages/product/add_basic_details_page.dart | 4 +- .../product/common/product_list_page.dart | 2 +- .../product/common/product_refresher.dart | 74 +++++++++++++++---- .../pages/product/edit_new_packagings.dart | 1 + .../edit_new_packagings_component.dart | 9 +++ .../edit_ocr/ocr_ingredients_helper.dart | 4 +- .../edit_ocr/ocr_packaging_helper.dart | 4 +- .../lib/pages/product/edit_product_page.dart | 62 +++++++++------- .../product/ordered_nutrients_cache.dart | 4 +- .../product/product_image_crop_button.dart | 12 ++- .../product/product_image_server_button.dart | 14 +++- .../product/simple_input_page_helpers.dart | 6 ++ .../product/simple_input_text_field.dart | 6 +- .../pages/product/simple_input_widget.dart | 1 + .../user_management/forgot_password_page.dart | 2 +- .../pages/user_management/sign_up_page.dart | 2 +- .../lib/query/paged_product_query.dart | 2 +- .../smooth_app/lib/query/product_query.dart | 49 +++++++++++- packages/smooth_app/pubspec.lock | 32 ++++---- packages/smooth_app/pubspec.yaml | 2 +- 34 files changed, 281 insertions(+), 101 deletions(-) diff --git a/packages/smooth_app/lib/background/background_task.dart b/packages/smooth_app/lib/background/background_task.dart index 068fa317d2c..5344986e3b6 100644 --- a/packages/smooth_app/lib/background/background_task.dart +++ b/packages/smooth_app/lib/background/background_task.dart @@ -183,7 +183,7 @@ abstract class BackgroundTask { // TODO(monsieurtanuki): store the uriProductHelper as well @protected - UriProductHelper get uriProductHelper => ProductQuery.uriProductHelper; + UriProductHelper get uriProductHelper => ProductQuery.getUriProductHelper(); /// Returns true if tasks with the same stamp would overwrite each-other. bool isDeduplicable() => true; diff --git a/packages/smooth_app/lib/background/background_task_download_products.dart b/packages/smooth_app/lib/background/background_task_download_products.dart index eaa312e4477..a600eadf314 100644 --- a/packages/smooth_app/lib/background/background_task_download_products.dart +++ b/packages/smooth_app/lib/background/background_task_download_products.dart @@ -133,9 +133,15 @@ class BackgroundTaskDownloadProducts extends BackgroundTaskProgressing { throw Exception('Something bad happened downloading products'); } final DaoProduct daoProduct = DaoProduct(localDatabase); + final ProductType? productType = + ProductQuery.extractProductType(uriProductHelper); for (final Product product in downloadedProducts) { if (await _shouldBeUpdated(daoProduct, product.barcode!)) { - await daoProduct.put(product, language); + await daoProduct.put( + product, + language, + productType: productType, + ); } } final int deleted = await daoWorkBarcode.deleteBarcodes(work, barcodes); diff --git a/packages/smooth_app/lib/data_models/login_result.dart b/packages/smooth_app/lib/data_models/login_result.dart index 7828962402e..4a89f07367c 100644 --- a/packages/smooth_app/lib/data_models/login_result.dart +++ b/packages/smooth_app/lib/data_models/login_result.dart @@ -38,7 +38,7 @@ class LoginResult { try { final LoginStatus? loginStatus = await OpenFoodAPIClient.login2( user, - uriHelper: ProductQuery.uriProductHelper, + uriHelper: ProductQuery.getUriProductHelper(), ); if (loginStatus == null) { return const LoginResult(LoginResultType.serverIssue); diff --git a/packages/smooth_app/lib/data_models/news_feed/newsfeed_provider.dart b/packages/smooth_app/lib/data_models/news_feed/newsfeed_provider.dart index c72b44ff146..2fe18d8d600 100644 --- a/packages/smooth_app/lib/data_models/news_feed/newsfeed_provider.dart +++ b/packages/smooth_app/lib/data_models/news_feed/newsfeed_provider.dart @@ -106,7 +106,8 @@ class AppNewsProvider extends ChangeNotifier { /// or [https://world.openfoodfacts.[org/net]/resources/files/tagline-off-android-v3.json] Future _fetchJSON() async { try { - final UriProductHelper uriProductHelper = ProductQuery.uriProductHelper; + final UriProductHelper uriProductHelper = + ProductQuery.getUriProductHelper(); final Map headers = {}; final Uri uri; diff --git a/packages/smooth_app/lib/data_models/onboarding_data_product.dart b/packages/smooth_app/lib/data_models/onboarding_data_product.dart index 4deb889c166..13f4f29cff8 100644 --- a/packages/smooth_app/lib/data_models/onboarding_data_product.dart +++ b/packages/smooth_app/lib/data_models/onboarding_data_product.dart @@ -40,7 +40,9 @@ class OnboardingDataProduct extends AbstractOnboardingData { AbstractOnboardingData.barcode, ProductQuery.getLanguage(), ), - uriHelper: ProductQuery.uriProductHelper, + uriHelper: ProductQuery.getUriProductHelper( + productType: ProductType.food, + ), ).timeout(SnackBarDuration.long); @override diff --git a/packages/smooth_app/lib/database/dao_product.dart b/packages/smooth_app/lib/database/dao_product.dart index f14eab03585..bbf0935c5c7 100644 --- a/packages/smooth_app/lib/database/dao_product.dart +++ b/packages/smooth_app/lib/database/dao_product.dart @@ -94,25 +94,34 @@ class DaoProduct extends AbstractSqlDao implements BulkDeletable { Future put( final Product product, - final OpenFoodFactsLanguage language, - ) async => + final OpenFoodFactsLanguage language, { + final ProductType? productType, + }) async => putAll( [product], language, + productType: productType, ); /// Replaces products in database Future putAll( final Iterable products, - final OpenFoodFactsLanguage language, - ) async => - localDatabase.database.transaction( - (final Transaction transaction) async => _bulkReplaceLoop( - transaction, - products, - language, - ), - ); + final OpenFoodFactsLanguage language, { + final ProductType? productType, + }) async { + if (productType != null) { + for (final Product product in products) { + product.productType = productType; + } + } + await localDatabase.database.transaction( + (final Transaction transaction) async => _bulkReplaceLoop( + transaction, + products, + language, + ), + ); + } Future> getAllKeys() async { final List result = []; diff --git a/packages/smooth_app/lib/helpers/product_cards_helper.dart b/packages/smooth_app/lib/helpers/product_cards_helper.dart index c229ad58177..f023ede0411 100644 --- a/packages/smooth_app/lib/helpers/product_cards_helper.dart +++ b/packages/smooth_app/lib/helpers/product_cards_helper.dart @@ -271,7 +271,9 @@ ProductImageData getProductImageData( imageUrl: productImage.getUrl( product.barcode!, imageSize: ImageSize.DISPLAY, - uriHelper: ProductQuery.uriProductHelper, + uriHelper: ProductQuery.getUriProductHelper( + productType: product.productType, + ), ), language: language, ); diff --git a/packages/smooth_app/lib/helpers/temp_product_list_share_helper.dart b/packages/smooth_app/lib/helpers/temp_product_list_share_helper.dart index 4daf5a3af55..d2adeb878e9 100644 --- a/packages/smooth_app/lib/helpers/temp_product_list_share_helper.dart +++ b/packages/smooth_app/lib/helpers/temp_product_list_share_helper.dart @@ -6,7 +6,7 @@ Uri shareProductList(List barcodes) { final String barcodesString = barcodes.join(','); return UriHelper.replaceSubdomain( - ProductQuery.uriProductHelper.getUri( + ProductQuery.getUriProductHelper().getUri( path: 'products/$barcodesString', addUserAgentParameters: false, ), diff --git a/packages/smooth_app/lib/pages/image/product_image_gallery_other_view.dart b/packages/smooth_app/lib/pages/image/product_image_gallery_other_view.dart index 1a25b281bef..f9dd275ef76 100644 --- a/packages/smooth_app/lib/pages/image/product_image_gallery_other_view.dart +++ b/packages/smooth_app/lib/pages/image/product_image_gallery_other_view.dart @@ -148,6 +148,7 @@ class _RawGridGallery extends StatelessWidget { squareSize: squareSize, imageSize: imageSize, heroTag: heroTag, + productType: product.productType, ), ), ); diff --git a/packages/smooth_app/lib/pages/image/product_image_other_page.dart b/packages/smooth_app/lib/pages/image/product_image_other_page.dart index f523c31699c..2d9dd6b83dd 100644 --- a/packages/smooth_app/lib/pages/image/product_image_other_page.dart +++ b/packages/smooth_app/lib/pages/image/product_image_other_page.dart @@ -66,6 +66,7 @@ class _ProductImageOtherPageState extends State { barcode: widget.product.barcode!, heroTag: widget.currentImage == image ? widget.heroTag : null, + productType: widget.product.productType, ); }, ).toList(growable: false), @@ -89,11 +90,13 @@ class _ProductImageViewer extends StatelessWidget { required this.image, required this.barcode, this.heroTag, + required this.productType, }); final ProductImage image; final String barcode; final String? heroTag; + final ProductType? productType; @override Widget build(BuildContext context) { @@ -111,7 +114,9 @@ class _ProductImageViewer extends StatelessWidget { image: NetworkImage( image.getUrl( barcode, - uriHelper: ProductQuery.uriProductHelper, + uriHelper: ProductQuery.getUriProductHelper( + productType: productType, + ), ), ), fit: BoxFit.cover, @@ -153,6 +158,7 @@ class _ProductImageViewer extends StatelessWidget { _ProductImageDetailsButton( image: image, barcode: barcode, + productType: productType, ), const Spacer(), if (image.expired) _ProductImageOutdatedLabel(colors: colors), @@ -211,10 +217,12 @@ class _ProductImageDetailsButton extends StatelessWidget { const _ProductImageDetailsButton({ required this.image, required this.barcode, + required this.productType, }); final ProductImage image; final String barcode; + final ProductType? productType; @override Widget build(BuildContext context) { @@ -222,7 +230,9 @@ class _ProductImageDetailsButton extends StatelessWidget { final String url = image.url ?? image.getUrl( barcode, - uriHelper: ProductQuery.uriProductHelper, + uriHelper: ProductQuery.getUriProductHelper( + productType: productType, + ), ); return DecoratedBox( diff --git a/packages/smooth_app/lib/pages/image/product_image_widget.dart b/packages/smooth_app/lib/pages/image/product_image_widget.dart index 030bef83cd2..f52b9566894 100644 --- a/packages/smooth_app/lib/pages/image/product_image_widget.dart +++ b/packages/smooth_app/lib/pages/image/product_image_widget.dart @@ -17,6 +17,7 @@ class ProductImageWidget extends StatelessWidget { required this.productImage, required this.barcode, required this.squareSize, + required this.productType, this.imageSize, this.heroTag, }); @@ -25,6 +26,7 @@ class ProductImageWidget extends StatelessWidget { final String barcode; final double squareSize; final String? heroTag; + final ProductType? productType; /// Allows to fetch the optimized version of the image final ImageSize? imageSize; @@ -45,7 +47,9 @@ class ProductImageWidget extends StatelessWidget { imageProvider: NetworkImage( productImage.getUrl( barcode, - uriHelper: ProductQuery.uriProductHelper, + uriHelper: ProductQuery.getUriProductHelper( + productType: productType, + ), imageSize: imageSize, ), ), diff --git a/packages/smooth_app/lib/pages/image/uploaded_image_gallery.dart b/packages/smooth_app/lib/pages/image/uploaded_image_gallery.dart index 8b20e3103a0..60634d851f0 100644 --- a/packages/smooth_app/lib/pages/image/uploaded_image_gallery.dart +++ b/packages/smooth_app/lib/pages/image/uploaded_image_gallery.dart @@ -25,12 +25,14 @@ class UploadedImageGallery extends StatelessWidget { required this.imageField, required this.language, required this.isLoggedInMandatory, + required this.productType, }); final String barcode; final List rawImages; final ImageField imageField; final bool isLoggedInMandatory; + final ProductType? productType; /// Language for which we'll save the cropped image. final OpenFoodFactsLanguage language; @@ -70,7 +72,9 @@ class UploadedImageGallery extends StatelessWidget { rawImage.getUrl( barcode, imageSize: ImageSize.ORIGINAL, - uriHelper: ProductQuery.uriProductHelper, + uriHelper: ProductQuery.getUriProductHelper( + productType: productType, + ), ), DaoInt(localDatabase), ); @@ -102,6 +106,7 @@ class UploadedImageGallery extends StatelessWidget { productImage: rawImage, barcode: barcode, squareSize: columnWidth, + productType: productType, ), ); }, diff --git a/packages/smooth_app/lib/pages/preferences/lazy_counter.dart b/packages/smooth_app/lib/pages/preferences/lazy_counter.dart index 68ae09dd87d..a8a4cea02b0 100644 --- a/packages/smooth_app/lib/pages/preferences/lazy_counter.dart +++ b/packages/smooth_app/lib/pages/preferences/lazy_counter.dart @@ -79,7 +79,7 @@ class LazyCounterUserSearch extends LazyCounter { final SearchResult result = await OpenFoodAPIClient.searchProducts( user, configuration, - uriHelper: ProductQuery.uriProductHelper, + uriHelper: ProductQuery.getUriProductHelper(), ); return result.count; } catch (e) { diff --git a/packages/smooth_app/lib/pages/preferences/user_preferences_dev_debug_info.dart b/packages/smooth_app/lib/pages/preferences/user_preferences_dev_debug_info.dart index 38c3b71a19e..07d846001da 100644 --- a/packages/smooth_app/lib/pages/preferences/user_preferences_dev_debug_info.dart +++ b/packages/smooth_app/lib/pages/preferences/user_preferences_dev_debug_info.dart @@ -27,10 +27,10 @@ class _UserPreferencesDebugInfoState extends State { 'IsLoggedIn': ProductQuery.isLoggedIn().toString(), 'UUID': OpenFoodAPIConfiguration.uuid.toString(), 'Matomo Visitor ID': AnalyticsHelper.matomoVisitorId, - 'QueryType': ProductQuery.uriProductHelper.isTestMode + 'QueryType': ProductQuery.getUriProductHelper().isTestMode ? 'QueryType.TEST' : 'QueryType.PROD', - 'Domain': ProductQuery.uriProductHelper.domain, + 'Domain': ProductQuery.getUriProductHelper().domain, 'UserAgent-name': '${OpenFoodAPIConfiguration.userAgent?.name}', 'UserAgent-system': '${OpenFoodAPIConfiguration.userAgent?.system}', }; 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 75480b7b753..5822fe3fa14 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 @@ -207,7 +207,9 @@ class _AddBasicDetailsPageState extends State { user: ProductQuery.getReadUser(), limit: 25, fuzziness: Fuzziness.none, - uriHelper: ProductQuery.uriProductHelper, + uriHelper: ProductQuery.getUriProductHelper( + productType: widget.product.productType, + ), ), ), ), diff --git a/packages/smooth_app/lib/pages/product/common/product_list_page.dart b/packages/smooth_app/lib/pages/product/common/product_list_page.dart index 5a183073e83..83a5076534b 100644 --- a/packages/smooth_app/lib/pages/product/common/product_list_page.dart +++ b/packages/smooth_app/lib/pages/product/common/product_list_page.dart @@ -450,7 +450,7 @@ class _ProductListPageState extends State barcodes, language, ), - uriHelper: ProductQuery.uriProductHelper, + uriHelper: ProductQuery.getUriProductHelper(), ); final List? freshProducts = searchResult.products; if (freshProducts == null) { diff --git a/packages/smooth_app/lib/pages/product/common/product_refresher.dart b/packages/smooth_app/lib/pages/product/common/product_refresher.dart index d3c4f54b07e..be1553bbf4e 100644 --- a/packages/smooth_app/lib/pages/product/common/product_refresher.dart +++ b/packages/smooth_app/lib/pages/product/common/product_refresher.dart @@ -153,6 +153,36 @@ class ProductRefresher { return true; } + /// Returns the product type stored locally for that product. + static Future getCurrentProductType({ + required final LocalDatabase localDatabase, + required final String barcode, + }) async { + final Product? localProduct = await DaoProduct(localDatabase).get(barcode); + return localProduct?.productType; + } + + /// Returns the list of types to use for that barcode. + Future> getOrderedProductTypes({ + required final LocalDatabase localDatabase, + required final String barcode, + }) async { + final List result = []; + final ProductType? productType = await getCurrentProductType( + localDatabase: localDatabase, + barcode: barcode, + ); + if (productType != null) { + result.add(productType); + } + for (final ProductType value in ProductType.values) { + if (!result.contains(value)) { + result.add(value); + } + } + return result; + } + /// Fetches the product from the server and refreshes the local database. /// /// Silent version. @@ -160,20 +190,34 @@ class ProductRefresher { required final LocalDatabase localDatabase, required final String barcode, }) async { + final List productTypes = await getOrderedProductTypes( + localDatabase: localDatabase, + barcode: barcode, + ); + late UriProductHelper uriProductHelper; + final OpenFoodFactsLanguage language = ProductQuery.getLanguage(); try { - final OpenFoodFactsLanguage language = ProductQuery.getLanguage(); - final ProductResultV3 result = await OpenFoodAPIClient.getProductV3( - getBarcodeQueryConfiguration( - barcode, - language, - ), - uriHelper: ProductQuery.uriProductHelper, - user: ProductQuery.getReadUser(), - ); - if (result.product != null) { - await DaoProduct(localDatabase).put(result.product!, language); - localDatabase.upToDate.setLatestDownloadedProduct(result.product!); - return FetchedProduct.found(result.product!); + for (final ProductType productType in productTypes) { + uriProductHelper = ProductQuery.getUriProductHelper( + productType: productType, + ); + final ProductResultV3 result = await OpenFoodAPIClient.getProductV3( + getBarcodeQueryConfiguration( + barcode, + language, + ), + uriHelper: uriProductHelper, + user: ProductQuery.getReadUser(), + ); + if (result.product != null) { + await DaoProduct(localDatabase).put( + result.product!, + language, + productType: productType, + ); + localDatabase.upToDate.setLatestDownloadedProduct(result.product!); + return FetchedProduct.found(result.product!); + } } return const FetchedProduct.internetNotFound(); } catch (e) { @@ -186,7 +230,7 @@ class ProductRefresher { isConnected: false, ); } - final String host = ProductQuery.uriProductHelper.host; + final String host = uriProductHelper.host; final PingData result = await Ping(host, count: 1).stream.first; return FetchedProduct.error( exceptionString: e.toString(), @@ -208,7 +252,7 @@ class ProductRefresher { final SearchResult searchResult = await OpenFoodAPIClient.searchProducts( ProductQuery.getReadUser(), getBarcodeListQueryConfiguration(barcodes, language), - uriHelper: ProductQuery.uriProductHelper, + uriHelper: ProductQuery.getUriProductHelper(), ); if (searchResult.products == null) { return null; 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 4c0a003929b..cfe8cef68f5 100644 --- a/packages/smooth_app/lib/pages/product/edit_new_packagings.dart +++ b/packages/smooth_app/lib/pages/product/edit_new_packagings.dart @@ -117,6 +117,7 @@ class _EditNewPackagingsState extends State setState(() => _removePackagingAt(deleteIndex)), helper: _helpers[index], categories: upToDateProduct.categories, + productType: upToDateProduct.productType, ), ), ); diff --git a/packages/smooth_app/lib/pages/product/edit_new_packagings_component.dart b/packages/smooth_app/lib/pages/product/edit_new_packagings_component.dart index dcdd3bf889b..6fe1f7379dd 100644 --- a/packages/smooth_app/lib/pages/product/edit_new_packagings_component.dart +++ b/packages/smooth_app/lib/pages/product/edit_new_packagings_component.dart @@ -17,12 +17,14 @@ class EditNewPackagingsComponent extends StatefulWidget { required this.deleteCallback, required this.helper, required this.categories, + required this.productType, }); final String title; final VoidCallback deleteCallback; final EditNewPackagingsHelper helper; final String? categories; + final ProductType? productType; @override State createState() => @@ -58,6 +60,7 @@ class _EditNewPackagingsComponentState iconColor: iconColor, minLengthForSuggestions: 0, categories: widget.categories, + productType: widget.productType, ), _EditTextLine( title: appLocalizations.edit_packagings_element_field_material, @@ -69,6 +72,7 @@ class _EditNewPackagingsComponentState minLengthForSuggestions: 0, categories: widget.categories, shapeProvider: () => widget.helper.controllerShape.text, + productType: widget.productType, ), _EditTextLine( title: appLocalizations.edit_packagings_element_field_recycling, @@ -76,12 +80,14 @@ class _EditNewPackagingsComponentState tagType: TagType.PACKAGING_RECYCLING, iconName: 'recycling', iconColor: iconColor, + productType: widget.productType, ), _EditTextLine( title: appLocalizations.edit_packagings_element_field_quantity, controller: widget.helper.controllerQuantity, iconName: 'quantity', iconColor: iconColor, + productType: widget.productType, ), _EditNumberLine( title: appLocalizations.edit_packagings_element_field_weight, @@ -133,6 +139,7 @@ class _EditTextLine extends StatefulWidget { this.minLengthForSuggestions = 1, this.categories, this.shapeProvider, + required this.productType, }); final String title; @@ -144,6 +151,7 @@ class _EditTextLine extends StatefulWidget { final int minLengthForSuggestions; final String? categories; final String? Function()? shapeProvider; + final ProductType? productType; @override State<_EditTextLine> createState() => _EditTextLineState(); @@ -195,6 +203,7 @@ class _EditTextLineState extends State<_EditTextLine> { minLengthForSuggestions: widget.minLengthForSuggestions, categories: widget.categories, shapeProvider: widget.shapeProvider, + productType: widget.productType, ), ), ), diff --git a/packages/smooth_app/lib/pages/product/edit_ocr/ocr_ingredients_helper.dart b/packages/smooth_app/lib/pages/product/edit_ocr/ocr_ingredients_helper.dart index d2720d98b57..dbc5173ec95 100644 --- a/packages/smooth_app/lib/pages/product/edit_ocr/ocr_ingredients_helper.dart +++ b/packages/smooth_app/lib/pages/product/edit_ocr/ocr_ingredients_helper.dart @@ -90,7 +90,9 @@ class OcrIngredientsHelper extends OcrHelper { getUser(), product.barcode!, language, - uriHelper: ProductQuery.uriProductHelper, + uriHelper: ProductQuery.getUriProductHelper( + productType: product.productType, + ), ); return result.ingredientsTextFromImage; } diff --git a/packages/smooth_app/lib/pages/product/edit_ocr/ocr_packaging_helper.dart b/packages/smooth_app/lib/pages/product/edit_ocr/ocr_packaging_helper.dart index 6590f15a993..78795345465 100644 --- a/packages/smooth_app/lib/pages/product/edit_ocr/ocr_packaging_helper.dart +++ b/packages/smooth_app/lib/pages/product/edit_ocr/ocr_packaging_helper.dart @@ -92,7 +92,9 @@ class OcrPackagingHelper extends OcrHelper { getUser(), product.barcode!, language, - uriHelper: ProductQuery.uriProductHelper, + uriHelper: ProductQuery.getUriProductHelper( + productType: product.productType, + ), ); return result.textFromImage; } 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 83d542fafc9..d9bc6e58495 100644 --- a/packages/smooth_app/lib/pages/product/edit_product_page.dart +++ b/packages/smooth_app/lib/pages/product/edit_product_page.dart @@ -181,34 +181,40 @@ class _EditProductPageState extends State with UpToDateMixin { product: upToDateProduct, ), ), - _getSimpleListTileItem(SimpleInputPageCategoryHelper()), - _ListTitleItem( - leading: - const SvgIcon('assets/cacheTintable/scale-balance.svg'), - title: appLocalizations - .edit_product_form_item_nutrition_facts_title, - subtitle: appLocalizations - .edit_product_form_item_nutrition_facts_subtitle, - onTap: () async { - if (!await ProductRefresher().checkIfLoggedIn( - context, - isLoggedInMandatory: true, - )) { - return; - } - AnalyticsHelper.trackProductEdit( - AnalyticsEditEvents.nutrition_Facts, - barcode, - ); - if (!context.mounted) { - return; - } - await NutritionPageLoaded.showNutritionPage( - product: upToDateProduct, - isLoggedInMandatory: true, - context: context, - ); - }), + if (upToDateProduct.productType == null || + upToDateProduct.productType == ProductType.food) + _getSimpleListTileItem(SimpleInputPageCategoryHelper()) + else + _getSimpleListTileItem(SimpleInputPageCategoryNotFoodHelper()), + if (upToDateProduct.productType != ProductType.beauty && + upToDateProduct.productType != ProductType.product) + _ListTitleItem( + leading: + const SvgIcon('assets/cacheTintable/scale-balance.svg'), + title: appLocalizations + .edit_product_form_item_nutrition_facts_title, + subtitle: appLocalizations + .edit_product_form_item_nutrition_facts_subtitle, + onTap: () async { + if (!await ProductRefresher().checkIfLoggedIn( + context, + isLoggedInMandatory: true, + )) { + return; + } + AnalyticsHelper.trackProductEdit( + AnalyticsEditEvents.nutrition_Facts, + barcode, + ); + if (!context.mounted) { + return; + } + await NutritionPageLoaded.showNutritionPage( + product: upToDateProduct, + isLoggedInMandatory: true, + context: context, + ); + }), _getSimpleListTileItem(SimpleInputPageLabelHelper()), _ListTitleItem( leading: const SvgIcon('assets/cacheTintable/packaging.svg'), diff --git a/packages/smooth_app/lib/pages/product/ordered_nutrients_cache.dart b/packages/smooth_app/lib/pages/product/ordered_nutrients_cache.dart index 9bbdf121a0b..1c08aa7d3a3 100644 --- a/packages/smooth_app/lib/pages/product/ordered_nutrients_cache.dart +++ b/packages/smooth_app/lib/pages/product/ordered_nutrients_cache.dart @@ -59,7 +59,7 @@ class OrderedNutrientsCache { final String string = await OpenFoodAPIClient.getOrderedNutrientsJsonString( country: ProductQuery.getCountry(), language: ProductQuery.getLanguage(), - uriHelper: ProductQuery.uriProductHelper, + uriHelper: ProductQuery.getUriProductHelper(), ); final OrderedNutrients result = OrderedNutrients.fromJson( jsonDecode(string) as Map, @@ -75,6 +75,6 @@ class OrderedNutrientsCache { return 'nutrients.pl' '/${country.offTag}' '/${language.code}' - '/${ProductQuery.uriProductHelper.domain}'; + '/${ProductQuery.getUriProductHelper().domain}'; } } diff --git a/packages/smooth_app/lib/pages/product/product_image_crop_button.dart b/packages/smooth_app/lib/pages/product/product_image_crop_button.dart index 165a07c4485..3164e89430e 100644 --- a/packages/smooth_app/lib/pages/product/product_image_crop_button.dart +++ b/packages/smooth_app/lib/pages/product/product_image_crop_button.dart @@ -53,7 +53,12 @@ class ProductImageCropButton extends ProductImageButton { if (productImage != null) { final int? imageId = int.tryParse(productImage.imgid!); if (imageId != null) { - await _openCropAgainPage(context, imageId, productImage); + await _openCropAgainPage( + context, + imageId, + productImage, + product.productType, + ); return; } } @@ -84,6 +89,7 @@ class ProductImageCropButton extends ProductImageButton { final BuildContext context, final int imageId, final ProductImage productImage, + final ProductType? productType, ) async { final NavigatorState navigatorState = Navigator.of(context); final LocalDatabase localDatabase = context.read(); @@ -94,7 +100,9 @@ class ProductImageCropButton extends ProductImageButton { size: ImageSize.ORIGINAL, ).getUrl( barcode, - uriHelper: ProductQuery.uriProductHelper, + uriHelper: ProductQuery.getUriProductHelper( + productType: productType, + ), ), DaoInt(localDatabase), ); diff --git a/packages/smooth_app/lib/pages/product/product_image_server_button.dart b/packages/smooth_app/lib/pages/product/product_image_server_button.dart index 74ed5113e05..cdc09431a3a 100644 --- a/packages/smooth_app/lib/pages/product/product_image_server_button.dart +++ b/packages/smooth_app/lib/pages/product/product_image_server_button.dart @@ -51,7 +51,11 @@ class ProductImageServerButton extends ProductImageButton { ImageSize.DISPLAY, ); if (rawImages.isNotEmpty) { - await _openGallery(context: context, rawImages: rawImages); + await _openGallery( + context: context, + rawImages: rawImages, + productType: product.productType, + ); return; } @@ -95,12 +99,17 @@ class ProductImageServerButton extends ProductImageButton { ); return; } - await _openGallery(context: context, rawImages: rawImages); + await _openGallery( + context: context, + rawImages: rawImages, + productType: product.productType, + ); } Future _openGallery({ required final BuildContext context, required final List rawImages, + required final ProductType? productType, }) => Navigator.push( context, @@ -111,6 +120,7 @@ class ProductImageServerButton extends ProductImageButton { imageField: imageField, language: language, isLoggedInMandatory: isLoggedInMandatory, + productType: productType, ), ), ); diff --git a/packages/smooth_app/lib/pages/product/simple_input_page_helpers.dart b/packages/smooth_app/lib/pages/product/simple_input_page_helpers.dart index ea673736718..0087300211f 100644 --- a/packages/smooth_app/lib/pages/product/simple_input_page_helpers.dart +++ b/packages/smooth_app/lib/pages/product/simple_input_page_helpers.dart @@ -445,6 +445,12 @@ class SimpleInputPageCategoryHelper extends AbstractSimpleInputPageHelper { AnalyticsEditEvents getAnalyticsEditEvent() => AnalyticsEditEvents.categories; } +class SimpleInputPageCategoryNotFoodHelper + extends SimpleInputPageCategoryHelper { + @override + Widget getIcon() => const Icon(Icons.edit); +} + /// Implementation for "Countries" of an [AbstractSimpleInputPageHelper]. class SimpleInputPageCountryHelper extends AbstractSimpleInputPageHelper { @override diff --git a/packages/smooth_app/lib/pages/product/simple_input_text_field.dart b/packages/smooth_app/lib/pages/product/simple_input_text_field.dart index 0e8a66d612c..8854c0b37ab 100644 --- a/packages/smooth_app/lib/pages/product/simple_input_text_field.dart +++ b/packages/smooth_app/lib/pages/product/simple_input_text_field.dart @@ -18,6 +18,7 @@ class SimpleInputTextField extends StatefulWidget { this.categories, this.shapeProvider, this.padding, + required this.productType, }); final FocusNode focusNode; @@ -31,6 +32,7 @@ class SimpleInputTextField extends StatefulWidget { final String? categories; final String? Function()? shapeProvider; final EdgeInsetsGeometry? padding; + final ProductType? productType; @override State createState() => _SimpleInputTextFieldState(); @@ -54,7 +56,9 @@ class _SimpleInputTextFieldState extends State { user: ProductQuery.getReadUser(), // number of suggestions the user can scroll through: compromise between quantity and readability of the suggestions limit: 15, - uriHelper: ProductQuery.uriProductHelper, + uriHelper: ProductQuery.getUriProductHelper( + productType: widget.productType, + ), ), ); } diff --git a/packages/smooth_app/lib/pages/product/simple_input_widget.dart b/packages/smooth_app/lib/pages/product/simple_input_widget.dart index 9c351f6ae83..f735779af0a 100644 --- a/packages/smooth_app/lib/pages/product/simple_input_widget.dart +++ b/packages/smooth_app/lib/pages/product/simple_input_widget.dart @@ -95,6 +95,7 @@ class _SimpleInputWidgetState extends State { padding: const EdgeInsetsDirectional.only( start: 9.0, ), + productType: widget.product.productType, ), ), Tooltip( diff --git a/packages/smooth_app/lib/pages/user_management/forgot_password_page.dart b/packages/smooth_app/lib/pages/user_management/forgot_password_page.dart index ca298f71335..7260c55bc95 100644 --- a/packages/smooth_app/lib/pages/user_management/forgot_password_page.dart +++ b/packages/smooth_app/lib/pages/user_management/forgot_password_page.dart @@ -37,7 +37,7 @@ class _ForgotPasswordPageState extends State _userIdController.text, country: ProductQuery.getCountry(), language: ProductQuery.getLanguage(), - uriHelper: ProductQuery.uriProductHelper, + uriHelper: ProductQuery.getUriProductHelper(), ); if (status.status == 200) { _send = true; diff --git a/packages/smooth_app/lib/pages/user_management/sign_up_page.dart b/packages/smooth_app/lib/pages/user_management/sign_up_page.dart index 8bbd6b10238..57ef4ab196f 100644 --- a/packages/smooth_app/lib/pages/user_management/sign_up_page.dart +++ b/packages/smooth_app/lib/pages/user_management/sign_up_page.dart @@ -336,7 +336,7 @@ class _SignUpPageState extends State with TraceableClientMixin { orgName: _foodProducer ? _brandController.trimmedText : null, country: ProductQuery.getCountry(), language: ProductQuery.getLanguage(), - uriHelper: ProductQuery.uriProductHelper, + uriHelper: ProductQuery.getUriProductHelper(), ), title: appLocalisations.sign_up_page_action_doing_it, ); diff --git a/packages/smooth_app/lib/query/paged_product_query.dart b/packages/smooth_app/lib/query/paged_product_query.dart index 2e399ef896a..4ef51cd4abc 100644 --- a/packages/smooth_app/lib/query/paged_product_query.dart +++ b/packages/smooth_app/lib/query/paged_product_query.dart @@ -38,7 +38,7 @@ abstract class PagedProductQuery { OpenFoodAPIClient.searchProducts( ProductQuery.getReadUser(), getQueryConfiguration(), - uriHelper: ProductQuery.uriProductHelper, + uriHelper: ProductQuery.getUriProductHelper(), ); AbstractQueryConfiguration getQueryConfiguration(); diff --git a/packages/smooth_app/lib/query/product_query.dart b/packages/smooth_app/lib/query/product_query.dart index 34838e45f06..a9046bd1011 100644 --- a/packages/smooth_app/lib/query/product_query.dart +++ b/packages/smooth_app/lib/query/product_query.dart @@ -164,7 +164,7 @@ abstract class ProductQuery { comment: 'Test user for project smoothie', ); - static late UriProductHelper uriProductHelper; + static late UriProductHelper _uriProductHelper; /// Product helper only for prices. static late UriProductHelper uriPricesHelper; @@ -178,7 +178,7 @@ abstract class ProductQuery { ? uriHelperFoodProd : getTestUriProductHelper(userPreferences); - uriProductHelper = getProductHelper( + _uriProductHelper = getProductHelper( UserPreferencesDevMode.userPreferencesFlagProd, ); uriPricesHelper = getProductHelper( @@ -201,6 +201,42 @@ abstract class ProductQuery { ); } + static ProductType? extractProductType( + final UriProductHelper uriProductHelper, + ) { + final String domain = uriProductHelper.domain; + for (final ProductType productType in ProductType.values) { + if (domain.contains(productType.getDomain())) { + return productType; + } + } + return null; + } + + // TODO(monsieurtanuki): make the parameter "required" + static UriProductHelper getUriProductHelper({ + final ProductType? productType, + }) { + final UriProductHelper currentUriProductHelper = _uriProductHelper; + if (productType == null) { + return currentUriProductHelper; + } + final ProductType? currentProductType = + extractProductType(currentUriProductHelper); + if (currentProductType == null) { + return currentUriProductHelper; + } + if (currentProductType == productType) { + return currentUriProductHelper; + } + return UriProductHelper( + domain: currentUriProductHelper.domain.replaceFirst( + currentProductType.getDomain(), + productType.getDomain(), + ), + ); + } + static List get fields => const [ ProductField.NAME, ProductField.NAME_ALL_LANGUAGES, @@ -255,3 +291,12 @@ 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', + }; +} diff --git a/packages/smooth_app/pubspec.lock b/packages/smooth_app/pubspec.lock index 3442b3a8f1b..2f2b1105394 100644 --- a/packages/smooth_app/pubspec.lock +++ b/packages/smooth_app/pubspec.lock @@ -964,18 +964,18 @@ packages: dependency: transitive description: name: leak_tracker - sha256: "3f87a60e8c63aecc975dda1ceedbc8f24de75f09e4856ea27daf8958f2f0ce05" + sha256: "7f0df31977cb2c0b88585095d168e689669a2cc9b97c309665e3386f3e9d341a" url: "https://pub.dev" source: hosted - version: "10.0.5" + version: "10.0.4" leak_tracker_flutter_testing: dependency: transitive description: name: leak_tracker_flutter_testing - sha256: "932549fb305594d82d7183ecd9fa93463e9914e1b67cacc34bc40906594a1806" + sha256: "06e98f569d004c1315b991ded39924b21af84cf14cc94791b8aea337d25b57f8" url: "https://pub.dev" source: hosted - version: "3.0.5" + version: "3.0.3" leak_tracker_testing: dependency: transitive description: @@ -1028,10 +1028,10 @@ packages: dependency: transitive description: name: material_color_utilities - sha256: f7142bb1154231d7ea5f96bc7bde4bda2a0945d2806bb11670e30b850d56bdec + sha256: "0e0a020085b65b6083975e499759762399b4475f766c21668c4ecca34ea74e5a" url: "https://pub.dev" source: hosted - version: "0.11.1" + version: "0.8.0" matomo_tracker: dependency: "direct main" description: @@ -1044,10 +1044,10 @@ packages: dependency: transitive description: name: meta - sha256: bdb68674043280c3428e9ec998512fb681678676b3c54e773629ffe74419f8c7 + sha256: "7687075e408b093f36e6bbf6c91878cc0d4cd10f409506f7bc996f68220b9136" url: "https://pub.dev" source: hosted - version: "1.15.0" + version: "1.12.0" mgrs_dart: dependency: transitive description: @@ -1100,10 +1100,10 @@ packages: dependency: "direct main" description: name: openfoodfacts - sha256: d3614d291942d7c44a9e5da3eacfe332ac778da75b352a0ce62ebecaed1d26d0 + sha256: "9ae8bfaed74c0e59bfe2cd217e2ad805d2d52710ec563861ea728176b8cd419f" url: "https://pub.dev" source: hosted - version: "3.14.0" + version: "3.15.0" openfoodfacts_flutter_lints: dependency: "direct dev" description: @@ -1269,10 +1269,10 @@ packages: dependency: transitive description: name: platform - sha256: "9b71283fc13df574056616011fb138fd3b793ea47cc509c189a6c3fa5f8a1a65" + sha256: "12220bb4b65720483f8fa9450b4332347737cf8213dd2840d8b2c823e47243ec" url: "https://pub.dev" source: hosted - version: "3.1.5" + version: "3.1.4" plugin_platform_interface: dependency: "direct dev" description: @@ -1599,10 +1599,10 @@ packages: dependency: transitive description: name: test_api - sha256: "5b8a98dafc4d5c4c9c72d8b31ab2b23fc13422348d2997120294d3bac86b4ddb" + sha256: "9955ae474176f7ac8ee4e989dadfb411a58c30415bcfb648fa04b2b8a03afa7f" url: "https://pub.dev" source: hosted - version: "0.7.2" + version: "0.7.0" typed_data: dependency: transitive description: @@ -1743,10 +1743,10 @@ packages: dependency: transitive description: name: vm_service - sha256: "5c5f338a667b4c644744b661f309fb8080bb94b18a7e91ef1dbd343bed00ed6d" + sha256: "3923c89304b715fb1eb6423f017651664a03bf5f4b29983627c4da791f74a4ec" url: "https://pub.dev" source: hosted - version: "14.2.5" + version: "14.2.1" watcher: dependency: transitive description: diff --git a/packages/smooth_app/pubspec.yaml b/packages/smooth_app/pubspec.yaml index 13b6614283b..d9d68e07036 100644 --- a/packages/smooth_app/pubspec.yaml +++ b/packages/smooth_app/pubspec.yaml @@ -99,7 +99,7 @@ dependencies: path: ../scanner/zxing - openfoodfacts: 3.14.0 + openfoodfacts: 3.15.0 # openfoodfacts: # path: ../../../openfoodfacts-dart