import 'package:flutter/material.dart'; import 'package:cached_network_image/cached_network_image.dart'; import '../services/storage_service.dart'; import '../theme/app_colors.dart'; /// A styled card for displaying a single die-cast car in the garage. class CarCard extends StatefulWidget { final String hwId; final String? name; final String? series; final int? year; final String? color; final String? imageUrl; final String? imagePath; final bool isVerified; final DateTime? addedAt; final VoidCallback? onTap; final VoidCallback? onLongPress; final VoidCallback? onImageError; final bool isSelected; const CarCard({ super.key, required this.hwId, this.name, this.series, this.year, this.color, this.imageUrl, this.imagePath, this.isVerified = false, this.addedAt, this.onTap, this.onLongPress, this.onImageError, this.isSelected = false, }); @override State createState() => _CarCardState(); } class _CarCardState extends State { Future? _signedUrlFuture; @override void initState() { super.initState(); _initSignedUrlFuture(); } @override void didUpdateWidget(CarCard oldWidget) { super.didUpdateWidget(oldWidget); if (oldWidget.imagePath != widget.imagePath) { _initSignedUrlFuture(); } } void _initSignedUrlFuture() { final hasImagePath = widget.imagePath != null && widget.imagePath!.isNotEmpty; _signedUrlFuture = hasImagePath ? StorageService.createSignedUrl(widget.imagePath!) : null; } @override Widget build(BuildContext context) { final theme = Theme.of(context); final isDark = theme.brightness == Brightness.dark; final resolvedImageUrl = widget.imageUrl; final hasResolvedUrl = resolvedImageUrl != null && resolvedImageUrl.isNotEmpty; final hasImagePath = widget.imagePath != null && widget.imagePath!.isNotEmpty; return Card( clipBehavior: Clip.antiAlias, color: widget.isSelected ? AppColors.orange.withValues(alpha: 0.18) : Theme.of(context).cardTheme.color, child: InkWell( onTap: widget.onTap, onLongPress: widget.onLongPress, borderRadius: BorderRadius.circular(16), child: Column( crossAxisAlignment: CrossAxisAlignment.start, children: [ // ── Image / placeholder area ── AspectRatio( aspectRatio: 16 / 10, child: Container( decoration: BoxDecoration( gradient: isDark ? LinearGradient( colors: [ AppColors.surfaceDark, AppColors.cardDark, ], begin: Alignment.topLeft, end: Alignment.bottomRight, ) : AppColors.brandGradientSoft, ), child: hasResolvedUrl ? CachedNetworkImage( imageUrl: resolvedImageUrl, fit: BoxFit.cover, fadeInDuration: Duration.zero, fadeOutDuration: Duration.zero, errorWidget: (context, url, error) { widget.onImageError?.call(); return _PlaceholderIcon(isDark: isDark); }, ) : hasImagePath ? FutureBuilder( future: _signedUrlFuture, builder: (context, snapshot) { final signedUrl = snapshot.data; if (signedUrl == null || signedUrl.isEmpty) { return _PlaceholderIcon(isDark: isDark); } return CachedNetworkImage( imageUrl: signedUrl, fit: BoxFit.cover, fadeInDuration: Duration.zero, fadeOutDuration: Duration.zero, errorWidget: (context, url, error) { widget.onImageError?.call(); return _PlaceholderIcon(isDark: isDark); }, ); }, ) : _PlaceholderIcon(isDark: isDark), ), ), // ── Info area ── Padding( padding: const EdgeInsets.fromLTRB(12, 10, 12, 12), child: Column( crossAxisAlignment: CrossAxisAlignment.start, children: [ if (widget.isSelected) Padding( padding: const EdgeInsets.only(bottom: 6), child: Row( children: const [ Icon(Icons.check_circle, size: 16, color: AppColors.orange), SizedBox(width: 6), Text( 'Selected', style: TextStyle( fontSize: 11, fontWeight: FontWeight.w600, color: AppColors.orange, ), ), ], ), ), // HW ID badge Container( padding: const EdgeInsets.symmetric(horizontal: 8, vertical: 3), decoration: BoxDecoration( color: AppColors.orange.withValues(alpha: 0.15), borderRadius: BorderRadius.circular(6), ), child: Text( widget.hwId, style: const TextStyle( fontSize: 13, fontWeight: FontWeight.w700, color: AppColors.orange, letterSpacing: 1, ), ), ), const SizedBox(height: 6), Row( children: [ Icon( widget.isVerified ? Icons.verified : Icons.hourglass_bottom, size: 12, color: widget.isVerified ? AppColors.success : AppColors.textHint, ), const SizedBox(width: 4), Text( widget.isVerified ? 'Verified' : 'Pending', style: theme.textTheme.bodySmall?.copyWith( color: widget.isVerified ? AppColors.success : AppColors.textHint, fontWeight: FontWeight.w600, ), ), ], ), if (widget.name != null && widget.name!.isNotEmpty) ...[ const SizedBox(height: 6), Text( widget.name!, maxLines: 1, overflow: TextOverflow.ellipsis, style: theme.textTheme.titleSmall?.copyWith( fontWeight: FontWeight.w600, ), ), ], if (widget.series != null && widget.series!.isNotEmpty) ...[ const SizedBox(height: 2), Text( widget.series!, maxLines: 1, overflow: TextOverflow.ellipsis, style: theme.textTheme.bodySmall?.copyWith( color: AppColors.textSecondary, ), ), ], if (widget.year != null) ...[ const SizedBox(height: 4), Row( children: [ const Icon(Icons.calendar_today, size: 12, color: AppColors.textHint), const SizedBox(width: 4), Text( '${widget.year}', style: theme.textTheme.bodySmall?.copyWith( color: AppColors.textHint, ), ), ], ), ], ], ), ), ], ), ), ); } } /// Placeholder icon when no image is available. class _PlaceholderIcon extends StatelessWidget { final bool isDark; const _PlaceholderIcon({required this.isDark}); @override Widget build(BuildContext context) { return Padding( padding: const EdgeInsets.all(14), child: Image.asset( 'assets/img/icon_bg_removed.png', fit: BoxFit.contain, errorBuilder: (context, error, stackTrace) => Center( child: Icon( Icons.directions_car_filled, size: 48, color: isDark ? Colors.white.withValues(alpha: 0.15) : AppColors.orange.withValues(alpha: 0.3), ), ), ), ); } }