697 lines
24 KiB
Dart
697 lines
24 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/responsive/responsive_layout.dart';
|
||
import '../../core/theme/theme_extensions.dart';
|
||
import 'widgets/motorcycle_photo.dart';
|
||
import 'garage_providers.dart';
|
||
|
||
class MotorcycleDetailScreen extends ConsumerWidget {
|
||
final int motorcycleId;
|
||
|
||
const MotorcycleDetailScreen({super.key, required this.motorcycleId});
|
||
|
||
@override
|
||
Widget build(BuildContext context, WidgetRef ref) {
|
||
final bikesAsync = ref.watch(garageMotorcyclesStreamProvider);
|
||
final statsAsync = ref.watch(motorcyclePerformanceStatsProvider(motorcycleId));
|
||
final colors = context.motorColors;
|
||
final theme = Theme.of(context);
|
||
|
||
return bikesAsync.when(
|
||
loading: () => const Scaffold(body: Center(child: CircularProgressIndicator())),
|
||
error: (err, _) => Scaffold(body: Center(child: Text('Error: $err'))),
|
||
data: (bikes) {
|
||
final bike = bikes.firstWhere(
|
||
(b) => b.id == motorcycleId,
|
||
orElse: () => GarageMotorcycle(
|
||
id: 0,
|
||
year: 2024,
|
||
makeId: '',
|
||
makeName: 'Unknown',
|
||
modelId: '',
|
||
modelName: 'Unknown',
|
||
isDefault: false,
|
||
dateAdded: DateTime.now(),
|
||
lastUpdated: DateTime.now(),
|
||
),
|
||
);
|
||
|
||
if (bike.id == 0) {
|
||
return Scaffold(
|
||
appBar: AppBar(),
|
||
body: const Center(child: Text('Motorcycle not found')),
|
||
);
|
||
}
|
||
|
||
MotorcycleSpecification? spec;
|
||
if (bike.specificationJson != null) {
|
||
try {
|
||
spec = MotorcycleSpecification.fromJson(json.decode(bike.specificationJson!));
|
||
} catch (_) {}
|
||
}
|
||
|
||
return Scaffold(
|
||
appBar: AppBar(
|
||
title: FittedBox(
|
||
fit: BoxFit.scaleDown,
|
||
alignment: Alignment.centerLeft,
|
||
child: Text(
|
||
'${bike.year} ${bike.makeName} ${bike.modelName}',
|
||
style: const TextStyle(fontWeight: FontWeight.bold),
|
||
),
|
||
),
|
||
actions: [
|
||
IconButton(
|
||
icon: const Icon(Icons.edit),
|
||
tooltip: 'Edit Nickname',
|
||
onPressed: () => _showEditNicknameDialog(context, ref, bike),
|
||
),
|
||
IconButton(
|
||
icon: Icon(Icons.delete_outline, color: colors.raceRed),
|
||
tooltip: 'Delete Motorcycle',
|
||
onPressed: () => _confirmDelete(context, ref, bike, colors),
|
||
),
|
||
],
|
||
),
|
||
body: ResponsiveLayout(
|
||
builder: (context, responsive) {
|
||
final isWide = responsive.isExpanded || (responsive.isMedium && responsive.isLandscape);
|
||
|
||
if (isWide) {
|
||
return SingleChildScrollView(
|
||
padding: const EdgeInsets.all(16.0),
|
||
child: Row(
|
||
crossAxisAlignment: CrossAxisAlignment.start,
|
||
children: [
|
||
Expanded(
|
||
flex: 4,
|
||
child: _buildHeaderCard(context, ref, bike, spec, colors, theme),
|
||
),
|
||
const SizedBox(width: 16),
|
||
Expanded(
|
||
flex: 6,
|
||
child: Column(
|
||
crossAxisAlignment: CrossAxisAlignment.stretch,
|
||
children: [
|
||
_buildMyPerformanceSection(context, statsAsync, spec, colors, theme),
|
||
const SizedBox(height: 20),
|
||
_buildSpecificationSheet(context, spec, colors, theme),
|
||
],
|
||
),
|
||
),
|
||
],
|
||
),
|
||
);
|
||
}
|
||
|
||
return SingleChildScrollView(
|
||
padding: const EdgeInsets.all(16.0),
|
||
child: Column(
|
||
crossAxisAlignment: CrossAxisAlignment.stretch,
|
||
children: [
|
||
_buildHeaderCard(context, ref, bike, spec, colors, theme),
|
||
const SizedBox(height: 20),
|
||
_buildMyPerformanceSection(context, statsAsync, spec, colors, theme),
|
||
const SizedBox(height: 20),
|
||
_buildSpecificationSheet(context, spec, colors, theme),
|
||
const SizedBox(height: 24),
|
||
],
|
||
),
|
||
);
|
||
},
|
||
),
|
||
);
|
||
},
|
||
);
|
||
}
|
||
|
||
Widget _buildHeaderCard(
|
||
BuildContext context,
|
||
WidgetRef ref,
|
||
GarageMotorcycle bike,
|
||
MotorcycleSpecification? spec,
|
||
MotorSignalThemeColors colors,
|
||
ThemeData theme,
|
||
) {
|
||
return Card(
|
||
elevation: 2,
|
||
color: colors.surfaceContainer,
|
||
shape: RoundedRectangleBorder(
|
||
borderRadius: BorderRadius.circular(16),
|
||
side: BorderSide(
|
||
color: bike.isDefault ? colors.cyberCyan : colors.outlineVariant,
|
||
width: bike.isDefault ? 1.8 : 1.0,
|
||
),
|
||
),
|
||
child: Padding(
|
||
padding: const EdgeInsets.all(16.0),
|
||
child: Column(
|
||
crossAxisAlignment: CrossAxisAlignment.start,
|
||
children: [
|
||
MotorcyclePhoto(
|
||
year: bike.year,
|
||
make: bike.makeName,
|
||
model: bike.modelName,
|
||
height: 190,
|
||
borderRadius: BorderRadius.circular(12),
|
||
showAttribution: true,
|
||
fit: BoxFit.cover,
|
||
),
|
||
const SizedBox(height: 16),
|
||
Column(
|
||
crossAxisAlignment: CrossAxisAlignment.start,
|
||
children: [
|
||
Text(
|
||
'${bike.year}',
|
||
style: TextStyle(
|
||
fontSize: 14,
|
||
color: colors.cyberCyan,
|
||
fontWeight: FontWeight.bold,
|
||
fontFamily: 'JetBrains Mono',
|
||
),
|
||
),
|
||
Text(
|
||
'${bike.makeName} ${bike.modelName}'.toUpperCase(),
|
||
style: TextStyle(
|
||
fontSize: 20,
|
||
fontWeight: FontWeight.w900,
|
||
fontFamily: 'Sora',
|
||
color: theme.colorScheme.onSurface,
|
||
),
|
||
),
|
||
if (bike.nickname != null && bike.nickname!.isNotEmpty)
|
||
Padding(
|
||
padding: const EdgeInsets.only(top: 4.0),
|
||
child: Text(
|
||
'"${bike.nickname}"',
|
||
style: TextStyle(
|
||
fontSize: 15,
|
||
fontStyle: FontStyle.italic,
|
||
color: colors.warningAmber,
|
||
fontWeight: FontWeight.w600,
|
||
),
|
||
),
|
||
),
|
||
],
|
||
),
|
||
const SizedBox(height: 16),
|
||
Divider(color: colors.outlineVariant, height: 1),
|
||
const SizedBox(height: 12),
|
||
Row(
|
||
mainAxisAlignment: MainAxisAlignment.spaceEvenly,
|
||
children: [
|
||
_buildHeaderStat(
|
||
'DISPLACEMENT',
|
||
spec?.displacementCc != null ? '${spec!.displacementCc!.toStringAsFixed(0)} cc' : '—',
|
||
theme,
|
||
),
|
||
_buildHeaderStat(
|
||
'POWER',
|
||
spec?.horsepower != null ? '${spec!.horsepower!.toStringAsFixed(0)} HP' : '—',
|
||
theme,
|
||
),
|
||
_buildHeaderStat(
|
||
'TORQUE',
|
||
spec?.torqueLbFt != null ? '${spec!.torqueLbFt!.toStringAsFixed(1)} lb-ft' : '—',
|
||
theme,
|
||
),
|
||
],
|
||
),
|
||
const SizedBox(height: 16),
|
||
SizedBox(
|
||
width: double.infinity,
|
||
child: bike.isDefault
|
||
? Container(
|
||
padding: const EdgeInsets.symmetric(vertical: 12),
|
||
decoration: BoxDecoration(
|
||
color: colors.cyberCyan.withValues(alpha: 0.15),
|
||
borderRadius: BorderRadius.circular(8),
|
||
border: Border.all(color: colors.cyberCyan),
|
||
),
|
||
child: Center(
|
||
child: Text(
|
||
'CURRENT MOTORCYCLE',
|
||
style: TextStyle(
|
||
fontFamily: 'Sora',
|
||
fontWeight: FontWeight.bold,
|
||
letterSpacing: 1.2,
|
||
color: colors.cyberCyan,
|
||
),
|
||
),
|
||
),
|
||
)
|
||
: OutlinedButton(
|
||
style: OutlinedButton.styleFrom(
|
||
padding: const EdgeInsets.symmetric(vertical: 12),
|
||
side: BorderSide(color: colors.cyberCyan),
|
||
foregroundColor: colors.cyberCyan,
|
||
),
|
||
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 MOTORCYCLE',
|
||
style: TextStyle(
|
||
fontFamily: 'Sora',
|
||
fontWeight: FontWeight.bold,
|
||
letterSpacing: 1.2,
|
||
),
|
||
),
|
||
),
|
||
),
|
||
],
|
||
),
|
||
),
|
||
);
|
||
}
|
||
|
||
Widget _buildHeaderStat(String label, String value, ThemeData theme) {
|
||
return Expanded(
|
||
child: Column(
|
||
children: [
|
||
FittedBox(
|
||
fit: BoxFit.scaleDown,
|
||
child: Text(
|
||
label,
|
||
style: TextStyle(
|
||
fontSize: 9.5,
|
||
color: theme.colorScheme.onSurface.withValues(alpha: 0.5),
|
||
letterSpacing: 1.0,
|
||
fontWeight: FontWeight.w700,
|
||
),
|
||
),
|
||
),
|
||
const SizedBox(height: 4),
|
||
FittedBox(
|
||
fit: BoxFit.scaleDown,
|
||
child: Text(
|
||
value,
|
||
style: TextStyle(
|
||
fontSize: 16,
|
||
fontWeight: FontWeight.bold,
|
||
fontFamily: 'JetBrains Mono',
|
||
color: theme.colorScheme.onSurface,
|
||
),
|
||
),
|
||
),
|
||
],
|
||
),
|
||
);
|
||
}
|
||
|
||
Widget _buildMyPerformanceSection(
|
||
BuildContext context,
|
||
AsyncValue<MotorcycleStats> statsAsync,
|
||
MotorcycleSpecification? spec,
|
||
MotorSignalThemeColors colors,
|
||
ThemeData theme,
|
||
) {
|
||
return Column(
|
||
crossAxisAlignment: CrossAxisAlignment.start,
|
||
children: [
|
||
Text(
|
||
'MY PERFORMANCE',
|
||
style: TextStyle(
|
||
fontFamily: 'Sora',
|
||
fontSize: 16,
|
||
fontWeight: FontWeight.bold,
|
||
color: colors.cyberCyan,
|
||
letterSpacing: 0.8,
|
||
),
|
||
),
|
||
const SizedBox(height: 12),
|
||
statsAsync.when(
|
||
loading: () => const Center(child: CircularProgressIndicator()),
|
||
error: (err, _) => Text('Error loading stats: $err'),
|
||
data: (stats) {
|
||
return Column(
|
||
children: [
|
||
Row(
|
||
children: [
|
||
Expanded(child: _buildPerfTile('TOTAL RIDES', '${stats.rideCount}', colors, theme)),
|
||
const SizedBox(width: 12),
|
||
Expanded(
|
||
child: _buildPerfTile(
|
||
'TOTAL DISTANCE',
|
||
'${stats.totalDistanceMiles.toStringAsFixed(1)} MI',
|
||
colors,
|
||
theme,
|
||
),
|
||
),
|
||
],
|
||
),
|
||
const SizedBox(height: 12),
|
||
Row(
|
||
children: [
|
||
Expanded(
|
||
child: _buildPerfTile(
|
||
'BEST TOP SPEED',
|
||
'${stats.topSpeedMph.toStringAsFixed(1)} MPH',
|
||
colors,
|
||
theme,
|
||
published: spec?.claimedTopSpeedMph != null
|
||
? '~${spec!.claimedTopSpeedMph!.toStringAsFixed(0)} MPH'
|
||
: null,
|
||
),
|
||
),
|
||
const SizedBox(width: 12),
|
||
Expanded(
|
||
child: _buildPerfTile(
|
||
'BEST 0–60',
|
||
stats.best0to60Ms != null ? '${(stats.best0to60Ms! / 1000.0).toStringAsFixed(2)} s' : '—',
|
||
colors,
|
||
theme,
|
||
published: spec?.claimedZeroToSixtySec != null
|
||
? '~${spec!.claimedZeroToSixtySec!.toStringAsFixed(1)} s'
|
||
: null,
|
||
),
|
||
),
|
||
],
|
||
),
|
||
const SizedBox(height: 12),
|
||
Row(
|
||
children: [
|
||
Expanded(
|
||
child: _buildPerfTile(
|
||
'MAX LEAN',
|
||
'${stats.maxLean.toStringAsFixed(1)}°',
|
||
colors,
|
||
theme,
|
||
),
|
||
),
|
||
const SizedBox(width: 12),
|
||
Expanded(
|
||
child: _buildPerfTile(
|
||
'MAX ACCEL G',
|
||
'${stats.maxAccelG.toStringAsFixed(2)} G',
|
||
colors,
|
||
theme,
|
||
),
|
||
),
|
||
],
|
||
),
|
||
],
|
||
);
|
||
},
|
||
),
|
||
],
|
||
);
|
||
}
|
||
|
||
Widget _buildPerfTile(
|
||
String label,
|
||
String value,
|
||
MotorSignalThemeColors colors,
|
||
ThemeData theme, {
|
||
String? published,
|
||
}) {
|
||
return Container(
|
||
padding: const EdgeInsets.all(14),
|
||
decoration: BoxDecoration(
|
||
color: colors.surfaceContainer,
|
||
borderRadius: BorderRadius.circular(12),
|
||
border: Border.all(color: colors.outlineVariant, width: 1.2),
|
||
),
|
||
child: Column(
|
||
crossAxisAlignment: CrossAxisAlignment.start,
|
||
children: [
|
||
Text(
|
||
label,
|
||
style: TextStyle(
|
||
fontSize: 10,
|
||
color: theme.colorScheme.onSurface.withValues(alpha: 0.5),
|
||
fontWeight: FontWeight.w700,
|
||
letterSpacing: 0.8,
|
||
),
|
||
),
|
||
const SizedBox(height: 4),
|
||
FittedBox(
|
||
fit: BoxFit.scaleDown,
|
||
child: Text(
|
||
value,
|
||
style: TextStyle(
|
||
fontSize: 18,
|
||
fontWeight: FontWeight.bold,
|
||
fontFamily: 'JetBrains Mono',
|
||
color: theme.colorScheme.onSurface,
|
||
),
|
||
),
|
||
),
|
||
if (published != null) ...[
|
||
const SizedBox(height: 4),
|
||
Text(
|
||
'PUBLISHED: $published',
|
||
style: TextStyle(
|
||
fontSize: 9.5,
|
||
color: colors.cyberCyan,
|
||
fontFamily: 'JetBrains Mono',
|
||
),
|
||
),
|
||
],
|
||
],
|
||
),
|
||
);
|
||
}
|
||
|
||
Widget _buildSpecificationSheet(
|
||
BuildContext context,
|
||
MotorcycleSpecification? spec,
|
||
MotorSignalThemeColors colors,
|
||
ThemeData theme,
|
||
) {
|
||
if (spec == null) {
|
||
return Card(
|
||
color: colors.surfaceContainer,
|
||
shape: RoundedRectangleBorder(
|
||
borderRadius: BorderRadius.circular(12),
|
||
side: BorderSide(color: colors.outlineVariant),
|
||
),
|
||
child: const Padding(
|
||
padding: EdgeInsets.all(16.0),
|
||
child: Text(
|
||
'Detailed specifications unavailable for this motorcycle.',
|
||
style: TextStyle(color: Colors.white54),
|
||
),
|
||
),
|
||
);
|
||
}
|
||
|
||
return Column(
|
||
crossAxisAlignment: CrossAxisAlignment.start,
|
||
children: [
|
||
Text(
|
||
'SPECIFICATIONS',
|
||
style: TextStyle(
|
||
fontFamily: 'Sora',
|
||
fontSize: 16,
|
||
fontWeight: FontWeight.bold,
|
||
color: colors.cyberCyan,
|
||
letterSpacing: 0.8,
|
||
),
|
||
),
|
||
const SizedBox(height: 12),
|
||
_buildSpecGroup('ENGINE', [
|
||
_SpecRow('Displacement', spec.displacementCc != null ? '${spec.displacementCc!.toStringAsFixed(0)} cc' : null),
|
||
_SpecRow('Configuration', spec.engineConfiguration),
|
||
_SpecRow('Cylinders', spec.cylinderCount?.toString()),
|
||
_SpecRow('Horsepower', spec.horsepower != null ? '${spec.horsepower!.toStringAsFixed(0)} HP @ ${spec.horsepowerRpm ?? "—"} RPM' : null),
|
||
_SpecRow('Torque', spec.torqueLbFt != null ? '${spec.torqueLbFt!.toStringAsFixed(1)} lb-ft @ ${spec.torqueRpm ?? "—"} RPM' : null),
|
||
_SpecRow('Redline', spec.redlineRpm != null ? '${spec.redlineRpm} RPM' : null),
|
||
_SpecRow('Compression Ratio', spec.compressionRatio),
|
||
_SpecRow('Cooling System', spec.coolingSystem),
|
||
_SpecRow('Fuel System', spec.fuelSystem),
|
||
], colors, theme),
|
||
_buildSpecGroup('PERFORMANCE (PUBLISHED)', [
|
||
_SpecRow('Claimed Top Speed', spec.claimedTopSpeedMph != null ? '~${spec.claimedTopSpeedMph!.toStringAsFixed(0)} MPH' : null),
|
||
_SpecRow('Claimed 0–60 MPH', spec.claimedZeroToSixtySec != null ? '~${spec.claimedZeroToSixtySec!.toStringAsFixed(1)} sec' : null),
|
||
_SpecRow('Claimed 1/4 Mile', spec.claimedQuarterMileSec != null ? '~${spec.claimedQuarterMileSec!.toStringAsFixed(1)} sec' : null),
|
||
], colors, theme),
|
||
_buildSpecGroup('WEIGHT & DIMENSIONS', [
|
||
_SpecRow('Wet Weight', spec.wetWeightLbs != null ? '${spec.wetWeightLbs!.toStringAsFixed(0)} lbs' : null),
|
||
_SpecRow('Dry Weight', spec.dryWeightLbs != null ? '${spec.dryWeightLbs!.toStringAsFixed(0)} lbs' : null),
|
||
_SpecRow('Seat Height', spec.seatHeightInches != null ? '${spec.seatHeightInches!.toStringAsFixed(1)} in' : null),
|
||
_SpecRow('Wheelbase', spec.wheelbaseInches != null ? '${spec.wheelbaseInches!.toStringAsFixed(1)} in' : null),
|
||
], colors, theme),
|
||
_buildSpecGroup('FUEL', [
|
||
_SpecRow('Tank Capacity', spec.fuelCapacityGallons != null ? '${spec.fuelCapacityGallons!.toStringAsFixed(1)} gal' : null),
|
||
_SpecRow('Fuel Economy', spec.fuelEconomyMpg != null ? '${spec.fuelEconomyMpg!.toStringAsFixed(0)} MPG' : null),
|
||
_SpecRow('Estimated Range', spec.estimatedRangeMiles != null ? '~${spec.estimatedRangeMiles!.toStringAsFixed(0)} miles' : null),
|
||
], colors, theme),
|
||
_buildSpecGroup('CHASSIS & BRAKES', [
|
||
_SpecRow('Frame Type', spec.frameType),
|
||
_SpecRow('Front Suspension', spec.frontSuspension),
|
||
_SpecRow('Rear Suspension', spec.rearSuspension),
|
||
_SpecRow('Front Brake', spec.frontBrake),
|
||
_SpecRow('Rear Brake', spec.rearBrake),
|
||
_SpecRow('ABS System', spec.absType),
|
||
_SpecRow('Front Tire', spec.frontTireSize),
|
||
_SpecRow('Rear Tire', spec.rearTireSize),
|
||
], colors, theme),
|
||
_buildSpecGroup('TRANSMISSION', [
|
||
_SpecRow('Transmission Type', spec.transmissionType),
|
||
_SpecRow('Gears', spec.gearCount?.toString()),
|
||
_SpecRow('Final Drive', spec.finalDrive),
|
||
], colors, theme),
|
||
if (spec.source != null) ...[
|
||
const SizedBox(height: 12),
|
||
Text(
|
||
'Specifications Source: ${spec.source}',
|
||
style: TextStyle(
|
||
fontSize: 11,
|
||
color: theme.colorScheme.onSurface.withValues(alpha: 0.4),
|
||
fontFamily: 'Inter',
|
||
),
|
||
),
|
||
],
|
||
],
|
||
);
|
||
}
|
||
|
||
Widget _buildSpecGroup(
|
||
String title,
|
||
List<_SpecRow> rows,
|
||
MotorSignalThemeColors colors,
|
||
ThemeData theme,
|
||
) {
|
||
final validRows = rows.where((r) => r.value != null && r.value!.isNotEmpty).toList();
|
||
if (validRows.isEmpty) return const SizedBox.shrink();
|
||
|
||
return Card(
|
||
elevation: 1,
|
||
color: colors.surfaceContainer,
|
||
shape: RoundedRectangleBorder(
|
||
borderRadius: BorderRadius.circular(12),
|
||
side: BorderSide(color: colors.outlineVariant),
|
||
),
|
||
margin: const EdgeInsets.only(bottom: 12),
|
||
child: Padding(
|
||
padding: const EdgeInsets.all(14.0),
|
||
child: Column(
|
||
crossAxisAlignment: CrossAxisAlignment.start,
|
||
children: [
|
||
Text(
|
||
title,
|
||
style: TextStyle(
|
||
fontFamily: 'Sora',
|
||
fontWeight: FontWeight.bold,
|
||
color: colors.cyberCyan,
|
||
fontSize: 12,
|
||
letterSpacing: 0.8,
|
||
),
|
||
),
|
||
const SizedBox(height: 8),
|
||
...validRows.map(
|
||
(r) => Padding(
|
||
padding: const EdgeInsets.symmetric(vertical: 4.0),
|
||
child: Row(
|
||
crossAxisAlignment: CrossAxisAlignment.start,
|
||
children: [
|
||
Text(
|
||
r.label,
|
||
style: TextStyle(
|
||
fontSize: 12,
|
||
color: theme.colorScheme.onSurface.withValues(alpha: 0.6),
|
||
),
|
||
),
|
||
const SizedBox(width: 8),
|
||
Expanded(
|
||
child: Text(
|
||
r.value!,
|
||
textAlign: TextAlign.right,
|
||
style: TextStyle(
|
||
fontWeight: FontWeight.bold,
|
||
fontFamily: 'JetBrains Mono',
|
||
fontSize: 12,
|
||
color: theme.colorScheme.onSurface,
|
||
),
|
||
),
|
||
),
|
||
],
|
||
),
|
||
),
|
||
),
|
||
],
|
||
),
|
||
),
|
||
);
|
||
}
|
||
|
||
void _showEditNicknameDialog(BuildContext context, WidgetRef ref, GarageMotorcycle bike) {
|
||
final controller = TextEditingController(text: bike.nickname ?? '');
|
||
showDialog(
|
||
context: context,
|
||
builder: (context) {
|
||
return AlertDialog(
|
||
title: const Text('Edit Nickname'),
|
||
content: TextField(
|
||
controller: controller,
|
||
decoration: const InputDecoration(hintText: 'Enter nickname'),
|
||
),
|
||
actions: [
|
||
TextButton(onPressed: () => Navigator.pop(context), child: const Text('Cancel')),
|
||
ElevatedButton(
|
||
onPressed: () {
|
||
ref.read(garageControllerProvider).updateNickname(
|
||
bike.id,
|
||
controller.text.trim().isNotEmpty ? controller.text.trim() : null,
|
||
);
|
||
Navigator.pop(context);
|
||
},
|
||
child: const Text('Save'),
|
||
),
|
||
],
|
||
);
|
||
},
|
||
);
|
||
}
|
||
|
||
void _confirmDelete(
|
||
BuildContext context,
|
||
WidgetRef ref,
|
||
GarageMotorcycle bike,
|
||
MotorSignalThemeColors colors,
|
||
) {
|
||
showDialog(
|
||
context: context,
|
||
builder: (context) {
|
||
return AlertDialog(
|
||
title: const Text('Delete Motorcycle?'),
|
||
content: Text(
|
||
'Are you sure you want to remove ${bike.makeName} ${bike.modelName} from your Garage? Historical rides associated with this motorcycle will be preserved.',
|
||
),
|
||
actions: [
|
||
TextButton(onPressed: () => Navigator.pop(context), child: const Text('Cancel')),
|
||
ElevatedButton(
|
||
style: ElevatedButton.styleFrom(backgroundColor: colors.raceRed),
|
||
onPressed: () {
|
||
ref.read(garageControllerProvider).deleteMotorcycle(bike.id);
|
||
Navigator.pop(context);
|
||
context.pop();
|
||
},
|
||
child: const Text('Delete', style: TextStyle(color: Colors.white)),
|
||
),
|
||
],
|
||
);
|
||
},
|
||
);
|
||
}
|
||
}
|
||
|
||
class _SpecRow {
|
||
final String label;
|
||
final String? value;
|
||
_SpecRow(this.label, this.value);
|
||
}
|
||
|