Skip to content

Commit

Permalink
fix: #571 - last step of migration of enums to dart 2.17 (#623)
Browse files Browse the repository at this point in the history
Impacted files:
* `AbstractQueryConfiguration.dart`: minor refactoring
* `api_getProduct_test.dart`: minor refactoring
* `CountryHelper.dart`: upgraded `OpenFoodFactsCountry` to dart 2.17
* `ImageHelper.dart`: minor refactoring
* `JsonHelper.dart`: minor refactoring
* `LanguageHelper.dart`: upgraded `OpenFoodFactsLanguage` to dart 2.17
* `NutrientsLevels.dart`: upgraded `Level` to dart 2.17
* `OpenFoodAPIConfiguration.dart`: minor refactoring
* `openfoodfacts.dart`: minor refactoring
* `ordered_nutrient_test.dart`: minor refactoring
* `Product.dart`: minor refactoring
* `ProductFields.dart`: minor refactoring
* `ProductImage.dart`: upgraded `ImageField`, `ImageSize`, `ImageAngle` to dart 2.17; minor refactoring
* `SendImage.dart`: minor refactoring
* `State.dart`: upgraded `State` to dart 2.17
* `TaxonomyQueryConfiguration.dart`: minor refactoring
* `UriHelper.dart`: minor refactoring
  • Loading branch information
monsieurtanuki committed Nov 13, 2022
1 parent dc2dfc3 commit cc1b388
Show file tree
Hide file tree
Showing 17 changed files with 692 additions and 1,078 deletions.
36 changes: 22 additions & 14 deletions lib/model/NutrientLevels.dart
Original file line number Diff line number Diff line change
@@ -1,19 +1,27 @@
enum Level { LOW, MODERATE, HIGH, UNDEFINED }
import 'package:openfoodfacts/model/OffTagged.dart';

enum Level implements OffTagged {
LOW(offTag: 'low'),
MODERATE(offTag: 'moderate'),
HIGH(offTag: 'high'),
UNDEFINED(offTag: 'undefined');

const Level({
required this.offTag,
});

@override
final String offTag;

/// Returns the first [Level] that matches the [offTag].
static Level? fromOffTag(final String? offTag) =>
OffTagged.fromOffTag(offTag, Level.values) as Level?;
}

extension LevelExtension on Level? {
static const Map<Level, String> _VALUES = {
Level.LOW: 'low',
Level.MODERATE: 'moderate',
Level.HIGH: 'high',
Level.UNDEFINED: 'undefined',
};

String get value => _VALUES[this] ?? 'undefined';

static Level getLevel(String? s) => Level.values.firstWhere(
(final Level key) => _VALUES[key] == s,
orElse: () => Level.UNDEFINED,
);
String get value => (this ?? Level.UNDEFINED).offTag;

static Level getLevel(String? s) => Level.fromOffTag(s) ?? Level.UNDEFINED;
}

class NutrientLevels {
Expand Down
4 changes: 2 additions & 2 deletions lib/model/Product.dart
Original file line number Diff line number Diff line change
Expand Up @@ -594,7 +594,7 @@ class Product extends JsonObject {
Map value, OpenFoodFactsLanguage language) {
final Map<ImageField, int> result = {};
for (final ImageField imageField in ImageField.values) {
final int? timestamp = value['${imageField.value}_${language.code}'];
final int? timestamp = value['${imageField.offTag}_${language.offTag}'];
if (timestamp != null) {
result[imageField] = timestamp;
}
Expand Down Expand Up @@ -627,7 +627,7 @@ class Product extends JsonObject {
'a proper language. Received: $langKey');
}
final keyNoLangs = key.substring(0, key.indexOf('_in_languages'));
final realKey = '${keyNoLangs}_${lang.code}';
final realKey = '${keyNoLangs}_${lang.offTag}';
json[realKey] = entry.value;
}
}
Expand Down
153 changes: 93 additions & 60 deletions lib/model/ProductImage.dart
Original file line number Diff line number Diff line change
@@ -1,96 +1,117 @@
import 'package:openfoodfacts/model/OffTagged.dart';
import 'package:openfoodfacts/utils/LanguageHelper.dart';

enum ImageField { FRONT, INGREDIENTS, NUTRITION, PACKAGING, OTHER }
enum ImageField implements OffTagged {
FRONT(offTag: 'front'),
INGREDIENTS(offTag: 'ingredients'),
NUTRITION(offTag: 'nutrition'),
PACKAGING(offTag: 'packaging'),
OTHER(offTag: 'other');

extension ImageFieldExtension on ImageField {
static const Map<ImageField, String> _VALUES = {
ImageField.FRONT: 'front',
ImageField.INGREDIENTS: 'ingredients',
ImageField.NUTRITION: 'nutrition',
ImageField.PACKAGING: 'packaging',
ImageField.OTHER: 'other',
};
const ImageField({
required this.offTag,
});

String get value => getValue(this);
@override
final String offTag;

static String getValue(ImageField field) => _VALUES[field] ?? 'other';
/// Returns the first [ImageField] that matches the [offTag].
static ImageField? fromOffTag(final String? offTag) =>
OffTagged.fromOffTag(offTag, ImageField.values) as ImageField?;

static ImageField getType(String s) => ImageField.values.firstWhere(
(final ImageField key) => _VALUES[key] == s.toLowerCase(),
orElse: () => ImageField.OTHER,
);
// TODO: deprecated from 2022-11-13; remove when old enough
@Deprecated('Use field.offTag instead')
String get value => offTag;
}

enum ImageSize {
THUMB, // width and height <= 100 px
SMALL, // width and height <= 200 px
DISPLAY, // width and height <= 400 px
ORIGINAL, // width and height: as uploaded
UNKNOWN, // size not available
extension ImageFieldExtension on ImageField {
// TODO: deprecated from 2022-11-13; remove when old enough
@Deprecated('Use field.offTag instead')
static String getValue(ImageField field) => field.offTag;

// TODO: deprecated from 2022-11-13; remove when old enough
@Deprecated('Use ImageField.fromOffTag instead')
static ImageField getType(String s) =>
ImageField.fromOffTag(s.toLowerCase()) ?? ImageField.OTHER;
}

enum ImageSize implements OffTagged {
/// Width and height <= 100 px
THUMB(offTag: 'thumb', number: '100'),

/// Width and height <= 200 px
SMALL(offTag: 'small', number: '200'),

/// Width and height <= 400 px
DISPLAY(offTag: 'display', number: '400'),

/// Width and height: as uploaded
ORIGINAL(offTag: 'original', number: 'full'),

/// Size not available
UNKNOWN(offTag: 'unknown', number: 'unknown');

const ImageSize({
required this.offTag,
required this.number,
});

@override
final String offTag;

final String number;
}

extension ImageSizeExtension on ImageSize? {
static const Map<ImageSize, String> _VALUES = {
ImageSize.THUMB: 'thumb',
ImageSize.SMALL: 'small',
ImageSize.DISPLAY: 'display',
ImageSize.ORIGINAL: 'original',
ImageSize.UNKNOWN: 'unknown',
};

static const Map<ImageSize, String> _NUMBERS = {
ImageSize.THUMB: '100',
ImageSize.SMALL: '200',
ImageSize.DISPLAY: '400',
ImageSize.ORIGINAL: 'full',
ImageSize.UNKNOWN: 'unknown',
};

String get value => _VALUES[this] ?? 'unknown';

String toNumber() => _NUMBERS[this] ?? 'unknown';
// TODO: deprecated from 2022-11-13; remove when old enough
@Deprecated('Use offTag instead')
String get value => (this ?? ImageSize.UNKNOWN).offTag;

// TODO: deprecated from 2022-11-13; remove when old enough
@Deprecated('Use number instead')
String toNumber() => (this ?? ImageSize.UNKNOWN).number;

static ImageSize getType(String s) => ImageSize.values.firstWhere(
(final ImageSize key) => _VALUES[key] == s.toLowerCase(),
(final ImageSize key) => key.offTag == s.toLowerCase(),
orElse: () => ImageSize.UNKNOWN,
);

static ImageSize fromNumber(String s) => ImageSize.values.firstWhere(
(final ImageSize key) => _NUMBERS[key] == s,
(final ImageSize key) => key.number == s,
orElse: () => ImageSize.UNKNOWN,
);
}

/// Angle for image rotation.
enum ImageAngle {
/// Noon = no rotation
NOON,
NOON(degree: 0),

/// 3 o'clock
THREE_O_CLOCK,
THREE_O_CLOCK(degree: 90),

/// 6 o'clock
SIX_O_CLOCK,
SIX_O_CLOCK(degree: 180),

/// 9 o'clock
NINE_O_CLOCK,
}
NINE_O_CLOCK(degree: 270);

extension ImageAngleExtension on ImageAngle {
static const Map<ImageAngle, int> _DEGREES_CLOCKWISE = {
ImageAngle.NOON: 0,
ImageAngle.THREE_O_CLOCK: 90,
ImageAngle.SIX_O_CLOCK: 180,
ImageAngle.NINE_O_CLOCK: 270,
};
const ImageAngle({
required this.degree,
});

String get degreesClockwise => _DEGREES_CLOCKWISE[this]?.toString() ?? '0';
/// Degree clockwise.
final int degree;

String get degreesClockwise => degree.toString();
}

extension ImageAngleExtension on ImageAngle {
/// Returns the corresponding [ImageAngle], or null if not found.
static ImageAngle? fromInt(final int? clockwiseDegree) {
for (final MapEntry<ImageAngle, int> entry in _DEGREES_CLOCKWISE.entries) {
if (entry.value == clockwiseDegree) {
return entry.key;
for (final ImageAngle imageAngle in ImageAngle.values) {
if (imageAngle.degree == clockwiseDegree) {
return imageAngle;
}
}
return null;
Expand Down Expand Up @@ -145,6 +166,18 @@ class ProductImage {
int? y2;

@override
String toString() =>
'ProductImage(${field.value}${size == null ? '' : ',size=${size.value}'}${language == null ? '' : ',language=${language.code}'}${angle == null ? '' : ',angle=${angle!.degreesClockwise}'}${url == null ? '' : ',url=$url'}${imgid == null ? '' : ',imgid=$imgid'}${rev == null ? '' : ',rev=$rev'}${coordinatesImageSize == null ? '' : ',coordinatesImageSize=$coordinatesImageSize'}${x1 == null ? '' : ',x1=$x1'}${y1 == null ? '' : ',y1=$y1'}${x2 == null ? '' : ',x2=$x2'}${y2 == null ? '' : ',y2=$y2'})';
String toString() => 'ProductImage('
'${field.offTag}'
'${size == null ? '' : ',size=${size!.offTag}'}'
'${language == null ? '' : ',language=${language.code}'}'
'${angle == null ? '' : ',angle=${angle!.degreesClockwise}'}'
'${url == null ? '' : ',url=$url'}'
'${imgid == null ? '' : ',imgid=$imgid'}'
'${rev == null ? '' : ',rev=$rev'}'
'${coordinatesImageSize == null ? '' : ',coordinatesImageSize=$coordinatesImageSize'}'
'${x1 == null ? '' : ',x1=$x1'}'
'${y1 == null ? '' : ',y1=$y1'}'
'${x2 == null ? '' : ',x2=$x2'}'
'${y2 == null ? '' : ',y2=$y2'}'
')';
}
8 changes: 4 additions & 4 deletions lib/model/SendImage.dart
Original file line number Diff line number Diff line change
Expand Up @@ -22,17 +22,17 @@ class SendImage extends JsonObject {

/// the json key depending on the image field of this object.
String getImageDataKey() {
String imageDataKey = 'imgupload_${imageField.value}';
String imageDataKey = 'imgupload_${imageField.offTag}';
if (lang != null) {
imageDataKey += '_${lang.code}';
imageDataKey += '_${lang!.offTag}';
}
return imageDataKey;
}

String _getImageFieldWithLang() {
String imageFieldWithLang = imageField.value;
String imageFieldWithLang = imageField.offTag;
if (lang != null) {
imageFieldWithLang += '_${lang.code}';
imageFieldWithLang += '_${lang!.offTag}';
}
return imageFieldWithLang;
}
Expand Down
111 changes: 45 additions & 66 deletions lib/model/State.dart
Original file line number Diff line number Diff line change
@@ -1,72 +1,51 @@
/// States of a [Product]. To be used in search API, with [StatesTagsParameter].
///
/// Provides tags for [State] and its status: completed or to-be-completed.
enum State {
CHECKED,
COMPLETED,
NUTRITION_FACTS_COMPLETED,
INGREDIENTS_COMPLETED,
EXPIRATION_DATE_COMPLETED,
PACKAGING_CODE_COMPLETED,
CHARACTERISTICS_COMPLETED,
ORIGINS_COMPLETED,
CATEGORIES_COMPLETED,
BRANDS_COMPLETED,
PACKAGING_COMPLETED,
QUANTITY_COMPLETED,
PRODUCT_NAME_COMPLETED,
PHOTOS_VALIDATED,
PACKAGING_PHOTO_SELECTED,
NUTRITION_PHOTO_SELECTED,
INGREDIENTS_PHOTO_SELECTED,
FRONT_PHOTO_SELECTED,
PHOTOS_UPLOADED,
}
CHECKED(completedTag: 'en:checked', toBeCompletedTag: 'en:to-be-checked'),
COMPLETED(
completedTag: 'en:complete', toBeCompletedTag: 'en:to-be-completed'),
NUTRITION_FACTS_COMPLETED.completed(tag: 'en:nutrition-facts'),
INGREDIENTS_COMPLETED.completed(tag: 'en:ingredients'),
EXPIRATION_DATE_COMPLETED.completed(tag: 'en:expiration-date'),
PACKAGING_CODE_COMPLETED.completed(tag: 'en:packaging-code'),
CHARACTERISTICS_COMPLETED.completed(tag: 'en:characteristics'),
ORIGINS_COMPLETED.completed(tag: 'en:origins'),
CATEGORIES_COMPLETED.completed(tag: 'en:categories'),
BRANDS_COMPLETED.completed(tag: 'en:brands'),
PACKAGING_COMPLETED.completed(tag: 'en:packaging'),
QUANTITY_COMPLETED.completed(tag: 'en:quantity'),
PRODUCT_NAME_COMPLETED.completed(tag: 'en:product-name'),
PHOTOS_VALIDATED.simple(tag: 'en:photos', action: 'validated'),
PACKAGING_PHOTO_SELECTED.selected(tag: 'en:packaging-photo'),
NUTRITION_PHOTO_SELECTED.selected(tag: 'en:nutrition-photo'),
INGREDIENTS_PHOTO_SELECTED.selected(tag: 'en:ingredients-photo'),
FRONT_PHOTO_SELECTED.selected(tag: 'en:front-photo'),
PHOTOS_UPLOADED.simple(tag: 'en:photos', action: 'uploaded');

/// Provides tags for [State] and its status: completed or to-be-completed.
extension StateExtension on State {
static const Map<State, String> _completed = <State, String>{
State.CHECKED: 'en:checked',
State.COMPLETED: 'en:complete',
State.NUTRITION_FACTS_COMPLETED: 'en:nutrition-facts-completed',
State.INGREDIENTS_COMPLETED: 'en:ingredients-completed',
State.EXPIRATION_DATE_COMPLETED: 'en:expiration-date-completed',
State.PACKAGING_CODE_COMPLETED: 'en:packaging-code-completed',
State.CHARACTERISTICS_COMPLETED: 'en:characteristics-completed',
State.ORIGINS_COMPLETED: 'en:origins-completed',
State.CATEGORIES_COMPLETED: 'en:categories-completed',
State.BRANDS_COMPLETED: 'en:brands-completed',
State.PACKAGING_COMPLETED: 'en:packaging-completed',
State.QUANTITY_COMPLETED: 'en:quantity-completed',
State.PRODUCT_NAME_COMPLETED: 'en:product-name-completed',
State.PHOTOS_VALIDATED: 'en:photos-validated',
State.PACKAGING_PHOTO_SELECTED: 'en:packaging-photo-selected',
State.NUTRITION_PHOTO_SELECTED: 'en:nutrition-photo-selected',
State.INGREDIENTS_PHOTO_SELECTED: 'en:ingredients-photo-selected',
State.FRONT_PHOTO_SELECTED: 'en:front-photo-selected',
State.PHOTOS_UPLOADED: 'en:photos-uploaded',
};
static const Map<State, String> _toBeCompleted = <State, String>{
State.CHECKED: 'en:to-be-checked',
State.COMPLETED: 'en:to-be-completed',
State.NUTRITION_FACTS_COMPLETED: 'en:nutrition-facts-to-be-completed',
State.INGREDIENTS_COMPLETED: 'en:ingredients-to-be-completed',
State.EXPIRATION_DATE_COMPLETED: 'en:expiration-date-to-be-completed',
State.PACKAGING_CODE_COMPLETED: 'en:packaging-code-to-be-completed',
State.CHARACTERISTICS_COMPLETED: 'en:characteristics-to-be-completed',
State.ORIGINS_COMPLETED: 'en:origins-to-be-completed',
State.CATEGORIES_COMPLETED: 'en:categories-to-be-completed',
State.BRANDS_COMPLETED: 'en:brands-to-be-completed',
State.PACKAGING_COMPLETED: 'en:packaging-to-be-completed',
State.QUANTITY_COMPLETED: 'en:quantity-to-be-completed',
State.PRODUCT_NAME_COMPLETED: 'en:product-name-to-be-completed',
State.PHOTOS_VALIDATED: 'en:photos-to-be-validated',
State.PACKAGING_PHOTO_SELECTED: 'en:packaging-photo-to-be-selected',
State.NUTRITION_PHOTO_SELECTED: 'en:nutrition-photo-to-be-selected',
State.INGREDIENTS_PHOTO_SELECTED: 'en:ingredients-photo-to-be-selected',
State.FRONT_PHOTO_SELECTED: 'en:front-photo-to-be-selected',
State.PHOTOS_UPLOADED: 'en:photos-to-be-uploaded',
};
/// Special case where we need the tag values as we cannot build them.
const State({
required this.completedTag,
required this.toBeCompletedTag,
});

/// Simple case where we can build the tag values.
const State.simple({
required final String tag,
required final String action,
}) : this(
completedTag: '$tag-$action',
toBeCompletedTag: '$tag-to-be-$action',
);

/// Simple case where we can build the tag values, for 'completed'.
const State.completed({required final String tag})
: this.simple(tag: tag, action: 'completed');

String get completedTag => _completed[this]!;
/// Simple case where we can build the tag values, for 'selected'.
const State.selected({required final String tag})
: this.simple(tag: tag, action: 'selected');

String get toBeCompletedTag => _toBeCompleted[this]!;
final String completedTag;
final String toBeCompletedTag;
}
Loading

0 comments on commit cc1b388

Please sign in to comment.