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 route planning feature allows users to search for optimal bus routes between campus locations, view multiple route options, and save favorites.

RouteService

The RouteService handles all route-related API calls.

Getting Available Nodes

Retrieve all available stops/locations:
final RouteService _routeService = RouteService();

Future<void> _loadNodes() async {
  try {
    final nodes = await _routeService.getNodes();
    setState(() {
      _nodes = nodes;
    });
  } catch (e) {
    print('Failed to load locations: $e');
  }
}
Source: lib/screens/home/home_screen.dart:38-53

Planning a Route

Search for routes between two locations:
Future<void> _searchRoutes() async {
  try {
    final routes = await _routeService.planRoute(
      from: _fromNode!,
      to: _toNode!,
      time: _time,
    );
    setState(() {
      _routes = routes;
    });
  } catch (e) {
    print('Failed to find routes: $e');
  }
}
Source: lib/screens/home/home_screen.dart:55-83

RouteService Implementation

class RouteService {
  final ApiService _api = ApiService();

  Future<List<Node>> getNodes() async {
    final data = await _api.get('/nodes');
    return (data['nodes'] as List).map((node) => Node.fromJson(node)).toList();
  }

  Future<List<RouteOption>> planRoute({
    required String from,
    required String to,
    required String time,
  }) async {
    final data = await _api.get('/routes', params: {
      'from': from,
      'to': to,
      'time': time,
    });
    
    return (data['options'] as List)
        .map((option) => RouteOption.fromJson(option))
        .toList();
  }
}
Source: lib/services/route_service.dart

RouteOption Model

Route search results are represented by the RouteOption model:
class RouteOption {
  final String label;
  final String category;
  final int totalTimeMin;
  final int totalCost;
  final int transfers;
  final int localTimeMin;
  final int localDistanceMeters;
  final List<RouteLeg> legs;

  RouteOption({
    required this.label,
    required this.category,
    required this.totalTimeMin,
    required this.totalCost,
    required this.transfers,
    required this.localTimeMin,
    required this.localDistanceMeters,
    required this.legs,
  });

  factory RouteOption.fromJson(Map<String, dynamic> json) {
    return RouteOption(
      label: json['label'],
      category: json['category'],
      totalTimeMin: json['totalTimeMin'],
      totalCost: json['totalCost'],
      transfers: json['transfers'],
      localTimeMin: json['localTimeMin'],
      localDistanceMeters: json['localDistanceMeters'],
      legs: (json['legs'] as List).map((leg) => RouteLeg.fromJson(leg)).toList(),
    );
  }
}
Source: lib/models/route_option.dart

Route Categories

  • fastest - Minimizes total travel time
  • cheapest - Minimizes total cost
  • Other categories as defined by the backend

Route Search UI

The HomeScreen provides the route search interface.

Search Form

Form(
  key: _formKey,
  child: Column(
    children: [
      // From Dropdown
      DropdownButtonFormField<String>(
        decoration: const InputDecoration(
          labelText: 'From',
          border: OutlineInputBorder(),
          filled: true,
          fillColor: Colors.white,
        ),
        value: _fromNode,
        items: _nodes.map((node) {
          return DropdownMenuItem(
            value: node.id,
            child: Text(node.name),
          );
        }).toList(),
        onChanged: (value) => setState(() => _fromNode = value),
        validator: (value) => value == null ? 'Please select an origin' : null,
      ),
      
      // To Dropdown
      DropdownButtonFormField<String>(
        decoration: const InputDecoration(
          labelText: 'To',
          border: OutlineInputBorder(),
        ),
        value: _toNode,
        items: _nodes.map((node) {
          return DropdownMenuItem(
            value: node.id,
            child: Text(node.name),
          );
        }).toList(),
        onChanged: (value) => setState(() => _toNode = value),
      ),
      
      // Time Input
      TextFormField(
        readOnly: true,
        controller: TextEditingController(text: _time),
        decoration: const InputDecoration(
          labelText: 'Time',
          suffixIcon: Icon(Icons.access_time),
        ),
        onTap: _selectTime,
      ),
      
      // Search Button
      ElevatedButton(
        onPressed: _loading ? null : _searchRoutes,
        child: const Text('Find Routes'),
      ),
    ],
  ),
)
Source: lib/screens/home/home_screen.dart:160-234

Time Picker

Future<void> _selectTime() async {
  final TimeOfDay? picked = await showTimePicker(
    context: context,
    initialTime: TimeOfDay(
      hour: int.parse(_time.split(':')[0]),
      minute: int.parse(_time.split(':')[1]),
    ),
  );
  if (picked != null) {
    final formattedTime = '${picked.hour.toString().padLeft(2, '0')}:${picked.minute.toString().padLeft(2, '0')}';
    setState(() {
      _time = formattedTime;
    });
  }
}
Source: lib/screens/home/home_screen.dart:127-142

RouteCard Widget

The RouteCard widget displays individual route options:
class RouteCard extends StatelessWidget {
  final RouteOption route;
  final VoidCallback? onFavorite;

  const RouteCard({
    super.key,
    required this.route,
    this.onFavorite,
  });

  @override
  Widget build(BuildContext context) {
    return Card(
      margin: EdgeInsets.only(bottom: 16),
      elevation: 2,
      child: Padding(
        padding: EdgeInsets.all(16),
        child: Column(
          crossAxisAlignment: CrossAxisAlignment.start,
          children: [
            // Header with label and category badge
            Row(
              mainAxisAlignment: MainAxisAlignment.spaceBetween,
              children: [
                Text(
                  route.label,
                  style: TextStyle(
                    fontSize: 18,
                    fontWeight: FontWeight.bold,
                  ),
                ),
                // Map button
                IconButton(
                  icon: Icon(Icons.map_outlined, color: Colors.blue),
                  tooltip: 'View on Map',
                  onPressed: () => _openRouteMap(context),
                ),
                // Favorite button
                if (onFavorite != null)
                  IconButton(
                    icon: Icon(Icons.favorite_border),
                    onPressed: onFavorite,
                  ),
                // Category badge
                Container(
                  padding: EdgeInsets.symmetric(horizontal: 8, vertical: 4),
                  decoration: BoxDecoration(
                    color: route.category == 'fastest'
                        ? Colors.green.withOpacity(0.2)
                        : Colors.blue.withOpacity(0.2),
                    borderRadius: BorderRadius.circular(12),
                  ),
                  child: Text(
                    route.category.toUpperCase(),
                    style: TextStyle(fontSize: 10, fontWeight: FontWeight.bold),
                  ),
                ),
              ],
            ),
            
            // Stats (time, cost, transfers)
            Row(
              children: [
                _buildStat(Icons.access_time, '${route.totalTimeMin} min'),
                SizedBox(width: 16),
                _buildStat(Icons.currency_rupee, '৳${route.totalCost}'),
                SizedBox(width: 16),
                _buildStat(Icons.transfer_within_a_station, '${route.transfers}'),
              ],
            ),
            
            // Walking/local time note
            if (route.localTimeMin > 0)
              Text(
                'Includes ${route.localTimeMin} min walking/local',
                style: TextStyle(color: Colors.grey, fontSize: 12),
              ),
            
            Divider(),
            
            // Route legs
            ...route.legs.map((leg) => Row(
              children: [
                Icon(
                  leg.mode == 'bus' ? Icons.directions_bus : Icons.directions_walk,
                  size: 20,
                  color: leg.mode == 'bus' ? Colors.blue : Colors.orange,
                ),
                SizedBox(width: 8),
                Expanded(
                  child: Text('${leg.from}${leg.to}'),
                ),
                if (leg.departure != null)
                  Text(
                    '${leg.departure} - ${leg.arrival}',
                    style: TextStyle(fontSize: 12, color: Colors.grey),
                  ),
              ],
            )),
          ],
        ),
      ),
    );
  }
}
Source: lib/widgets/route_card.dart:24-124

Displaying Route Results

Expanded(
  child: _routes.isEmpty
      ? Center(
          child: Text(
            _loading ? 'Searching...' : 'No routes found',
            style: const TextStyle(color: Colors.grey),
          ),
        )
      : ListView.builder(
          padding: const EdgeInsets.all(16),
          itemCount: _routes.length,
          itemBuilder: (context, index) {
            final route = _routes[index];
            return RouteCard(
              route: route,
              onFavorite: auth.isLoggedIn ? () => _addFavorite(route) : null,
            );
          },
        ),
)
Source: lib/screens/home/home_screen.dart:238-257

Adding to Favorites

Users can save routes for quick access:
Future<void> _addFavorite(RouteOption route) async {
  final auth = Provider.of<AuthProvider>(context, listen: false);
  
  // Require authentication
  if (!auth.isLoggedIn) {
    await Navigator.push(
      context,
      MaterialPageRoute(builder: (_) => const LoginScreen()),
    );
    if (!mounted || !auth.isLoggedIn) return;
  }

  try {
    final favorite = Favorite(
      id: 'temp', // Server will assign ID
      label: route.label,
      from: _nodes.firstWhere((n) => n.id == _fromNode).name,
      to: _nodes.firstWhere((n) => n.id == _toNode).name,
      defaultTime: _time,
    );
    
    await _favoriteService.addFavorite(favorite);

    ScaffoldMessenger.of(context).showSnackBar(
      SnackBar(content: Text('Added "${route.label}" to favorites')),
    );
  } catch (e) {
    ScaffoldMessenger.of(context).showSnackBar(
      SnackBar(content: Text('Failed to add favorite: $e')),
    );
  }
}
Source: lib/screens/home/home_screen.dart:85-125

Opening Map View

Tap the map icon to visualize the route:
void _openRouteMap(BuildContext context) {
  Navigator.of(context).push(
    MaterialPageRoute(
      builder: (context) => RouteMapScreen(routeOption: route),
    ),
  );
}
Source: lib/widgets/route_card.dart:15-21

Example Usage

class MyRouteScreen extends StatefulWidget {
  @override
  _MyRouteScreenState createState() => _MyRouteScreenState();
}

class _MyRouteScreenState extends State<MyRouteScreen> {
  final RouteService _routeService = RouteService();
  List<RouteOption> _routes = [];
  
  Future<void> searchRoutes() async {
    final routes = await _routeService.planRoute(
      from: 'CAMPUS',
      to: 'AMBARKHANA',
      time: '08:30',
    );
    
    setState(() {
      _routes = routes;
    });
  }
  
  @override
  Widget build(BuildContext context) {
    return ListView.builder(
      itemCount: _routes.length,
      itemBuilder: (context, index) {
        return RouteCard(route: _routes[index]);
      },
    );
  }
}

Error Handling

Handle network errors gracefully:
try {
  final routes = await _routeService.planRoute(
    from: fromNode,
    to: toNode,
    time: time,
  );
  // Success
} catch (e) {
  // Network error, invalid input, or server error
  ScaffoldMessenger.of(context).showSnackBar(
    SnackBar(content: Text('Failed to find routes: $e')),
  );
}

Next Steps

Build docs developers (and LLMs) love