hwhub/lib/widgets/car_card.dart
Lukas Müllner 91f25e0cef feat(v1.1): complete UI overhaul with branded theme, garage, and image uploads
- Custom Material 3 theme with Hot Wheels branding (orange/red gradient, navy accents)
- Locally bundled Poppins font (Regular, Medium, SemiBold, Bold)
- Redesigned login screen with full-bleed background image and frosted glass form
- Bottom navigation shell: My Garage / Scan / Profile tabs
- My Garage screen with grid view, search, stats, detail bottom sheet
- Edit and delete car details from the detail sheet
- Skip button for quick-add with minimal info
- Camera photo upload to Supabase Storage (UUID-based unguessable paths)
- Change/add photo from car detail view
- Profile screen with change password, about dialog, sign out
- Scan tab with camera scanner and manual entry
- Styled scanner screen with crosshair overlay and gradient buttons
- Custom app icon with transparent background and adaptive icon support
- Native splash screen with brand colors
- Auto-refresh garage on tab switch
2026-02-24 07:46:06 +01:00

156 lines
5 KiB
Dart

import 'package:flutter/material.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 DateTime? addedAt;
final VoidCallback? onTap;
const CarCard({
super.key,
required this.hwId,
this.name,
this.series,
this.year,
this.color,
this.imageUrl,
this.addedAt,
this.onTap,
});
@override
Widget build(BuildContext context) {
final theme = Theme.of(context);
final isDark = theme.brightness == Brightness.dark;
return Card(
clipBehavior: Clip.antiAlias,
child: InkWell(
onTap: onTap,
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
? Image.network(
imageUrl!,
fit: BoxFit.cover,
errorBuilder: (context, error, stackTrace) =>
_PlaceholderIcon(isDark: isDark),
)
: _PlaceholderIcon(isDark: isDark),
),
),
// ── Info area ──
Padding(
padding: const EdgeInsets.fromLTRB(12, 10, 12, 12),
child: Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
// 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,
),
),
),
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 Center(
child: Icon(
Icons.directions_car_filled,
size: 48,
color: isDark
? Colors.white.withValues(alpha: 0.15)
: AppColors.orange.withValues(alpha: 0.3),
),
);
}
}