feat: meal tracking, dish recognition UX improvements, English AI prompts
Backend: - Translate all recognition prompts (receipt, products, dish) from Russian to English - Add lang parameter to Recognizer interface and pass locale.FromContext in handlers - DishResult type uses candidates array for multi-candidate responses Client: - Add meal tracking: diary provider, date selector, meal type model - DishResult parser: backward-compatible with legacy flat format and new candidates format - DishResultScreen: sticky bottom button, full-width portion/meal-type inputs, КБЖУ disclaimer moved under nutrition card, add date field to diary POST body - Recognition prompts now return dish/product names in user's preferred language - Onboarding, profile, home screen visual updates Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
This commit is contained in:
@@ -61,7 +61,8 @@ class ReceiptResult {
|
||||
const ReceiptResult({required this.items, required this.unrecognized});
|
||||
}
|
||||
|
||||
class DishResult {
|
||||
/// A single dish recognition candidate with estimated nutrition for the portion in the photo.
|
||||
class DishCandidate {
|
||||
final String dishName;
|
||||
final int weightGrams;
|
||||
final double calories;
|
||||
@@ -69,9 +70,8 @@ class DishResult {
|
||||
final double fatG;
|
||||
final double carbsG;
|
||||
final double confidence;
|
||||
final List<String> similarDishes;
|
||||
|
||||
const DishResult({
|
||||
const DishCandidate({
|
||||
required this.dishName,
|
||||
required this.weightGrams,
|
||||
required this.calories,
|
||||
@@ -79,11 +79,10 @@ class DishResult {
|
||||
required this.fatG,
|
||||
required this.carbsG,
|
||||
required this.confidence,
|
||||
required this.similarDishes,
|
||||
});
|
||||
|
||||
factory DishResult.fromJson(Map<String, dynamic> json) {
|
||||
return DishResult(
|
||||
factory DishCandidate.fromJson(Map<String, dynamic> json) {
|
||||
return DishCandidate(
|
||||
dishName: json['dish_name'] as String? ?? '',
|
||||
weightGrams: json['weight_grams'] as int? ?? 0,
|
||||
calories: (json['calories'] as num?)?.toDouble() ?? 0,
|
||||
@@ -91,14 +90,46 @@ class DishResult {
|
||||
fatG: (json['fat_g'] as num?)?.toDouble() ?? 0,
|
||||
carbsG: (json['carbs_g'] as num?)?.toDouble() ?? 0,
|
||||
confidence: (json['confidence'] as num?)?.toDouble() ?? 0,
|
||||
similarDishes: (json['similar_dishes'] as List<dynamic>?)
|
||||
?.map((e) => e as String)
|
||||
.toList() ??
|
||||
[],
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
/// Result of dish recognition: ordered list of candidates (best match first).
|
||||
class DishResult {
|
||||
final List<DishCandidate> candidates;
|
||||
|
||||
const DishResult({required this.candidates});
|
||||
|
||||
/// The best matching candidate.
|
||||
DishCandidate get best => candidates.first;
|
||||
|
||||
// Convenience getters delegating to the best candidate.
|
||||
String get dishName => best.dishName;
|
||||
int get weightGrams => best.weightGrams;
|
||||
double get calories => best.calories;
|
||||
double get proteinG => best.proteinG;
|
||||
double get fatG => best.fatG;
|
||||
double get carbsG => best.carbsG;
|
||||
double get confidence => best.confidence;
|
||||
|
||||
factory DishResult.fromJson(Map<String, dynamic> json) {
|
||||
// New format: {"candidates": [...]}
|
||||
if (json['candidates'] is List) {
|
||||
final candidatesList = (json['candidates'] as List<dynamic>)
|
||||
.map((element) => DishCandidate.fromJson(element as Map<String, dynamic>))
|
||||
.toList();
|
||||
return DishResult(candidates: candidatesList);
|
||||
}
|
||||
|
||||
// Legacy flat format: {"dish_name": "...", "calories": ..., ...}
|
||||
if (json['dish_name'] != null) {
|
||||
return DishResult(candidates: [DishCandidate.fromJson(json)]);
|
||||
}
|
||||
|
||||
return const DishResult(candidates: []);
|
||||
}
|
||||
}
|
||||
|
||||
// ---------------------------------------------------------------------------
|
||||
// Service
|
||||
// ---------------------------------------------------------------------------
|
||||
|
||||
Reference in New Issue
Block a user