Skip to main content

Documentation Index

Fetch the complete documentation index at: https://mintlify.com/ihfaz297/MND/llms.txt

Use this file to discover all available pages before exploring further.

The bus schedules feature allows users to view upcoming departures at any stop, with real-time countdown timers and route information.

BusService

The BusService handles all bus schedule-related API calls.

Implementation

class BusService {
  final ApiService _api = ApiService();

  Future<List<BusSchedule>> getUpcomingBuses(String from, {int limit = 5}) async {
    final data = await _api.get('/buses/upcoming', params: {
      'from': from,
      'limit': limit.toString(),
    });
    
    return (data['buses'] as List)
        .map((bus) => BusSchedule.fromJson(bus))
        .toList();
  }
}
Source: lib/services/bus_service.dart

API Parameters

  • from (required): Node ID of the stop
  • limit (optional): Maximum number of results (default: 5)

BusSchedule Model

Bus schedule data is represented by the BusSchedule model:
class BusSchedule {
  final String routeId;
  final String routeName;
  final String departure;
  final int minutesUntil;
  final String destination;

  BusSchedule({
    required this.routeId,
    required this.routeName,
    required this.departure,
    required this.minutesUntil,
    required this.destination,
  });

  factory BusSchedule.fromJson(Map<String, dynamic> json) {
    return BusSchedule(
      routeId: json['route_id'],
      routeName: json['route_name'],
      departure: json['departure'],
      minutesUntil: json['minutesUntil'],
      destination: json['destination'],
    );
  }
}
Source: lib/models/bus_schedule.dart

Model Fields

  • routeId: Unique identifier for the route
  • routeName: Display name (e.g., “Campus Express”)
  • departure: Departure time (e.g., “08:30”)
  • minutesUntil: Minutes until departure (for countdown)
  • destination: Final destination of the bus

UpcomingBusesScreen

The UpcomingBusesScreen displays bus schedules at a selected stop.

Screen Structure

class UpcomingBusesScreen extends StatefulWidget {
  @override
  _UpcomingBusesScreenState createState() => _UpcomingBusesScreenState();
}

class _UpcomingBusesScreenState extends State<UpcomingBusesScreen> {
  final BusService _busService = BusService();
  final RouteService _routeService = RouteService();
  
  List<Node> _nodes = [];
  List<BusSchedule> _buses = [];
  String? _selectedStop;
  bool _loadingBuses = false;
  
  @override
  void initState() {
    super.initState();
    _loadNodes();
  }
}
Source: lib/screens/buses/upcoming_buses_screen.dart:7-27

Loading Bus Schedules

Future<void> _loadBuses() async {
  if (_selectedStop == null) return;

  setState(() {
    _loadingBuses = true;
    _error = null;
  });

  try {
    final buses = await _busService.getUpcomingBuses(
      _selectedStop!, 
      limit: 10
    );
    setState(() {
      _buses = buses;
      _loadingBuses = false;
    });
  } catch (e) {
    setState(() {
      _error = e.toString();
      _loadingBuses = false;
    });
  }
}
Source: lib/screens/buses/upcoming_buses_screen.dart:44-64

Stop Selector

Users select a stop from a dropdown menu:
DropdownButtonFormField<String>(
  decoration: InputDecoration(
    labelText: 'Select Your Stop',
    prefixIcon: Icon(Icons.location_on),
    border: OutlineInputBorder(
      borderRadius: BorderRadius.circular(12),
    ),
    filled: true,
    fillColor: Colors.white,
  ),
  value: _selectedStop,
  items: _nodes.map((node) {
    return DropdownMenuItem(
      value: node.id,
      child: Text(node.name),
    );
  }).toList(),
  onChanged: (value) {
    setState(() => _selectedStop = value);
    _loadBuses();
  },
)
Source: lib/screens/buses/upcoming_buses_screen.dart:94-115

Bus List Display

Buses are displayed in a scrollable list with countdown timers:
Widget _buildBusList() {
  return RefreshIndicator(
    onRefresh: _loadBuses,
    child: ListView.builder(
      padding: EdgeInsets.all(16),
      itemCount: _buses.length,
      itemBuilder: (ctx, index) {
        final bus = _buses[index];
        final statusColor = _getStatusColor(
          bus.minutesUntil == 0 ? 'arriving' : 
          bus.minutesUntil <= 5 ? 'soon' : 'scheduled'
        );

        return Card(
          margin: EdgeInsets.only(bottom: 12),
          child: Padding(
            padding: EdgeInsets.all(16),
            child: Row(
              children: [
                // Bus Icon & Time
                Container(
                  width: 60,
                  height: 60,
                  decoration: BoxDecoration(
                    color: statusColor.withOpacity(0.2),
                    borderRadius: BorderRadius.circular(12),
                  ),
                  child: Column(
                    mainAxisAlignment: MainAxisAlignment.center,
                    children: [
                      Icon(Icons.directions_bus, color: statusColor),
                      Text(
                        bus.minutesUntil == 0 ? 'NOW' : '${bus.minutesUntil}m',
                        style: TextStyle(
                          fontWeight: FontWeight.bold,
                          color: statusColor,
                          fontSize: 12,
                        ),
                      ),
                    ],
                  ),
                ),
                SizedBox(width: 16),

                // Route Info
                Expanded(
                  child: Column(
                    crossAxisAlignment: CrossAxisAlignment.start,
                    children: [
                      Text(
                        bus.routeName,
                        style: TextStyle(
                          fontWeight: FontWeight.bold,
                          fontSize: 16,
                        ),
                      ),
                      SizedBox(height: 4),
                      Text(
                        'To: ${bus.destination}',
                        style: TextStyle(color: Colors.grey),
                      ),
                    ],
                  ),
                ),

                // Departure Time
                Column(
                  crossAxisAlignment: CrossAxisAlignment.end,
                  children: [
                    Text(
                      bus.departure,
                      style: TextStyle(
                        fontWeight: FontWeight.bold,
                        fontSize: 18,
                      ),
                    ),
                    Container(
                      padding: EdgeInsets.symmetric(horizontal: 8, vertical: 2),
                      decoration: BoxDecoration(
                        color: statusColor.withOpacity(0.2),
                        borderRadius: BorderRadius.circular(8),
                      ),
                      child: Text(
                        bus.minutesUntil == 0 ? 'Arriving' : 
                        bus.minutesUntil <= 5 ? 'Soon' : 'Scheduled',
                        style: TextStyle(
                          fontSize: 10,
                          color: statusColor,
                          fontWeight: FontWeight.bold,
                        ),
                      ),
                    ),
                  ],
                ),
              ],
            ),
          ),
        );
      },
    ),
  );
}
Source: lib/screens/buses/upcoming_buses_screen.dart:195-298

Status Colors

Buses are color-coded based on arrival time:
Color _getStatusColor(String status) {
  switch (status) {
    case 'arriving':
      return Colors.green;   // 0 minutes (arriving now)
    case 'soon':
      return Colors.orange;  // 1-5 minutes
    default:
      return Colors.blue;    // 6+ minutes
  }
}
Source: lib/screens/buses/upcoming_buses_screen.dart:66-75

Countdown Display

Text(
  bus.minutesUntil == 0 ? 'NOW' : '${bus.minutesUntil}m',
  style: TextStyle(
    fontWeight: FontWeight.bold,
    color: statusColor,
    fontSize: 12,
  ),
)

Status Badge

Container(
  padding: EdgeInsets.symmetric(horizontal: 8, vertical: 2),
  decoration: BoxDecoration(
    color: statusColor.withOpacity(0.2),
    borderRadius: BorderRadius.circular(8),
  ),
  child: Text(
    bus.minutesUntil == 0 ? 'Arriving' : 
    bus.minutesUntil <= 5 ? 'Soon' : 'Scheduled',
    style: TextStyle(
      fontSize: 10,
      color: statusColor,
      fontWeight: FontWeight.bold,
    ),
  ),
)
Source: lib/screens/buses/upcoming_buses_screen.dart:270-288

Pull to Refresh

Users can refresh the schedule by pulling down:
RefreshIndicator(
  onRefresh: _loadBuses,
  child: ListView.builder(
    // ... list content
  ),
)
Source: lib/screens/buses/upcoming_buses_screen.dart:196-198

Empty States

No Stop Selected

Widget _buildSelectPrompt() {
  return Center(
    child: Column(
      mainAxisAlignment: MainAxisAlignment.center,
      children: [
        Icon(Icons.directions_bus, size: 80, color: Colors.grey),
        SizedBox(height: 16),
        Text(
          'Select a Stop',
          style: TextStyle(fontSize: 20, fontWeight: FontWeight.bold),
        ),
        Text(
          'Choose your location to see upcoming buses',
          style: TextStyle(color: Colors.grey),
        ),
      ],
    ),
  );
}
Source: lib/screens/buses/upcoming_buses_screen.dart:137-155

No Buses Available

Widget _buildNoBuses() {
  return Center(
    child: Column(
      mainAxisAlignment: MainAxisAlignment.center,
      children: [
        Icon(Icons.schedule, size: 80, color: Colors.grey),
        SizedBox(height: 16),
        Text(
          'No Buses Right Now',
          style: TextStyle(fontSize: 20, fontWeight: FontWeight.bold),
        ),
        Text(
          'Check back later or try a different stop',
          style: TextStyle(color: Colors.grey),
        ),
      ],
    ),
  );
}
Source: lib/screens/buses/upcoming_buses_screen.dart:175-193

Example Usage

class MyBusScheduleWidget extends StatefulWidget {
  @override
  _MyBusScheduleWidgetState createState() => _MyBusScheduleWidgetState();
}

class _MyBusScheduleWidgetState extends State<MyBusScheduleWidget> {
  final BusService _busService = BusService();
  List<BusSchedule> _buses = [];
  
  Future<void> loadBuses() async {
    final buses = await _busService.getUpcomingBuses(
      'CAMPUS',
      limit: 10,
    );
    
    setState(() {
      _buses = buses;
    });
  }
  
  @override
  Widget build(BuildContext context) {
    return ListView.builder(
      itemCount: _buses.length,
      itemBuilder: (context, index) {
        final bus = _buses[index];
        return ListTile(
          leading: Icon(Icons.directions_bus),
          title: Text(bus.routeName),
          subtitle: Text('To: ${bus.destination}'),
          trailing: Text('${bus.minutesUntil}m'),
        );
      },
    );
  }
}

Error Handling

try {
  final buses = await _busService.getUpcomingBuses(stopId, limit: 10);
  // Success
} catch (e) {
  // Network error or server error
  setState(() {
    _error = e.toString();
  });
  ScaffoldMessenger.of(context).showSnackBar(
    SnackBar(content: Text('Failed to load bus schedules: $e')),
  );
}
Source: lib/screens/buses/upcoming_buses_screen.dart:52-63

UI Features

  • Real-time countdown timers
  • Color-coded status indicators
  • Pull-to-refresh functionality
  • Loading states and error handling
  • Empty state messaging
  • Searchable stop dropdown

Next Steps

Build docs developers (and LLMs) love