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 MND mobile app integrates Google Maps to provide interactive route visualization with color-coded polylines, markers, and real-time directions.

Google Maps Integration

The app uses the google_maps_flutter package for map rendering:
dependencies:
  google_maps_flutter: ^2.5.0
Source: pubspec.yaml:54

RouteMapScreen

The RouteMapScreen displays a route on an interactive map.

Basic Setup

class RouteMapScreen extends StatefulWidget {
  final RouteOption routeOption;

  const RouteMapScreen({
    super.key,
    required this.routeOption,
  });

  @override
  State<RouteMapScreen> createState() => _RouteMapScreenState();
}

class _RouteMapScreenState extends State<RouteMapScreen> {
  final Completer<GoogleMapController> _mapController = Completer();
  Set<Polyline> _polylines = {};
  Set<Marker> _markers = {};
  bool _isLoading = true;
  
  @override
  void initState() {
    super.initState();
    _loadRoutePolylines();
  }
}
Source: lib/screens/route_map/route_map_screen.dart:39-70

Rendering the Map

@override
Widget build(BuildContext context) {
  return Scaffold(
    appBar: AppBar(
      title: Text(widget.routeOption.label),
    ),
    body: GoogleMap(
      initialCameraPosition: const CameraPosition(
        target: LatLng(24.9178, 91.8320), // Sylhet center
        zoom: 12,
      ),
      onMapCreated: (controller) {
        _mapController.complete(controller);
        controller.setMapStyle(_darkMapStyle);
      },
      polylines: _polylines,
      markers: _markers,
      myLocationEnabled: false,
      zoomControlsEnabled: true,
      mapToolbarEnabled: false,
    ),
  );
}
Source: lib/screens/route_map/route_map_screen.dart:254-283

DirectionsService

The DirectionsService fetches route polylines from Google Directions API.

Getting Directions

class DirectionsService {
  static const String _baseUrl =
      'https://maps.googleapis.com/maps/api/directions/json';

  static Future<DirectionsResult?> getDirections({
    required String origin,
    required String destination,
  }) async {
    final url = Uri.parse(
      '$_baseUrl?origin=${Uri.encodeComponent(origin)}'
      '&destination=${Uri.encodeComponent(destination)}'
      '&mode=driving'
      '&key=${ApiConfig.googleMapsApiKey}',
    );

    try {
      final response = await http.get(url);

      if (response.statusCode != 200) {
        return null;
      }

      final data = json.decode(response.body);

      if (data['status'] != 'OK') {
        return null;
      }

      final route = data['routes'][0];
      final leg = route['legs'][0];

      // Decode polyline
      final polylinePoints =
          _decodePolyline(route['overview_polyline']['points']);

      // Extract bounds
      final bounds = route['bounds'];
      final northeast = LatLng(
        bounds['northeast']['lat'].toDouble(),
        bounds['northeast']['lng'].toDouble(),
      );
      final southwest = LatLng(
        bounds['southwest']['lat'].toDouble(),
        bounds['southwest']['lng'].toDouble(),
      );

      return DirectionsResult(
        polylinePoints: polylinePoints,
        distanceMeters: leg['distance']['value'],
        distanceText: leg['distance']['text'],
        durationSeconds: leg['duration']['value'],
        durationText: leg['duration']['text'],
        startLocation: LatLng(
          leg['start_location']['lat'].toDouble(),
          leg['start_location']['lng'].toDouble(),
        ),
        endLocation: LatLng(
          leg['end_location']['lat'].toDouble(),
          leg['end_location']['lng'].toDouble(),
        ),
        bounds: LatLngBounds(southwest: southwest, northeast: northeast),
      );
    } catch (e) {
      return null;
    }
  }
}
Source: lib/services/directions_service.dart:7-85

DirectionsResult Model

class DirectionsResult {
  final List<LatLng> polylinePoints;
  final int distanceMeters;
  final String distanceText;
  final int durationSeconds;
  final String durationText;
  final LatLng startLocation;
  final LatLng endLocation;
  final LatLngBounds bounds;

  DirectionsResult({
    required this.polylinePoints,
    required this.distanceMeters,
    required this.distanceText,
    required this.durationSeconds,
    required this.durationText,
    required this.startLocation,
    required this.endLocation,
    required this.bounds,
  });
}
Source: lib/services/directions_service.dart:127-148

Drawing Polylines

Polylines are drawn for each leg of the route.

Color Coding by Mode

class RouteColors {
  static const Color bus = Color(0xFF4285F4);      // Google Blue
  static const Color local = Color(0xFFFF9800);    // Orange
  static const Color walk = Color(0xFF4CAF50);     // Green
}

Color _getModeColor(String mode) {
  switch (mode.toLowerCase()) {
    case 'bus':
      return RouteColors.bus;
    case 'local':
    case 'cng':
      return RouteColors.local;
    case 'walk':
    case 'walking':
      return RouteColors.walk;
    default:
      return RouteColors.bus;
  }
}
Source: lib/screens/route_map/route_map_screen.dart:31-91

Creating Polylines

Future<void> _loadRoutePolylines() async {
  final Set<Polyline> polylines = {};
  int polylineIdCounter = 0;

  // Process each leg of the route
  for (final leg in widget.routeOption.legs) {
    final color = _getModeColor(leg.mode);

    // Get the origin and destination addresses for this leg
    final originAddress = _getNodeAddress(leg.from);
    final destinationAddress = _getNodeAddress(leg.to);

    // Fetch directions from Google API
    final directions = await DirectionsService.getDirections(
      origin: originAddress,
      destination: destinationAddress,
    );

    if (directions != null) {
      // Add polyline for this leg
      polylines.add(
        Polyline(
          polylineId: PolylineId('leg_$polylineIdCounter'),
          points: directions.polylinePoints,
          color: color,
          width: 6,
          patterns: leg.mode.toLowerCase() == 'walk'
              ? [PatternItem.dot, PatternItem.gap(10)]
              : [],
        ),
      );
      polylineIdCounter++;
    }

    // Small delay to avoid rate limiting
    await Future.delayed(const Duration(milliseconds: 150));
  }

  setState(() {
    _polylines = polylines;
    _isLoading = false;
  });
}
Source: lib/screens/route_map/route_map_screen.dart:94-187

Walking Segments

Walking segments use dotted lines:
Polyline(
  polylineId: PolylineId('leg_$polylineIdCounter'),
  points: directions.polylinePoints,
  color: color,
  width: 6,
  patterns: leg.mode.toLowerCase() == 'walk'
      ? [PatternItem.dot, PatternItem.gap(10)]  // Dotted for walking
      : [],  // Solid for bus/vehicle
)
Source: lib/screens/route_map/route_map_screen.dart:123-132

Markers

Markers indicate start, end, and transfer points.

Creating Markers

final Set<Marker> markers = {};
for (int i = 0; i < allStopPositions.length; i++) {
  final isStart = i == 0;
  final isEnd = i == allStopPositions.length - 1;
  final stop = allStopPositions[i];

  markers.add(
    Marker(
      markerId: MarkerId('stop_$i'),
      position: stop.position,
      icon: isStart
          ? BitmapDescriptor.defaultMarkerWithHue(BitmapDescriptor.hueGreen)
          : (isEnd
              ? BitmapDescriptor.defaultMarkerWithHue(BitmapDescriptor.hueRed)
              : BitmapDescriptor.defaultMarkerWithHue(BitmapDescriptor.hueAzure)),
      infoWindow: InfoWindow(
        title: isStart ? 'Start' : (isEnd ? 'End' : 'Stop ${i + 1}'),
        snippet: stop.nodeId,
      ),
    ),
  );
}
Source: lib/screens/route_map/route_map_screen.dart:190-211

Marker Colors

  • Green: Start point
  • Red: End point
  • Blue: Transfer/intermediate stops

Camera Bounds

The map automatically fits all route points in view:
if (totalBounds != null) {
  final controller = await _mapController.future;
  controller.animateCamera(
    CameraUpdate.newLatLngBounds(totalBounds, 60),
  );
}
Source: lib/screens/route_map/route_map_screen.dart:220-225

Node Address Mapping

Node IDs are mapped to real addresses for Google Directions API:
const Map<String, String> nodeAddresses = {
  'TILAGOR': 'Tilagor, Sylhet, Bangladesh',
  'SHIBGONJ': 'Shibgonj, Sylhet, Bangladesh',
  'NAIORPUL': 'Naiorpul, Sylhet, Bangladesh',
  'KUMARPARA': 'Kumarpara, Sylhet, Bangladesh',
  'CAMPUS': 'Shahjalal University of Science and Technology, Sylhet, Bangladesh',
  'AMBARKHANA': 'Ambarkhana, Sylhet, Bangladesh',
  // ... more nodes
};

String _getNodeAddress(String nodeId) {
  return nodeAddresses[nodeId] ?? '$nodeId, Sylhet, Bangladesh';
}
Source: lib/screens/route_map/route_map_screen.dart:8-75

Statistics Card

Display route statistics at the bottom:
Widget _buildStatsCard() {
  return Card(
    elevation: 8,
    color: Colors.grey.shade900,
    child: Padding(
      padding: const EdgeInsets.all(16),
      child: Row(
        mainAxisAlignment: MainAxisAlignment.spaceAround,
        children: [
          _buildStatItem(
            icon: Icons.straighten,
            value: _formatDistance(_totalDistanceMeters),
            label: 'Distance',
          ),
          _buildStatItem(
            icon: Icons.access_time,
            value: _formatDuration(_totalDurationSeconds),
            label: 'Duration',
          ),
          _buildStatItem(
            icon: Icons.swap_horiz,
            value: '${widget.routeOption.transfers}',
            label: 'Transfers',
          ),
        ],
      ),
    ),
  );
}
Source: lib/screens/route_map/route_map_screen.dart:352-390

Legend

Show color legend for transport modes:
Widget _buildLegend() {
  return Card(
    color: Colors.grey.shade900.withOpacity(0.9),
    child: Padding(
      padding: const EdgeInsets.all(12),
      child: Column(
        crossAxisAlignment: CrossAxisAlignment.start,
        children: [
          const Text(
            'Legend',
            style: TextStyle(
              color: Colors.white,
              fontWeight: FontWeight.bold,
            ),
          ),
          _buildLegendItem(RouteColors.bus, 'Bus'),
          _buildLegendItem(RouteColors.local, 'Local/CNG'),
          _buildLegendItem(RouteColors.walk, 'Walking'),
        ],
      ),
    ),
  );
}
Source: lib/screens/route_map/route_map_screen.dart:421-447

Dark Map Style

Custom dark theme for the map:
static const String _darkMapStyle = '''
[
  {"elementType": "geometry", "stylers": [{"color": "#242f3e"}]},
  {"elementType": "labels.text.stroke", "stylers": [{"color": "#242f3e"}]},
  {"elementType": "labels.text.fill", "stylers": [{"color": "#746855"}]},
  {"featureType": "road", "elementType": "geometry", "stylers": [{"color": "#38414e"}]},
  {"featureType": "water", "elementType": "geometry", "stylers": [{"color": "#17263c"}]}
]
''';
Source: lib/screens/route_map/route_map_screen.dart:474-486

Example Usage

// Navigate to map screen from route card
void _openRouteMap(BuildContext context, RouteOption route) {
  Navigator.of(context).push(
    MaterialPageRoute(
      builder: (context) => RouteMapScreen(routeOption: route),
    ),
  );
}

Error Handling

try {
  final directions = await DirectionsService.getDirections(
    origin: originAddress,
    destination: destinationAddress,
  );
  
  if (directions == null) {
    print('Failed to get directions');
    // Show error to user
  }
} catch (e) {
  setState(() {
    _errorMessage = 'Failed to load route: $e';
  });
}
Source: lib/screens/route_map/route_map_screen.dart:226-231

Next Steps

Build docs developers (and LLMs) love