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:
@@ -5,6 +5,7 @@ import 'package:flutter_riverpod/flutter_riverpod.dart';
|
||||
import '../../core/auth/auth_provider.dart';
|
||||
import '../../core/locale/language_provider.dart';
|
||||
import '../../core/theme/app_colors.dart';
|
||||
import '../../shared/models/meal_type.dart';
|
||||
import '../../shared/models/user.dart';
|
||||
import 'profile_provider.dart';
|
||||
import 'profile_service.dart';
|
||||
@@ -92,7 +93,7 @@ class _ProfileBody extends ConsumerWidget {
|
||||
]),
|
||||
const SizedBox(height: 16),
|
||||
|
||||
// Calories
|
||||
// Calories + meal types
|
||||
_SectionLabel('ПИТАНИЕ'),
|
||||
const SizedBox(height: 6),
|
||||
_InfoCard(children: [
|
||||
@@ -102,6 +103,14 @@ class _ProfileBody extends ConsumerWidget {
|
||||
? '${user.dailyCalories} ккал/день'
|
||||
: null,
|
||||
),
|
||||
const Divider(height: 1, indent: 16),
|
||||
_InfoRow(
|
||||
'Приёмы пищи',
|
||||
user.mealTypes
|
||||
.map((mealTypeId) =>
|
||||
mealTypeById(mealTypeId)?.label ?? mealTypeId)
|
||||
.join(', '),
|
||||
),
|
||||
]),
|
||||
if (user.dailyCalories != null) ...[
|
||||
const SizedBox(height: 4),
|
||||
@@ -342,6 +351,7 @@ class _EditProfileSheetState extends ConsumerState<EditProfileSheet> {
|
||||
String? _goal;
|
||||
String? _activity;
|
||||
String? _language;
|
||||
List<String> _mealTypes = [];
|
||||
bool _saving = false;
|
||||
|
||||
@override
|
||||
@@ -358,6 +368,7 @@ class _EditProfileSheetState extends ConsumerState<EditProfileSheet> {
|
||||
_goal = u.goal;
|
||||
_activity = u.activity;
|
||||
_language = u.preferences['language'] as String? ?? 'ru';
|
||||
_mealTypes = List<String>.from(u.mealTypes);
|
||||
}
|
||||
|
||||
@override
|
||||
@@ -400,6 +411,7 @@ class _EditProfileSheetState extends ConsumerState<EditProfileSheet> {
|
||||
goal: _goal,
|
||||
activity: _activity,
|
||||
language: _language,
|
||||
mealTypes: _mealTypes,
|
||||
);
|
||||
|
||||
final ok = await ref.read(profileProvider.notifier).update(req);
|
||||
@@ -590,12 +602,39 @@ class _EditProfileSheetState extends ConsumerState<EditProfileSheet> {
|
||||
),
|
||||
const SizedBox(height: 20),
|
||||
|
||||
// Meal types
|
||||
Text('Приёмы пищи', style: theme.textTheme.labelMedium),
|
||||
const SizedBox(height: 8),
|
||||
Wrap(
|
||||
spacing: 8,
|
||||
runSpacing: 6,
|
||||
children: kAllMealTypes.map((mealTypeOption) {
|
||||
final isSelected =
|
||||
_mealTypes.contains(mealTypeOption.id);
|
||||
return FilterChip(
|
||||
label: Text(
|
||||
'${mealTypeOption.emoji} ${mealTypeOption.label}'),
|
||||
selected: isSelected,
|
||||
onSelected: (selected) {
|
||||
setState(() {
|
||||
if (selected) {
|
||||
_mealTypes.add(mealTypeOption.id);
|
||||
} else if (_mealTypes.length > 1) {
|
||||
_mealTypes.remove(mealTypeOption.id);
|
||||
}
|
||||
});
|
||||
},
|
||||
);
|
||||
}).toList(),
|
||||
),
|
||||
const SizedBox(height: 20),
|
||||
|
||||
// Language
|
||||
ref.watch(supportedLanguagesProvider).when(
|
||||
data: (languages) => DropdownButtonFormField<String>(
|
||||
value: _language,
|
||||
decoration: const InputDecoration(
|
||||
labelText: 'Язык интерфейса'),
|
||||
initialValue: _language,
|
||||
items: languages.entries
|
||||
.map((e) => DropdownMenuItem(
|
||||
value: e.key,
|
||||
|
||||
Reference in New Issue
Block a user