359 lines
11 KiB
Dart
359 lines
11 KiB
Dart
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 = OrderModel().obs;
|
|
Rx<UserModel> driverUserModel = UserModel().obs;
|
|
RxBool isLoading = true.obs;
|
|
|
|
Rx<location.LatLng> source = location.LatLng(0, 0).obs;
|
|
Rx<location.LatLng> destination = location.LatLng(0, 0).obs;
|
|
Rx<location.LatLng> driverCurrent = location.LatLng(0, 0).obs;
|
|
|
|
RxList<location.LatLng> routePoints = <location.LatLng>[].obs;
|
|
RxMap<MarkerId, Marker> markers = <MarkerId, Marker>{}.obs;
|
|
RxMap<PolylineId, Polyline> polyLines = <PolylineId, Polyline>{}.obs;
|
|
RxList<flutterMap.Marker> osmMarkers = <flutterMap.Marker>[].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<void> 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<void> 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<void> 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<void> 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<void> 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<location.LatLng>(
|
|
(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<flutterMap.Marker> 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<void> getPolyline({
|
|
required double sourceLatitude,
|
|
required double sourceLongitude,
|
|
required double destinationLatitude,
|
|
required double destinationLongitude,
|
|
bool showPickup = false,
|
|
bool showDrop = false,
|
|
}) async {
|
|
List<LatLng> 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<void> 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<void> _addPolyLine(List<LatLng> 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<void> updateCameraBounds(List<LatLng> 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),
|
|
);
|
|
}
|
|
}
|