import 'dart:async'; import 'dart:convert'; import 'dart:developer'; import 'package:cached_network_image/cached_network_image.dart'; import 'package:cloud_firestore/cloud_firestore.dart'; import 'package:driver/app/wallet_screen/payment_list_screen.dart'; import 'package:driver/constant/collection_name.dart'; import 'package:driver/constant/constant.dart'; import 'package:driver/constant/send_notification.dart'; import 'package:driver/constant/show_toast_dialog.dart'; import 'package:driver/models/cab_order_model.dart'; import 'package:driver/models/user_model.dart'; import 'package:driver/models/wallet_transaction_model.dart'; import 'package:driver/services/audio_player_service.dart'; import 'package:driver/themes/app_them_data.dart'; import 'package:driver/utils/fire_store_utils.dart'; import 'package:flutter/foundation.dart'; import 'package:flutter/material.dart'; import 'package:flutter_map/flutter_map.dart' as flutterMap; import 'package:flutter_polyline_points/flutter_polyline_points.dart'; import 'package:get/get.dart'; import 'package:google_maps_flutter/google_maps_flutter.dart'; import 'package:http/http.dart' as http; import 'package:latlong2/latlong.dart' as location; class CabHomeController extends GetxController { RxBool isLoading = true.obs; flutterMap.MapController osmMapController = flutterMap.MapController(); RxList osmMarkers = [].obs; StreamSubscription>>? _driverSub; StreamSubscription>>? _orderDocSub; StreamSubscription>>? _orderQuerySub; @override void onInit() { getData(); super.onInit(); } @override void onClose() { _driverSub?.cancel(); _orderDocSub?.cancel(); _orderQuerySub?.cancel(); super.onClose(); } Future getData() async { _subscribeDriver(); isLoading.value = false; } Rx currentOrder = CabOrderModel().obs; Rx driverModel = UserModel().obs; Rx ownerModel = UserModel().obs; Future acceptOrder() async { try { await AudioPlayerService.playSound(false); ShowToastDialog.showLoader("Please wait".tr); driverModel.value.inProgressOrderID ??= []; driverModel.value.inProgressOrderID!.add(currentOrder.value.id); driverModel.value.orderCabRequestData = null; await FireStoreUtils.updateUser(driverModel.value); currentOrder.value.status = Constant.driverAccepted; currentOrder.value.driverId = driverModel.value.id; currentOrder.value.driver = driverModel.value; await FireStoreUtils.setCabOrder(currentOrder.value); ShowToastDialog.closeLoader(); await SendNotification.sendFcmMessage(Constant.driverAcceptedNotification, currentOrder.value.author?.fcmToken ?? "", {}); } catch (e, s) { ShowToastDialog.closeLoader(); debugPrint("Error in acceptOrder: $e"); debugPrintStack(stackTrace: s); ShowToastDialog.showToast("Something went wrong. Please try again."); } } Future rejectOrder() async { try { await AudioPlayerService.playSound(false); // 1️⃣ Immediately update local state (UI) currentOrder.value.status = Constant.driverRejected; currentOrder.value.rejectedByDrivers ??= []; if (!currentOrder.value.rejectedByDrivers!.contains(driverModel.value.id)) { currentOrder.value.rejectedByDrivers!.add(driverModel.value.id); } // Immediately update UI so bottom sheet hides right away currentOrder.refresh(); // 2️⃣ Update driver local state right away driverModel.value.orderCabRequestData = null; driverModel.value.inProgressOrderID = []; await FireStoreUtils.updateUser(driverModel.value); // 3️⃣ Close bottom sheet immediately (don’t wait for Firestore) if (Get.isBottomSheetOpen ?? false) { Get.back(); } else if (Constant.singleOrderReceive == false) { Get.back(); } // 4️⃣ Clear map immediately await clearMap(); // 5️⃣ Update Firestore in background (no UI wait) unawaited(FireStoreUtils.setCabOrder(currentOrder.value)); // 6️⃣ Reset local current order after short delay Future.delayed(const Duration(milliseconds: 300), () { currentOrder.value = CabOrderModel(); }); } catch (e, s) { print("rejectOrder() error: $e\n$s"); } } bool get shouldShowOrderSheet { final status = currentOrder.value.status; return currentOrder.value.id != null && ![Constant.driverPending, Constant.driverRejected, Constant.orderCompleted, Constant.orderCancelled].contains(status); } Future clearMap() async { await AudioPlayerService.playSound(false); if (Constant.selectedMapType != 'osm') { markers.clear(); polyLines.clear(); } else { osmMarkers.clear(); routePoints.clear(); } update(); } Future onRideStatus() async { await AudioPlayerService.playSound(false); ShowToastDialog.showLoader("Please wait".tr); currentOrder.value.status = Constant.orderInTransit; await FireStoreUtils.setCabOrder(currentOrder.value); ShowToastDialog.closeLoader(); Get.back(); } Future completeRide() async { try { ShowToastDialog.showLoader("Please wait".tr); await updateCabWalletAmount(currentOrder.value); await FireStoreUtils.getFirestOrderOrNOtCabService(currentOrder.value).then((value) async { if (value == true) { await FireStoreUtils.updateReferralAmountCabService(currentOrder.value); } }); currentOrder.value.status = Constant.orderCompleted; driverModel.value.inProgressOrderID = []; driverModel.value.orderCabRequestData = null; await FireStoreUtils.setCabOrder(currentOrder.value); await FireStoreUtils.updateUser(driverModel.value); ShowToastDialog.closeLoader(); } catch (e) { ShowToastDialog.closeLoader(); log("Error in completeRide(): $e"); } } Future updateCabWalletAmount(CabOrderModel orderModel) async { try { double totalTax = 0.0; double discount = 0.0; double subTotal = 0.0; double adminComm = 0.0; double totalAmount = 0.0; subTotal = double.tryParse(orderModel.subTotal ?? '0.0') ?? 0.0; discount = double.tryParse(orderModel.discount ?? '0.0') ?? 0.0; if (orderModel.taxSetting != null) { for (var element in orderModel.taxSetting!) { totalTax += Constant.calculateTax(amount: subTotal.toString(), taxModel: element); } } if ((orderModel.adminCommission ?? '').isNotEmpty) { adminComm = Constant.calculateAdminCommission( amount: (subTotal - discount).toString(), adminCommissionType: orderModel.adminCommissionType.toString(), adminCommission: orderModel.adminCommission ?? '0'); } totalAmount = (subTotal + totalTax) - discount; final ownerId = orderModel.driver?.ownerId; final userIdForWallet = (ownerId != null && ownerId.isNotEmpty) ? ownerId : FireStoreUtils.getCurrentUid(); if (orderModel.paymentMethod.toString() != PaymentGateway.cod.name) { WalletTransactionModel transactionModel = WalletTransactionModel( id: Constant.getUuid(), amount: totalAmount, date: Timestamp.now(), paymentMethod: orderModel.paymentMethod ?? '', transactionUser: "driver", userId: userIdForWallet, isTopup: true, orderId: orderModel.id, note: "Booking amount credited", paymentStatus: "success"); final setTx = await FireStoreUtils.setWalletTransaction(transactionModel); if (setTx == true) { await FireStoreUtils.updateUserWallet(amount: totalAmount.toString(), userId: userIdForWallet); } } WalletTransactionModel adminTx = WalletTransactionModel( id: Constant.getUuid(), amount: adminComm, date: Timestamp.now(), paymentMethod: orderModel.paymentMethod ?? '', transactionUser: "driver", userId: userIdForWallet, isTopup: false, orderId: orderModel.id, note: "Admin commission deducted", paymentStatus: "success"); final setAdmin = await FireStoreUtils.setWalletTransaction(adminTx); if (setAdmin == true) { await FireStoreUtils.updateUserWallet(amount: "-${adminComm.toString()}", userId: userIdForWallet); } } catch (e) { log("Error in updateCabWalletAmount(): $e"); } } Future getCurrentOrder() async { try { await _orderDocSub?.cancel(); await _orderQuerySub?.cancel(); final inProgress = driverModel.value.inProgressOrderID; if (inProgress != null && inProgress.isNotEmpty) { final String id = inProgress.first.toString(); _orderDocSub = FireStoreUtils.fireStore .collection(CollectionName.ridesBooking) .doc(id) .snapshots() .listen((docSnap) => _handleOrderDoc(docSnap, id)); return; } final pendingRequest = driverModel.value.orderCabRequestData; if (pendingRequest != null) { final id = pendingRequest.id?.toString(); if (id != null && id.isNotEmpty) { _orderDocSub = FireStoreUtils.fireStore .collection(CollectionName.ridesBooking) .doc(id) .snapshots() .listen((docSnap) => _handleOrderDoc(docSnap, id)); return; } } currentOrder.value = CabOrderModel(); await clearMap(); await AudioPlayerService.playSound(false); update(); } catch (e) { log("getCurrentOrder() error: $e"); } } Future _handleOrderDoc(DocumentSnapshot> docSnap, String id) async { try { if (docSnap.exists) { final data = docSnap.data(); if (data != null) { currentOrder.value = CabOrderModel.fromJson(data); await changeData(); if (currentOrder.value.status == Constant.orderCompleted) { driverModel.value.inProgressOrderID = []; await FireStoreUtils.updateUser(driverModel.value); currentOrder.value = CabOrderModel(); await clearMap(); await AudioPlayerService.playSound(false); update(); return; } else if (currentOrder.value.status == Constant.orderRejected || currentOrder.value.status == Constant.orderCancelled) { driverModel.value.inProgressOrderID = []; driverModel.value.orderCabRequestData = null; await FireStoreUtils.updateUser(driverModel.value); currentOrder.value = CabOrderModel(); await clearMap(); await AudioPlayerService.playSound(false); update(); return; } update(); return; } } _orderQuerySub = FireStoreUtils.fireStore .collection(CollectionName.ridesBooking) .where('id', isEqualTo: id) .limit(1) .snapshots() .listen((qSnap) => _handleOrderQuery(qSnap)); } catch (e) { log("Error listening to order doc: $e"); } } Future _handleOrderQuery(QuerySnapshot> qSnap) async { try { if (qSnap.docs.isNotEmpty) { final doc = qSnap.docs.first; final data = doc.data(); currentOrder.value = CabOrderModel.fromJson(data); await changeData(); if (currentOrder.value.status == Constant.orderCompleted) { driverModel.value.inProgressOrderID = []; await FireStoreUtils.updateUser(driverModel.value); currentOrder.value = CabOrderModel(); await clearMap(); await AudioPlayerService.playSound(false); update(); return; } update(); return; } else { currentOrder.value = CabOrderModel(); await AudioPlayerService.playSound(false); update(); } } catch (e) { log("Error parsing order from query fallback: $e"); } } RxBool isChange = false.obs; Future changeData() async { if (Constant.mapType == "inappmap") { if (Constant.selectedMapType == "osm") { await getOSMPolyline(); } else { await getGooglePolyline(); } } if (currentOrder.value.status == Constant.driverPending) { await AudioPlayerService.playSound(true); } else { await AudioPlayerService.playSound(false); } } Future _subscribeDriver() async { _driverSub = FireStoreUtils.fireStore .collection(CollectionName.users) .doc(FireStoreUtils.getCurrentUid()) .snapshots() .listen((event) => _onDriverSnapshot(event)); if (Constant.userModel!.ownerId != null && Constant.userModel!.ownerId!.isNotEmpty) { FireStoreUtils.fireStore.collection(CollectionName.users).doc(Constant.userModel!.ownerId).snapshots().listen( (event) async { if (event.exists) { ownerModel.value = UserModel.fromJson(event.data()!); } }, ); } } Future _onDriverSnapshot(DocumentSnapshot> event) async { try { if (event.exists && event.data() != null) { driverModel.value = UserModel.fromJson(event.data()!); _updateCurrentLocationMarkers(); if (driverModel.value.id != null) { await getCurrentOrder(); await changeData(); if (driverModel.value.sectionId != null && driverModel.value.sectionId!.isNotEmpty) { await FireStoreUtils.getSectionBySectionId(driverModel.value.sectionId!).then((sectionValue) { if (sectionValue != null) { Constant.sectionModel = sectionValue; } }); } update(); } } } catch (e) { log("getDriver() listener error: $e"); } } void _updateCurrentLocationMarkers() async { try { final loc = driverModel.value.location; final latLng = _safeLatLngFromLocation(loc); // Update reactive current location current.value = location.LatLng(latLng.latitude, latLng.longitude); // --- OSM Section --- try { setOsmMapMarker(); if (latLng.latitude != 0.0 || latLng.longitude != 0.0) { osmMapController.move(location.LatLng(latLng.latitude, latLng.longitude), 16); } } catch (e) { log("OSM map move ignored (controller not ready): $e"); } // --- GOOGLE MAP Section --- try { final driverIcon = await _bitmapDescriptorFromUrl( Constant.sectionModel?.markerIcon ?? '', width: 120, ); // Remove old driver marker markers.remove("Driver"); // Create new Google Marker markers["Driver"] = Marker( markerId: const MarkerId("Driver"), infoWindow: const InfoWindow(title: "Driver"), position: LatLng(current.value.latitude, current.value.longitude), icon: driverIcon, rotation: _safeRotation(), anchor: const Offset(0.5, 0.5), flat: true, ); // Animate camera to current driver location if (mapController != null && !(current.value.latitude == 0.0 && current.value.longitude == 0.0)) { mapController!.animateCamera( CameraUpdate.newCameraPosition( CameraPosition( target: LatLng(current.value.latitude, current.value.longitude), zoom: 16, bearing: _safeRotation(), ), ), ); } } catch (e) { log("Google map update ignored (controller not ready): $e"); } update(); log('_updateCurrentLocationMarkers: lat=${latLng.latitude}, lng=${latLng.longitude}, ' 'osmMarkers=${osmMarkers.length}, googleMarkers=${markers.length}'); } catch (e) { log("_updateCurrentLocationMarkers error: $e"); } } GoogleMapController? mapController; Rx polylinePoints = PolylinePoints(apiKey: Constant.mapAPIKey).obs; RxMap polyLines = {}.obs; RxMap markers = {}.obs; // BitmapDescriptor? departureIcon; // BitmapDescriptor? destinationIcon; // BitmapDescriptor? taxiIcon; // Future setIcons() async { // try { // if (Constant.selectedMapType == 'google') { // final Uint8List departure = await Constant().getBytesFromAsset('assets/images/location_black3x.png', 100); // final Uint8List destination = await Constant().getBytesFromAsset('assets/images/location_orange3x.png', 100); // final Uint8List driver = Constant.sectionModel!.markerIcon == null || Constant.sectionModel!.markerIcon!.isEmpty // ? await Constant().getBytesFromAsset('assets/images/ic_cab.png', 50) // : await Constant().getBytesFromUrl(Constant.sectionModel!.markerIcon.toString(), width: 120); // // departureIcon = BitmapDescriptor.fromBytes(departure); // destinationIcon = BitmapDescriptor.fromBytes(destination); // taxiIcon = BitmapDescriptor.fromBytes(driver); // } // } catch (e) { // log("setIcons error: $e"); // } // } LatLng _safeLatLngFromLocation(dynamic loc) { final lat = (loc?.latitude is num) ? loc.latitude.toDouble() : 0.0; final lng = (loc?.longitude is num) ? loc.longitude.toDouble() : 0.0; return LatLng(lat, lng); } double _safeRotation() { return double.tryParse(driverModel.value.rotation.toString()) ?? 0.0; } Future getGooglePolyline() async { try { if (currentOrder.value.id == null) return; final driverLatLng = _safeLatLngFromLocation(driverModel.value.location); List polylineCoordinates = []; // Check order status if (currentOrder.value.status != Constant.driverPending) { // Case 1: Driver Accepted or Order Shipped → Driver → Pickup if (currentOrder.value.status == Constant.driverAccepted || currentOrder.value.status == Constant.orderShipped) { final sourceLatLng = _safeLatLngFromLocation(currentOrder.value.sourceLocation); await _drawGoogleRoute( origin: driverLatLng, destination: sourceLatLng, addDriver: true, addSource: true, addDestination: false, ); animateToSource(); } // Case 2: Order In Transit → Driver → Destination else if (currentOrder.value.status == Constant.orderInTransit) { final destLatLng = _safeLatLngFromLocation(currentOrder.value.destinationLocation); await _drawGoogleRoute( origin: driverLatLng, destination: destLatLng, addDriver: true, addSource: false, addDestination: true, ); animateToSource(); } } // Case 3: Before driver assigned → Source → Destination else { final sourceLatLng = _safeLatLngFromLocation(currentOrder.value.sourceLocation); final destLatLng = _safeLatLngFromLocation(currentOrder.value.destinationLocation); await _drawGoogleRoute( origin: sourceLatLng, destination: destLatLng, addDriver: false, addSource: true, addDestination: true, ); animateToSource(); } } catch (e, s) { log('getGooglePolyline() error: $e'); debugPrintStack(stackTrace: s); } } Future _drawGoogleRoute({ required LatLng origin, required LatLng destination, bool addDriver = true, bool addSource = true, bool addDestination = true, }) async { try { if ((origin.latitude == 0.0 && origin.longitude == 0.0) || (destination.latitude == 0.0 && destination.longitude == 0.0)) return; // Get route points from Google Directions API final result = await polylinePoints.value.getRouteBetweenCoordinates( request: PolylineRequest( origin: PointLatLng(origin.latitude, origin.longitude), destination: PointLatLng(destination.latitude, destination.longitude), mode: TravelMode.driving, ), ); if (result.points.isEmpty) { log('Google route not found'); return; } final List polylineCoordinates = result.points.map((p) => LatLng(p.latitude, p.longitude)).toList(); // Draw polyline addPolyLine(polylineCoordinates); // --- Update markers (same style as OSM) --- await _updateGoogleMarkers( driverLatLng: addDriver ? origin : null, sourceLatLng: addSource ? _safeLatLngFromLocation(currentOrder.value.sourceLocation) : null, destinationLatLng: addDestination ? _safeLatLngFromLocation(currentOrder.value.destinationLocation) : null, ); } catch (e) { log('_drawGoogleRoute error: $e'); } } Future _updateGoogleMarkers({ LatLng? driverLatLng, LatLng? sourceLatLng, LatLng? destinationLatLng, }) async { final Map newMarkers = {}; // Driver Marker (Network Icon) if (driverLatLng != null) { final driverIcon = await _bitmapDescriptorFromUrl( Constant.sectionModel?.markerIcon ?? '', width: 120, ); newMarkers['Driver'] = Marker( markerId: const MarkerId('Driver'), position: driverLatLng, rotation: _safeRotation(), anchor: const Offset(0.5, 0.5), flat: true, icon: driverIcon, ); } // Source Marker if (sourceLatLng != null) { final srcIcon = await _bitmapDescriptorFromAsset( 'assets/images/location_black3x.png', width: 100, ); newMarkers['Source'] = Marker( markerId: const MarkerId('Source'), position: sourceLatLng, icon: srcIcon, ); } // Destination Marker if (destinationLatLng != null) { final dstIcon = await _bitmapDescriptorFromAsset( 'assets/images/location_orange3x.png', width: 100, ); newMarkers['Destination'] = Marker( markerId: const MarkerId('Destination'), position: destinationLatLng, icon: dstIcon, ); } // Apply all markers // ✅ Apply all markers to your RxMap markers ..clear() ..addAll(newMarkers); update(); } Future _bitmapDescriptorFromUrl(String url, {int width = 100}) async { try { final Uint8List bytes = await Constant().getBytesFromUrl(url, width: width); return BitmapDescriptor.fromBytes(bytes); } catch (e) { log('Error loading network icon: $e'); return BitmapDescriptor.defaultMarker; } } Future _bitmapDescriptorFromAsset(String asset, {int width = 100}) async { try { final Uint8List bytes = await Constant().getBytesFromAsset(asset, width); return BitmapDescriptor.fromBytes(bytes); } catch (e) { log('Error loading asset icon: $e'); return BitmapDescriptor.defaultMarker; } } void addPolyLine(List polylineCoordinates) { if (polylineCoordinates.isEmpty) { // nothing to draw, but ensure markers updated update(); return; } PolylineId id = const PolylineId("poly"); Polyline polyline = Polyline( polylineId: id, color: AppThemeData.primary300, points: polylineCoordinates, width: 8, geodesic: true, ); polyLines[id] = polyline; update(); updateCameraLocation(polylineCoordinates.first); } Future updateCameraLocation([LatLng? source]) async { try { if (mapController == null || source == null) return; await mapController!.animateCamera( CameraUpdate.newCameraPosition( CameraPosition( target: source, zoom: currentOrder.value.id == null || currentOrder.value.status == Constant.driverPending ? 16 : 20, bearing: _safeRotation(), ), ), ); } catch (e) { log("updateCameraLocation error: $e"); } } void animateToSource() { double lat = 0.0; double lng = 0.0; final loc = driverModel.value.location; if (loc != null) { // Use string parsing to avoid nullable-toDouble issues and handle numbers/strings. lat = double.tryParse('${loc.latitude}') ?? 0.0; lng = double.tryParse('${loc.longitude}') ?? 0.0; } _updateCurrentLocationMarkers(); osmMapController.move(location.LatLng(lat, lng), 16); } Rx source = location.LatLng(21.1702, 72.8311).obs; // Start (e.g., Surat) Rx current = location.LatLng(21.1800, 72.8400).obs; // Moving marker Rx destination = location.LatLng(21.2000, 72.8600).obs; // Destination void setOsmMapMarker() { final List mk = []; // Add driver/current marker only when we have a valid location if (!(current.value.latitude == 0.0 && current.value.longitude == 0.0)) { mk.add(flutterMap.Marker( point: current.value, width: 45, height: 45, rotate: true, child: CachedNetworkImage( width: 50, height: 50, imageUrl: Constant.sectionModel!.markerIcon.toString(), placeholder: (context, url) => Constant.loader(), errorWidget: (context, url, error) => SizedBox( width: 30, height: 30, child: CircularProgressIndicator(strokeWidth: 2), ), ), )); } // Add source marker if we have a valid source location (or an active order with non-zero coords) final hasSource = currentOrder.value.sourceLocation != null && !(currentOrder.value.sourceLocation?.latitude == null || currentOrder.value.sourceLocation?.longitude == null) && !(currentOrder.value.sourceLocation?.latitude == 0.0 && currentOrder.value.sourceLocation?.longitude == 0.0); if (hasSource) { source.value = location.LatLng(currentOrder.value.sourceLocation!.latitude ?? 0.0, currentOrder.value.sourceLocation!.longitude ?? 0.0); mk.add(flutterMap.Marker( point: source.value, width: 40, height: 40, child: Image.asset('assets/images/location_black3x.png'), )); } // Add destination marker if valid final hasDest = currentOrder.value.destinationLocation != null && !(currentOrder.value.destinationLocation?.latitude == null || currentOrder.value.destinationLocation?.longitude == null) && !(currentOrder.value.destinationLocation?.latitude == 0.0 && currentOrder.value.destinationLocation?.longitude == 0.0); if (hasDest) { destination.value = location.LatLng( currentOrder.value.destinationLocation!.latitude ?? 0.0, currentOrder.value.destinationLocation!.longitude ?? 0.0); mk.add(flutterMap.Marker( point: destination.value, width: 40, height: 40, child: Image.asset('assets/images/location_orange3x.png'), )); } osmMarkers.value = mk; } Future getOSMPolyline() async { try { if (currentOrder.value.id == null) return; if (currentOrder.value.status != Constant.driverPending) { if (currentOrder.value.status == Constant.driverAccepted || currentOrder.value.status == Constant.orderShipped) { final lat = (driverModel.value.location?.latitude as num?)?.toDouble() ?? 0.0; final lng = (driverModel.value.location?.longitude as num?)?.toDouble() ?? 0.0; current.value = location.LatLng(lat, lng); source.value = location.LatLng( currentOrder.value.sourceLocation?.latitude ?? 0.0, currentOrder.value.sourceLocation?.longitude ?? 0.0, ); animateToSource(); await fetchRoute(current.value, source.value); setOsmMapMarker(); } else if (currentOrder.value.status == Constant.orderInTransit) { final lat = (driverModel.value.location?.latitude as num?)?.toDouble() ?? 0.0; final lng = (driverModel.value.location?.longitude as num?)?.toDouble() ?? 0.0; current.value = location.LatLng(lat, lng); destination.value = location.LatLng( currentOrder.value.destinationLocation?.latitude ?? 0.0, currentOrder.value.destinationLocation?.longitude ?? 0.0, ); await fetchRoute(current.value, destination.value); setOsmMapMarker(); animateToSource(); } } else { current.value = location.LatLng(currentOrder.value.sourceLocation?.latitude ?? 0.0, currentOrder.value.sourceLocation?.longitude ?? 0.0); destination.value = location.LatLng( currentOrder.value.destinationLocation?.latitude ?? 0.0, currentOrder.value.destinationLocation?.longitude ?? 0.0); await fetchRoute(current.value, destination.value); setOsmMapMarker(); animateToSource(); } } catch (e) { log('getOSMPolyline error: $e'); } } RxList routePoints = [].obs; Future fetchRoute(location.LatLng source, location.LatLng destination) async { try { // ensure valid coords final bothZero = source.latitude == 0.0 && source.longitude == 0.0 && destination.latitude == 0.0 && destination.longitude == 0.0; if (bothZero) { routePoints.clear(); return; } final url = Uri.parse( 'https://router.project-osrm.org/route/v1/driving/${source.longitude},${source.latitude};${destination.longitude},${destination.latitude}?overview=full&geometries=geojson', ); final response = await http.get(url); if (response.statusCode == 200) { final decoded = json.decode(response.body); if (decoded != null && decoded['routes'] != null && decoded['routes'] is List && (decoded['routes'] as List).isNotEmpty && decoded['routes'][0]['geometry'] != null) { final geometry = decoded['routes'][0]['geometry']['coordinates']; routePoints.clear(); for (var coord in geometry) { if (coord is List && coord.length >= 2) { final lon = coord[0]; final lat = coord[1]; if (lat is num && lon is num) { routePoints.add(location.LatLng(lat.toDouble(), lon.toDouble())); } } } return; } routePoints.clear(); } else { log("Failed to get route: ${response.statusCode} ${response.body}"); routePoints.clear(); } } catch (e) { log("fetchRoute error: $e"); routePoints.clear(); } } }