Skip to content

Commit

Permalink
feat: 688 - new method "getProductImageIds" (#689)
Browse files Browse the repository at this point in the history
New file:
* `api_get_product_image_ids_test.dart`: tests around `OpenFoodAPIClient.getProductImageIds`.

Impacted files:
* `api_get_product_test.dart`: unrelated fix about server data change
* `api_matched_product_v1_test.dart`: unrelated fix about server data change
* `api_matched_product_v2_test.dart`: unrelated fix about server data change
* `api_search_products_test.dart`: unrelated fix about server data change
* `image_helper.dart`: new method `getUploadedImageUrl`
* `open_food_api_client.dart`: new method `getProductImageIds`
  • Loading branch information
monsieurtanuki committed Jan 26, 2023
1 parent 57d0685 commit 71e4067
Show file tree
Hide file tree
Showing 7 changed files with 93 additions and 9 deletions.
36 changes: 36 additions & 0 deletions lib/src/open_food_api_client.dart
Original file line number Diff line number Diff line change
Expand Up @@ -340,6 +340,42 @@ class OpenFoodAPIClient {
return result;
}

/// Returns the ids of all uploaded images for that product.
///
/// To be used in combination with [ImageHelper.getUploadedImageUrl].
/// Does not depend on language or country.
static Future<List<int>> getProductImageIds(
final String barcode, {
final User? user,
final QueryType? queryType,
}) async {
final ProductQueryConfiguration configuration = ProductQueryConfiguration(
barcode,
version: ProductQueryVersion.v3,
fields: <ProductField>[ProductField.IMAGES],
);
final String productString = await getProductString(
configuration,
user: user,
queryType: queryType,
);
final String jsonStr = _replaceQuotes(productString);
final json = jsonDecode(jsonStr);
if (json['status'] != 'success') {
throw Exception('Error: ${json['status']}');
}
final Map<String, dynamic> images = json['product']['images'];
final List<int> result = <int>[];
for (final String key in images.keys) {
final int? value = int.tryParse(key);
if (value != null) {
result.add(value);
}
}
result.sort();
return result;
}

/// Returns the response body of "get product" API for the given barcode.
static Future<String> getProductString(
final ProductQueryConfiguration configuration, {
Expand Down
33 changes: 33 additions & 0 deletions lib/src/utils/image_helper.dart
Original file line number Diff line number Diff line change
Expand Up @@ -21,6 +21,19 @@ class ImageHelper {
'/'
'${getProductImageFilename(image, imageSize: imageSize)}';

/// Returns the [image] full url for an uploaded image.
///
/// E.g. "https://static.openfoodfacts.org/images/products/359/671/046/2858/1.400.jpg"
static String getUploadedImageUrl(
final String barcode,
final int imageId,
final ImageSize imageSize, {
final QueryType? queryType,
}) =>
'${getProductImageRootUrl(barcode, queryType: queryType)}'
'/'
'${_getUploadedImageFilename(imageId, imageSize)}';

/// Returns the [image] filename - for a specific [imageSize] if needed.
///
/// By default uses the own [image]'s size field.
Expand All @@ -34,6 +47,26 @@ class ImageHelper {
'.${((imageSize ?? image.size) ?? ImageSize.UNKNOWN).number}'
'.jpg';

/// Returns the filename of an uploaded image.
static String _getUploadedImageFilename(
final int imageId,
final ImageSize imageSize,
) {
switch (imageSize) {
case ImageSize.THUMB:
case ImageSize.DISPLAY:
// adapted size
return '$imageId.${imageSize.number}.jpg';
case ImageSize.SMALL:
// not available, we take the best other choice instead
return '$imageId.${ImageSize.DISPLAY.number}.jpg';
case ImageSize.ORIGINAL:
case ImageSize.UNKNOWN:
// full size
return '$imageId.jpg';
}
}

/// Returns the web folder of the product images (without trailing '/')
///
/// E.g. "https://static.openfoodfacts.org/images/products/359/671/046/2858"
Expand Down
16 changes: 16 additions & 0 deletions test/api_get_product_image_ids_test.dart
Original file line number Diff line number Diff line change
@@ -0,0 +1,16 @@
import 'package:openfoodfacts/openfoodfacts.dart';
import 'package:test/test.dart';

import 'test_constants.dart';

void main() {
test('get product image ids', () async {
const String barcode = '3019081238643';
final List<int> result = await OpenFoodAPIClient.getProductImageIds(
barcode,
user: TestConstants.PROD_USER,
queryType: QueryType.PROD,
);
expect(result.length, greaterThanOrEqualTo(34)); // was 34 on 2023-01-25
});
}
2 changes: 1 addition & 1 deletion test/api_get_product_test.dart
Original file line number Diff line number Diff line change
Expand Up @@ -1922,7 +1922,7 @@ void main() {
}

expect(product.packagings, isNotNull);
expect(product.packagings!.length, 3);
expect(product.packagings!.length, greaterThanOrEqualTo(3));
for (final ProductPackaging packaging in product.packagings!) {
checkLocalizedTag(packaging.shape);
checkLocalizedTag(packaging.material);
Expand Down
5 changes: 1 addition & 4 deletions test/api_matched_product_v1_test.dart
Original file line number Diff line number Diff line change
Expand Up @@ -89,10 +89,7 @@ void main() {

matchedProduct = MatchedProduct(result.product!, manager);
expect(matchedProduct.score, greaterThan(37.5));
expect(
matchedProduct.status,
MatchedProductStatus
.YES); // because the score for FOREST is not good enough
expect(matchedProduct.status, MatchedProductStatus.NO);

await manager.clearImportances(); // no attribute parameters at all
expect(refreshCounter, 5);
Expand Down
7 changes: 4 additions & 3 deletions test/api_matched_product_v2_test.dart
Original file line number Diff line number Diff line change
Expand Up @@ -26,7 +26,7 @@ void main() {
const String BARCODE_HACK = '7613037672756';
const String BARCODE_SCHNITZEL = '4061458069878';
const String BARCODE_CHIPOLATA = '3770016162098';
const String BARCODE_FLEISCHWURST = '4003171036379';
const String BARCODE_FLEISCHWURST = '4003171036379'; // now veggie!
const String BARCODE_POULET = '40897837';
const String BARCODE_SAUCISSON = '20045456';
const String BARCODE_PIZZA = '4260414150470';
Expand Down Expand Up @@ -92,21 +92,22 @@ void main() {
BARCODE_HACK: _Score(100, MatchedProductStatusV2.VERY_GOOD_MATCH),
BARCODE_SCHNITZEL: _Score(100, MatchedProductStatusV2.VERY_GOOD_MATCH),
BARCODE_CHIPOLATA: _Score(0, MatchedProductStatusV2.UNKNOWN_MATCH),
BARCODE_FLEISCHWURST: _Score(0, MatchedProductStatusV2.UNKNOWN_MATCH),
BARCODE_FLEISCHWURST:
_Score(100, MatchedProductStatusV2.VERY_GOOD_MATCH),
BARCODE_POULET: _Score(0, MatchedProductStatusV2.UNKNOWN_MATCH),
BARCODE_SAUCISSON: _Score(0, MatchedProductStatusV2.DOES_NOT_MATCH),
BARCODE_PIZZA: _Score(0, MatchedProductStatusV2.DOES_NOT_MATCH),
BARCODE_ARDECHE: _Score(0, MatchedProductStatusV2.DOES_NOT_MATCH),
BARCODE_CHORIZO: _Score(0, MatchedProductStatusV2.DOES_NOT_MATCH),
};
final List<String> expectedBarcodeOrder = <String>[
BARCODE_FLEISCHWURST,
BARCODE_KNACKI,
BARCODE_CORDONBLEU,
BARCODE_ORIENTALES,
BARCODE_HACK,
BARCODE_SCHNITZEL,
BARCODE_CHIPOLATA,
BARCODE_FLEISCHWURST,
BARCODE_POULET,
BARCODE_SAUCISSON,
BARCODE_PIZZA,
Expand Down
3 changes: 2 additions & 1 deletion test/api_search_products_test.dart
Original file line number Diff line number Diff line change
Expand Up @@ -491,7 +491,7 @@ void main() {
const String nutritionGrades = 'A';
const String states = 'en:nutrition-facts-completed';
const String ingredients = 'en:cereal';
const int novaGroup = 1;
const int novaGroup = 3;
const String languages = 'ar';
const String creator = 'sebleouf';
const String editors = 'foodrepo';
Expand Down Expand Up @@ -565,6 +565,7 @@ void main() {
expect(product.statesTags, contains(states));
expect(product.ingredientsTags, contains(ingredients));
expect(product.lang.code, lang);
expect(product.novaGroup, novaGroup);
// TODO(monsieurtanuki): extract the origins, manufactoringPlaces, purchasePlaces, languages, creator and editors from the product, and compare them to expected values
}
});
Expand Down

0 comments on commit 71e4067

Please sign in to comment.