import 'package:flutter/material.dart'; import 'package:flutter_riverpod/flutter_riverpod.dart'; import 'package:go_router/go_router.dart'; import 'package:image_picker/image_picker.dart'; import '../../core/auth/auth_provider.dart'; import 'recognition_service.dart'; // Provider wired to the shared ApiClient. final _recognitionServiceProvider = Provider((ref) { return RecognitionService(ref.read(apiClientProvider)); }); /// Entry screen — lets the user choose how to add products. /// If [GoRouterState.extra] is a non-null String, it is treated as a meal type ID /// and the screen immediately opens the camera for dish recognition. class ScanScreen extends ConsumerStatefulWidget { const ScanScreen({super.key}); @override ConsumerState createState() => _ScanScreenState(); } class _ScanScreenState extends ConsumerState { bool _autoStarted = false; @override void didChangeDependencies() { super.didChangeDependencies(); if (_autoStarted) return; final mealType = GoRouterState.of(context).extra as String?; if (mealType != null && mealType.isNotEmpty) { _autoStarted = true; // Defer to avoid calling context navigation during build. WidgetsBinding.instance.addPostFrameCallback((_) { if (mounted) { _pickAndRecognize(context, _Mode.dish, mealType: mealType); } }); } } @override Widget build(BuildContext context) { return Scaffold( appBar: AppBar(title: const Text('Добавить продукты')), body: ListView( padding: const EdgeInsets.all(20), children: [ const SizedBox(height: 16), Text( 'Выберите способ', style: Theme.of(context).textTheme.titleLarge, textAlign: TextAlign.center, ), const SizedBox(height: 32), _ModeCard( emoji: '🧾', title: 'Сфотографировать чек', subtitle: 'Распознаем все продукты из чека', onTap: () => _pickAndRecognize(context, _Mode.receipt), ), const SizedBox(height: 16), _ModeCard( emoji: '🥦', title: 'Сфотографировать продукты', subtitle: 'Холодильник, стол, полка — до 3 фото', onTap: () => _pickAndRecognize(context, _Mode.products), ), const SizedBox(height: 16), _ModeCard( emoji: '🍽️', title: 'Определить блюдо', subtitle: 'КБЖУ≈ по фото готового блюда', onTap: () => _pickAndRecognize(context, _Mode.dish), ), const SizedBox(height: 16), _ModeCard( emoji: '✏️', title: 'Добавить вручную', subtitle: 'Ввести название, количество и срок', onTap: () => context.push('/products/add'), ), ], ), ); } Future _pickAndRecognize( BuildContext context, _Mode mode, { String? mealType, }) async { final picker = ImagePicker(); List files = []; if (mode == _Mode.products) { // Allow up to 3 images. final picked = await picker.pickMultiImage( imageQuality: 70, maxWidth: 1024, maxHeight: 1024, ); if (picked.isEmpty) return; files = picked.take(3).toList(); } else { final source = await _chooseSource(context); if (source == null) return; final picked = await picker.pickImage( source: source, imageQuality: 70, maxWidth: 1024, maxHeight: 1024, ); if (picked == null) return; files = [picked]; } if (!context.mounted) return; final service = ref.read(_recognitionServiceProvider); // Show loading overlay while the AI processes. showDialog( context: context, barrierDismissible: false, builder: (_) => const _LoadingDialog(), ); try { switch (mode) { case _Mode.receipt: final result = await service.recognizeReceipt(files.first); if (context.mounted) { Navigator.pop(context); // close loading context.push('/scan/confirm', extra: result.items); } case _Mode.products: final items = await service.recognizeProducts(files); if (context.mounted) { Navigator.pop(context); context.push('/scan/confirm', extra: items); } case _Mode.dish: final dish = await service.recognizeDish(files.first); if (context.mounted) { Navigator.pop(context); context.push('/scan/dish', extra: {'dish': dish, 'meal_type': mealType}); } } } catch (recognitionError) { debugPrint('Recognition error: $recognitionError'); if (context.mounted) { Navigator.pop(context); // close loading ScaffoldMessenger.of(context).showSnackBar( const SnackBar( content: Text('Не удалось распознать. Попробуйте ещё раз.'), ), ); } } } Future _chooseSource(BuildContext context) async { return showModalBottomSheet( context: context, builder: (_) => SafeArea( child: Column( mainAxisSize: MainAxisSize.min, children: [ ListTile( leading: const Icon(Icons.camera_alt), title: const Text('Камера'), onTap: () => Navigator.pop(context, ImageSource.camera), ), ListTile( leading: const Icon(Icons.photo_library), title: const Text('Галерея'), onTap: () => Navigator.pop(context, ImageSource.gallery), ), ], ), ), ); } } // --------------------------------------------------------------------------- // Mode enum // --------------------------------------------------------------------------- enum _Mode { receipt, products, dish } // --------------------------------------------------------------------------- // Widgets // --------------------------------------------------------------------------- class _ModeCard extends StatelessWidget { const _ModeCard({ required this.emoji, required this.title, required this.subtitle, required this.onTap, }); final String emoji; final String title; final String subtitle; final VoidCallback onTap; @override Widget build(BuildContext context) { final theme = Theme.of(context); return Card( child: ListTile( contentPadding: const EdgeInsets.symmetric(horizontal: 20, vertical: 12), leading: Text(emoji, style: const TextStyle(fontSize: 32)), title: Text(title, style: theme.textTheme.titleMedium), subtitle: Text(subtitle, style: theme.textTheme.bodySmall), trailing: const Icon(Icons.chevron_right), onTap: onTap, ), ); } } class _LoadingDialog extends StatelessWidget { const _LoadingDialog(); @override Widget build(BuildContext context) { return const AlertDialog( content: Column( mainAxisSize: MainAxisSize.min, children: [ CircularProgressIndicator(), SizedBox(height: 16), Text('Распознаём...'), ], ), ); } }