MotorSignal-App/lib/features/garage/garage_screen.dart

318 lines
12 KiB
Dart

import 'dart:convert';
import 'package:flutter/material.dart';
import 'package:flutter_riverpod/flutter_riverpod.dart';
import 'package:go_router/go_router.dart';
import '../../database/database.dart';
import '../../models/motorcycle_models.dart';
import '../../core/theme/evolution_theme_config.dart';
import 'garage_providers.dart';
import 'widgets/motorcycle_photo.dart';
import '../../services/entitlements/entitlement_providers.dart';
import '../../services/entitlements/feature_access_service.dart';
import '../entitlements/widgets/pro_lock_badge.dart';
class GarageScreen extends ConsumerWidget {
const GarageScreen({super.key});
@override
Widget build(BuildContext context, WidgetRef ref) {
final bikesAsync = ref.watch(garageMotorcyclesStreamProvider);
final themeMode = ref.watch(dashboardThemeModeProvider);
return Scaffold(
backgroundColor: themeMode == DashboardThemeMode.evolution
? Colors.black
: null,
appBar: AppBar(
backgroundColor: themeMode == DashboardThemeMode.evolution
? Colors.transparent
: null,
elevation: 0,
title: Text(
'My Garage',
style: TextStyle(
fontFamily: themeMode == DashboardThemeMode.evolution
? 'Sora'
: null,
fontWeight: FontWeight.bold,
),
),
actions: [
IconButton(
icon: const Icon(
Icons.add_circle_outline,
color: Color(0xFF00E5FF),
),
tooltip: 'Add Motorcycle',
onPressed: () => _onAddMotorcycle(context, ref, bikesAsync.value),
),
],
),
body: bikesAsync.when(
loading: () => const Center(child: CircularProgressIndicator()),
error: (err, stack) =>
Center(child: Text('Error loading garage: $err')),
data: (bikes) {
if (bikes.isEmpty) return _buildEmptyState(context);
return ListView.builder(
padding: const EdgeInsets.all(16.0),
itemCount: bikes.length,
itemBuilder: (context, index) {
return _buildMotorcycleCard(
context,
ref,
bikes[index],
themeMode,
);
},
);
},
),
floatingActionButton:
bikesAsync.value != null && bikesAsync.value!.isNotEmpty
? FloatingActionButton.extended(
backgroundColor: Colors.cyanAccent,
foregroundColor: Colors.black,
icon: const Icon(Icons.two_wheeler),
label: const Text(
'ADD MOTORCYCLE',
style: TextStyle(fontWeight: FontWeight.bold),
),
onPressed: () => _onAddMotorcycle(context, ref, bikesAsync.value),
)
: null,
);
}
void _onAddMotorcycle(
BuildContext context,
WidgetRef ref,
List<GarageMotorcycle>? bikes,
) {
if (bikes != null && bikes.isNotEmpty) {
final canAddMore = ref
.read(featureAccessProvider)
.canAccess(MotorSignalFeature.useMultipleMotorcycles);
if (!canAddMore) {
showProUpgradeSheet(
context,
ref,
feature: MotorSignalFeature.useMultipleMotorcycles,
);
return;
}
}
context.push('/garage/add');
}
Widget _buildEmptyState(BuildContext context) {
return Center(
child: Padding(
padding: const EdgeInsets.all(32.0),
child: Column(
mainAxisAlignment: MainAxisAlignment.center,
children: [
const Icon(Icons.two_wheeler, size: 80, color: Colors.white24),
const SizedBox(height: 24),
const Text(
'YOUR GARAGE IS EMPTY',
style: TextStyle(
fontSize: 20,
fontWeight: FontWeight.bold,
letterSpacing: 1.5,
),
),
const SizedBox(height: 12),
const Text(
'Add your motorcycle to personalize your ride history and performance data.',
textAlign: TextAlign.center,
style: TextStyle(color: Colors.white70, height: 1.4),
),
const SizedBox(height: 32),
ElevatedButton.icon(
style: ElevatedButton.styleFrom(
backgroundColor: Colors.cyanAccent,
foregroundColor: Colors.black,
padding: const EdgeInsets.symmetric(
horizontal: 24,
vertical: 14,
),
shape: RoundedRectangleBorder(
borderRadius: BorderRadius.circular(8),
),
),
icon: const Icon(Icons.add),
label: const Text(
'ADD MOTORCYCLE',
style: TextStyle(
fontWeight: FontWeight.bold,
letterSpacing: 1.2,
),
),
onPressed: () => context.push('/garage/add'),
),
],
),
),
);
}
Widget _buildMotorcycleCard(
BuildContext context,
WidgetRef ref,
GarageMotorcycle bike,
DashboardThemeMode themeMode,
) {
MotorcycleSpecification? spec;
if (bike.specificationJson != null) {
try {
spec = MotorcycleSpecification.fromJson(
json.decode(bike.specificationJson!),
);
} catch (_) {}
}
final displayName = bike.nickname != null && bike.nickname!.isNotEmpty
? '${bike.nickname} (${bike.year} ${bike.makeName} ${bike.modelName})'
: '${bike.year} ${bike.makeName} ${bike.modelName}';
final isEvolution = themeMode == DashboardThemeMode.evolution;
return Container(
margin: const EdgeInsets.only(bottom: 16),
decoration: BoxDecoration(
color: isEvolution ? const Color(0xFF0D0D0D) : null,
borderRadius: BorderRadius.circular(isEvolution ? 14 : 12),
border: Border.all(
color: bike.isDefault
? const Color(0xFF00E5FF)
: (isEvolution ? const Color(0xFF1F1F1F) : Colors.white12),
width: bike.isDefault ? 1.8 : 1.0,
),
),
child: Material(
color: Colors.transparent,
child: InkWell(
borderRadius: BorderRadius.circular(isEvolution ? 14 : 12),
onTap: () => context.push('/garage/${bike.id}'),
child: Padding(
padding: const EdgeInsets.all(12.0),
child: Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
MotorcyclePhoto(
year: bike.year,
make: bike.makeName,
model: bike.modelName,
height: 190,
borderRadius: BorderRadius.circular(isEvolution ? 10 : 8),
),
const SizedBox(height: 14),
Row(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
Expanded(
child: Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
Row(
children: [
Expanded(
child: Text(
displayName,
style: TextStyle(
fontFamily: isEvolution ? 'Sora' : null,
fontSize: 17,
fontWeight: FontWeight.w800,
color: Colors.white,
),
),
),
if (bike.isDefault)
Container(
padding: const EdgeInsets.symmetric(
horizontal: 8,
vertical: 2,
),
decoration: BoxDecoration(
color: const Color(
0xFF00E5FF,
).withValues(alpha: 0.15),
borderRadius: BorderRadius.circular(4),
border: Border.all(
color: const Color(0xFF00E5FF),
),
),
child: const Text(
'CURRENT',
style: TextStyle(
fontFamily: 'Inter',
fontSize: 10,
fontWeight: FontWeight.w900,
color: Color(0xFF00E5FF),
letterSpacing: 0.5,
),
),
),
],
),
const SizedBox(height: 6),
Text(
spec != null
? '${spec.displacementCc?.toStringAsFixed(0) ?? "—"} cc • ${spec.horsepower?.toStringAsFixed(0) ?? "—"} hp'
: '${bike.year} • ${bike.makeName}',
style: TextStyle(
fontFamily: isEvolution ? 'JetBrains Mono' : null,
color: isEvolution
? const Color(0xFF00E5FF)
: Colors.white70,
fontSize: 13,
fontWeight: isEvolution
? FontWeight.w700
: FontWeight.normal,
),
),
],
),
),
],
),
const SizedBox(height: 12),
Row(
mainAxisAlignment: MainAxisAlignment.spaceBetween,
children: [
TextButton.icon(
icon: const Icon(Icons.directions_bike, size: 18),
label: const Text('View Specs & Stats'),
onPressed: () => context.push('/garage/${bike.id}'),
),
if (!bike.isDefault)
OutlinedButton(
style: OutlinedButton.styleFrom(
foregroundColor: Colors.white,
side: const BorderSide(color: Colors.white30),
),
onPressed: () {
ref
.read(garageControllerProvider)
.setDefaultMotorcycle(bike.id);
ScaffoldMessenger.of(context).showSnackBar(
SnackBar(
content: Text(
'Set ${bike.makeName} ${bike.modelName} as current motorcycle.',
),
),
);
},
child: const Text('Set as Current'),
),
],
),
],
),
),
),
),
);
}
}