diff --git a/packages/smooth_app/lib/background/background_task_crop.dart b/packages/smooth_app/lib/background/background_task_crop.dart index ae6078cfd7a..d944a092765 100644 --- a/packages/smooth_app/lib/background/background_task_crop.dart +++ b/packages/smooth_app/lib/background/background_task_crop.dart @@ -101,6 +101,7 @@ class BackgroundTaskCrop extends AbstractBackgroundTask { /// Adds the background task about uploading a product image. static Future addTask( final String barcode, { + required final OpenFoodFactsLanguage language, required final int imageId, required final ImageField imageField, required final File croppedFile, @@ -117,6 +118,7 @@ class BackgroundTaskCrop extends AbstractBackgroundTask { barcode, ); final AbstractBackgroundTask task = _getNewTask( + language, barcode, imageId, imageField, @@ -137,6 +139,7 @@ class BackgroundTaskCrop extends AbstractBackgroundTask { /// Returns a new background task about cropping an existing image. static BackgroundTaskCrop _getNewTask( + final OpenFoodFactsLanguage language, final String barcode, final int imageId, final ImageField imageField, @@ -160,13 +163,13 @@ class BackgroundTaskCrop extends AbstractBackgroundTask { cropY1: cropY1, cropX2: cropX2, cropY2: cropY2, - languageCode: ProductQuery.getLanguage().code, + languageCode: language.code, user: jsonEncode(ProductQuery.getUser().toJson()), country: ProductQuery.getCountry()!.offTag, stamp: BackgroundTaskImage.getStamp( barcode, imageField.offTag, - ProductQuery.getLanguage().code, + language.code, ), ); diff --git a/packages/smooth_app/lib/background/background_task_image.dart b/packages/smooth_app/lib/background/background_task_image.dart index e033a0ad5ec..07153035588 100644 --- a/packages/smooth_app/lib/background/background_task_image.dart +++ b/packages/smooth_app/lib/background/background_task_image.dart @@ -117,6 +117,7 @@ class BackgroundTaskImage extends AbstractBackgroundTask { /// Adds the background task about uploading a product image. static Future addTask( final String barcode, { + required final OpenFoodFactsLanguage language, required final ImageField imageField, required final File fullFile, required final File croppedFile, @@ -133,6 +134,7 @@ class BackgroundTaskImage extends AbstractBackgroundTask { barcode, ); final AbstractBackgroundTask task = _getNewTask( + language, barcode, imageField, fullFile, @@ -153,6 +155,7 @@ class BackgroundTaskImage extends AbstractBackgroundTask { /// Returns a new background task about changing a product. static BackgroundTaskImage _getNewTask( + final OpenFoodFactsLanguage language, final String barcode, final ImageField imageField, final File fullFile, @@ -176,13 +179,13 @@ class BackgroundTaskImage extends AbstractBackgroundTask { cropY1: cropY1, cropX2: cropX2, cropY2: cropY2, - languageCode: ProductQuery.getLanguage().code, + languageCode: language.code, user: jsonEncode(ProductQuery.getUser().toJson()), country: ProductQuery.getCountry()!.offTag, stamp: getStamp( barcode, imageField.offTag, - ProductQuery.getLanguage().code, + language.code, ), ); diff --git a/packages/smooth_app/lib/background/background_task_unselect.dart b/packages/smooth_app/lib/background/background_task_unselect.dart index 88c9e3dbfbd..a2dbd9763a0 100644 --- a/packages/smooth_app/lib/background/background_task_unselect.dart +++ b/packages/smooth_app/lib/background/background_task_unselect.dart @@ -9,6 +9,7 @@ import 'package:smooth_app/background/background_task_image.dart'; import 'package:smooth_app/background/background_task_refresh_later.dart'; import 'package:smooth_app/data_models/operation_type.dart'; import 'package:smooth_app/database/local_database.dart'; +import 'package:smooth_app/helpers/image_field_extension.dart'; import 'package:smooth_app/query/product_query.dart'; /// Background task about unselecting a product image. @@ -74,6 +75,7 @@ class BackgroundTaskUnselect extends AbstractBackgroundTask { final String barcode, { required final ImageField imageField, required final State widget, + required final OpenFoodFactsLanguage language, }) async { final LocalDatabase localDatabase = widget.context.read(); final String uniqueId = await _operationType.getNewKey( @@ -84,6 +86,7 @@ class BackgroundTaskUnselect extends AbstractBackgroundTask { barcode, imageField, uniqueId, + language, ); await task.addToManager(localDatabase, widget: widget); } @@ -97,20 +100,21 @@ class BackgroundTaskUnselect extends AbstractBackgroundTask { final String barcode, final ImageField imageField, final String uniqueId, + final OpenFoodFactsLanguage language, ) => BackgroundTaskUnselect._( uniqueId: uniqueId, barcode: barcode, processName: _PROCESS_NAME, imageField: imageField.offTag, - languageCode: ProductQuery.getLanguage().code, + languageCode: language.code, user: jsonEncode(ProductQuery.getUser().toJson()), country: ProductQuery.getCountry()!.offTag, // same stamp as image upload stamp: BackgroundTaskImage.getStamp( barcode, imageField.offTag, - ProductQuery.getLanguage().code, + language.code, ), ); @@ -123,6 +127,7 @@ class BackgroundTaskUnselect extends AbstractBackgroundTask { final LocalDatabase localDatabase, final bool success, ) async { + // TODO(monsieurtanuki): we should also remove the hypothetical transient file, shouldn't we? localDatabase.upToDate.terminate(uniqueId); localDatabase.notifyListeners(); if (success) { @@ -150,22 +155,7 @@ class BackgroundTaskUnselect extends AbstractBackgroundTask { /// Here we put an empty string instead, to be understood as "force to null!". Product _getUnselectedProduct() { final Product result = Product(barcode: barcode); - switch (ImageField.fromOffTag(imageField)!) { - case ImageField.FRONT: - result.imageFrontUrl = ''; - break; - case ImageField.INGREDIENTS: - result.imageIngredientsUrl = ''; - break; - case ImageField.NUTRITION: - result.imageNutritionUrl = ''; - break; - case ImageField.PACKAGING: - result.imagePackagingUrl = ''; - break; - case ImageField.OTHER: - // We do nothing. Actually we're not supposed to unselect other images. - } + ImageField.fromOffTag(imageField)!.setUrl(result, ''); return result; } } diff --git a/packages/smooth_app/lib/cards/data_cards/image_upload_card.dart b/packages/smooth_app/lib/cards/data_cards/image_upload_card.dart index 8d0f654a801..cadb04c979a 100644 --- a/packages/smooth_app/lib/cards/data_cards/image_upload_card.dart +++ b/packages/smooth_app/lib/cards/data_cards/image_upload_card.dart @@ -46,6 +46,7 @@ class _ImageUploadCardState extends State { this, barcode: widget.product.barcode!, imageField: widget.productImageData.imageField, + language: ProductQuery.getLanguage(), ), icon: const Icon(Icons.add_a_photo), label: Text( diff --git a/packages/smooth_app/lib/database/transient_file.dart b/packages/smooth_app/lib/database/transient_file.dart index 34227f57631..1a25bb8b766 100644 --- a/packages/smooth_app/lib/database/transient_file.dart +++ b/packages/smooth_app/lib/database/transient_file.dart @@ -58,13 +58,20 @@ class TransientFile { return File(path); } - /// Returns the key of the transient image for [imageField] and [barcode]. + /// Returns the key of the transient image. static String _getImageKey( final ImageField imageField, final String barcode, final OpenFoodFactsLanguage language, ) => - '$barcode;$imageField;${language.code}'; + '${_getImageKeyPrefix(imageField, barcode)}${language.code}'; + + /// Returns the key prefix of the transient image (without language). + static String _getImageKeyPrefix( + final ImageField imageField, + final String barcode, + ) => + '$barcode;$imageField;'; /// Returns a way to display the image, either locally or from the server. static ImageProvider? getImageProvider( @@ -109,4 +116,28 @@ class TransientFile { ) => getImage(imageData.imageField, barcode, language) == null && imageData.imageUrl != null; + + /// Returns the languages that have currently transient images. + static Iterable getImageLanguages( + final ImageField imageField, + final String barcode, + ) { + final Set result = {}; + final String prefix = _getImageKeyPrefix(imageField, barcode); + final int prefixLength = prefix.length; + for (final String key in _transientFiles.keys) { + if (key.length <= prefixLength) { + continue; + } + if (!key.startsWith(prefix)) { + continue; + } + final String lc = key.substring(prefixLength); + final OpenFoodFactsLanguage language = LanguageHelper.fromJson(lc); + if (language != OpenFoodFactsLanguage.UNDEFINED) { + result.add(language); + } + } + return result; + } } diff --git a/packages/smooth_app/lib/helpers/image_field_extension.dart b/packages/smooth_app/lib/helpers/image_field_extension.dart index a37b745c660..76838786ac4 100644 --- a/packages/smooth_app/lib/helpers/image_field_extension.dart +++ b/packages/smooth_app/lib/helpers/image_field_extension.dart @@ -9,7 +9,15 @@ extension ImageFieldSmoothieExtension on ImageField { ImageField.PACKAGING, ]; - String? getImageFieldUrl(final Product product) { + static const List orderedAll = [ + ImageField.FRONT, + ImageField.INGREDIENTS, + ImageField.NUTRITION, + ImageField.PACKAGING, + ImageField.OTHER, + ]; + + String? getUrl(final Product product) { switch (this) { case ImageField.FRONT: return product.imageFrontUrl; @@ -24,6 +32,25 @@ extension ImageFieldSmoothieExtension on ImageField { } } + void setUrl(final Product product, final String url) { + switch (this) { + case ImageField.FRONT: + product.imageFrontUrl = url; + break; + case ImageField.INGREDIENTS: + product.imageIngredientsUrl = url; + break; + case ImageField.NUTRITION: + product.imageNutritionUrl = url; + break; + case ImageField.PACKAGING: + product.imagePackagingUrl = url; + break; + case ImageField.OTHER: + // We do nothing. + } + } + String getProductImageButtonText(final AppLocalizations appLocalizations) { switch (this) { case ImageField.FRONT: @@ -70,4 +97,19 @@ extension ImageFieldSmoothieExtension on ImageField { return appLocalizations.more_photos; } } + + String getAddPhotoButtonText(final AppLocalizations appLocalizations) { + switch (this) { + case ImageField.FRONT: + return appLocalizations.front_packaging_photo_button_label; + case ImageField.INGREDIENTS: + return appLocalizations.ingredients_photo_button_label; + case ImageField.NUTRITION: + return appLocalizations.nutritional_facts_photo_button_label; + case ImageField.PACKAGING: + return appLocalizations.recycling_photo_button_label; + case ImageField.OTHER: + return appLocalizations.other_interesting_photo_button_label; + } + } } diff --git a/packages/smooth_app/lib/helpers/product_cards_helper.dart b/packages/smooth_app/lib/helpers/product_cards_helper.dart index eec64b0291a..49cecaa31a9 100644 --- a/packages/smooth_app/lib/helpers/product_cards_helper.dart +++ b/packages/smooth_app/lib/helpers/product_cards_helper.dart @@ -193,11 +193,15 @@ List getProductMainImagesData( return result; } +/// Returns data about the "best" image: for the language, or the default. +/// +/// With [forceLanguage] you say you don't want the default as a fallback. ProductImageData getProductImageData( final Product product, final ImageField imageField, - final OpenFoodFactsLanguage language, -) { + final OpenFoodFactsLanguage language, { + final bool forceLanguage = false, +}) { final ProductImage? productImage = getLocalizedProductImage( product, imageField, @@ -206,11 +210,12 @@ ProductImageData getProductImageData( final String? imageUrl; final OpenFoodFactsLanguage? imageLanguage; if (productImage != null) { + // we found a localized version for this image imageLanguage = language; imageUrl = getLocalizedProductImageUrl(product, productImage); } else { imageLanguage = null; - imageUrl = imageField.getImageFieldUrl(product); + imageUrl = forceLanguage ? null : imageField.getUrl(product); } return ProductImageData( @@ -257,29 +262,6 @@ List> getSelectedImages( return result.entries.toList(); } -OpenFoodFactsLanguage? getProductImageUrlLanguage( - final Product product, - final ImageField imageField, - final String url, -) { - if (product.images == null) { - return null; - } - for (final ProductImage productImage in product.images!) { - if (productImage.field != imageField || - productImage.language == null || - productImage.rev == null) { - continue; - } - final String localizedUrl = - getLocalizedProductImageUrl(product, productImage); - if (url == localizedUrl) { - return productImage.language; - } - } - return null; -} - // TODO(monsieurtanuki): move to off-dart in ImageHelper String _getBarcodeSubPath(final String barcode) { if (barcode.length < 9) { @@ -295,26 +277,31 @@ String _getBarcodeSubPath(final String barcode) { return '$p1/$p2/$p3/$p4'; } +String _getImageRoot() => + OpenFoodAPIConfiguration.globalQueryType == QueryType.PROD + ? 'https://images.openfoodfacts.org/images/products' + : 'https://images.openfoodfacts.net/images/products'; + String getLocalizedProductImageUrl( final Product product, final ProductImage productImage, ) => -// TODO(monsieurtanuki): make it work in TEST env too (= .net) - 'https://images.openfoodfacts.org/images/products/' + '${_getImageRoot()}/' '${_getBarcodeSubPath(product.barcode!)}/' '${ImageHelper.getProductImageFilename(productImage, imageSize: ImageSize.DISPLAY)}'; -/// Returns the languages for which image fields have images. +/// Returns the languages for which [imageField] has images for that [product]. Iterable getProductImageLanguages( final Product product, - final Iterable imageFields, + final ImageField imageField, ) { final Set result = {}; + result.addAll(TransientFile.getImageLanguages(imageField, product.barcode!)); if (product.images == null) { return result; } for (final ProductImage productImage in product.images!) { - if (imageFields.contains(productImage.field) && + if (imageField == productImage.field && productImage.rev != null && productImage.language != null) { result.add(productImage.language!); diff --git a/packages/smooth_app/lib/l10n/app_en.arb b/packages/smooth_app/lib/l10n/app_en.arb index df8bb437ffc..27193a997c2 100644 --- a/packages/smooth_app/lib/l10n/app_en.arb +++ b/packages/smooth_app/lib/l10n/app_en.arb @@ -1583,6 +1583,14 @@ "@edit_photo_select_existing_downloaded_none": { "description": "Error message" }, + "edit_photo_language_not_this_one": "No image in that language yet", + "@edit_photo_language_not_this_one": { + "description": "Warning message: for this product and this field, there are 'translated' images, but not in that language" + }, + "edit_photo_language_none": "No image yet", + "@edit_photo_language_none": { + "description": "Warning message: for this product and this field, there are no images at all, in any language" + }, "category_picker_screen_title": "Categories", "@category_picker_screen_title": { "description": "Categories picker screen title" diff --git a/packages/smooth_app/lib/l10n/app_fr.arb b/packages/smooth_app/lib/l10n/app_fr.arb index d4f7cedd5d9..1ddebec7235 100644 --- a/packages/smooth_app/lib/l10n/app_fr.arb +++ b/packages/smooth_app/lib/l10n/app_fr.arb @@ -1574,6 +1574,14 @@ "@edit_photo_select_existing_downloaded_none": { "description": "Error message" }, + "edit_photo_language_not_this_one": "Pas encore de photo dans cette langue", + "@edit_photo_language_not_this_one": { + "description": "Warning message: for this product and this field, there are 'translated' images, but not in that language" + }, + "edit_photo_language_none": "Pas encore de photo", + "@edit_photo_language_none": { + "description": "Warning message: for this product and this field, there are no images at all, in any language" + }, "category_picker_screen_title": "Catégories", "@category_picker_screen_title": { "description": "Categories picker screen title" 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 8fd5c1732cf..ade65ec584f 100644 --- a/packages/smooth_app/lib/pages/image/uploaded_image_gallery.dart +++ b/packages/smooth_app/lib/pages/image/uploaded_image_gallery.dart @@ -18,12 +18,16 @@ class UploadedImageGallery extends StatelessWidget { required this.barcode, required this.imageIds, required this.imageField, + required this.language, }); final String barcode; final List imageIds; final ImageField imageField; + /// Language for which we'll save the cropped image. + final OpenFoodFactsLanguage language; + @override Widget build(BuildContext context) { final AppLocalizations appLocalizations = AppLocalizations.of(context); @@ -77,6 +81,7 @@ class UploadedImageGallery extends StatelessWidget { inputFile: imageFile, imageId: imageId, initiallyDifferent: true, + language: language, ), fullscreenDialog: true, ), diff --git a/packages/smooth_app/lib/pages/image_crop_page.dart b/packages/smooth_app/lib/pages/image_crop_page.dart index 32e1b284f15..b3be78baefa 100644 --- a/packages/smooth_app/lib/pages/image_crop_page.dart +++ b/packages/smooth_app/lib/pages/image_crop_page.dart @@ -91,6 +91,7 @@ Future confirmAndUploadNewPicture( final State widget, { required final ImageField imageField, required final String barcode, + required final OpenFoodFactsLanguage language, }) async { final XFile? croppedPhoto = await pickImageFile(widget); if (croppedPhoto == null) { @@ -107,6 +108,7 @@ Future confirmAndUploadNewPicture( imageField: imageField, inputFile: File(croppedPhoto.path), initiallyDifferent: true, + language: language, ), fullscreenDialog: true, ), diff --git a/packages/smooth_app/lib/pages/product/add_new_product_page.dart b/packages/smooth_app/lib/pages/product/add_new_product_page.dart index e24d44168b5..25eb189eca6 100644 --- a/packages/smooth_app/lib/pages/product/add_new_product_page.dart +++ b/packages/smooth_app/lib/pages/product/add_new_product_page.dart @@ -9,24 +9,17 @@ import 'package:smooth_app/database/dao_product_list.dart'; import 'package:smooth_app/database/local_database.dart'; import 'package:smooth_app/generic_lib/buttons/smooth_large_button_with_icon.dart'; import 'package:smooth_app/generic_lib/design_constants.dart'; +import 'package:smooth_app/helpers/image_field_extension.dart'; import 'package:smooth_app/pages/image_crop_page.dart'; import 'package:smooth_app/pages/product/add_basic_details_page.dart'; import 'package:smooth_app/pages/product/nutrition_page_loaded.dart'; +import 'package:smooth_app/query/product_query.dart'; import 'package:smooth_app/widgets/smooth_scaffold.dart'; const EdgeInsetsGeometry _ROW_PADDING_TOP = EdgeInsetsDirectional.only( top: VERY_LARGE_SPACE, ); -// Buttons to add images will appear in this order. -const List _SORTED_IMAGE_FIELD_LIST = [ - ImageField.FRONT, - ImageField.NUTRITION, - ImageField.INGREDIENTS, - ImageField.PACKAGING, - ImageField.OTHER, -]; - /// "Create a product we couldn't find on the server" page. class AddNewProductPage extends StatefulWidget { const AddNewProductPage(this.barcode); @@ -144,7 +137,8 @@ class _AddNewProductPageState extends State { List _buildImageCaptureRows(BuildContext context) { final List rows = []; // First build rows for buttons to ask user to upload images. - for (final ImageField imageField in _SORTED_IMAGE_FIELD_LIST) { + for (final ImageField imageField + in ImageFieldSmoothieExtension.orderedAll) { // Always add a button to "Add other photos" because there can be multiple // "other photos" uploaded by the user. if (imageField == ImageField.OTHER) { @@ -176,13 +170,14 @@ class _AddNewProductPageState extends State { return Padding( padding: _ROW_PADDING_TOP, child: SmoothLargeButtonWithIcon( - text: _getAddPhotoButtonText(context, imageField), + text: imageField.getAddPhotoButtonText(AppLocalizations.of(context)), icon: Icons.camera_alt, onPressed: () async { final File? finalPhoto = await confirmAndUploadNewPicture( this, barcode: widget.barcode, imageField: imageField, + language: ProductQuery.getLanguage(), ); if (finalPhoto != null) { setState(() { @@ -199,28 +194,14 @@ class _AddNewProductPageState extends State { } Widget _buildImageUploadedRow( - BuildContext context, ImageField imageType, File image) { - return _InfoAddedRow( - text: _getAddPhotoButtonText(context, imageType), - imgStart: image, - ); - } - - String _getAddPhotoButtonText(BuildContext context, ImageField imageType) { - final AppLocalizations appLocalizations = AppLocalizations.of(context); - switch (imageType) { - case ImageField.FRONT: - return appLocalizations.front_packaging_photo_button_label; - case ImageField.INGREDIENTS: - return appLocalizations.ingredients_photo_button_label; - case ImageField.NUTRITION: - return appLocalizations.nutritional_facts_photo_button_label; - case ImageField.PACKAGING: - return appLocalizations.recycling_photo_button_label; - case ImageField.OTHER: - return appLocalizations.other_interesting_photo_button_label; - } - } + BuildContext context, + ImageField imageField, + File image, + ) => + _InfoAddedRow( + text: imageField.getAddPhotoButtonText(AppLocalizations.of(context)), + imgStart: image, + ); Widget _buildNutritionInputButton() { // if the nutrition image is null, ie no image , we return nothing diff --git a/packages/smooth_app/lib/pages/product/edit_ingredients_page.dart b/packages/smooth_app/lib/pages/product/edit_ingredients_page.dart index f1f5f5608ba..f22aa422078 100644 --- a/packages/smooth_app/lib/pages/product/edit_ingredients_page.dart +++ b/packages/smooth_app/lib/pages/product/edit_ingredients_page.dart @@ -20,7 +20,6 @@ import 'package:smooth_app/pages/product/multilingual_helper.dart'; import 'package:smooth_app/pages/product/ocr_helper.dart'; import 'package:smooth_app/pages/product/product_image_local_button.dart'; import 'package:smooth_app/pages/product/product_image_server_button.dart'; -import 'package:smooth_app/query/product_query.dart'; import 'package:smooth_app/widgets/smooth_app_bar.dart'; import 'package:smooth_app/widgets/smooth_scaffold.dart'; @@ -213,7 +212,8 @@ class _EditOcrPageState extends State { Widget _getOcrWidget(final ProductImageData productImageData) { final AppLocalizations appLocalizations = AppLocalizations.of(context); - final OpenFoodFactsLanguage language = ProductQuery.getLanguage(); + final OpenFoodFactsLanguage language = + _multilingualHelper.getCurrentLanguage(); return Align( alignment: AlignmentDirectional.bottomStart, child: Column( @@ -241,6 +241,7 @@ class _EditOcrPageState extends State { child: ProductImageServerButton( barcode: widget.product.barcode!, imageField: _helper.getImageField(), + language: language, ), ), ), @@ -256,6 +257,7 @@ class _EditOcrPageState extends State { ), barcode: widget.product.barcode!, imageField: _helper.getImageField(), + language: language, ), ), ), @@ -327,6 +329,7 @@ class _EditOcrPageState extends State { this, imageField: ImageField.OTHER, barcode: widget.product.barcode!, + language: language, ), iconData: Icons.add_a_photo, ), 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 475d44cdef4..6fc02aa8676 100644 --- a/packages/smooth_app/lib/pages/product/edit_new_packagings.dart +++ b/packages/smooth_app/lib/pages/product/edit_new_packagings.dart @@ -16,6 +16,7 @@ import 'package:smooth_app/pages/product/edit_new_packagings_helper.dart'; import 'package:smooth_app/pages/product/may_exit_page_helper.dart'; import 'package:smooth_app/pages/product/simple_input_number_field.dart'; import 'package:smooth_app/pages/product/simple_input_text_field.dart'; +import 'package:smooth_app/query/product_query.dart'; import 'package:smooth_app/themes/color_schemes.dart'; import 'package:smooth_app/widgets/smooth_app_bar.dart'; import 'package:smooth_app/widgets/smooth_scaffold.dart'; @@ -164,6 +165,7 @@ class _EditNewPackagingsState extends State { this, imageField: ImageField.OTHER, barcode: widget.product.barcode!, + language: ProductQuery.getLanguage(), ), iconData: Icons.add_a_photo, ), 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 db4d4a95edb..841afcfb9d0 100644 --- a/packages/smooth_app/lib/pages/product/nutrition_page_loaded.dart +++ b/packages/smooth_app/lib/pages/product/nutrition_page_loaded.dart @@ -20,7 +20,6 @@ import 'package:smooth_app/pages/product/ordered_nutrients_cache.dart'; import 'package:smooth_app/pages/product/product_image_swipeable_view.dart'; import 'package:smooth_app/pages/product/simple_input_number_field.dart'; import 'package:smooth_app/pages/text_field_helper.dart'; -import 'package:smooth_app/query/product_query.dart'; import 'package:smooth_app/widgets/smooth_app_bar.dart'; import 'package:smooth_app/widgets/smooth_scaffold.dart'; @@ -354,7 +353,6 @@ class _NutritionPageLoadedState extends State { builder: (_) => ProductImageSwipeableView.imageField( imageField: ImageField.NUTRITION, product: _product, - language: ProductQuery.getLanguage(), ), ), ), diff --git a/packages/smooth_app/lib/pages/product/product_image_gallery_view.dart b/packages/smooth_app/lib/pages/product/product_image_gallery_view.dart index 64158a3dea3..2323adf1ac0 100644 --- a/packages/smooth_app/lib/pages/product/product_image_gallery_view.dart +++ b/packages/smooth_app/lib/pages/product/product_image_gallery_view.dart @@ -5,7 +5,6 @@ import 'package:provider/provider.dart'; import 'package:smooth_app/background/background_task_manager.dart'; import 'package:smooth_app/data_models/product_image_data.dart'; import 'package:smooth_app/database/local_database.dart'; -import 'package:smooth_app/generic_lib/widgets/language_selector.dart'; import 'package:smooth_app/generic_lib/widgets/smooth_back_button.dart'; import 'package:smooth_app/generic_lib/widgets/smooth_list_tile_card.dart'; import 'package:smooth_app/helpers/analytics_helper.dart'; @@ -37,7 +36,6 @@ class _ProductImageGalleryViewState extends State { late Product _product; late List> _selectedImages; - late OpenFoodFactsLanguage _currentLanguage; String get _barcode => _initialProduct.barcode!; @@ -47,7 +45,6 @@ class _ProductImageGalleryViewState extends State { _initialProduct = widget.product; _localDatabase = context.read(); _localDatabase.upToDate.showInterest(_barcode); - _currentLanguage = ProductQuery.getLanguage(); } @override @@ -63,7 +60,7 @@ class _ProductImageGalleryViewState extends State { final ThemeData theme = Theme.of(context); context.watch(); _product = _localDatabase.upToDate.getLocalUpToDate(_initialProduct); - _selectedImages = getSelectedImages(_product, _currentLanguage); + _selectedImages = getSelectedImages(_product, ProductQuery.getLanguage()); return SmoothScaffold( appBar: SmoothAppBar( title: Text(appLocalizations.edit_product_form_item_photos_title), @@ -86,6 +83,7 @@ class _ProductImageGalleryViewState extends State { this, imageField: ImageField.OTHER, barcode: _barcode, + language: ProductQuery.getLanguage(), ); }, label: Text(appLocalizations.add_photo_button_label), @@ -97,25 +95,8 @@ class _ProductImageGalleryViewState extends State { widget: this, ), child: ListView.builder( - // the language header and then the images - itemCount: 1 + _selectedImages.length, + itemCount: _selectedImages.length, itemBuilder: (final BuildContext context, int index) { - if (index == 0) { - return LanguageSelector( - setLanguage: (final OpenFoodFactsLanguage? newLanguage) async { - if (newLanguage == null || newLanguage == _currentLanguage) { - return; - } - setState(() => _currentLanguage = newLanguage); - }, - displayedLanguage: _currentLanguage, - selectedLanguages: getProductImageLanguages( - _product, - ImageFieldSmoothieExtension.orderedMain, - ), - ); - } - index--; final MapEntry item = _selectedImages[index]; return SmoothListTileCard.image( @@ -145,7 +126,6 @@ class _ProductImageGalleryViewState extends State { builder: (_) => ProductImageSwipeableView( initialImageIndex: initialImageIndex, product: _product, - language: _currentLanguage, ), ), ); diff --git a/packages/smooth_app/lib/pages/product/product_image_local_button.dart b/packages/smooth_app/lib/pages/product/product_image_local_button.dart index 58c1e848374..b6f1a85518f 100644 --- a/packages/smooth_app/lib/pages/product/product_image_local_button.dart +++ b/packages/smooth_app/lib/pages/product/product_image_local_button.dart @@ -11,11 +11,13 @@ class ProductImageLocalButton extends StatefulWidget { required this.firstPhoto, required this.barcode, required this.imageField, + required this.language, }); final bool firstPhoto; final String barcode; final ImageField imageField; + final OpenFoodFactsLanguage language; @override State createState() => @@ -47,6 +49,7 @@ class _ProductImageLocalButtonState extends State { this, imageField: widget.imageField, barcode: widget.barcode, + language: widget.language, ); } } 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 272b9d02796..633df14847f 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 @@ -13,10 +13,12 @@ class ProductImageServerButton extends StatelessWidget { const ProductImageServerButton({ required this.barcode, required this.imageField, + required this.language, }); final String barcode; final ImageField imageField; + final OpenFoodFactsLanguage language; @override Widget build(BuildContext context) => EditImageButton( @@ -76,6 +78,7 @@ class ProductImageServerButton extends StatelessWidget { barcode: barcode, imageIds: result, imageField: imageField, + language: language, ), ), ); diff --git a/packages/smooth_app/lib/pages/product/product_image_swipeable_view.dart b/packages/smooth_app/lib/pages/product/product_image_swipeable_view.dart index 3891a869467..310287deea8 100644 --- a/packages/smooth_app/lib/pages/product/product_image_swipeable_view.dart +++ b/packages/smooth_app/lib/pages/product/product_image_swipeable_view.dart @@ -10,6 +10,7 @@ import 'package:smooth_app/generic_lib/widgets/smooth_back_button.dart'; import 'package:smooth_app/helpers/image_field_extension.dart'; import 'package:smooth_app/helpers/product_cards_helper.dart'; import 'package:smooth_app/pages/product/product_image_viewer.dart'; +import 'package:smooth_app/query/product_query.dart'; import 'package:smooth_app/widgets/smooth_scaffold.dart'; /// Widget to display swipeable product images of particular category. @@ -19,20 +20,17 @@ class ProductImageSwipeableView extends StatefulWidget { super.key, required this.product, required this.initialImageIndex, - required this.language, }) : imageField = null; /// Version with only one main [ImageField]. const ProductImageSwipeableView.imageField({ super.key, required this.product, - required this.language, required this.imageField, }) : initialImageIndex = 0; final Product product; final int initialImageIndex; - final OpenFoodFactsLanguage language; final ImageField? imageField; @override @@ -63,7 +61,7 @@ class _ProductImageSwipeableViewState extends State { initialPage: widget.initialImageIndex, ); _currentImageDataIndex = ValueNotifier(widget.initialImageIndex); - _currentLanguage = widget.language; + _currentLanguage = ProductQuery.getLanguage(); } @override diff --git a/packages/smooth_app/lib/pages/product/product_image_viewer.dart b/packages/smooth_app/lib/pages/product/product_image_viewer.dart index d57c4e0aa73..6e1bfaf8288 100644 --- a/packages/smooth_app/lib/pages/product/product_image_viewer.dart +++ b/packages/smooth_app/lib/pages/product/product_image_viewer.dart @@ -48,7 +48,6 @@ class _ProductImageViewerState extends State { late final Product _initialProduct; late final LocalDatabase _localDatabase; late ProductImageData _imageData; - late OpenFoodFactsLanguage _actualImageLanguage; String get _barcode => _initialProduct.barcode!; @@ -74,27 +73,19 @@ class _ProductImageViewerState extends State { _imageData = getProductImageData( _product, widget.imageField, - // we want the image for this language, if possible widget.language, + forceLanguage: true, ); - // fallback language - _actualImageLanguage = widget.language; - if (_imageData.language == null && _imageData.imageUrl != null) { - // let's find the language the imageUrl is related to. - final OpenFoodFactsLanguage? language = getProductImageUrlLanguage( - _product, - _imageData.imageField, - _imageData.imageUrl!, - ); - if (language != null) { - _actualImageLanguage = language; - } - } final ImageProvider? imageProvider = TransientFile.getImageProvider( _imageData, _barcode, widget.language, ); + final Iterable selectedLanguages = + getProductImageLanguages( + _product, + widget.imageField, + ); return Column( mainAxisAlignment: MainAxisAlignment.spaceBetween, crossAxisAlignment: CrossAxisAlignment.center, @@ -103,7 +94,25 @@ class _ProductImageViewerState extends State { child: Padding( padding: const EdgeInsets.all(MINIMUM_TOUCH_SIZE / 2), child: imageProvider == null - ? const SizedBox.expand(child: PictureNotFound()) + ? Stack( + children: [ + const SizedBox.expand(child: PictureNotFound()), + Center( + child: Text( + selectedLanguages.isEmpty + ? appLocalizations.edit_photo_language_none + : appLocalizations + .edit_photo_language_not_this_one, + style: Theme.of(context) + .textTheme + .headlineMedium + ?.copyWith(color: Colors.black) ?? + const TextStyle(color: Colors.black), + textAlign: TextAlign.center, + ), + ), + ], + ) : PhotoView( minScale: 0.2, imageProvider: imageProvider, @@ -126,11 +135,8 @@ class _ProductImageViewerState extends State { padding: const EdgeInsets.symmetric(horizontal: SMALL_SPACE), child: LanguageSelector( setLanguage: widget.setLanguage, - displayedLanguage: _actualImageLanguage, - selectedLanguages: getProductImageLanguages( - _product, - [widget.imageField], - ), + displayedLanguage: widget.language, + selectedLanguages: selectedLanguages, foregroundColor: Colors.white, ), ), @@ -148,6 +154,7 @@ class _ProductImageViewerState extends State { child: ProductImageServerButton( barcode: _barcode, imageField: widget.imageField, + language: widget.language, ), ), ), @@ -158,6 +165,7 @@ class _ProductImageViewerState extends State { firstPhoto: imageProvider == null, barcode: _barcode, imageField: widget.imageField, + language: widget.language, ), ), ), @@ -274,6 +282,7 @@ class _ProductImageViewerState extends State { _barcode, imageField: widget.imageField, widget: this, + language: widget.language, ); _localDatabase.notifyListeners(); navigatorState.pop(); @@ -290,6 +299,7 @@ class _ProductImageViewerState extends State { navigatorState.push( MaterialPageRoute( builder: (BuildContext context) => CropPage( + language: widget.language, barcode: _product.barcode!, imageField: _imageData.imageField, inputFile: imageFile, diff --git a/packages/smooth_app/lib/tmp_crop_image/new_crop_page.dart b/packages/smooth_app/lib/tmp_crop_image/new_crop_page.dart index 85e7b80f32c..94a4c2c7446 100644 --- a/packages/smooth_app/lib/tmp_crop_image/new_crop_page.dart +++ b/packages/smooth_app/lib/tmp_crop_image/new_crop_page.dart @@ -33,6 +33,7 @@ class CropPage extends StatefulWidget { required this.inputFile, required this.barcode, required this.imageField, + required this.language, required this.initiallyDifferent, this.imageId, this.initialCropRect, @@ -44,6 +45,7 @@ class CropPage extends StatefulWidget { final ImageField imageField; final String barcode; + final OpenFoodFactsLanguage language; /// Is the full picture initially different from the current selection? final bool initiallyDifferent; @@ -273,6 +275,7 @@ class _CropPageState extends State { final Rect cropRect = _getLocalCropRect(); await BackgroundTaskImage.addTask( widget.barcode, + language: widget.language, imageField: widget.imageField, fullFile: fullFile, croppedFile: croppedFile, @@ -291,6 +294,7 @@ class _CropPageState extends State { final Rect cropRect = _getServerCropRect(); await BackgroundTaskCrop.addTask( widget.barcode, + language: widget.language, imageField: widget.imageField, imageId: widget.imageId!, croppedFile: croppedFile,