Files
Fondex-Driver/lib/controllers/cab_home_controller.dart
2025-12-08 23:25:00 +05:00

893 lines
31 KiB
Dart
Raw Permalink Blame History

This file contains invisible Unicode characters

This file contains invisible Unicode characters that are indistinguishable to humans but may be processed differently by a computer. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

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();
}
}
}