import 'dart:io'; import 'package:flutter/material.dart'; import 'package:image_picker/image_picker.dart'; import '../main.dart'; import '../scanner_screen.dart'; import '../services/collection_service.dart'; import '../services/storage_service.dart'; import '../theme/app_colors.dart'; /// The "Scan" tab — quick-access view for scanning / adding cars. class ScanTab extends StatefulWidget { const ScanTab({super.key}); @override State createState() => _ScanTabState(); } class _ScanTabState extends State { bool _isBusy = false; List _collections = []; Collection? _selectedCollection; bool _loadingCollections = true; @override void initState() { super.initState(); _loadCollections(); } Future _loadCollections() async { try { final list = await CollectionService.getMyCollections(); if (!mounted) return; setState(() { _collections = list; _selectedCollection = list.isNotEmpty ? list.first : null; _loadingCollections = false; }); } catch (e) { if (!mounted) return; setState(() => _loadingCollections = false); } } @override Widget build(BuildContext context) { return Scaffold( body: SafeArea( child: Padding( padding: const EdgeInsets.symmetric(horizontal: 24), child: Column( mainAxisAlignment: MainAxisAlignment.center, children: [ // ── Illustration ── Container( width: 120, height: 120, decoration: BoxDecoration( gradient: AppColors.brandGradientSoft, shape: BoxShape.circle, ), child: const Icon( Icons.qr_code_scanner, size: 56, color: AppColors.orange, ), ), const SizedBox(height: 28), const Text( 'Scan a Hot Wheels Car', style: TextStyle( fontSize: 22, fontWeight: FontWeight.w700, ), ), const SizedBox(height: 10), const Text( 'Point your camera at the model ID on the\npackaging to instantly add it to your collection.', textAlign: TextAlign.center, style: TextStyle( fontSize: 14, color: AppColors.textSecondary, height: 1.5, ), ), const SizedBox(height: 24), // ── Collection picker ── if (_loadingCollections) const Padding( padding: EdgeInsets.symmetric(vertical: 8), child: SizedBox( width: 20, height: 20, child: CircularProgressIndicator(strokeWidth: 2), ), ) else if (_collections.isEmpty) const Text( 'Create a collection first!', style: TextStyle(color: AppColors.error), ) else Container( width: double.infinity, padding: const EdgeInsets.symmetric(horizontal: 16), decoration: BoxDecoration( borderRadius: BorderRadius.circular(14), border: Border.all( color: AppColors.orange.withValues(alpha: 0.4), ), ), child: DropdownButtonHideUnderline( child: DropdownButton( isExpanded: true, value: _selectedCollection?.id, icon: const Icon(Icons.arrow_drop_down, color: AppColors.orange), items: _collections .map((c) => DropdownMenuItem( value: c.id, child: Row( children: [ Icon( c.isOwner ? Icons.collections_bookmark : Icons.group, size: 18, color: AppColors.orange, ), const SizedBox(width: 8), Expanded( child: Text( c.name, overflow: TextOverflow.ellipsis, ), ), ], ), )) .toList(), onChanged: (id) { setState(() { _selectedCollection = _collections .firstWhere((c) => c.id == id); }); }, ), ), ), const SizedBox(height: 24), // ── Scan button (gradient) ── SizedBox( width: double.infinity, height: 56, child: DecoratedBox( decoration: BoxDecoration( gradient: AppColors.brandGradient, borderRadius: BorderRadius.circular(50), boxShadow: [ BoxShadow( color: AppColors.orange.withValues(alpha: 0.35), blurRadius: 14, offset: const Offset(0, 5), ), ], ), child: ElevatedButton.icon( onPressed: _isBusy || _selectedCollection == null ? null : _openScanner, icon: _isBusy ? const SizedBox( width: 22, height: 22, child: CircularProgressIndicator( strokeWidth: 2.5, color: Colors.white, ), ) : const Icon(Icons.camera_alt, color: Colors.white), label: Text( _isBusy ? 'Processing…' : 'Open Scanner', style: const TextStyle( fontSize: 16, fontWeight: FontWeight.w600, color: Colors.white, ), ), style: ElevatedButton.styleFrom( backgroundColor: Colors.transparent, shadowColor: Colors.transparent, shape: RoundedRectangleBorder( borderRadius: BorderRadius.circular(50), ), ), ), ), ), const SizedBox(height: 16), // ── Manual entry ── SizedBox( width: double.infinity, child: OutlinedButton.icon( onPressed: _isBusy || _selectedCollection == null ? null : _manualEntry, icon: const Icon(Icons.keyboard), label: const Text('Enter ID Manually'), ), ), ], ), ), ), ); } Future _openScanner() async { final hwId = await navigatorKey.currentState!.push( MaterialPageRoute(builder: (_) => const ScannerScreen()), ); if (hwId == null || !mounted) return; await _processHwId(hwId); } Future _manualEntry() async { final controller = TextEditingController(); final result = await showDialog( context: context, builder: (_) => AlertDialog( title: const Text('Enter HW ID'), content: TextField( controller: controller, autofocus: true, textCapitalization: TextCapitalization.characters, decoration: const InputDecoration(hintText: 'e.g. JKF21'), onSubmitted: (v) { final val = v.trim().toUpperCase(); if (val.isNotEmpty) Navigator.pop(context, val); }, ), actions: [ TextButton( onPressed: () => Navigator.pop(context), child: const Text('Cancel'), ), ElevatedButton( onPressed: () { final val = controller.text.trim().toUpperCase(); if (val.isNotEmpty) Navigator.pop(context, val); }, child: const Text('OK'), ), ], ), ); if (result == null || !mounted) return; await _processHwId(result); } Future _processHwId(String hwId) async { final collection = _selectedCollection; if (collection == null) return; setState(() => _isBusy = true); try { // Check if this hw_id already exists in the selected collection. final data = await supabase .from('hotwheels') .select() .eq('hw_id', hwId) .eq('collection_id', collection.id) .maybeSingle(); if (!mounted) return; setState(() => _isBusy = false); if (data != null) { // Already in collection await showDialog( context: context, builder: (_) => AlertDialog( icon: const Icon(Icons.check_circle, color: AppColors.success, size: 48), title: const Text('Already in Collection!'), content: Text('$hwId is already in "${collection.name}".'), actions: [ ElevatedButton( onPressed: () => Navigator.pop(context), child: const Text('Got it'), ), ], ), ); } else { // New — offer to add final added = await showDialog( context: context, builder: (_) => _AddCarDialog( hwId: hwId, collectionId: collection.id, collectionName: collection.name, ), ); if (added == true) { showGlobalSnackBar('$hwId added to "${collection.name}"! 🎉'); } } } catch (e) { if (mounted) setState(() => _isBusy = false); showGlobalSnackBar('DB error: $e', isError: true); } } } // ── Add Car Dialog (inline, styled) ────────────────────────────────── class _AddCarDialog extends StatefulWidget { final String hwId; final String collectionId; final String collectionName; const _AddCarDialog({ required this.hwId, required this.collectionId, required this.collectionName, }); @override State<_AddCarDialog> createState() => _AddCarDialogState(); } class _AddCarDialogState extends State<_AddCarDialog> { final _nameController = TextEditingController(); final _seriesController = TextEditingController(); final _yearController = TextEditingController(); final _notesController = TextEditingController(); bool _isAdding = false; File? _pickedImage; @override void dispose() { _nameController.dispose(); _seriesController.dispose(); _yearController.dispose(); _notesController.dispose(); super.dispose(); } Future _pickImage() async { final picker = ImagePicker(); final xFile = await picker.pickImage( source: ImageSource.camera, maxWidth: 800, maxHeight: 800, imageQuality: 60, ); if (xFile != null && mounted) { setState(() => _pickedImage = File(xFile.path)); } } Future _quickAdd() async { setState(() => _isAdding = true); try { await supabase.from('hotwheels').insert({ 'hw_id': widget.hwId, 'user_id': supabase.auth.currentUser!.id, 'collection_id': widget.collectionId, }); if (!mounted) return; Navigator.pop(context, true); } catch (e) { if (!mounted) return; setState(() => _isAdding = false); showGlobalSnackBar('Failed to add: $e', isError: true); } } Future _add() async { setState(() => _isAdding = true); try { final row = { 'hw_id': widget.hwId, 'user_id': supabase.auth.currentUser!.id, 'collection_id': widget.collectionId, }; // Optional fields — only include if filled in. final name = _nameController.text.trim(); final series = _seriesController.text.trim(); final yearStr = _yearController.text.trim(); final notes = _notesController.text.trim(); if (name.isNotEmpty) row['name'] = name; if (series.isNotEmpty) row['series'] = series; if (yearStr.isNotEmpty) { final y = int.tryParse(yearStr); if (y != null) row['year'] = y; } if (notes.isNotEmpty) row['notes'] = notes; // Upload image if one was taken. if (_pickedImage != null) { final url = await StorageService.uploadCarImage( file: _pickedImage!, ); if (url != null) row['image_url'] = url; } await supabase.from('hotwheels').insert(row); if (!mounted) return; Navigator.pop(context, true); } catch (e) { if (!mounted) return; setState(() => _isAdding = false); showGlobalSnackBar('Failed to add: $e', isError: true); } } @override Widget build(BuildContext context) { return AlertDialog( icon: Container( padding: const EdgeInsets.all(12), decoration: const BoxDecoration( gradient: AppColors.brandGradient, shape: BoxShape.circle, ), child: const Icon(Icons.add, color: Colors.white, size: 28), ), title: Text('Add ${widget.hwId}'), content: SingleChildScrollView( child: Column( mainAxisSize: MainAxisSize.min, children: [ // ── Photo picker ── GestureDetector( onTap: _pickImage, child: Container( width: double.infinity, height: 140, decoration: BoxDecoration( color: AppColors.backgroundLight, borderRadius: BorderRadius.circular(14), border: Border.all( color: AppColors.orange.withValues(alpha: 0.4), width: 1.5, ), image: _pickedImage != null ? DecorationImage( image: FileImage(_pickedImage!), fit: BoxFit.cover, ) : null, ), child: _pickedImage == null ? Column( mainAxisAlignment: MainAxisAlignment.center, children: [ Icon(Icons.add_a_photo, size: 36, color: AppColors.orange.withValues(alpha: 0.6)), const SizedBox(height: 8), const Text( 'Tap to take a photo', style: TextStyle( fontSize: 13, color: AppColors.textSecondary, ), ), ], ) : Align( alignment: Alignment.topRight, child: Padding( padding: const EdgeInsets.all(6), child: CircleAvatar( radius: 16, backgroundColor: Colors.black54, child: IconButton( icon: const Icon(Icons.close, size: 16, color: Colors.white), padding: EdgeInsets.zero, onPressed: () => setState(() => _pickedImage = null), ), ), ), ), ), ), const SizedBox(height: 16), TextField( controller: _nameController, decoration: const InputDecoration( labelText: 'Car Name', hintText: "e.g. '70 Dodge Charger", ), ), const SizedBox(height: 12), TextField( controller: _seriesController, decoration: const InputDecoration( labelText: 'Series', hintText: 'e.g. HW Flames', ), ), const SizedBox(height: 12), TextField( controller: _yearController, keyboardType: TextInputType.number, decoration: const InputDecoration( labelText: 'Year', hintText: 'e.g. 2025', ), ), const SizedBox(height: 12), TextField( controller: _notesController, maxLines: 2, decoration: const InputDecoration( labelText: 'Notes', hintText: 'Any extra info…', ), ), ], ), ), actions: [ TextButton( onPressed: _isAdding ? null : () => Navigator.pop(context), child: const Text('Cancel'), ), OutlinedButton( onPressed: _isAdding ? null : _quickAdd, child: const Text('Skip'), ), ElevatedButton( onPressed: _isAdding ? null : _add, child: _isAdding ? const SizedBox( width: 18, height: 18, child: CircularProgressIndicator(strokeWidth: 2), ) : const Text('Add to Collection'), ), ], ); } }