The route planning feature allows users to search for optimal bus routes between campus locations, view multiple route options, and save favorites.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.
RouteService
TheRouteService 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');
}
}
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');
}
}
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();
}
}
lib/services/route_service.dart
RouteOption Model
Route search results are represented by theRouteOption 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(),
);
}
}
lib/models/route_option.dart
Route Categories
fastest- Minimizes total travel timecheapest- Minimizes total cost- Other categories as defined by the backend
Route Search UI
TheHomeScreen 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'),
),
],
),
)
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;
});
}
}
lib/screens/home/home_screen.dart:127-142
RouteCard Widget
TheRouteCard 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),
),
],
)),
],
),
),
);
}
}
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,
);
},
),
)
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')),
);
}
}
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),
),
);
}
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')),
);
}