Skip to content

Commit

Permalink
feat: 3897 - no language selector for gallery, only for swipeable page (
Browse files Browse the repository at this point in the history
#3924)

* feat: 3897 - no language selector for gallery, only for swipeable page

Main changes:
* `app_en.arb`: added 2 translations ("no image" / "no image for that language")
* `app_fr.arb`: added 2 translations ("no image" / "no image for that language")
* `nutrition_page_loaded.dart`: removed an implicit `language` parameter
* `product_cards_helper.dart`: refactoring
* `product_image_gallery_view.dart`: removed the language selector
* `product_image_swipeable_view.dart`: removed the language parameter; now we always start with the app language
* `product_image_viewer.dart`: now always displays the (non) image for the specified language, with additional labels; added explicit `language` parameters
* `transient_file.dart`: new method `getImageLanguages`

Added explicit `language` parameters for:
* `add_new_product_page.dart`: added an explicit `language` parameter
* `background_task_crop.dart`: added an explicit `language` parameter
* `background_task_image.dart`: added an explicit `language` parameter
* `background_task_unselect.dart`: added an explicit `language` parameter
* `edit_ingredients_page.dart`: added an explicit `language` parameter
* `edit_new_packagings.dart`: added an explicit `language` parameter
* `image_crop_page.dart`: added an explicit `language` parameter
* `image_upload_card.dart`: added an explicit `language` parameter
* `new_crop_page.dart`: added an explicit `language` parameter
* `product_image_local_button.dart`: added an explicit `language` parameter
* `product_image_server_button.dart`: added an explicit `language` parameter
* `uploaded_image_gallery.dart`: added an explicit `language` parameter

* feat: 3897 - slightly related refactoring around ImageFieldSmoothieExtension

* feat: 3897 - minor fix for TEST env
  • Loading branch information
monsieurtanuki authored Apr 30, 2023
1 parent 558034f commit a8366fd
Show file tree
Hide file tree
Showing 21 changed files with 203 additions and 141 deletions.
7 changes: 5 additions & 2 deletions packages/smooth_app/lib/background/background_task_crop.dart
Original file line number Diff line number Diff line change
Expand Up @@ -101,6 +101,7 @@ class BackgroundTaskCrop extends AbstractBackgroundTask {
/// Adds the background task about uploading a product image.
static Future<void> addTask(
final String barcode, {
required final OpenFoodFactsLanguage language,
required final int imageId,
required final ImageField imageField,
required final File croppedFile,
Expand All @@ -117,6 +118,7 @@ class BackgroundTaskCrop extends AbstractBackgroundTask {
barcode,
);
final AbstractBackgroundTask task = _getNewTask(
language,
barcode,
imageId,
imageField,
Expand All @@ -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,
Expand All @@ -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,
),
);

Expand Down
7 changes: 5 additions & 2 deletions packages/smooth_app/lib/background/background_task_image.dart
Original file line number Diff line number Diff line change
Expand Up @@ -117,6 +117,7 @@ class BackgroundTaskImage extends AbstractBackgroundTask {
/// Adds the background task about uploading a product image.
static Future<void> addTask(
final String barcode, {
required final OpenFoodFactsLanguage language,
required final ImageField imageField,
required final File fullFile,
required final File croppedFile,
Expand All @@ -133,6 +134,7 @@ class BackgroundTaskImage extends AbstractBackgroundTask {
barcode,
);
final AbstractBackgroundTask task = _getNewTask(
language,
barcode,
imageField,
fullFile,
Expand All @@ -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,
Expand All @@ -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,
),
);

Expand Down
26 changes: 8 additions & 18 deletions packages/smooth_app/lib/background/background_task_unselect.dart
Original file line number Diff line number Diff line change
Expand Up @@ -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.
Expand Down Expand Up @@ -74,6 +75,7 @@ class BackgroundTaskUnselect extends AbstractBackgroundTask {
final String barcode, {
required final ImageField imageField,
required final State<StatefulWidget> widget,
required final OpenFoodFactsLanguage language,
}) async {
final LocalDatabase localDatabase = widget.context.read<LocalDatabase>();
final String uniqueId = await _operationType.getNewKey(
Expand All @@ -84,6 +86,7 @@ class BackgroundTaskUnselect extends AbstractBackgroundTask {
barcode,
imageField,
uniqueId,
language,
);
await task.addToManager(localDatabase, widget: widget);
}
Expand All @@ -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,
),
);

Expand All @@ -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) {
Expand Down Expand Up @@ -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;
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -46,6 +46,7 @@ class _ImageUploadCardState extends State<ImageUploadCard> {
this,
barcode: widget.product.barcode!,
imageField: widget.productImageData.imageField,
language: ProductQuery.getLanguage(),
),
icon: const Icon(Icons.add_a_photo),
label: Text(
Expand Down
35 changes: 33 additions & 2 deletions packages/smooth_app/lib/database/transient_file.dart
Original file line number Diff line number Diff line change
Expand Up @@ -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(
Expand Down Expand Up @@ -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<OpenFoodFactsLanguage> getImageLanguages(
final ImageField imageField,
final String barcode,
) {
final Set<OpenFoodFactsLanguage> result = <OpenFoodFactsLanguage>{};
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;
}
}
44 changes: 43 additions & 1 deletion packages/smooth_app/lib/helpers/image_field_extension.dart
Original file line number Diff line number Diff line change
Expand Up @@ -9,7 +9,15 @@ extension ImageFieldSmoothieExtension on ImageField {
ImageField.PACKAGING,
];

String? getImageFieldUrl(final Product product) {
static const List<ImageField> orderedAll = <ImageField>[
ImageField.FRONT,
ImageField.INGREDIENTS,
ImageField.NUTRITION,
ImageField.PACKAGING,
ImageField.OTHER,
];

String? getUrl(final Product product) {
switch (this) {
case ImageField.FRONT:
return product.imageFrontUrl;
Expand All @@ -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:
Expand Down Expand Up @@ -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;
}
}
}
49 changes: 18 additions & 31 deletions packages/smooth_app/lib/helpers/product_cards_helper.dart
Original file line number Diff line number Diff line change
Expand Up @@ -193,11 +193,15 @@ List<ProductImageData> 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,
Expand All @@ -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(
Expand Down Expand Up @@ -257,29 +262,6 @@ List<MapEntry<ProductImageData, ImageProvider?>> 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) {
Expand All @@ -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<OpenFoodFactsLanguage> getProductImageLanguages(
final Product product,
final Iterable<ImageField> imageFields,
final ImageField imageField,
) {
final Set<OpenFoodFactsLanguage> result = <OpenFoodFactsLanguage>{};
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!);
Expand Down
8 changes: 8 additions & 0 deletions packages/smooth_app/lib/l10n/app_en.arb
Original file line number Diff line number Diff line change
Expand Up @@ -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"
Expand Down
Loading

0 comments on commit a8366fd

Please sign in to comment.