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

448 lines
14 KiB
Dart

import 'package:flutter/material.dart';
import 'package:flutter_riverpod/flutter_riverpod.dart';
import 'package:go_router/go_router.dart';
import '../../models/motorcycle_models.dart';
import 'garage_providers.dart';
class AddMotorcycleScreen extends ConsumerStatefulWidget {
const AddMotorcycleScreen({super.key});
@override
ConsumerState<AddMotorcycleScreen> createState() => _AddMotorcycleScreenState();
}
class _AddMotorcycleScreenState extends ConsumerState<AddMotorcycleScreen> {
int _step = 1; // 1: Year, 2: Make, 3: Model, 4: Variant, 5: Confirm
int? _selectedYear;
MotorcycleMake? _selectedMake;
MotorcycleModel? _selectedModel;
MotorcycleVariant? _selectedVariant;
MotorcycleSpecification? _previewSpec;
final TextEditingController _nicknameController = TextEditingController();
final TextEditingController _searchController = TextEditingController();
bool _isLoading = false;
List<MotorcycleYear> _years = [];
List<MotorcycleMake> _makes = [];
List<MotorcycleModel> _models = [];
List<MotorcycleVariant> _variants = [];
@override
void initState() {
super.initState();
_loadYears();
}
@override
void dispose() {
_nicknameController.dispose();
_searchController.dispose();
super.dispose();
}
Future<void> _loadYears() async {
setState(() => _isLoading = true);
final repo = ref.read(motorcycleRepositoryProvider);
final years = await repo.getYears();
setState(() {
_years = years;
_isLoading = false;
});
}
Future<void> _selectYear(int year) async {
setState(() {
_selectedYear = year;
_step = 2;
_isLoading = true;
_searchController.clear();
});
final repo = ref.read(motorcycleRepositoryProvider);
final makes = await repo.getMakes(year);
setState(() {
_makes = makes;
_isLoading = false;
});
}
Future<void> _selectMake(MotorcycleMake make) async {
setState(() {
_selectedMake = make;
_step = 3;
_isLoading = true;
_searchController.clear();
});
final repo = ref.read(motorcycleRepositoryProvider);
final models = await repo.getModels(_selectedYear!, make.id);
setState(() {
_models = models;
_isLoading = false;
});
}
Future<void> _selectModel(MotorcycleModel model) async {
setState(() {
_selectedModel = model;
_isLoading = true;
});
final repo = ref.read(motorcycleRepositoryProvider);
final variants = await repo.getVariants(_selectedYear!, _selectedMake!.id, model.id);
if (variants.length <= 1) {
_selectedVariant = variants.isNotEmpty ? variants.first : null;
await _loadSpecSummary();
} else {
setState(() {
_variants = variants;
_step = 4;
_isLoading = false;
});
}
}
Future<void> _selectVariant(MotorcycleVariant variant) async {
setState(() {
_selectedVariant = variant;
});
await _loadSpecSummary();
}
Future<void> _loadSpecSummary() async {
setState(() => _isLoading = true);
final selection = MotorcycleSelection(
year: _selectedYear!,
makeId: _selectedMake!.id,
makeName: _selectedMake!.name,
modelId: _selectedModel!.id,
modelName: _selectedModel!.name,
variantId: _selectedVariant?.id,
variantName: _selectedVariant?.name,
);
final repo = ref.read(motorcycleRepositoryProvider);
final spec = await repo.getSpecifications(selection);
setState(() {
_previewSpec = spec;
_step = 5;
_isLoading = false;
});
}
Future<void> _saveMotorcycle() async {
final selection = MotorcycleSelection(
year: _selectedYear!,
makeId: _selectedMake!.id,
makeName: _selectedMake!.name,
modelId: _selectedModel!.id,
modelName: _selectedModel!.name,
variantId: _selectedVariant?.id,
variantName: _selectedVariant?.name,
);
final controller = ref.read(garageControllerProvider);
await controller.addMotorcycle(
selection,
nickname: _nicknameController.text.trim().isNotEmpty ? _nicknameController.text.trim() : null,
);
if (mounted) {
ScaffoldMessenger.of(context).showSnackBar(
SnackBar(content: Text('Added ${selection.displayName} to your Garage!')),
);
context.pop();
}
}
void _goBack() {
if (_step > 1) {
setState(() {
_step -= 1;
_searchController.clear();
});
} else {
context.pop();
}
}
@override
Widget build(BuildContext context) {
return PopScope(
canPop: _step == 1,
onPopInvokedWithResult: (didPop, result) {
if (!didPop && _step > 1) {
_goBack();
}
},
child: Scaffold(
appBar: AppBar(
title: Text(_getStepTitle()),
leading: IconButton(
icon: const Icon(Icons.arrow_back),
onPressed: _goBack,
),
),
body: _isLoading
? const Center(child: CircularProgressIndicator())
: Column(
children: [
LinearProgressIndicator(value: _step / 5.0, backgroundColor: Colors.white10, valueColor: const AlwaysStoppedAnimation(Colors.cyanAccent)),
Expanded(child: _buildStepContent()),
],
),
),
);
}
String _getStepTitle() {
switch (_step) {
case 1:
return 'Select Year';
case 2:
return 'Select Manufacturer';
case 3:
return 'Select Model (${_selectedMake?.name ?? ""})';
case 4:
return 'Select Variant';
case 5:
return 'Confirm Motorcycle';
default:
return 'Add Motorcycle';
}
}
Widget _buildStepContent() {
switch (_step) {
case 1:
return _buildYearStep();
case 2:
return _buildMakeStep();
case 3:
return _buildModelStep();
case 4:
return _buildVariantStep();
case 5:
return _buildConfirmStep();
default:
return const SizedBox();
}
}
Widget _buildYearStep() {
return GridView.builder(
padding: const EdgeInsets.all(16),
gridDelegate: const SliverGridDelegateWithFixedCrossAxisCount(
crossAxisCount: 3,
childAspectRatio: 2.2,
crossAxisSpacing: 10,
mainAxisSpacing: 10,
),
itemCount: _years.length,
itemBuilder: (context, index) {
final year = _years[index].year;
return OutlinedButton(
style: OutlinedButton.styleFrom(
side: BorderSide(color: _selectedYear == year ? Colors.cyanAccent : Colors.white24),
backgroundColor: _selectedYear == year ? Colors.cyanAccent.withValues(alpha: 0.2) : null,
),
onPressed: () => _selectYear(year),
child: Text('$year', style: const TextStyle(fontWeight: FontWeight.bold, fontSize: 16, color: Colors.white)),
);
},
);
}
Widget _buildMakeStep() {
final query = _searchController.text.toLowerCase();
final filtered = _makes.where((m) => m.name.toLowerCase().contains(query)).toList();
return Column(
children: [
Padding(
padding: const EdgeInsets.all(16.0),
child: TextField(
controller: _searchController,
decoration: InputDecoration(
hintText: 'Search Manufacturer...',
prefixIcon: const Icon(Icons.search, color: Colors.cyanAccent),
filled: true,
fillColor: Colors.white10,
border: OutlineInputBorder(borderRadius: BorderRadius.circular(10), borderSide: BorderSide.none),
),
onChanged: (_) => setState(() {}),
),
),
Expanded(
child: ListView.separated(
padding: const EdgeInsets.symmetric(horizontal: 16),
itemCount: filtered.length,
separatorBuilder: (_, __) => const Divider(height: 1, color: Colors.white10),
itemBuilder: (context, index) {
final make = filtered[index];
return ListTile(
title: Text(make.name, style: const TextStyle(fontWeight: FontWeight.w600, fontSize: 18)),
trailing: const Icon(Icons.chevron_right, color: Colors.white30),
onTap: () => _selectMake(make),
);
},
),
),
],
);
}
Widget _buildModelStep() {
final query = _searchController.text.toLowerCase();
final filtered = _models.where((m) => m.name.toLowerCase().contains(query)).toList();
return Column(
children: [
Padding(
padding: const EdgeInsets.all(16.0),
child: TextField(
controller: _searchController,
decoration: InputDecoration(
hintText: 'Search Model...',
prefixIcon: const Icon(Icons.search, color: Colors.cyanAccent),
filled: true,
fillColor: Colors.white10,
border: OutlineInputBorder(borderRadius: BorderRadius.circular(10), borderSide: BorderSide.none),
),
onChanged: (_) => setState(() {}),
),
),
Expanded(
child: ListView.separated(
padding: const EdgeInsets.symmetric(horizontal: 16),
itemCount: filtered.length,
separatorBuilder: (_, __) => const Divider(height: 1, color: Colors.white10),
itemBuilder: (context, index) {
final model = filtered[index];
return ListTile(
title: Text(model.name, style: const TextStyle(fontWeight: FontWeight.w600, fontSize: 18)),
subtitle: Text('${_selectedYear} ${_selectedMake?.name}'),
trailing: const Icon(Icons.chevron_right, color: Colors.white30),
onTap: () => _selectModel(model),
);
},
),
),
],
);
}
Widget _buildVariantStep() {
return ListView.separated(
padding: const EdgeInsets.all(16),
itemCount: _variants.length,
separatorBuilder: (_, __) => const SizedBox(height: 12),
itemBuilder: (context, index) {
final variant = _variants[index];
return Card(
child: ListTile(
title: Text(variant.name, style: const TextStyle(fontWeight: FontWeight.bold, fontSize: 18)),
trailing: const Icon(Icons.check_circle_outline, color: Colors.cyanAccent),
onTap: () => _selectVariant(variant),
),
);
},
);
}
Widget _buildConfirmStep() {
final spec = _previewSpec;
return SingleChildScrollView(
padding: const EdgeInsets.all(24.0),
child: Column(
crossAxisAlignment: CrossAxisAlignment.stretch,
children: [
Container(
padding: const EdgeInsets.all(20),
decoration: BoxDecoration(
color: Colors.white10,
borderRadius: BorderRadius.circular(16),
border: Border.all(color: Colors.cyanAccent, width: 2),
),
child: Column(
children: [
const Icon(Icons.two_wheeler, size: 64, color: Colors.cyanAccent),
const SizedBox(height: 12),
Text(
'${_selectedYear} ${_selectedMake?.name} ${_selectedModel?.name}',
textAlign: TextAlign.center,
style: const TextStyle(fontSize: 24, fontWeight: FontWeight.bold),
),
if (_selectedVariant?.name != null && _selectedVariant!.name != 'Standard') ...[
const SizedBox(height: 4),
Text(_selectedVariant!.name, style: const TextStyle(color: Colors.white70, fontSize: 16)),
],
],
),
),
const SizedBox(height: 24),
if (spec != null) ...[
const Text('SPECIFICATION SUMMARY', style: TextStyle(color: Colors.cyanAccent, fontWeight: FontWeight.bold, letterSpacing: 1.2)),
const SizedBox(height: 12),
Row(
children: [
Expanded(child: _buildSpecChip('DISPLACEMENT', spec.displacementCc != null ? '${spec.displacementCc!.toStringAsFixed(0)} cc' : '—')),
const SizedBox(width: 12),
Expanded(child: _buildSpecChip('HORSEPOWER', spec.horsepower != null ? '${spec.horsepower!.toStringAsFixed(0)} HP' : '—')),
],
),
const SizedBox(height: 12),
Row(
children: [
Expanded(child: _buildSpecChip('TORQUE', spec.torqueLbFt != null ? '${spec.torqueLbFt!.toStringAsFixed(1)} lb-ft' : '—')),
const SizedBox(width: 12),
Expanded(child: _buildSpecChip('WET WEIGHT', spec.wetWeightLbs != null ? '${spec.wetWeightLbs!.toStringAsFixed(0)} lbs' : '—')),
],
),
const SizedBox(height: 24),
],
TextField(
controller: _nicknameController,
decoration: InputDecoration(
labelText: 'Nickname (Optional)',
hintText: 'e.g., Blue Beast, The Track Bike',
border: OutlineInputBorder(borderRadius: BorderRadius.circular(10)),
prefixIcon: const Icon(Icons.edit, color: Colors.cyanAccent),
),
),
const SizedBox(height: 32),
ElevatedButton(
style: ElevatedButton.styleFrom(
backgroundColor: Colors.cyanAccent,
foregroundColor: Colors.black,
padding: const EdgeInsets.symmetric(vertical: 16),
shape: RoundedRectangleBorder(borderRadius: BorderRadius.circular(10)),
),
onPressed: _saveMotorcycle,
child: const Text('ADD TO GARAGE', style: TextStyle(fontSize: 18, fontWeight: FontWeight.bold, letterSpacing: 1.5)),
),
],
),
);
}
Widget _buildSpecChip(String label, String value) {
return Container(
padding: const EdgeInsets.all(12),
decoration: BoxDecoration(
color: Colors.white.withValues(alpha: 0.05),
borderRadius: BorderRadius.circular(10),
border: Border.all(color: Colors.white12),
),
child: Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
Text(label, style: const TextStyle(fontSize: 10, color: Colors.white54, letterSpacing: 1.0)),
const SizedBox(height: 4),
Text(value, style: const TextStyle(fontSize: 18, fontWeight: FontWeight.bold, color: Colors.white)),
],
),
);
}
}