hwhub/lib/widgets/car_card.dart
2026-03-05 08:36:25 +01:00

223 lines
7.5 KiB
Dart

import 'package:flutter/material.dart';
import 'package:cached_network_image/cached_network_image.dart';
import '../theme/app_colors.dart';
/// A styled card for displaying a single Hot Wheels car in the garage.
class CarCard extends StatelessWidget {
final String hwId;
final String? name;
final String? series;
final int? year;
final String? color;
final String? imageUrl;
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.isVerified = false,
this.addedAt,
this.onTap,
this.onLongPress,
this.onImageError,
this.isSelected = false,
});
@override
Widget build(BuildContext context) {
final theme = Theme.of(context);
final isDark = theme.brightness == Brightness.dark;
return Card(
clipBehavior: Clip.antiAlias,
color: isSelected
? AppColors.orange.withValues(alpha: 0.18)
: Theme.of(context).cardTheme.color,
child: InkWell(
onTap: onTap,
onLongPress: 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: imageUrl != null && imageUrl!.isNotEmpty
? CachedNetworkImage(
imageUrl: imageUrl!,
fit: BoxFit.cover,
fadeInDuration: Duration.zero,
fadeOutDuration: Duration.zero,
errorWidget: (context, url, error) {
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 (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(
hwId,
style: const TextStyle(
fontSize: 13,
fontWeight: FontWeight.w700,
color: AppColors.orange,
letterSpacing: 1,
),
),
),
const SizedBox(height: 6),
Row(
children: [
Icon(
isVerified
? Icons.verified
: Icons.hourglass_bottom,
size: 12,
color: isVerified
? AppColors.success
: AppColors.textHint,
),
const SizedBox(width: 4),
Text(
isVerified ? 'Verified' : 'Pending',
style: theme.textTheme.bodySmall?.copyWith(
color: isVerified
? AppColors.success
: AppColors.textHint,
fontWeight: FontWeight.w600,
),
),
],
),
if (name != null && name!.isNotEmpty) ...[
const SizedBox(height: 6),
Text(
name!,
maxLines: 1,
overflow: TextOverflow.ellipsis,
style: theme.textTheme.titleSmall?.copyWith(
fontWeight: FontWeight.w600,
),
),
],
if (series != null && series!.isNotEmpty) ...[
const SizedBox(height: 2),
Text(
series!,
maxLines: 1,
overflow: TextOverflow.ellipsis,
style: theme.textTheme.bodySmall?.copyWith(
color: AppColors.textSecondary,
),
),
],
if (year != null) ...[
const SizedBox(height: 4),
Row(
children: [
const Icon(Icons.calendar_today,
size: 12, color: AppColors.textHint),
const SizedBox(width: 4),
Text(
'$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),
),
),
),
);
}
}