import 'dart:async'; import 'dart:convert'; import 'package:customer/constant/collection_name.dart'; import 'package:customer/constant/constant.dart'; import 'package:customer/models/order_model.dart'; import 'package:customer/models/user_model.dart'; import 'package:customer/service/fire_store_utils.dart'; import 'package:flutter/material.dart'; import 'package:get/get.dart'; import 'package:google_maps_flutter/google_maps_flutter.dart'; import 'package:latlong2/latlong.dart' as location; import 'package:flutter_map/flutter_map.dart' as flutterMap; import 'package:flutter_polyline_points/flutter_polyline_points.dart'; import 'package:http/http.dart' as http; class LiveTrackingController extends GetxController { GoogleMapController? mapController; final flutterMap.MapController osmMapController = flutterMap.MapController(); Rx orderModel = OrderModel().obs; Rx driverUserModel = UserModel().obs; RxBool isLoading = true.obs; Rx source = location.LatLng(0, 0).obs; Rx destination = location.LatLng(0, 0).obs; Rx driverCurrent = location.LatLng(0, 0).obs; RxList routePoints = [].obs; RxMap markers = {}.obs; RxMap polyLines = {}.obs; RxList osmMarkers = [].obs; BitmapDescriptor? pickupIcon; BitmapDescriptor? dropoffIcon; BitmapDescriptor? driverIcon; PolylinePoints polylinePoints = PolylinePoints(apiKey: Constant.mapAPIKey); StreamSubscription? orderSub; StreamSubscription? driverSub; @override void onInit() { super.onInit(); addMarkerIcons(); getArguments(); } @override void onClose() { orderSub?.cancel(); driverSub?.cancel(); super.onClose(); } Future getArguments() async { final args = Get.arguments; if (args == null) return; orderModel.value = args['orderModel']; orderSub = FireStoreUtils.fireStore .collection(CollectionName.vendorOrders) .doc(orderModel.value.id) .snapshots() .listen((orderSnap) { if (orderSnap.data() == null) return; orderModel.value = OrderModel.fromJson(orderSnap.data()!); if (orderModel.value.driverID != null) { driverSub?.cancel(); driverSub = FireStoreUtils.fireStore .collection(CollectionName.users) .doc(orderModel.value.driverID) .snapshots() .listen((driverSnap) async { if (driverSnap.data() == null) return; driverUserModel.value = UserModel.fromJson( driverSnap.data()!, ); await updateLiveTracking(); }); } if (orderModel.value.status == Constant.orderCompleted) { Get.back(); } }); isLoading.value = false; } Future updateLiveTracking() async { driverCurrent.value = location.LatLng( driverUserModel.value.location?.latitude ?? 0.0, driverUserModel.value.location?.longitude ?? 0.0, ); source.value = location.LatLng( orderModel.value.vendor?.latitude ?? 0.0, orderModel.value.vendor?.longitude ?? 0.0, ); destination.value = location.LatLng( orderModel.value.address?.location?.latitude ?? 0.0, orderModel.value.address?.location?.longitude ?? 0.0, ); if (orderModel.value.status == Constant.orderPlaced || orderModel.value.status == Constant.orderAccepted) { await showDriverToRestaurantRoute(); } else if (orderModel.value.status == Constant.orderShipped || orderModel.value.status == Constant.orderInTransit) { await showDriverToCustomerRoute(); } } Future showDriverToRestaurantRoute() async { clearOldData(); if (Constant.selectedMapType == 'osm') { await fetchRoute(driverCurrent.value, source.value); addOsmMarkers(showPickup: true, showDrop: false); animateToOSMLocation(driverCurrent.value); } else { await getPolyline( sourceLatitude: driverCurrent.value.latitude, sourceLongitude: driverCurrent.value.longitude, destinationLatitude: source.value.latitude, destinationLongitude: source.value.longitude, showPickup: true, showDrop: false, ); } } Future showDriverToCustomerRoute() async { clearOldData(); if (Constant.selectedMapType == 'osm') { await fetchRoute(driverCurrent.value, destination.value); addOsmMarkers(showPickup: false, showDrop: true); animateToOSMLocation(driverCurrent.value); } else { await getPolyline( sourceLatitude: driverCurrent.value.latitude, sourceLongitude: driverCurrent.value.longitude, destinationLatitude: destination.value.latitude, destinationLongitude: destination.value.longitude, showPickup: false, showDrop: true, ); } } void clearOldData() { markers.clear(); polyLines.clear(); routePoints.clear(); } Future fetchRoute( location.LatLng source, location.LatLng destination, ) async { 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 data = json.decode(response.body); final coords = data['routes'][0]['geometry']['coordinates']; routePoints.value = coords .map( (c) => location.LatLng(c[1].toDouble(), c[0].toDouble()), ) .toList(); } } void animateToOSMLocation(location.LatLng loc) { osmMapController.move(loc, 15); } void addOsmMarkers({bool showPickup = false, bool showDrop = false}) { final List tempMarkers = [ // Driver Marker flutterMap.Marker( point: driverCurrent.value, width: 40, height: 40, child: Image.asset('assets/images/food_delivery.png'), ), ]; if (showPickup) { tempMarkers.add( flutterMap.Marker( point: source.value, width: 40, height: 40, child: Image.asset('assets/images/pickup.png'), ), ); } if (showDrop) { tempMarkers.add( flutterMap.Marker( point: destination.value, width: 40, height: 40, child: Image.asset('assets/images/dropoff.png'), ), ); } osmMarkers.value = tempMarkers; } Future getPolyline({ required double sourceLatitude, required double sourceLongitude, required double destinationLatitude, required double destinationLongitude, bool showPickup = false, bool showDrop = false, }) async { List polylineCoordinates = []; PolylineResult result = await polylinePoints.getRouteBetweenCoordinates( request: PolylineRequest( origin: PointLatLng(sourceLatitude, sourceLongitude), destination: PointLatLng(destinationLatitude, destinationLongitude), mode: TravelMode.driving, ), ); if (result.points.isNotEmpty) { polylineCoordinates = result.points.map((e) => LatLng(e.latitude, e.longitude)).toList(); } addGoogleMarkers(showPickup: showPickup, showDrop: showDrop); _addPolyLine(polylineCoordinates); } void addGoogleMarkers({bool showPickup = false, bool showDrop = false}) { markers.clear(); // Always show driver marker if (driverUserModel.value.location != null && driverIcon != null) { addMarker( id: "Driver", latitude: driverUserModel.value.location?.latitude ?? 0.0, longitude: driverUserModel.value.location?.longitude ?? 0.0, descriptor: driverIcon!, rotation: (driverUserModel.value.rotation ?? 0).toDouble(), ); } if (showPickup && orderModel.value.vendor?.latitude != null && pickupIcon != null) { addMarker( id: "Pickup", latitude: orderModel.value.vendor!.latitude ?? 0.0, longitude: orderModel.value.vendor!.longitude ?? 0.0, descriptor: pickupIcon!, rotation: 0.0, ); } else if (showDrop && orderModel.value.address?.location?.latitude != null && dropoffIcon != null) { addMarker( id: "Drop", latitude: orderModel.value.address!.location!.latitude ?? 0.0, longitude: orderModel.value.address!.location!.longitude ?? 0.0, descriptor: dropoffIcon!, rotation: 0.0, ); } } void addMarker({ required String id, required double latitude, required double longitude, required BitmapDescriptor descriptor, required double rotation, }) { MarkerId markerId = MarkerId(id); markers[markerId] = Marker( markerId: markerId, icon: descriptor, position: LatLng(latitude, longitude), rotation: rotation, anchor: const Offset(0.5, 0.5), ); } Future addMarkerIcons() async { if (Constant.selectedMapType == 'osm') return; pickupIcon = BitmapDescriptor.fromBytes( await Constant().getBytesFromAsset('assets/images/pickup.png', 100), ); dropoffIcon = BitmapDescriptor.fromBytes( await Constant().getBytesFromAsset('assets/images/dropoff.png', 100), ); driverIcon = BitmapDescriptor.fromBytes( await Constant().getBytesFromAsset( 'assets/images/food_delivery.png', 100, ), ); } Future _addPolyLine(List polylineCoordinates) async { if (polylineCoordinates.isEmpty) return; PolylineId id = const PolylineId("poly"); Polyline polyline = Polyline( polylineId: id, color: Colors.blue, width: 5, points: polylineCoordinates, ); polyLines[id] = polyline; await updateCameraBounds(polylineCoordinates); } Future updateCameraBounds(List points) async { if (mapController == null || points.isEmpty) return; double minLat = points .map((e) => e.latitude) .reduce((a, b) => a < b ? a : b); double maxLat = points .map((e) => e.latitude) .reduce((a, b) => a > b ? a : b); double minLng = points .map((e) => e.longitude) .reduce((a, b) => a < b ? a : b); double maxLng = points .map((e) => e.longitude) .reduce((a, b) => a > b ? a : b); LatLngBounds bounds = LatLngBounds( southwest: LatLng(minLat, minLng), northeast: LatLng(maxLat, maxLng), ); await mapController!.animateCamera( CameraUpdate.newLatLngBounds(bounds, 80), ); } }