Files
Fondex/lib/controllers/live_tracking_controller.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),
);
}
}