Initial commit

This commit is contained in:
2025-12-08 23:25:00 +05:00
commit ee5cb4ac1a
851 changed files with 115172 additions and 0 deletions

View File

@@ -0,0 +1,892 @@
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<flutterMap.Marker> osmMarkers = <flutterMap.Marker>[].obs;
StreamSubscription<DocumentSnapshot<Map<String, dynamic>>>? _driverSub;
StreamSubscription<DocumentSnapshot<Map<String, dynamic>>>? _orderDocSub;
StreamSubscription<QuerySnapshot<Map<String, dynamic>>>? _orderQuerySub;
@override
void onInit() {
getData();
super.onInit();
}
@override
void onClose() {
_driverSub?.cancel();
_orderDocSub?.cancel();
_orderQuerySub?.cancel();
super.onClose();
}
Future<void> getData() async {
_subscribeDriver();
isLoading.value = false;
}
Rx<CabOrderModel> currentOrder = CabOrderModel().obs;
Rx<UserModel> driverModel = UserModel().obs;
Rx<UserModel> ownerModel = UserModel().obs;
Future<void> 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<void> 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 (dont 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<void> clearMap() async {
await AudioPlayerService.playSound(false);
if (Constant.selectedMapType != 'osm') {
markers.clear();
polyLines.clear();
} else {
osmMarkers.clear();
routePoints.clear();
}
update();
}
Future<void> 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<void> 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<void> 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<void> 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<void> _handleOrderDoc(DocumentSnapshot<Map<String, dynamic>> 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<void> _handleOrderQuery(QuerySnapshot<Map<String, dynamic>> 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<void> 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<void> _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<void> _onDriverSnapshot(DocumentSnapshot<Map<String, dynamic>> 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 = PolylinePoints(apiKey: Constant.mapAPIKey).obs;
RxMap<PolylineId, Polyline> polyLines = <PolylineId, Polyline>{}.obs;
RxMap<String, Marker> markers = <String, Marker>{}.obs;
// BitmapDescriptor? departureIcon;
// BitmapDescriptor? destinationIcon;
// BitmapDescriptor? taxiIcon;
// Future<void> 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<void> getGooglePolyline() async {
try {
if (currentOrder.value.id == null) return;
final driverLatLng = _safeLatLngFromLocation(driverModel.value.location);
List<LatLng> 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<void> _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<LatLng> 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<void> _updateGoogleMarkers({
LatLng? driverLatLng,
LatLng? sourceLatLng,
LatLng? destinationLatLng,
}) async {
final Map<String, Marker> 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<String, Marker>
markers
..clear()
..addAll(newMarkers);
update();
}
Future<BitmapDescriptor> _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<BitmapDescriptor> _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<LatLng> 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<void> 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<location.LatLng> source = location.LatLng(21.1702, 72.8311).obs; // Start (e.g., Surat)
Rx<location.LatLng> current = location.LatLng(21.1800, 72.8400).obs; // Moving marker
Rx<location.LatLng> destination = location.LatLng(21.2000, 72.8600).obs; // Destination
void setOsmMapMarker() {
final List<flutterMap.Marker> 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<void> 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<location.LatLng> routePoints = <location.LatLng>[].obs;
Future<void> 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();
}
}
}