import 'dart:io'; import 'package:flutter/material.dart'; import 'package:image_picker/image_picker.dart'; import '../main.dart'; import '../services/storage_service.dart'; import '../theme/app_colors.dart'; import '../widgets/car_card.dart'; /// The "My Garage" screen — shows a collection's cars in a grid. class GarageScreen extends StatefulWidget { final String collectionId; final String collectionName; final bool isOwner; const GarageScreen({ super.key, required this.collectionId, required this.collectionName, this.isOwner = true, }); @override State createState() => GarageScreenState(); } class GarageScreenState extends State { List> _cars = []; bool _isLoading = true; String? _error; String _searchQuery = ''; final _searchController = TextEditingController(); @override void initState() { super.initState(); _loadCars(); } @override void dispose() { _searchController.dispose(); super.dispose(); } /// Public method so other screens can trigger a refresh. void refresh() => _loadCars(); Future _loadCars() async { setState(() { _isLoading = true; _error = null; }); try { final data = await supabase .from('hotwheels') .select() .eq('collection_id', widget.collectionId) .order('created_at', ascending: false); if (!mounted) return; setState(() { _cars = List>.from(data); _isLoading = false; }); } catch (e) { if (!mounted) return; setState(() { _error = e.toString(); _isLoading = false; }); } } List> get _filteredCars { if (_searchQuery.isEmpty) return _cars; final q = _searchQuery.toLowerCase(); return _cars.where((car) { final id = (car['hw_id'] as String? ?? '').toLowerCase(); final name = (car['name'] as String? ?? '').toLowerCase(); final series = (car['series'] as String? ?? '').toLowerCase(); return id.contains(q) || name.contains(q) || series.contains(q); }).toList(); } @override Widget build(BuildContext context) { return Scaffold( body: CustomScrollView( slivers: [ // ── Header ── SliverAppBar( expandedHeight: 140, pinned: true, flexibleSpace: FlexibleSpaceBar( titlePadding: const EdgeInsets.only(left: 20, bottom: 16), title: Text( widget.collectionName, style: TextStyle( fontFamily: 'Poppins', fontWeight: FontWeight.w700, fontSize: 22, color: Colors.white, shadows: [ Shadow( color: Colors.black.withValues(alpha: 0.3), blurRadius: 4, ), ], ), ), background: Container( decoration: const BoxDecoration( gradient: AppColors.brandGradient, ), child: Align( alignment: Alignment.centerRight, child: Padding( padding: const EdgeInsets.only(right: 24), child: Icon( Icons.directions_car_filled, size: 72, color: Colors.white.withValues(alpha: 0.15), ), ), ), ), ), ), // ── Stats bar ── SliverToBoxAdapter( child: Padding( padding: const EdgeInsets.fromLTRB(16, 16, 16, 4), child: Row( children: [ _StatChip( icon: Icons.directions_car, label: '${_cars.length}', subtitle: 'Total Cars', ), const SizedBox(width: 12), _StatChip( icon: Icons.new_releases, label: _recentCount(), subtitle: 'This Week', ), ], ), ), ), // ── Search bar ── SliverToBoxAdapter( child: Padding( padding: const EdgeInsets.fromLTRB(16, 12, 16, 8), child: TextField( controller: _searchController, onChanged: (v) => setState(() => _searchQuery = v), decoration: InputDecoration( hintText: 'Search by ID, name, or series…', prefixIcon: const Icon(Icons.search), suffixIcon: _searchQuery.isEmpty ? null : IconButton( icon: const Icon(Icons.clear), onPressed: () { _searchController.clear(); setState(() => _searchQuery = ''); }, ), ), ), ), ), // ── Content ── if (_isLoading) const SliverFillRemaining( child: Center(child: CircularProgressIndicator()), ) else if (_error != null) SliverFillRemaining( child: _ErrorView( message: _error!, onRetry: _loadCars, ), ) else if (_filteredCars.isEmpty) SliverFillRemaining( child: _EmptyGarage(hasSearch: _searchQuery.isNotEmpty), ) else SliverPadding( padding: const EdgeInsets.fromLTRB(8, 4, 8, 100), sliver: SliverGrid( gridDelegate: const SliverGridDelegateWithFixedCrossAxisCount( crossAxisCount: 2, childAspectRatio: 0.72, mainAxisSpacing: 4, crossAxisSpacing: 0, ), delegate: SliverChildBuilderDelegate( (context, index) { final car = _filteredCars[index]; return CarCard( hwId: car['hw_id'] as String? ?? '???', name: car['name'] as String?, series: car['series'] as String?, year: car['year'] as int?, color: car['color'] as String?, imageUrl: car['image_url'] as String?, addedAt: car['created_at'] != null ? DateTime.tryParse(car['created_at']) : null, onTap: () => _showCarDetails(car), ); }, childCount: _filteredCars.length, ), ), ), ], ), ); } String _recentCount() { final weekAgo = DateTime.now().subtract(const Duration(days: 7)); final count = _cars.where((c) { final ts = c['created_at']; if (ts == null) return false; final d = DateTime.tryParse(ts.toString()); return d != null && d.isAfter(weekAgo); }).length; return '$count'; } void _showCarDetails(Map car) { final hwId = car['hw_id'] as String? ?? '???'; final name = car['name'] as String?; final series = car['series'] as String?; final year = car['year'] as int?; final notes = car['notes'] as String?; final imageUrl = car['image_url'] as String?; showModalBottomSheet( context: context, isScrollControlled: true, shape: const RoundedRectangleBorder( borderRadius: BorderRadius.vertical(top: Radius.circular(24)), ), builder: (_) => DraggableScrollableSheet( initialChildSize: 0.55, minChildSize: 0.3, maxChildSize: 0.85, expand: false, builder: (context, scrollController) => ListView( controller: scrollController, padding: const EdgeInsets.all(24), children: [ // Drag handle Center( child: Container( width: 40, height: 4, margin: const EdgeInsets.only(bottom: 20), decoration: BoxDecoration( color: Colors.grey.shade300, borderRadius: BorderRadius.circular(2), ), ), ), // ── Car image ── ClipRRect( borderRadius: BorderRadius.circular(16), child: AspectRatio( aspectRatio: 16 / 10, child: imageUrl != null && imageUrl.isNotEmpty ? Image.network( imageUrl, fit: BoxFit.cover, errorBuilder: (context, error, stackTrace) => _imagePlaceholder(), ) : _imagePlaceholder(), ), ), const SizedBox(height: 8), // Change / Add photo button Align( alignment: Alignment.centerRight, child: TextButton.icon( onPressed: () => _updatePhoto(car, context), icon: Icon( imageUrl != null ? Icons.camera_alt : Icons.add_a_photo, size: 18, ), label: Text(imageUrl != null ? 'Change Photo' : 'Add Photo'), ), ), const SizedBox(height: 4), // ID badge Align( alignment: Alignment.centerLeft, child: Container( padding: const EdgeInsets.symmetric( horizontal: 12, vertical: 6), decoration: BoxDecoration( gradient: AppColors.brandGradient, borderRadius: BorderRadius.circular(8), ), child: Text( hwId, style: const TextStyle( color: Colors.white, fontWeight: FontWeight.w700, fontSize: 18, letterSpacing: 1.5, ), ), ), ), if (name != null && name.isNotEmpty) ...[ const SizedBox(height: 16), Text( name, style: Theme.of(context).textTheme.headlineSmall?.copyWith( fontWeight: FontWeight.w600, ), ), ], const SizedBox(height: 12), const Divider(), const SizedBox(height: 8), if (series != null && series.isNotEmpty) _DetailRow(icon: Icons.collections, label: 'Series', value: series), if (year != null) _DetailRow(icon: Icons.calendar_today, label: 'Year', value: '$year'), if (notes != null && notes.isNotEmpty) _DetailRow(icon: Icons.notes, label: 'Notes', value: notes), const SizedBox(height: 24), // Edit & Delete buttons Row( children: [ Expanded( child: ElevatedButton.icon( onPressed: () => _editCar(car, context), icon: const Icon(Icons.edit, size: 18), label: const Text('Edit Details'), ), ), const SizedBox(width: 12), Expanded( child: OutlinedButton.icon( onPressed: () => _deleteCar(car, context), icon: const Icon(Icons.delete_outline, color: AppColors.error), label: const Text('Remove', style: TextStyle(color: AppColors.error)), style: OutlinedButton.styleFrom( side: const BorderSide(color: AppColors.error), ), ), ), ], ), ], ), ), ); } Widget _imagePlaceholder() { final isDark = Theme.of(context).brightness == Brightness.dark; return Container( color: isDark ? AppColors.surfaceDark : AppColors.backgroundLight, child: Center( child: Icon( Icons.directions_car_filled, size: 48, color: isDark ? Colors.white.withValues(alpha: 0.15) : AppColors.orange.withValues(alpha: 0.25), ), ), ); } /// Take a new photo and update the image_url for this car. Future _updatePhoto( Map car, BuildContext sheetContext) async { final picker = ImagePicker(); final xFile = await picker.pickImage( source: ImageSource.camera, maxWidth: 800, maxHeight: 800, imageQuality: 60, ); if (xFile == null) return; showGlobalSnackBar('Uploading photo…'); final oldImageUrl = car['image_url'] as String?; final url = await StorageService.uploadCarImage( file: File(xFile.path), oldImageUrl: oldImageUrl, ); if (url == null) { showGlobalSnackBar('Failed to upload photo.', isError: true); return; } try { await supabase .from('hotwheels') .update({'image_url': url}) .eq('id', car['id']); showGlobalSnackBar('Photo updated!'); if (sheetContext.mounted) Navigator.pop(sheetContext); _loadCars(); // refresh grid } catch (e) { showGlobalSnackBar('Failed to save: $e', isError: true); } } /// Open an edit dialog for this car, then update Supabase. Future _editCar( Map car, BuildContext sheetContext) async { final updated = await showDialog>( context: sheetContext, builder: (_) => _EditCarDialog(car: car), ); if (updated == null) return; try { await supabase .from('hotwheels') .update(updated) .eq('id', car['id']); showGlobalSnackBar('Car updated!'); if (sheetContext.mounted) Navigator.pop(sheetContext); _loadCars(); } catch (e) { showGlobalSnackBar('Failed to update: $e', isError: true); } } Future _deleteCar( Map car, BuildContext sheetContext) async { if (!sheetContext.mounted) return; final confirmed = await showDialog( context: sheetContext, builder: (_) => AlertDialog( title: const Text('Remove Car?'), content: Text( 'Remove ${car['hw_id']} from your garage? This cannot be undone.'), actions: [ TextButton( onPressed: () => Navigator.pop(sheetContext, false), child: const Text('Cancel'), ), ElevatedButton( onPressed: () => Navigator.pop(sheetContext, true), style: ElevatedButton.styleFrom(backgroundColor: AppColors.error), child: const Text('Remove'), ), ], ), ); if (confirmed != true) return; try { // Delete image from storage first (best-effort). await StorageService.deleteCarImage(car['image_url'] as String?); await supabase .from('hotwheels') .delete() .eq('id', car['id']); if (!mounted) return; if (sheetContext.mounted) { Navigator.pop(sheetContext); // close bottom sheet } showGlobalSnackBar('${car['hw_id']} removed from your garage.'); _loadCars(); } catch (e) { showGlobalSnackBar('Failed to remove: $e', isError: true); } } } // ── Helper widgets ────────────────────────────────────────────────── class _StatChip extends StatelessWidget { final IconData icon; final String label; final String subtitle; const _StatChip({ required this.icon, required this.label, required this.subtitle, }); @override Widget build(BuildContext context) { return Expanded( child: Container( padding: const EdgeInsets.symmetric(horizontal: 16, vertical: 14), decoration: BoxDecoration( color: Theme.of(context).cardTheme.color, borderRadius: BorderRadius.circular(14), boxShadow: const [ BoxShadow( color: Colors.black12, blurRadius: 6, offset: Offset(0, 2), ), ], ), child: Row( children: [ Container( padding: const EdgeInsets.all(8), decoration: BoxDecoration( color: AppColors.orange.withValues(alpha: 0.12), borderRadius: BorderRadius.circular(10), ), child: Icon(icon, size: 20, color: AppColors.orange), ), const SizedBox(width: 12), Column( crossAxisAlignment: CrossAxisAlignment.start, children: [ Text( label, style: const TextStyle( fontSize: 20, fontWeight: FontWeight.w700, ), ), Text( subtitle, style: const TextStyle( fontSize: 11, color: AppColors.textSecondary, ), ), ], ), ], ), ), ); } } class _EmptyGarage extends StatelessWidget { final bool hasSearch; const _EmptyGarage({required this.hasSearch}); @override Widget build(BuildContext context) { return Center( child: Padding( padding: const EdgeInsets.all(40), child: Column( mainAxisSize: MainAxisSize.min, children: [ Icon( hasSearch ? Icons.search_off : Icons.garage_outlined, size: 64, color: AppColors.textHint, ), const SizedBox(height: 16), Text( hasSearch ? 'No cars match your search' : 'Your garage is empty', style: const TextStyle( fontSize: 18, fontWeight: FontWeight.w600, color: AppColors.textSecondary, ), ), const SizedBox(height: 8), Text( hasSearch ? 'Try a different search term' : 'Scan your first Hot Wheels car to get started!', textAlign: TextAlign.center, style: const TextStyle(color: AppColors.textHint), ), ], ), ), ); } } class _ErrorView extends StatelessWidget { final String message; final VoidCallback onRetry; const _ErrorView({required this.message, required this.onRetry}); @override Widget build(BuildContext context) { return Center( child: Padding( padding: const EdgeInsets.all(40), child: Column( mainAxisSize: MainAxisSize.min, children: [ const Icon(Icons.error_outline, size: 56, color: AppColors.error), const SizedBox(height: 16), Text(message, textAlign: TextAlign.center, style: const TextStyle(color: AppColors.textSecondary)), const SizedBox(height: 16), ElevatedButton.icon( onPressed: onRetry, icon: const Icon(Icons.refresh), label: const Text('Retry'), ), ], ), ), ); } } // ── Edit Car Dialog ───────────────────────────────────────────────── class _EditCarDialog extends StatefulWidget { final Map car; const _EditCarDialog({required this.car}); @override State<_EditCarDialog> createState() => _EditCarDialogState(); } class _EditCarDialogState extends State<_EditCarDialog> { late final TextEditingController _nameCtrl; late final TextEditingController _seriesCtrl; late final TextEditingController _yearCtrl; late final TextEditingController _notesCtrl; @override void initState() { super.initState(); _nameCtrl = TextEditingController(text: widget.car['name'] as String? ?? ''); _seriesCtrl = TextEditingController(text: widget.car['series'] as String? ?? ''); final year = widget.car['year']; _yearCtrl = TextEditingController(text: year != null ? '$year' : ''); _notesCtrl = TextEditingController(text: widget.car['notes'] as String? ?? ''); } @override void dispose() { _nameCtrl.dispose(); _seriesCtrl.dispose(); _yearCtrl.dispose(); _notesCtrl.dispose(); super.dispose(); } void _save() { final updates = {}; final name = _nameCtrl.text.trim(); final series = _seriesCtrl.text.trim(); final yearStr = _yearCtrl.text.trim(); final notes = _notesCtrl.text.trim(); updates['name'] = name.isEmpty ? null : name; updates['series'] = series.isEmpty ? null : series; updates['year'] = yearStr.isEmpty ? null : int.tryParse(yearStr); updates['notes'] = notes.isEmpty ? null : notes; Navigator.pop(context, updates); } @override Widget build(BuildContext context) { final hwId = widget.car['hw_id'] as String? ?? '???'; return AlertDialog( icon: Container( padding: const EdgeInsets.all(12), decoration: const BoxDecoration( gradient: AppColors.brandGradient, shape: BoxShape.circle, ), child: const Icon(Icons.edit, color: Colors.white, size: 28), ), title: Text('Edit $hwId'), content: SingleChildScrollView( child: Column( mainAxisSize: MainAxisSize.min, children: [ TextField( controller: _nameCtrl, decoration: const InputDecoration( labelText: 'Car Name', hintText: "e.g. '70 Dodge Charger", ), ), const SizedBox(height: 12), TextField( controller: _seriesCtrl, decoration: const InputDecoration( labelText: 'Series', hintText: 'e.g. HW Flames', ), ), const SizedBox(height: 12), TextField( controller: _yearCtrl, keyboardType: TextInputType.number, decoration: const InputDecoration( labelText: 'Year', hintText: 'e.g. 2025', ), ), const SizedBox(height: 12), TextField( controller: _notesCtrl, maxLines: 2, decoration: const InputDecoration( labelText: 'Notes', hintText: 'Any extra info…', ), ), ], ), ), actions: [ TextButton( onPressed: () => Navigator.pop(context), child: const Text('Cancel'), ), ElevatedButton( onPressed: _save, child: const Text('Save'), ), ], ); } } class _DetailRow extends StatelessWidget { final IconData icon; final String label; final String value; const _DetailRow({ required this.icon, required this.label, required this.value, }); @override Widget build(BuildContext context) { return Padding( padding: const EdgeInsets.symmetric(vertical: 6), child: Row( children: [ Icon(icon, size: 18, color: AppColors.textHint), const SizedBox(width: 10), Text( '$label: ', style: const TextStyle( fontWeight: FontWeight.w500, color: AppColors.textSecondary, ), ), Expanded( child: Text(value, style: const TextStyle(fontWeight: FontWeight.w500)), ), ], ), ); } }