import "package:flutter/material.dart"; import "package:file_picker/file_picker.dart"; import "package:go_router/go_router.dart"; import "../models/trip.dart"; import "../parsers/arriva_schedule_parser.dart"; import "../parsers/stagecoach_schedule_parser.dart"; import "../services/brr_export_service.dart"; import "../services/storage_service.dart"; class HomePage extends StatefulWidget { const HomePage({super.key}); static GoRoute route = GoRoute( path: "/", builder: (context, state) => const HomePage(), ); @override State createState() => _HomePageState(); } class _HomePageState extends State { bool _isUploading = false; String? _errorMessage; BRROperator _selectedOperator = BRROperator.arriva; @override void initState() { super.initState(); _restoreSession(); } Future _restoreSession() async { final state = await StorageService().loadState(); if (state != null && state.trips.isNotEmpty && mounted) { context.go("/trips", extra: { "trips": state.trips, "fileName": state.scheduleFileName ?? "", "operator": _selectedOperator, }); } } String get _fileExtension { switch (_selectedOperator) { case BRROperator.arriva: return "docx"; case BRROperator.stagecoach: return "pdf"; } } String get _formatLabel { switch (_selectedOperator) { case BRROperator.arriva: return "Arriva schedule format (.docx)"; case BRROperator.stagecoach: return "Stagecoach schedule format (.pdf)"; } } Future _uploadSchedule() async { print("Upload button pressed"); setState(() { _isUploading = true; _errorMessage = null; }); try { final result = await FilePicker.platform.pickFiles( type: FileType.custom, allowedExtensions: [_fileExtension], allowMultiple: false, withData: true, ); if (result == null || result.files.isEmpty) { setState(() { _isUploading = false; }); return; } final file = result.files.first; if (file.bytes == null) throw Exception("Could not read file bytes"); String? routeName; final List trips; if (_selectedOperator == BRROperator.stagecoach) { final parser = StagecoachScheduleParser(); trips = await parser.parseBytes(file.bytes!); routeName = parser.parsedRouteName; } else { trips = await ArrivaScheduleParser().parseBytes(file.bytes!); } if (trips.isEmpty) throw Exception("No trips found in schedule"); if (mounted) { context.go("/station-selection", extra: { "trips": trips, "fileName": file.name, "operator": _selectedOperator, if (routeName != null) "routeName": routeName, }); } } catch (e) { print("Error: $e"); setState(() { _errorMessage = e.toString(); }); } finally { setState(() { _isUploading = false; }); } } @override Widget build(BuildContext context) { return Scaffold( body: SafeArea( child: Padding( padding: const EdgeInsets.all(20), child: Column( crossAxisAlignment: CrossAxisAlignment.start, children: [ const SizedBox(height: 40), const Text( "BUS\nRUNNING\nRECORD", style: TextStyle( fontSize: 40, fontWeight: FontWeight.w900, height: 1.0, letterSpacing: -1, color: Color(0xFFEEEEEE), ), ), const SizedBox(height: 8), Container( width: 40, height: 3, color: const Color(0xFF00A9CE), ), const Spacer(), // operator selector const Text( "OPERATOR", style: TextStyle( fontSize: 11, fontWeight: FontWeight.w700, color: Color(0xFF777777), letterSpacing: 1.5, ), ), const SizedBox(height: 8), Row( children: [ _OperatorChip( label: "ARRIVA", selected: _selectedOperator == BRROperator.arriva, onTap: () => setState(() => _selectedOperator = BRROperator.arriva), ), const SizedBox(width: 8), _OperatorChip( label: "STAGECOACH", selected: _selectedOperator == BRROperator.stagecoach, onTap: () => setState(() => _selectedOperator = BRROperator.stagecoach), ), ], ), const SizedBox(height: 16), // format info Container( padding: const EdgeInsets.all(14), decoration: BoxDecoration( border: Border.all(color: const Color(0xFF2E2E2E)), borderRadius: BorderRadius.circular(3), ), child: Row( children: [ const Icon(Icons.info_outline, size: 16, color: Color(0xFF777777)), const SizedBox(width: 10), Text( _formatLabel, style: const TextStyle(fontSize: 13, color: Color(0xFF777777)), ), ], ), ), const SizedBox(height: 12), if (_errorMessage != null) ...[ Container( width: double.infinity, padding: const EdgeInsets.all(14), decoration: BoxDecoration( color: const Color(0xFF2A1515), border: Border.all(color: const Color(0xFF7A2020)), borderRadius: BorderRadius.circular(3), ), child: Text( _errorMessage!, style: const TextStyle( color: Color(0xFFCF6679), fontSize: 13, ), ), ), const SizedBox(height: 12), ], SizedBox( width: double.infinity, height: 54, child: ElevatedButton( onPressed: _isUploading ? null : _uploadSchedule, child: _isUploading ? const SizedBox( width: 18, height: 18, child: CircularProgressIndicator( strokeWidth: 2, color: Colors.black, ), ) : const Text("LOAD SCHEDULE"), ), ), ], ), ), ), ); } } class _OperatorChip extends StatelessWidget { final String label; final bool selected; final VoidCallback onTap; const _OperatorChip({ required this.label, required this.selected, required this.onTap, }); @override Widget build(BuildContext context) { return Expanded( child: GestureDetector( onTap: onTap, child: AnimatedContainer( duration: const Duration(milliseconds: 150), padding: const EdgeInsets.symmetric(vertical: 14), decoration: BoxDecoration( color: selected ? const Color(0xFF002A33) : const Color(0xFF1E1E1E), border: Border.all( color: selected ? const Color(0xFF00A9CE) : const Color(0xFF2E2E2E), width: selected ? 1.5 : 1, ), borderRadius: BorderRadius.circular(3), ), child: Center( child: Text( label, style: TextStyle( fontSize: 13, fontWeight: FontWeight.w700, color: selected ? const Color(0xFF00A9CE) : const Color(0xFF777777), letterSpacing: 1, ), ), ), ), ), ); } }