recipe-manager/mobile/lib/core/models/recipe.dart

210 lines
6.4 KiB
Dart
Raw Permalink Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

class Recipe {
final String? id;
final String title;
final String? description;
final String? servings;
final String? prepTime;
final String? cookTime;
final String? totalTime;
final String? image;
final List<String> images;
final List<String> ingredients;
final List<String> instructions;
final String? sourceUrl;
final String? shareToken;
final String? createdAt;
final String? updatedAt;
Recipe({
this.id,
required this.title,
this.description,
this.servings,
this.prepTime,
this.cookTime,
this.totalTime,
this.image,
List<String>? images,
List<String>? ingredients,
List<String>? instructions,
this.sourceUrl,
this.shareToken,
this.createdAt,
this.updatedAt,
}) : images = images ?? [],
ingredients = ingredients ?? [],
instructions = instructions ?? [];
factory Recipe.fromJson(Map<String, dynamic> json) {
List<String> parseStringList(dynamic val) {
if (val is List) {
return val.map((e) => e.toString().trim()).where((s) => s.isNotEmpty).toList();
}
return [];
}
return Recipe(
id: json['id']?.toString(),
title: json['title']?.toString() ?? 'Untitled Recipe',
description: json['description']?.toString(),
servings: json['servings']?.toString(),
prepTime: json['prepTime']?.toString(),
cookTime: json['cookTime']?.toString(),
totalTime: json['totalTime']?.toString(),
image: json['image']?.toString(),
images: parseStringList(json['images']),
ingredients: parseStringList(json['ingredients']),
instructions: parseStringList(json['instructions']),
sourceUrl: json['sourceUrl']?.toString(),
shareToken: json['shareToken']?.toString(),
createdAt: json['createdAt']?.toString(),
updatedAt: json['updatedAt']?.toString(),
);
}
Map<String, dynamic> toJson() {
return {
if (id != null) 'id': id,
'title': title,
'description': description ?? '',
'servings': servings ?? '',
'prepTime': prepTime ?? '',
'cookTime': cookTime ?? '',
'totalTime': totalTime ?? '',
'image': image ?? '',
'images': images,
'ingredients': ingredients,
'instructions': instructions,
'sourceUrl': sourceUrl ?? '',
};
}
String get calculatedTotalTime => RecipeTimeUtils.calculateTotalTime(
prepTime: prepTime,
cookTime: cookTime,
totalTime: totalTime,
);
Recipe copyWith({
String? id,
String? title,
String? description,
String? servings,
String? prepTime,
String? cookTime,
String? totalTime,
String? image,
List<String>? images,
List<String>? ingredients,
List<String>? instructions,
String? sourceUrl,
String? shareToken,
String? createdAt,
String? updatedAt,
}) {
return Recipe(
id: id ?? this.id,
title: title ?? this.title,
description: description ?? this.description,
servings: servings ?? this.servings,
prepTime: prepTime ?? this.prepTime,
cookTime: cookTime ?? this.cookTime,
totalTime: totalTime ?? this.totalTime,
image: image ?? this.image,
images: images ?? this.images,
ingredients: ingredients ?? this.ingredients,
instructions: instructions ?? this.instructions,
sourceUrl: sourceUrl ?? this.sourceUrl,
shareToken: shareToken ?? this.shareToken,
createdAt: createdAt ?? this.createdAt,
updatedAt: updatedAt ?? this.updatedAt,
);
}
}
class RecipeTimeUtils {
static int parseDurationToMinutes(String? str) {
if (str == null) return 0;
final s = str.trim();
if (s.isEmpty) return 0;
// ISO 8601 Durations (e.g. PT1H30M, PT600S, PT45M, P0DT1H)
if (s.startsWith('P') || s.startsWith('p')) {
final isoRegex = RegExp(r'P(?:(\d+)D)?T?(?:(\d+(?:\.\d+)?)H)?(?:(\d+)M)?(?:(\d+)S)?', caseSensitive: false);
final match = isoRegex.firstMatch(s);
if (match != null) {
final days = int.tryParse(match.group(1) ?? '0') ?? 0;
final hours = double.tryParse(match.group(2) ?? '0') ?? 0.0;
final mins = int.tryParse(match.group(3) ?? '0') ?? 0;
final secs = int.tryParse(match.group(4) ?? '0') ?? 0;
final totalSecs = (days * 86400) + (hours * 3600).round() + (mins * 60) + secs;
if (totalSecs > 0) return (totalSecs / 60).round();
}
}
int totalMinutes = 0;
bool matched = false;
// Hours: e.g. 1 hr, 1.5 hours, 2 hrs
final hrRegex = RegExp(r'(\d+(?:\.\d+)?)\s*(?:hours?|hrs?|h\b)', caseSensitive: false);
final hrMatch = hrRegex.firstMatch(s);
if (hrMatch != null) {
final h = double.tryParse(hrMatch.group(1) ?? '0') ?? 0.0;
totalMinutes += (h * 60).round();
matched = true;
}
// Minutes: e.g. 15 mins, 10-14 mins, 45m
final minRegex = RegExp(r'(?:(\d+)\s*[-–to]+\s*)?(\d+)\s*(?:minutes?|mins?|m\b)', caseSensitive: false);
final minMatch = minRegex.firstMatch(s);
if (minMatch != null) {
final m = int.tryParse(minMatch.group(2) ?? '0') ?? 0;
totalMinutes += m;
matched = true;
}
if (!matched && RegExp(r'^\d+$').hasMatch(s)) {
return int.tryParse(s) ?? 0;
}
return totalMinutes;
}
static String formatMinutes(int minutes) {
if (minutes <= 0) return '';
final hrs = minutes ~/ 60;
final mins = minutes % 60;
if (hrs > 0 && mins > 0) {
return '$hrs hr${hrs > 1 ? 's' : ''} $mins min${mins > 1 ? 's' : ''}';
}
if (hrs > 0) {
return '$hrs hr${hrs > 1 ? 's' : ''}';
}
return '$mins min${mins > 1 ? 's' : ''}';
}
static String calculateTotalTime({String? prepTime, String? cookTime, String? totalTime}) {
if (totalTime != null && totalTime.trim().isNotEmpty) {
final t = totalTime.trim();
if (t.startsWith('P') || t.startsWith('p')) {
final m = parseDurationToMinutes(t);
if (m > 0) return formatMinutes(m);
}
return t;
}
final prepM = parseDurationToMinutes(prepTime);
final cookM = parseDurationToMinutes(cookTime);
if (prepM > 0 || cookM > 0) {
return formatMinutes(prepM + cookM);
}
if (cookTime != null && cookTime.trim().isNotEmpty) {
final cM = parseDurationToMinutes(cookTime);
return cM > 0 ? formatMinutes(cM) : cookTime.trim();
}
if (prepTime != null && prepTime.trim().isNotEmpty) {
final pM = parseDurationToMinutes(prepTime);
return pM > 0 ? formatMinutes(pM) : prepTime.trim();
}
return '';
}
}