import 'dart:developer'; import 'package:flutter/material.dart'; import 'package:latlong2/latlong.dart' as latlng; import 'package:provider/provider.dart'; import '../models/SocketRequest/ride_stop_request.dart'; import '../providers/ride_providers.dart'; import 'package:mapbox_maps_flutter/mapbox_maps_flutter.dart'; class ChooseRideMap extends StatefulWidget { final latlng.LatLng startLocation; final latlng.LatLng destinationLocation; final List stopLocations; final Function(MapboxMap)? onMapCreated; const ChooseRideMap({ Key? key, required this.startLocation, required this.destinationLocation, required this.stopLocations, this.onMapCreated, }) : super(key: key); @override _ChooseRideMapState createState() => _ChooseRideMapState(); } class _ChooseRideMapState extends State { MapboxMap? _mapboxMap; bool _isLoadingRoute = false; @override void initState() { super.initState(); } /// Initialize map and add markers/polylines void _onMapCreated(MapboxMap mapboxMap) async { _mapboxMap = mapboxMap; // Call the parent's onMapCreated callback if provided if (widget.onMapCreated != null) { widget.onMapCreated!(mapboxMap); } await _addMarkers(); await _fetchShortestRoute(); } /// Add markers to the map Future _addMarkers() async { if (_mapboxMap == null) return; try { // Add start location marker (green) await _mapboxMap!.annotations.createPointAnnotationManager().then((manager) async { await manager.create( PointAnnotationOptions( geometry: Point( coordinates: Position( widget.startLocation.longitude, widget.startLocation.latitude, ), ), iconImage: "default_marker", // You can customize this iconColor: Colors.green.value, ), ); }); // Add destination marker (red) await _mapboxMap!.annotations.createPointAnnotationManager().then((manager) async { await manager.create( PointAnnotationOptions( geometry: Point( coordinates: Position( widget.destinationLocation.longitude, widget.destinationLocation.latitude, ), ), iconImage: "default_marker", iconColor: Colors.red.value, ), ); }); // Add stop markers (blue) for (int i = 0; i < widget.stopLocations.length; i++) { final stopPosition = parseLatLng(widget.stopLocations[i].stopLocation); await _mapboxMap!.annotations.createPointAnnotationManager().then((manager) async { await manager.create( PointAnnotationOptions( geometry: Point(coordinates: stopPosition), iconImage: "default_marker", iconColor: Colors.blue.value, ), ); }); } } catch (e) { print("Error adding markers: $e"); } } /// Fetch the shortest route from OpenStreetMap API and add polyline Future _fetchShortestRoute() async { if (_mapboxMap == null) return; setState(() => _isLoadingRoute = true); try { final ridesProvider = Provider.of(context, listen: false); if (ridesProvider.routedata != null && ridesProvider.routedata!.points.isNotEmpty) { // Create polyline coordinates final coordinates = ridesProvider.routedata!.points .map((Position coord) => Position(coord.lng, coord.lat)) .toList(); // Add polyline to map await _mapboxMap!.annotations.createPolylineAnnotationManager().then((manager) async { await manager.create( PolylineAnnotationOptions( geometry: LineString(coordinates: coordinates), lineColor: Colors.blue.value, lineWidth: 6.0, ), ); }); // Fit camera to show all points await _fitCameraToRoute(coordinates); } } catch (e) { print("Error fetching route: $e"); } finally { setState(() => _isLoadingRoute = false); } } /// Fit camera to show the entire route Future _fitCameraToRoute(List coordinates) async { if (_mapboxMap == null || coordinates.isEmpty) return; try { // Calculate bounds double minLat = coordinates.first.lat.toDouble(); double maxLat = coordinates.first.lat.toDouble(); double minLng = coordinates.first.lng.toDouble(); double maxLng = coordinates.first.lng.toDouble(); for (final coord in coordinates) { final lat = coord.lat.toDouble(); final lng = coord.lng.toDouble(); minLat = lat < minLat ? lat : minLat; maxLat = lat > maxLat ? lat : maxLat; minLng = lng < minLng ? lng : minLng; maxLng = lng > maxLng ? lng : maxLng; } // Add padding const padding = 0.01; // Calculate center and zoom level final centerLng = (minLng + maxLng) / 2; final centerLat = (minLat + maxLat) / 2; final zoomLevel = _calculateZoomLevel(minLat, maxLat, minLng, maxLng); // Fit camera to show the route await _mapboxMap!.easeTo( CameraOptions( center: Point(coordinates: Position(centerLng, centerLat)), zoom: zoomLevel, ), MapAnimationOptions(duration: 1000), ); } catch (e) { print("Error fitting camera: $e"); } } /// Calculate appropriate zoom level based on bounds double _calculateZoomLevel(double minLat, double maxLat, double minLng, double maxLng) { final latDiff = (maxLat - minLat).abs(); final lngDiff = (maxLng - minLng).abs(); final maxDiff = latDiff > lngDiff ? latDiff : lngDiff; if (maxDiff > 0.1) return 10.0; if (maxDiff > 0.05) return 11.0; if (maxDiff > 0.01) return 12.0; if (maxDiff > 0.005) return 13.0; return 14.0; } Position parseLatLng(String input) { final parts = input.split(','); if (parts.length != 2) { throw const FormatException('Invalid LatLng format, expected "lat,lng"'); } final lat = double.tryParse(parts[0].trim()); final lng = double.tryParse(parts[1].trim()); if (lat == null || lng == null) { throw const FormatException( 'Could not parse latitude or longitude as double'); } // Return Position with longitude, latitude order (Mapbox format) return Position(lng, lat); } @override Widget build(BuildContext context) { return Stack( children: [ MapWidget( cameraOptions: CameraOptions( center: Point( coordinates: Position( widget.startLocation.longitude, widget.startLocation.latitude, ), ), zoom: 12.0, ), styleUri: MapboxStyles.MAPBOX_STREETS, // You can change this style onMapCreated: _onMapCreated, ), if (_isLoadingRoute) Positioned( top: 20, left: MediaQuery.of(context).size.width / 2 - 20, child: const CircularProgressIndicator(color: Colors.lightBlueAccent), ), ], ); } }