glp-companion/lib/features/nutrition/domain/entities/food_product.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)';
}