import 'package:flutter/material.dart'; import 'package:flutter_riverpod/flutter_riverpod.dart'; import '../../core/locale/unit_provider.dart'; import '../products/user_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; @override void initState() { super.initState(); _items = widget.items .map((item) => _EditableItem( name: item.name, quantity: item.quantity, unit: item.unit, category: item.category, primaryProductId: item.primaryProductId, storageDays: item.storageDays, confidence: item.confidence, )) .toList(); } @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: ref.watch(unitsProvider).valueOrNull ?? {}, 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(userProductsProvider.notifier).create( name: item.name, quantity: item.quantity, unit: item.unit, category: item.category, storageDays: item.storageDays, primaryProductId: item.primaryProductId, ); } 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? primaryProductId; final int storageDays; final double confidence; _EditableItem({ required this.name, required this.quantity, required this.unit, required this.category, this.primaryProductId, 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 Map 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), widget.units.isEmpty ? const SizedBox(width: 48) : Builder(builder: (builderContext) { // Reconcile item.unit with valid server codes so that the // submitted value matches what the dropdown displays. if (!widget.units.containsKey(widget.item.unit)) { widget.item.unit = widget.units.keys.first; } return DropdownButton( value: widget.item.unit, underline: const SizedBox(), items: widget.units.entries .map((e) => DropdownMenuItem(value: e.key, child: Text(e.value))) .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('Назад')), ], ), ); } }