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>
73 lines
1.9 KiB
Dart
73 lines
1.9 KiB
Dart
import 'package:json_annotation/json_annotation.dart';
|
|
|
|
import 'meal_type.dart';
|
|
|
|
part 'user.g.dart';
|
|
|
|
@JsonSerializable()
|
|
class User {
|
|
final String id;
|
|
final String email;
|
|
final String name;
|
|
@JsonKey(name: 'avatar_url')
|
|
final String? avatarUrl;
|
|
@JsonKey(name: 'height_cm')
|
|
final int? heightCm;
|
|
@JsonKey(name: 'weight_kg')
|
|
final double? weightKg;
|
|
@JsonKey(name: 'date_of_birth')
|
|
final String? dateOfBirth;
|
|
final String? gender;
|
|
final String? activity;
|
|
final String? goal;
|
|
@JsonKey(name: 'daily_calories')
|
|
final int? dailyCalories;
|
|
final String plan;
|
|
@JsonKey(defaultValue: {})
|
|
final Map<String, dynamic> preferences;
|
|
|
|
const User({
|
|
required this.id,
|
|
required this.email,
|
|
required this.name,
|
|
this.avatarUrl,
|
|
this.heightCm,
|
|
this.weightKg,
|
|
this.dateOfBirth,
|
|
this.gender,
|
|
this.activity,
|
|
this.goal,
|
|
this.dailyCalories,
|
|
required this.plan,
|
|
this.preferences = const {},
|
|
});
|
|
|
|
factory User.fromJson(Map<String, dynamic> json) => _$UserFromJson(json);
|
|
Map<String, dynamic> toJson() => _$UserToJson(this);
|
|
|
|
int? get age {
|
|
if (dateOfBirth == null) return null;
|
|
final dob = DateTime.tryParse(dateOfBirth!);
|
|
if (dob == null) return null;
|
|
final now = DateTime.now();
|
|
int years = now.year - dob.year;
|
|
if (now.month < dob.month ||
|
|
(now.month == dob.month && now.day < dob.day)) {
|
|
years--;
|
|
}
|
|
return years;
|
|
}
|
|
|
|
bool get hasCompletedOnboarding =>
|
|
heightCm != null && weightKg != null && dateOfBirth != null &&
|
|
gender != null && goal != null && activity != null;
|
|
|
|
/// Returns the user's configured meal type IDs from preferences,
|
|
/// falling back to the default set if not yet configured.
|
|
List<String> get mealTypes {
|
|
final value = preferences['meal_types'];
|
|
if (value is List && value.isNotEmpty) return List<String>.from(value);
|
|
return List<String>.from(kDefaultMealTypeIds);
|
|
}
|
|
}
|