112 lines
3.6 KiB
Dart
112 lines
3.6 KiB
Dart
/// Immutable nutritional product record resolved from the local cache,
|
|
/// Open Food Facts, or the USDA FoodData Central fallback.
|
|
///
|
|
/// Pure Dart (no framework imports) so the entity can be shared by the food
|
|
/// search repository, the drift-backed cache mapper, and the Riverpod
|
|
/// presentation layer without leaking infrastructure concerns into the
|
|
/// domain.
|
|
class FoodProduct {
|
|
const FoodProduct({
|
|
required this.id,
|
|
required this.productName,
|
|
required this.servingSizeGrams,
|
|
required this.servingSizeUnit,
|
|
required this.calories,
|
|
required this.proteinGrams,
|
|
required this.carbsGrams,
|
|
required this.fatGrams,
|
|
required this.lastUpdated,
|
|
this.barcode,
|
|
this.brand,
|
|
this.fiberGrams,
|
|
this.sodiumMg,
|
|
this.imageUrl,
|
|
});
|
|
|
|
/// Stable primary key: the source barcode when known, otherwise a
|
|
/// source-prefixed identifier (e.g. `fdc:12345` for USDA records).
|
|
final String id;
|
|
|
|
/// UPC/EAN barcode when the product carries one; `null` for USDA-only
|
|
/// foundation/branded records without a resolvable GTIN.
|
|
final String? barcode;
|
|
|
|
/// Display name of the product (e.g. "Greek Yogurt, Plain").
|
|
final String productName;
|
|
|
|
/// Manufacturer or distributor brand (e.g. "Fage"); `null` when unknown.
|
|
final String? brand;
|
|
|
|
/// Gram weight of one serving; anchors the serving-size multiplier math in
|
|
/// the action sheet.
|
|
final double servingSizeGrams;
|
|
|
|
/// Unit label persisted alongside [servingSizeGrams] (e.g. `g`, `ml`,
|
|
/// `count`) — kept for display and pantry stock mapping.
|
|
final String servingSizeUnit;
|
|
|
|
/// Energy content in kilocalories per serving.
|
|
final double calories;
|
|
|
|
/// Protein content in grams per serving; the primary lean-mass metric.
|
|
final double proteinGrams;
|
|
|
|
/// Carbohydrate content in grams per serving.
|
|
final double carbsGrams;
|
|
|
|
/// Fat content in grams per serving.
|
|
final double fatGrams;
|
|
|
|
/// Dietary fiber in grams per serving; `null` when the source omits it.
|
|
final double? fiberGrams;
|
|
|
|
/// Sodium in milligrams per serving; `null` when the source omits it.
|
|
final double? sodiumMg;
|
|
|
|
/// Thumbnail/front-image URL; `null` when no product photo is available.
|
|
final String? imageUrl;
|
|
|
|
/// When the record was resolved from its remote source (drives cache
|
|
/// freshness checks).
|
|
final DateTime lastUpdated;
|
|
|
|
/// Whether the product is identified by a scannable UPC/EAN barcode.
|
|
bool get hasBarcode => barcode != null && barcode!.isNotEmpty;
|
|
|
|
FoodProduct copyWith({
|
|
String? id,
|
|
String? barcode,
|
|
String? productName,
|
|
String? brand,
|
|
double? servingSizeGrams,
|
|
String? servingSizeUnit,
|
|
double? calories,
|
|
double? proteinGrams,
|
|
double? carbsGrams,
|
|
double? fatGrams,
|
|
double? fiberGrams,
|
|
double? sodiumMg,
|
|
String? imageUrl,
|
|
DateTime? lastUpdated,
|
|
}) =>
|
|
FoodProduct(
|
|
id: id ?? this.id,
|
|
barcode: barcode ?? this.barcode,
|
|
productName: productName ?? this.productName,
|
|
brand: brand ?? this.brand,
|
|
servingSizeGrams: servingSizeGrams ?? this.servingSizeGrams,
|
|
servingSizeUnit: servingSizeUnit ?? this.servingSizeUnit,
|
|
calories: calories ?? this.calories,
|
|
proteinGrams: proteinGrams ?? this.proteinGrams,
|
|
carbsGrams: carbsGrams ?? this.carbsGrams,
|
|
fatGrams: fatGrams ?? this.fatGrams,
|
|
fiberGrams: fiberGrams ?? this.fiberGrams,
|
|
sodiumMg: sodiumMg ?? this.sodiumMg,
|
|
imageUrl: imageUrl ?? this.imageUrl,
|
|
lastUpdated: lastUpdated ?? this.lastUpdated,
|
|
);
|
|
|
|
@override
|
|
String toString() =>
|
|
'FoodProduct($id, "$productName" ${brand ?? ''}, $calories kcal)';
|
|
} |