9fe85a719c
Test Suite / test (24.15.0) (push) Has been cancelled
- Added RecipeAnalysisService to handle recipe ingredient analysis, including methods for checking ingredient availability and calculating quantities. - Introduced new TypeScript definitions for recipe analysis results, including ingredient status and summary. - Created corresponding Dart models for recipe analysis, including RecipeIngredientAnalysis, RecipeAnalysisSummary, and RecipeShoppingCandidate. - Updated Flutter UI to reflect changes in ingredient availability status. - Fixed color opacity issue in recipe image card.
140 lines
4.5 KiB
Dart
140 lines
4.5 KiB
Dart
enum RecipeIngredientAvailabilityStatus {
|
|
exactMatch,
|
|
coveredByPantry,
|
|
substitutable,
|
|
missing,
|
|
}
|
|
|
|
class RecipeIngredientAnalysis {
|
|
final int ingredientId;
|
|
final String rawName;
|
|
final double quantity;
|
|
final String unit;
|
|
final String? note;
|
|
final RecipeIngredientAvailabilityStatus status;
|
|
final int? matchedProductId;
|
|
final String? matchedProductName;
|
|
final String? source;
|
|
final double availableQuantity;
|
|
final double missingQuantity;
|
|
|
|
const RecipeIngredientAnalysis({
|
|
required this.ingredientId,
|
|
required this.rawName,
|
|
required this.quantity,
|
|
required this.unit,
|
|
this.note,
|
|
required this.status,
|
|
this.matchedProductId,
|
|
this.matchedProductName,
|
|
this.source,
|
|
required this.availableQuantity,
|
|
required this.missingQuantity,
|
|
});
|
|
|
|
factory RecipeIngredientAnalysis.fromJson(Map<String, dynamic> json) {
|
|
final rawStatus = json['status'] as String? ?? 'missing';
|
|
final status = switch (rawStatus) {
|
|
'exact_match' => RecipeIngredientAvailabilityStatus.exactMatch,
|
|
'covered_by_pantry' => RecipeIngredientAvailabilityStatus.coveredByPantry,
|
|
'substitutable' => RecipeIngredientAvailabilityStatus.substitutable,
|
|
_ => RecipeIngredientAvailabilityStatus.missing,
|
|
};
|
|
|
|
return RecipeIngredientAnalysis(
|
|
ingredientId: (json['ingredientId'] as num?)?.toInt() ?? 0,
|
|
rawName: (json['rawName'] as String? ?? '').trim(),
|
|
quantity: (json['quantity'] as num? ?? 0).toDouble(),
|
|
unit: json['unit'] as String? ?? '',
|
|
note: json['note'] as String?,
|
|
status: status,
|
|
matchedProductId: (json['matchedProductId'] as num?)?.toInt(),
|
|
matchedProductName: json['matchedProductName'] as String?,
|
|
source: json['source'] as String?,
|
|
availableQuantity: (json['availableQuantity'] as num? ?? 0).toDouble(),
|
|
missingQuantity: (json['missingQuantity'] as num? ?? 0).toDouble(),
|
|
);
|
|
}
|
|
}
|
|
|
|
class RecipeAnalysisSummary {
|
|
final int exactCount;
|
|
final int pantryCount;
|
|
final int substituteCount;
|
|
final int missingCount;
|
|
|
|
const RecipeAnalysisSummary({
|
|
required this.exactCount,
|
|
required this.pantryCount,
|
|
required this.substituteCount,
|
|
required this.missingCount,
|
|
});
|
|
|
|
factory RecipeAnalysisSummary.fromJson(Map<String, dynamic> json) {
|
|
return RecipeAnalysisSummary(
|
|
exactCount: (json['exactCount'] as num?)?.toInt() ?? 0,
|
|
pantryCount: (json['pantryCount'] as num?)?.toInt() ?? 0,
|
|
substituteCount: (json['substituteCount'] as num?)?.toInt() ?? 0,
|
|
missingCount: (json['missingCount'] as num?)?.toInt() ?? 0,
|
|
);
|
|
}
|
|
}
|
|
|
|
class RecipeShoppingCandidate {
|
|
final int ingredientId;
|
|
final String rawName;
|
|
final double quantity;
|
|
final String unit;
|
|
final double missingQuantity;
|
|
|
|
const RecipeShoppingCandidate({
|
|
required this.ingredientId,
|
|
required this.rawName,
|
|
required this.quantity,
|
|
required this.unit,
|
|
required this.missingQuantity,
|
|
});
|
|
|
|
factory RecipeShoppingCandidate.fromJson(Map<String, dynamic> json) {
|
|
return RecipeShoppingCandidate(
|
|
ingredientId: (json['ingredientId'] as num?)?.toInt() ?? 0,
|
|
rawName: (json['rawName'] as String? ?? '').trim(),
|
|
quantity: (json['quantity'] as num? ?? 0).toDouble(),
|
|
unit: json['unit'] as String? ?? '',
|
|
missingQuantity: (json['missingQuantity'] as num? ?? 0).toDouble(),
|
|
);
|
|
}
|
|
}
|
|
|
|
class RecipeAnalysis {
|
|
final int recipeId;
|
|
final List<RecipeIngredientAnalysis> ingredients;
|
|
final RecipeAnalysisSummary summary;
|
|
final List<RecipeShoppingCandidate> shoppingListCandidates;
|
|
|
|
const RecipeAnalysis({
|
|
required this.recipeId,
|
|
required this.ingredients,
|
|
required this.summary,
|
|
required this.shoppingListCandidates,
|
|
});
|
|
|
|
factory RecipeAnalysis.fromJson(Map<String, dynamic> json) {
|
|
final rawIngredients = json['ingredients'] as List<dynamic>? ?? const [];
|
|
final rawShopping = json['shoppingListCandidates'] as List<dynamic>? ?? const [];
|
|
|
|
return RecipeAnalysis(
|
|
recipeId: (json['recipeId'] as num?)?.toInt() ?? 0,
|
|
ingredients: rawIngredients
|
|
.map((e) => RecipeIngredientAnalysis.fromJson(e as Map<String, dynamic>))
|
|
.toList(),
|
|
summary: RecipeAnalysisSummary.fromJson(
|
|
json['summary'] as Map<String, dynamic>? ?? const {},
|
|
),
|
|
shoppingListCandidates: rawShopping
|
|
.map((e) => RecipeShoppingCandidate.fromJson(e as Map<String, dynamic>))
|
|
.toList(),
|
|
);
|
|
}
|
|
}
|