import "package:flutter/material.dart"; import "package:go_router/go_router.dart"; import "../models/trip.dart"; import "../services/brr_export_service.dart"; class StationSelectionPage extends StatefulWidget { final List trips; final String fileName; final BRROperator operator; final String? routeName; const StationSelectionPage({ super.key, required this.trips, required this.fileName, this.operator = BRROperator.arriva, this.routeName, }); static GoRoute route = GoRoute( path: "/station-selection", builder: (context, state) { final extra = state.extra as Map?; if (extra == null) { WidgetsBinding.instance.addPostFrameCallback((_) { context.go("/"); }); return const SizedBox.shrink(); } return StationSelectionPage( trips: (extra["trips"] as List).cast(), fileName: extra["fileName"] as String, operator: extra["operator"] as BRROperator? ?? BRROperator.arriva, routeName: extra["routeName"] as String?, ); }, ); @override State createState() => _StationSelectionPageState(); } class _StationSelectionPageState extends State { String? _selectedStation; String? _selectedDirection; List _availableStations = []; @override void initState() { super.initState(); _extractStations(); } void _extractStations() { final stationsSet = {}; for (final trip in widget.trips) { stationsSet.addAll(trip.stationOrder); } _availableStations = stationsSet.toList()..sort(); } bool _isTerminus(String station) { for (final trip in widget.trips) { if (trip.stationOrder.isEmpty) continue; if (trip.stationOrder.first == station || trip.stationOrder.last == station) { return true; } } return false; } // For a given station, find where buses are headed in each direction String? _getTerminus(String station, String direction) { for (final trip in widget.trips) { if (!trip.stationOrder.contains(station)) continue; if (trip.stationOrder.isEmpty) continue; // "departing" = outbound trips, show where they end up if (direction == "departing" && trip.direction == "outbound") { return trip.stationOrder.last; } // "arriving" = inbound trips, show where they end up (opposite terminus) if (direction == "arriving" && trip.direction == "inbound") { return trip.stationOrder.last; } } return null; } String _getDirectionLabel(String direction) { if (_selectedStation == null) { return direction == "departing" ? "DEPARTING" : "ARRIVING"; } if (_isTerminus(_selectedStation!)) { return direction == "departing" ? "DEPARTURES" : "ARRIVALS"; } final terminus = _getTerminus(_selectedStation!, direction); return "towards\n${terminus ?? "..."}"; } void _onContinue() { if (_selectedStation == null || _selectedDirection == null) { ScaffoldMessenger.of(context).showSnackBar( const SnackBar(content: Text("Select both a station and direction")), ); return; } final filteredTrips = _filterAndUpdateTrips(); if (filteredTrips.isEmpty) { ScaffoldMessenger.of(context).showSnackBar( const SnackBar(content: Text("No trips found for that selection")), ); return; } context.go("/trips", extra: { "trips": filteredTrips, "fileName": widget.fileName, "station": _selectedStation, "direction": _selectedDirection, "operator": widget.operator, if (widget.routeName != null) "routeName": widget.routeName, }); } List _filterAndUpdateTrips() { final filtered = []; final isTerminusStation = _isTerminus(_selectedStation!); for (final trip in widget.trips) { if (!trip.stationTimes.containsKey(_selectedStation)) continue; if (_selectedDirection != "both") { // For terminus stations, the direction logic flips: // "departing" from a terminus that's first on inbound = show inbound trips // "arriving" at a terminus thats last on outbound = show outbound trips final isFirstStation = trip.stationOrder.isNotEmpty && trip.stationOrder.first == _selectedStation; final isLastStation = trip.stationOrder.isNotEmpty && trip.stationOrder.last == _selectedStation; if (isTerminusStation) { if (_selectedDirection == "departing" && !isFirstStation) continue; if (_selectedDirection == "arriving" && !isLastStation) continue; } else { if (_selectedDirection == "departing" && trip.direction != "outbound") continue; if (_selectedDirection == "arriving" && trip.direction != "inbound") continue; } } final stationTime = trip.stationTimes[_selectedStation]; if (stationTime != null) { filtered.add(trip.copyWith(scheduledTime: stationTime)); } } return filtered; } @override Widget build(BuildContext context) { return Scaffold( appBar: AppBar( leading: IconButton( icon: const Icon(Icons.arrow_back, size: 20), onPressed: () => context.go("/"), ), title: const Text("SELECT STATION"), ), body: SafeArea( child: Padding( padding: const EdgeInsets.all(20), child: Column( crossAxisAlignment: CrossAxisAlignment.start, children: [ // file info strip Container( padding: const EdgeInsets.symmetric(horizontal: 12, vertical: 8), color: const Color(0xFF1E1E1E), child: Row( children: [ const Icon(Icons.description_outlined, size: 14, color: Color(0xFF777777)), const SizedBox(width: 8), Expanded( child: Text( widget.fileName, style: const TextStyle( fontSize: 12, color: Color(0xFF777777), ), overflow: TextOverflow.ellipsis, ), ), Text( "${widget.trips.length} trips", style: const TextStyle( fontSize: 12, color: Color(0xFF00A9CE), fontWeight: FontWeight.w600, ), ), ], ), ), const SizedBox(height: 28), const Text( "STATION", style: TextStyle( fontSize: 11, fontWeight: FontWeight.w700, color: Color(0xFF777777), letterSpacing: 1.5, ), ), const SizedBox(height: 8), DropdownButtonFormField( initialValue: _selectedStation, dropdownColor: const Color(0xFF252525), style: const TextStyle(color: Color(0xFFEEEEEE), fontSize: 15), decoration: const InputDecoration( hintText: "Choose station...", ), items: _availableStations.map((station) { return DropdownMenuItem( value: station, child: Text(station), ); }).toList(), onChanged: (value) => setState(() => _selectedStation = value), ), const SizedBox(height: 24), const Text( "DIRECTION", style: TextStyle( fontSize: 11, fontWeight: FontWeight.w700, color: Color(0xFF777777), letterSpacing: 1.5, ), ), const SizedBox(height: 8), Row( children: [ _DirButton( label: _getDirectionLabel("departing"), selected: _selectedDirection == "departing", onTap: () => setState(() => _selectedDirection = "departing"), ), const SizedBox(width: 10), _DirButton( label: _getDirectionLabel("arriving"), selected: _selectedDirection == "arriving", onTap: () => setState(() => _selectedDirection = "arriving"), ), if (widget.operator == BRROperator.stagecoach) ...[ const SizedBox(width: 10), _DirButton( label: "BOTH", selected: _selectedDirection == "both", onTap: () => setState(() => _selectedDirection = "both"), ), ], ], ), const Spacer(), SizedBox( width: double.infinity, height: 54, child: ElevatedButton( onPressed: _onContinue, child: const Text("VIEW TRIPS"), ), ), ], ), ), ), ); } } class _DirButton extends StatelessWidget { final String label; final bool selected; final VoidCallback onTap; const _DirButton({ 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: 16, horizontal: 12), 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: Text( label, style: TextStyle( fontSize: 12, fontWeight: FontWeight.w700, color: selected ? const Color(0xFF00A9CE) : const Color(0xFF777777), letterSpacing: 0.3, ), ), ), ), ); } }