import 'package:flutter/material.dart'; import 'package:flutter_riverpod/flutter_riverpod.dart'; import '../products/product_provider.dart'; import 'recognition_service.dart'; /// Editable confirmation screen shown after receipt/products recognition. /// The user can adjust quantities, units, remove items, then batch-add to pantry. class RecognitionConfirmScreen extends ConsumerStatefulWidget { const RecognitionConfirmScreen({super.key, required this.items}); final List items; @override ConsumerState createState() => _RecognitionConfirmScreenState(); } class _RecognitionConfirmScreenState extends ConsumerState { late final List<_EditableItem> _items; bool _saving = false; static const _units = ['г', 'кг', 'мл', 'л', 'шт', 'уп']; @override void initState() { super.initState(); _items = widget.items .map((item) => _EditableItem( name: item.name, quantity: item.quantity, unit: _mapUnit(item.unit), category: item.category, mappingId: item.mappingId, storageDays: item.storageDays, confidence: item.confidence, )) .toList(); } String _mapUnit(String unit) { // Backend may return 'pcs', 'g', 'kg', etc. — normalise to display units. switch (unit.toLowerCase()) { case 'g': return 'г'; case 'kg': return 'кг'; case 'ml': return 'мл'; case 'l': return 'л'; case 'pcs': case 'шт': return 'шт'; case 'уп': return 'уп'; default: return unit; } } @override Widget build(BuildContext context) { return Scaffold( appBar: AppBar( title: Text('Найдено ${_items.length} продуктов'), actions: [ if (_items.isNotEmpty) TextButton( onPressed: _saving ? null : _addAll, child: const Text('Добавить всё'), ), ], ), body: _items.isEmpty ? _EmptyState(onBack: () => Navigator.pop(context)) : ListView.builder( padding: const EdgeInsets.only(bottom: 80), itemCount: _items.length, itemBuilder: (_, i) => _ItemTile( item: _items[i], units: _units, onDelete: () => setState(() => _items.removeAt(i)), onChanged: () => setState(() {}), ), ), floatingActionButton: _items.isEmpty ? null : FloatingActionButton.extended( onPressed: _saving ? null : _addAll, icon: _saving ? const SizedBox( width: 20, height: 20, child: CircularProgressIndicator(strokeWidth: 2), ) : const Icon(Icons.add_shopping_cart), label: const Text('В запасы'), ), ); } Future _addAll() async { setState(() => _saving = true); try { for (final item in _items) { await ref.read(productsProvider.notifier).create( name: item.name, quantity: item.quantity, unit: item.unit, category: item.category, storageDays: item.storageDays, mappingId: item.mappingId, ); } if (mounted) { ScaffoldMessenger.of(context).showSnackBar( SnackBar( content: Text('Добавлено ${_items.length} продуктов'), ), ); // Pop back to products screen. int count = 0; Navigator.popUntil(context, (_) => count++ >= 2); } } catch (_) { if (mounted) { ScaffoldMessenger.of(context).showSnackBar( const SnackBar(content: Text('Не удалось добавить продукты')), ); } } finally { if (mounted) setState(() => _saving = false); } } } // --------------------------------------------------------------------------- // Editable item model // --------------------------------------------------------------------------- class _EditableItem { String name; double quantity; String unit; final String category; final String? mappingId; final int storageDays; final double confidence; _EditableItem({ required this.name, required this.quantity, required this.unit, required this.category, this.mappingId, required this.storageDays, required this.confidence, }); } // --------------------------------------------------------------------------- // Item tile with inline editing // --------------------------------------------------------------------------- class _ItemTile extends StatefulWidget { const _ItemTile({ required this.item, required this.units, required this.onDelete, required this.onChanged, }); final _EditableItem item; final List units; final VoidCallback onDelete; final VoidCallback onChanged; @override State<_ItemTile> createState() => _ItemTileState(); } class _ItemTileState extends State<_ItemTile> { late final _qtyController = TextEditingController(text: _formatQty(widget.item.quantity)); @override void dispose() { _qtyController.dispose(); super.dispose(); } String _formatQty(double v) => v == v.roundToDouble() ? v.toInt().toString() : v.toStringAsFixed(1); @override Widget build(BuildContext context) { final theme = Theme.of(context); final conf = widget.item.confidence; final confColor = conf >= 0.8 ? Colors.green : conf >= 0.5 ? Colors.orange : Colors.red; return Dismissible( key: ValueKey(widget.item.name), direction: DismissDirection.endToStart, background: Container( color: theme.colorScheme.error, alignment: Alignment.centerRight, padding: const EdgeInsets.only(right: 20), child: Icon(Icons.delete_outline, color: theme.colorScheme.onError), ), onDismissed: (_) => widget.onDelete(), child: Padding( padding: const EdgeInsets.symmetric(horizontal: 16, vertical: 8), child: Row( children: [ Expanded( child: Column( crossAxisAlignment: CrossAxisAlignment.start, children: [ Text(widget.item.name, style: theme.textTheme.bodyLarge), Row( children: [ Container( width: 8, height: 8, decoration: BoxDecoration( color: confColor, shape: BoxShape.circle, ), ), const SizedBox(width: 4), Text( '${(conf * 100).toInt()}% уверенность', style: theme.textTheme.labelSmall ?.copyWith(color: confColor), ), ], ), ], ), ), const SizedBox(width: 8), SizedBox( width: 72, child: TextField( controller: _qtyController, keyboardType: const TextInputType.numberWithOptions(decimal: true), textAlign: TextAlign.center, decoration: const InputDecoration( isDense: true, contentPadding: EdgeInsets.symmetric(vertical: 8), border: OutlineInputBorder(), ), onChanged: (v) { final parsed = double.tryParse(v); if (parsed != null) { widget.item.quantity = parsed; widget.onChanged(); } }, ), ), const SizedBox(width: 8), DropdownButton( value: widget.units.contains(widget.item.unit) ? widget.item.unit : widget.units.last, underline: const SizedBox(), items: widget.units .map((u) => DropdownMenuItem(value: u, child: Text(u))) .toList(), onChanged: (v) { if (v != null) { setState(() => widget.item.unit = v); widget.onChanged(); } }, ), IconButton( icon: const Icon(Icons.close), onPressed: widget.onDelete, ), ], ), ), ); } } // --------------------------------------------------------------------------- // Empty state // --------------------------------------------------------------------------- class _EmptyState extends StatelessWidget { const _EmptyState({required this.onBack}); final VoidCallback onBack; @override Widget build(BuildContext context) { return Center( child: Column( mainAxisSize: MainAxisSize.min, children: [ const Icon(Icons.search_off, size: 64), const SizedBox(height: 12), const Text('Продукты не найдены'), const SizedBox(height: 16), FilledButton(onPressed: onBack, child: const Text('Назад')), ], ), ); } }