Skip to content
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
4 changes: 4 additions & 0 deletions lib/openfoodfacts.dart
Original file line number Diff line number Diff line change
Expand Up @@ -34,6 +34,10 @@ export 'src/model/ordered_nutrients.dart';
export 'src/model/origins_of_ingredients.dart';
export 'src/model/owner_field.dart';
export 'src/model/packaging.dart';
export 'src/model/flexible/flexible_map.dart';
export 'src/model/flexible/flexible_product.dart';
export 'src/model/flexible/flexible_product_result.dart';
export 'src/model/flexible/flexible_search_result.dart';
export 'src/model/parameter/allergens_parameter.dart';
export 'src/model/parameter/barcode_parameter.dart';
export 'src/model/parameter/ingredients_analysis_parameter.dart';
Expand Down
11 changes: 11 additions & 0 deletions lib/src/model/flexible/flexible_map.dart
Original file line number Diff line number Diff line change
@@ -0,0 +1,11 @@
import 'package:meta/meta.dart';

@experimental
typedef JsonMap = Map<String, dynamic>;

@experimental
abstract class FlexibleMap {
const FlexibleMap(this.json);

final JsonMap json;
}
212 changes: 212 additions & 0 deletions lib/src/model/flexible/flexible_product.dart
Original file line number Diff line number Diff line change
@@ -0,0 +1,212 @@
import 'package:meta/meta.dart';

import 'flexible_map.dart';
import '../additives.dart';
import '../attribute_group.dart';
import '../ingredient.dart';
import '../knowledge_panels.dart';
import '../nutriments.dart';
import '../product_image.dart';
import '../product_packaging.dart';
import '../product_type.dart';
import '../../utils/json_helper.dart';
import '../../utils/language_helper.dart';
import '../../utils/product_fields.dart';
import '../../utils/uri_helper.dart';

/// Product without predefined structure, relying mainly on the json.
///
/// The typical use case would be "extending" this class in order to control
/// brand new field values.
/// The current field list matches what is used in Smoothie.
@experimental
class FlexibleProduct extends FlexibleMap {
FlexibleProduct(super.json);

FlexibleProduct.fromServer(
super.json, {
required final UriProductHelper uriHelper,
}) {
json['image_url_base'] = uriHelper.getImageUrlBase();
}

// mandatory for images
String get imageUrlBase => json['image_url_base'];

String? get barcode => json['code'];

ProductType? get productType => ProductType.fromOffTag(json['product_type']);

int? get schemaVersion => json['schema_version'];

String? get abbreviatedName => json['abbreviated_product_name'];

Additives? get additives =>
Additives.additivesFromJson(json['additives_tags'] as List?);

Iterable<AttributeGroup>? get attributeGroups =>
(json['attribute_groups'] as List<dynamic>?)
?.map(AttributeGroup.fromJson);

static const String _brandsSeparator = ', ';

String? get brandsAsString {
final result = json['brands'];
if (result == null) {
return null;
}
if (result is String) {
return result;
}
return (result as List<String>).join(_brandsSeparator);
}

List<String>? get brandsAsList {
final result = json['brands'];
if (result == null) {
return null;
}
if (result is List<String>) {
return result;
}
return (result as String).split(_brandsSeparator);
}

String? get categories => json['categories'] as String?;

Iterable<String>? get categoriesTags =>
(json['categories_tags'] as List<dynamic>?)?.map(
(e) => e as String,
);

Map<OpenFoodFactsLanguage, List<String>>? get categoriesTagsInLanguages =>
LanguageHelper.fromJsonStringsListMapWithPrefix(
json,
inProductField: ProductField.CATEGORIES_TAGS_IN_LANGUAGES,
);

String? get comparedToCategory => json['compared_to_category'] as String?;

String? get countries => json['countries'] as String?;

Map<OpenFoodFactsLanguage, List<String>>? get countriesTagsInLanguages =>
LanguageHelper.fromJsonStringsListMapWithPrefix(
json,
inProductField: ProductField.COUNTRIES_TAGS_IN_LANGUAGES,
);

String? get embCodes => json['emb_codes'] as String?;

String? get genericName => json['generic_name'] as String?;

String? get imageFrontUrl => json['image_front_url'] as String?;

String? get imageFrontSmallUrl => json['image_front_small_url'] as String?;

String? get imageIngredientsUrl => json['image_ingredients_url'] as String?;

String? get imageIngredientsSmallUrl =>
json['image_ingredients_small_url'] as String?;

String? get imageNutritionUrl => json['image_nutrition_url'] as String?;

String? get imageNutritionSmallUrl =>
json['image_nutrition_small_url'] as String?;

String? get imagePackagingUrl => json['image_packaging_url'] as String?;

String? get imagePackagingSmallUrl =>
json['image_packaging_small_url'] as String?;

List<ProductImage>? get images =>
JsonHelper.allImagesFromJson(json['images'] as Map?);

Iterable<Ingredient>? get ingredients =>
(json['ingredients'] as List<dynamic>?)
?.map((e) => Ingredient.fromJson(e as Map<String, dynamic>));

String? get ingredientsText => json['ingredients_text'] as String?;

Map<OpenFoodFactsLanguage, String>? get ingredientsTextInLanguages =>
LanguageHelper.fromJsonStringMapWithPrefix(
json,
inProductField: ProductField.INGREDIENTS_TEXT_IN_LANGUAGES,
allProductField: ProductField.INGREDIENTS_TEXT_ALL_LANGUAGES,
);

KnowledgePanels? get knowledgePanels =>
KnowledgePanels.fromJsonHelper(json['knowledge_panels'] as Map?);

String? get labels => json['labels'] as String?;

Map<OpenFoodFactsLanguage, List<String>>? get labelsTagsInLanguages =>
LanguageHelper.fromJsonStringsListMapWithPrefix(
json,
inProductField: ProductField.LABELS_TAGS_IN_LANGUAGES,
);

OpenFoodFactsLanguage? get lang =>
LanguageHelper.fromJson(json['lang'] as String?);

DateTime? get lastImage => JsonHelper.timestampToDate(json['last_image_t']);

Iterable<String>? get lastImageDates =>
(json['last_image_dates_tags'] as List<dynamic>?)
?.map((e) => e as String);

bool? get noNutritionData =>
JsonHelper.checkboxFromJSON(json['no_nutrition_data']);

String? get nutrimentDataPer => json['nutrition_data_per'] as String?;

Nutriments? get nutriments => noNutritionData == true
? null
: json['nutriments'] == null
? null
: Nutriments.fromJson(json['nutriments'] as Map<String, dynamic>);

bool? get obsolete => JsonHelper.checkboxFromJSON(json['obsolete']);

String? get origins => json['origins'] as String?;

Map<String, int>? get ownerFields =>
(json['owner_fields'] as Map<String, dynamic>?)?.map(
(k, e) => MapEntry(k, (e as num).toInt()),
);

Iterable<ProductPackaging>? get packagings =>
(json['packagings'] as List<dynamic>?)?.map(ProductPackaging.fromJson);

bool? get packagingsComplete =>
JsonHelper.boolFromJSON(json['packagings_complete']);

Map<OpenFoodFactsLanguage, String>? get packagingTextInLanguages =>
LanguageHelper.fromJsonStringMapWithPrefix(
json,
inProductField: ProductField.PACKAGING_TEXT_IN_LANGUAGES,
allProductField: ProductField.PACKAGING_TEXT_ALL_LANGUAGES,
);

String? get productName => json['product_name'] as String?;

Map<OpenFoodFactsLanguage, String>? get productNameInLanguages =>
LanguageHelper.fromJsonStringMapWithPrefix(
json,
inProductField: ProductField.NAME_IN_LANGUAGES,
allProductField: ProductField.NAME_ALL_LANGUAGES,
);

String? get quantity => json['quantity'] as String?;

List<ProductImage>? get selectedImages =>
JsonHelper.selectedImagesFromJson(json['selected_images'] as Map?);

String? get servingSize => json['serving_size'] as String?;

Iterable<String>? get statesTags =>
(json['states_tags'] as List<dynamic>?)?.map((e) => e as String);

String? get stores => json['stores'] as String?;

String? get website => json['link'] as String?;
}
40 changes: 40 additions & 0 deletions lib/src/model/flexible/flexible_product_result.dart
Original file line number Diff line number Diff line change
@@ -0,0 +1,40 @@
import 'package:meta/meta.dart';

import 'flexible_map.dart';
import 'flexible_product.dart';
import '../localized_tag.dart';
import '../product_result_field_answer.dart';
import '../product_result_v3.dart';
import '../../utils/uri_helper.dart';

/// API answer from a call to /api/v???/product/$barcode, in a flexible manner.
@experimental
class FlexibleProductResult extends FlexibleMap {
FlexibleProductResult(
super.json, {
required this.uriHelper,
});

final UriProductHelper uriHelper;

String? get barcode => json['code'] as String?;

LocalizedTag? get result => json['result'] == null
? null
: LocalizedTag.fromJson(json['result'] as Map<String, dynamic>);

String? get status => json['status'] as String?;

List<ProductResultFieldAnswer>? get errors =>
ProductResultV3.fromJsonListAnswerForField(json['errors']);

List<ProductResultFieldAnswer>? get warnings =>
ProductResultV3.fromJsonListAnswerForField(json['warnings']);

FlexibleProduct? get product => json['product'] == null
? null
: FlexibleProduct.fromServer(
json['product'] as JsonMap,
uriHelper: uriHelper,
);
}
35 changes: 35 additions & 0 deletions lib/src/model/flexible/flexible_search_result.dart
Original file line number Diff line number Diff line change
@@ -0,0 +1,35 @@
import 'package:meta/meta.dart';

import 'flexible_map.dart';
import 'flexible_product.dart';
import '../../interface/json_object.dart';
import '../../utils/uri_helper.dart';

/// API answer from a call product search, in a flexible manner.
@experimental
class FlexibleSearchResult extends FlexibleMap {
FlexibleSearchResult(
super.json, {
required this.uriHelper,
});

final UriProductHelper uriHelper;

int? get page => JsonObject.parseInt(json['page']);

int? get pageSize => JsonObject.parseInt(json['page_size']);

int? get count => JsonObject.parseInt(json['count']);

int? get pageCount => JsonObject.parseInt(json['page_count']);

int? get skip => JsonObject.parseInt(json['skip']);

Iterable<FlexibleProduct>? get products =>
(json['products'] as List<dynamic>?)?.map(
(toElement) => FlexibleProduct.fromServer(
toElement,
uriHelper: uriHelper,
),
);
}
13 changes: 13 additions & 0 deletions lib/src/model/product_image.dart
Original file line number Diff line number Diff line change
@@ -1,3 +1,5 @@
import 'package:meta/meta.dart';

import '../utils/language_helper.dart';
import '../utils/open_food_api_configuration.dart';
import '../utils/uri_helper.dart';
Expand Down Expand Up @@ -191,6 +193,17 @@ class ProductImage {
'/'
'${getUrlFilename(imageSize: imageSize)}';

/// Returns the url to display this image, for [FlexibleProduct].
@experimental
String getFlexibleUrl(
final String barcode, {
final ImageSize? imageSize,
required final String imageUrlBase,
}) =>
'$imageUrlBase'
'/${UriProductHelper.getBarcodeSubPath(barcode)}'
'/${getUrlFilename(imageSize: imageSize)}';

/// Returns just the filename of the url to display this image.
///
/// See also [getUrl].
Expand Down
6 changes: 3 additions & 3 deletions lib/src/model/product_result_v3.dart
Original file line number Diff line number Diff line change
Expand Up @@ -33,13 +33,13 @@ class ProductResultV3 extends JsonObject {
/// Errors.
///
/// Typically populated if [status] is [statusFailure].
@JsonKey(includeIfNull: false, fromJson: _fromJsonListAnswerForField)
@JsonKey(includeIfNull: false, fromJson: fromJsonListAnswerForField)
List<ProductResultFieldAnswer>? errors;

/// Warnings.
///
/// Typically populated if [status] is [statusWarning].
@JsonKey(includeIfNull: false, fromJson: _fromJsonListAnswerForField)
@JsonKey(includeIfNull: false, fromJson: fromJsonListAnswerForField)
List<ProductResultFieldAnswer>? warnings;

@JsonKey(includeIfNull: false)
Expand Down Expand Up @@ -70,7 +70,7 @@ class ProductResultV3 extends JsonObject {
String toString() => toJson().toString();

/// From a `List<AnswerForField>` in `dynamic`'s clothing (JsonKey)
static List<ProductResultFieldAnswer>? _fromJsonListAnswerForField(
static List<ProductResultFieldAnswer>? fromJsonListAnswerForField(
dynamic list) {
if (list == null) {
return null;
Expand Down
4 changes: 2 additions & 2 deletions lib/src/model/product_result_v3.g.dart

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

Loading
Loading