import 'dart:async'; import 'dart:convert'; import 'dart:io'; import 'dart:math'; import 'dart:math' as math; import 'package:sefer_driver/controller/home/captin/behavior_controller.dart'; import 'package:sefer_driver/controller/home/captin/home_captain_controller.dart'; import 'package:sefer_driver/views/widgets/mydialoug.dart'; import 'package:bubble_head/bubble.dart'; import 'package:flutter/material.dart'; import 'package:geolocator/geolocator.dart'; import 'package:get/get.dart'; import 'package:google_maps_flutter/google_maps_flutter.dart'; import 'package:google_polyline_algorithm/google_polyline_algorithm.dart'; import 'package:url_launcher/url_launcher.dart'; import '../../../constant/api_key.dart'; import '../../../constant/box_name.dart'; import '../../../constant/colors.dart'; import '../../../constant/links.dart'; import '../../../constant/table_names.dart'; import '../../../main.dart'; import '../../../print.dart'; import '../../../views/Rate/rate_passenger.dart'; import '../../../views/home/Captin/home_captain/home_captin.dart'; import '../../firebase/firbase_messge.dart'; import '../../functions/crud.dart'; import '../../functions/encrypt_decrypt.dart'; import '../../functions/location_controller.dart'; import '../../functions/tts.dart'; class MapDriverController extends GetxController { bool isLoading = true; final formKey1 = GlobalKey(); final formKey2 = GlobalKey(); final formKeyCancel = GlobalKey(); final messageToPassenger = TextEditingController(); final sosEmergincyNumberCotroller = TextEditingController(); final cancelTripCotroller = TextEditingController(); List data = []; List dataDestination = []; LatLngBounds? boundsData; BitmapDescriptor carIcon = BitmapDescriptor.defaultMarker; BitmapDescriptor passengerIcon = BitmapDescriptor.defaultMarker; BitmapDescriptor startIcon = BitmapDescriptor.defaultMarker; BitmapDescriptor endIcon = BitmapDescriptor.defaultMarker; final List polylineCoordinates = []; final List polylineCoordinatesDestination = []; List polyLines = []; List polyLinesDestination = []; Set markers = {}; late String passengerLocation; late String passengerDestination; late String step0; late String step1; late String step2; late String step3; late String step4; late String passengerWalletBurc; late String timeOfOrder; late String duration; late String totalCost; late String distance; late String passengerName; late String passengerEmail; late String totalPricePassenger; late String passengerPhone; late String rideId; late String isHaveSteps; late String paymentAmount; late String paymentMethod; late String passengerId; late String driverId; late String tokenPassenger; late String durationToPassenger; late String walletChecked; late String direction; late String durationOfRideValue; late String status; int timeWaitingPassenger = 5; //5 miniute bool isPassengerInfoWindow = false; bool isBtnRideBegin = false; bool isArrivedSend = true; bool isdriverWaitTimeEnd = false; bool isRideFinished = false; bool isRideStarted = false; bool isPriceWindow = false; double passengerInfoWindowHeight = Get.height * .38; double driverEndPage = 100; double progress = 0; double progressToPassenger = 0; double progressInPassengerLocationFromDriver = 0; bool isRideBegin = false; int progressTimerToShowPassengerInfoWindowFromDriver = 25; int remainingTimeToShowPassengerInfoWindowFromDriver = 25; int remainingTimeToPassenger = 60; int remainingTimeInPassengerLocatioWait = 60; bool isDriverNearPassengerStart = false; GoogleMapController? mapController; late LatLng myLocation; int remainingTimeTimerRideBegin = 60; String stringRemainingTimeRideBegin = ''; String stringRemainingTimeRideBegin1 = ''; double progressTimerRideBegin = 0; late Timer timer; String? mapAPIKEY; final zones = []; String canelString = 'yet'; LatLng latLngPassengerLocation = LatLng(0, 0); late LatLng latLngPassengerDestination = LatLng(0, 0); List> routeSteps = []; String currentInstruction = ""; int currentStepIndex = 0; @override void onClose() { print("--- Controller is closing. Cleaning up listener. ---"); // This will stop the listener when you leave the page _posSub?.cancel(); _navigationTimer?.cancel(); // It's also good practice to dispose the map controller mapController?.dispose(); super.onClose(); } void onMapCreated(GoogleMapController controller) async { myLocation = Get.find().myLocation; // myLocation = myLocation; mapController = controller; controller.getVisibleRegion(); // LatLngBounds bounds = await controller.getVisibleRegion(); controller.animateCamera( CameraUpdate.newLatLng(Get.find().myLocation), ); update(); // Set up a timer or interval to trigger the marker update every 3 seconds. timer = Timer.periodic(const Duration(seconds: 1), (_) { updateMarker(); }); } void changeStatusDriver() { status = 'On'; update(); } void changeDriverEndPage() { remainingTimeTimerRideBegin < 60 ? driverEndPage = 160 : 100; update(); } takeSnapMap() { mapController!.takeSnapshot(); } @override void dispose() { mapController!.dispose(); super.dispose(); } Future openGoogleMapFromDriverToPassenger() async { var endLat = latLngPassengerLocation.latitude; var endLng = latLngPassengerLocation.longitude; var startLat = Get.find().myLocation.latitude; var startLng = Get.find().myLocation.longitude; String url = 'https://www.google.com/maps/dir/$startLat,$startLng/$endLat,$endLng/&directionsmode=driving'; if (await canLaunchUrl(Uri.parse(url))) { await launchUrl(Uri.parse(url)); } else { throw 'Could not launch google maps'; } } void clearPolyline() { polyLines = []; polyLinesDestination = []; polylineCoordinates.clear(); polylineCoordinatesDestination.clear(); update(); } void changeRideToBeginToPassenger() { isRideBegin = true; passengerInfoWindowHeight = Get.height * .22; update(); } cancelTripFromDriverAfterApplied() async { if (formKeyCancel.currentState!.validate()) { box.write(BoxName.statusDriverLocation, 'off'); Get.find().sendNotificationToDriverMAP( "Cancel Trip from driver", "Trip Cancelled from driver. We are looking for a new driver. Please wait." .tr, tokenPassenger, [], 'cancel.wav', ); await CRUD().post( link: "${AppLink.seferCairoServer}/ride/rides/update.php", payload: { "id": (rideId).toString(), // Convert to String "status": 'CancelFromDriverAfterApply' }); CRUD().postFromDialogue( link: '${AppLink.seferCairoServer}/driver_order/add.php', payload: { 'driver_id': box.read(BoxName.driverID).toString(), // box.read(BoxName.driverID).toString(), 'order_id': (rideId).toString(), 'status': 'CancelFromDriverAfterApply' }); await CRUD().post( link: "${AppLink.seferCairoServer}/ride/cancelRide/addCancelTripFromDriverAfterApplied.php", payload: { "order_id": (rideId).toString(), "driver_id": box.read(BoxName.driverID).toString(), "status": 'reject After Applied', "notes": (cancelTripCotroller.text).toString() }); if (AppLink.endPoint != AppLink.seferCairoServer) { CRUD() .post(link: "${AppLink.endPoint}/ride/rides/update.php", payload: { "id": (rideId).toString(), // Convert to String "status": 'CancelFromDriverAfterApply' }); CRUD().postFromDialogue( link: '${AppLink.endPoint}/driver_order/add.php', payload: { 'driver_id': box.read(BoxName.driverID).toString(), // box.read(BoxName.driverID).toString(), 'order_id': (rideId).toString(), 'status': 'CancelFromDriverAfterApply' }); CRUD().post( link: "${AppLink.endPoint}/ride/cancelRide/addCancelTripFromDriverAfterApplied.php", payload: { "order_id": (rideId).toString(), "driver_id": box.read(BoxName.driverID).toString(), "status": 'reject After Applied', "notes": (cancelTripCotroller.text).toString() }); } sql.insertData({ 'order_id': (rideId), 'created_at': DateTime.now().toString(), 'driver_id': box.read(BoxName.driverID).toString(), }, TableName.driverOrdersRefuse); box.write(BoxName.rideStatus, 'Cancel'); Log.print('rideStatus from map 240 : ${box.read(BoxName.rideStatus)}'); Get.find().getRefusedOrderByCaptain(); Get.offAll(() => HomeCaptain()); } } void startTimerToShowPassengerInfoWindowFromDriver() async { if (box.read(BoxName.rideStatus) == 'Begin') { Log.print('rideStatus from map 248 : ${box.read(BoxName.rideStatus)}'); isPassengerInfoWindow = false; } else { isPassengerInfoWindow = true; for (int i = 0; i <= int.parse(durationToPassenger); i++) { await Future.delayed(const Duration(seconds: 1)); progressToPassenger = i / int.parse(durationToPassenger); remainingTimeToPassenger = int.parse(durationToPassenger) - i; if (remainingTimeToPassenger == 0) { isBtnRideBegin = true; update(); } int minutes = (remainingTimeToPassenger / 60).floor(); int seconds = remainingTimeToPassenger % 60; stringRemainingTimeToPassenger = '$minutes:${seconds.toString().padLeft(2, '0')}'; update(); } } // update(); // startTimerToShowDriverToPassengerDuration(); } String stringRemainingTimeToPassenger = ''; String stringRemainingTimeWaitingPassenger = ''; void startTimerToShowDriverWaitPassengerDuration() async { for (int i = 0; i <= timeWaitingPassenger * 60; i++) { await Future.delayed(const Duration(seconds: 1)); progressInPassengerLocationFromDriver = i / (timeWaitingPassenger * 60); remainingTimeInPassengerLocatioWait = (timeWaitingPassenger * 60) - i; if (isRideBegin == true) { remainingTimeInPassengerLocatioWait = 0; update(); } if (remainingTimeInPassengerLocatioWait == 0) { isdriverWaitTimeEnd = true; update(); } 'remainingTimeInPassengerLocatioWait $remainingTimeInPassengerLocatioWait'; int minutes = (remainingTimeInPassengerLocatioWait / 60).floor(); int seconds = remainingTimeInPassengerLocatioWait % 60; stringRemainingTimeWaitingPassenger = '$minutes:${seconds.toString().padLeft(2, '0')}'; update(); } } void driverGoToPassenger() async { changeRideToBeginToPassenger(); box.write(BoxName.rideStatus, 'Applied'); Log.print('rideStatus from map 304 : ${box.read(BoxName.rideStatus)}'); update(); await CRUD().post( link: "${AppLink.seferCairoServer}/ride/rides/update.php", payload: { 'id': (rideId), 'driverGoToPassengerTime': DateTime.now().toString(), 'status': 'Applied' }); if (AppLink.endPoint != AppLink.seferCairoServer) { CRUD().post(link: "${AppLink.endPoint}/ride/rides/update.php", payload: { 'id': (rideId), 'driverGoToPassengerTime': DateTime.now().toString(), 'status': 'Applied' }); } // Get.find().changeToAppliedRide('Applied'); Get.find().sendNotificationToDriverMAP( 'Driver Is Going To Passenger'.tr, box.read(BoxName.nameDriver).toString(), //todo name driver tokenPassenger, [], 'start.wav'); } bool isSocialPressed = false; driverCallPassenger() async { String scam = await getDriverScam(); if (scam != 'failure') { if (int.parse(scam) > 3) { box.write(BoxName.statusDriverLocation, 'on'); Get.find().stopLocationUpdates(); await CRUD().post(link: AppLink.addNotificationCaptain, payload: { 'driverID': box.read(BoxName.driverID), 'title': 'scams operations'.tr, 'body': 'you have connect to passengers and let them cancel the order'.tr, }); } else if (isSocialPressed == true) { box.write(BoxName.statusDriverLocation, 'off'); await CRUD().post(link: AppLink.addDriverScam, payload: { 'driverID': box.read(BoxName.driverID), 'passengerID': passengerId, 'rideID': rideId, 'isDriverCalledPassenger': '$isSocialPressed' }); } } } Future getDriverScam() async { var res = await CRUD().post(link: AppLink.getDriverScam, payload: { 'driverID': box.read(BoxName.driverID), }); if (res == 'failure') { box.write(BoxName.statusDriverLocation, 'off'); return '0'; } var d = jsonDecode(res); return d['message'][0]['count']; } void startRideFromStartApp() { // if (box.read(BoxName.rideStatus) == 'Begin') { changeRideToBeginToPassenger(); isPassengerInfoWindow = false; isRideStarted = true; isRideFinished = false; remainingTimeInPassengerLocatioWait = 0; timeWaitingPassenger = 0; box.write(BoxName.statusDriverLocation, 'on'); update(); // } // rideIsBeginPassengerTimer(); } Position? currentPosition; startRideFromDriver() async { double _distance = await calculateDistanceBetweenDriverAndPassengerLocation(); if (_distance < 400) { changeRideToBeginToPassenger(); isPassengerInfoWindow = false; isRideStarted = true; isRideFinished = false; remainingTimeInPassengerLocatioWait = 0; timeWaitingPassenger = 0; box.write(BoxName.statusDriverLocation, 'on'); // // ابدأ التتبع الحيّ لخطوات الملاحة await startListeningStepNavigation(); // box.write(BoxName.rideStatus, 'Begin'); // // todo ride details // Get.find().changeToAppliedRide('Begin'); box.write(BoxName.rideStatus, 'Begin'); Log.print('rideStatus from map 399 : ${box.read(BoxName.rideStatus)}'); // Get.find().update(); update(); await CRUD().post(link: AppLink.updateRides, payload: { 'id': (rideId), 'rideTimeStart': DateTime.now().toString(), 'status': 'Begin', }); CRUD().post(link: AppLink.addDriverOrder, payload: { 'driver_id': box.read(BoxName.driverID).toString(), 'order_id': (rideId).toString(), 'status': 'Begin' }); Get.find().sendNotificationToDriverMAP( 'Trip is Begin'.tr, box.read(BoxName.nameDriver).toString(), tokenPassenger, [], 'start.wav'); rideIsBeginPassengerTimer(); update(); } else { Get.back(); MyDialog().getDialog('Your are far from passenger location'.tr, 'go to your passenger location before\nPassenger cancel trip'.tr, () { Get.back(); }); } } calculateDistanceInMeter(LatLng prev, LatLng current) async { double distance2 = Geolocator.distanceBetween( prev.latitude, prev.longitude, current.latitude, current.longitude, ); return distance2; } double speedoMeter = 0; void updateLocation() async { try { for (var i = 0; i < remainingTimeTimerRideBegin; i++) { await Future.delayed(const Duration(seconds: 1)); mapController!.animateCamera( CameraUpdate.newCameraPosition( CameraPosition( bearing: Get.find().heading, target: myLocation, zoom: 17, // Adjust zoom level as needed ), ), ); // }); update(); } // Stop listening after ride finishes if (!isRideBegin) {} } catch (error) { debugPrint('Error listening to GPS: $error'); // Handle GPS errors gracefully } // Periodically call updateLocation again await Future.delayed(const Duration(seconds: 1)); updateLocation(); } calculateDistanceBetweenDriverAndPassengerLocation() async { Get.put(LocationController()); var res = await CRUD().get( link: AppLink.getLatestLocationPassenger, payload: {'rideId': (rideId)}); if (res != 'failure') { var passengerLatestLocationString = jsonDecode(res)['message']; double distance2 = Geolocator.distanceBetween( double.parse(passengerLatestLocationString[0]['lat'].toString()), double.parse(passengerLatestLocationString[0]['lng'].toString()), Get.find().myLocation.latitude, Get.find().myLocation.longitude, ); return distance2; } else { double distance2 = Geolocator.distanceBetween( latLngPassengerLocation.latitude, latLngPassengerLocation.longitude, Get.find().myLocation.latitude, Get.find().myLocation.longitude, ); return distance2; } } addWaitingTimeCostFromPassengerToDriverWallet() async { double distance2 = await calculateDistanceBetweenDriverAndPassengerLocation(); if (distance2 > 60) { MyDialog().getDialog('Your are far from passenger location'.tr, 'go to your passenger location before\nPassenger cancel trip'.tr, () { Get.back(); }); } else { double costOfWaiting5Minute = box.read(BoxName.countryCode) == 'Egypt' ? (distanceBetweenDriverAndPassengerWhenConfirm * .08) + (5 * 1) : (distanceBetweenDriverAndPassengerWhenConfirm * .06) + (5 * .06); //for Eygpt other like jordan .06 per minute await CRUD().post(link: AppLink.updateRides, payload: { 'id': (rideId), 'rideTimeStart': DateTime.now().toString(), 'status': 'CancelAfterWait', }); CRUD().post(link: AppLink.addDriverOrder, payload: { 'driver_id': box.read(BoxName.driverID).toString(), 'order_id': (rideId).toString(), 'status': 'CancelAfterWait' }); if (AppLink.endPoint != AppLink.seferCairoServer) { CRUD().post(link: "${AppLink.endPoint}/rides/update.php", payload: { 'id': (rideId), 'rideTimeStart': DateTime.now().toString(), 'status': 'CancelAfterWait', }); CRUD().post(link: "${AppLink.endPoint}/rides/update.php", payload: { 'driver_id': box.read(BoxName.driverID).toString(), 'order_id': (rideId).toString(), 'status': 'CancelAfterWait' }); } var paymentTokenWait = await generateTokenDriver(costOfWaiting5Minute.toString()); var res = await CRUD().post(link: AppLink.addDrivePayment, payload: { 'rideId': (rideId), 'amount': (costOfWaiting5Minute.toString()), 'payment_method': 'wait-cancel', 'passengerID': (passengerId), 'token': paymentTokenWait, 'driverID': box.read(BoxName.driverID).toString(), }); var paymentTokenWait1 = await generateTokenDriver(costOfWaiting5Minute.toString()); var res1 = await CRUD().post(link: AppLink.addDriversWalletPoints, payload: { 'paymentID': 'rideId${(rideId)}', 'amount': (costOfWaiting5Minute).toStringAsFixed(0), 'paymentMethod': 'wait', 'token': paymentTokenWait1, 'driverID': box.read(BoxName.driverID).toString(), }); if (res != 'failure') { Get.snackbar( 'You will get cost of your work for this trip'.tr, '${'you gain'.tr} $costOfWaiting5Minute \$${' in your wallet'.tr}', backgroundColor: AppColor.deepPurpleAccent, ); } var paymentTokenWaitPassenger1 = await generateTokenPassenger((costOfWaiting5Minute * -1).toString()); await CRUD().post(link: AppLink.addPassengersWallet, payload: { 'passenger_id': (passengerId), 'balance': (costOfWaiting5Minute * -1).toString(), 'token': paymentTokenWaitPassenger1, }); box.write(BoxName.statusDriverLocation, 'off'); Get.offAll(HomeCaptain()); } } Future finishRideFromDriver() async { // double distanceToDestination = Geolocator.distanceBetween( // latLngPassengerDestination.latitude, // latLngPassengerDestination.longitude, // Get.find().myLocation.latitude, // Get.find().myLocation.longitude, // ); final originalDistanceM = double.parse(distance.toString()) * 1000; // 2. احسب المسافة التي قطعها السائق حتى الآن final movedDistanceM = Geolocator.distanceBetween( Get.find().myLocation.latitude, Get.find().myLocation.longitude, latLngPassengerLocation.latitude, latLngPassengerLocation.longitude, ); // originalDistanceM - distanceToDestination; // 3. عتبة ثلث المسافة final oneThirdDistanceM = originalDistanceM / 5; // Logging للتتبع Log.print('originalDistanceM: $originalDistanceM'); // Log.print('distanceToDestinationM: $distanceToDestination'); Log.print('movedDistanceM: $movedDistanceM'); Log.print('oneThirdDistanceM: $oneThirdDistanceM'); // 4. إذا لم يقطع السائق ثلث المسافة، نعرض التأكيد if (movedDistanceM > oneThirdDistanceM) { MyDialog().getDialog( 'Are you sure to exit ride?'.tr, '', () { Get.back(); finishRideFromDriver1(); }, ); } ///// else { final textToSpeechController = Get.put(TextToSpeechController()); MyDialog().getDialog( "You haven't moved sufficiently!".tr, '', () => Get.back(), ); await textToSpeechController .speakText("You haven't moved sufficiently!".tr); } } String paymentToken = ''; Future generateTokenDriver(String amount) async { var res = await CRUD().postWallet(link: AppLink.addPaymentTokenDriver, payload: { 'driverID': box.read(BoxName.driverID).toString(), 'amount': amount.toString(), }); var d = (res); return d['message']; } String paymentTokenPassenger = ''; Future generateTokenPassenger(String amount) async { var res = await CRUD() .postWallet(link: AppLink.addPaymentTokenPassenger, payload: { 'passengerId': passengerId, 'amount': amount.toString(), }); var d = (res); return d['message']; } void finishRideFromDriver1() async { isRideFinished = true; isRideStarted = false; isPriceWindow = false; box.write(BoxName.rideStatus, 'Finished'); Log.print('rideStatus from map 664 : ${box.read(BoxName.rideStatus)}'); // Calculate totalCost more concisely if (price < 16000) { totalCost = (carType == 'Comfort' || carType == 'Mishwar Vip' || carType == 'Lady') ? '20000' : '16000'; } else if (price < double.parse(totalPricePassenger)) { totalCost = totalPricePassenger; } else { totalCost = (carType == 'Comfort' || carType == 'Mishwar Vip' || carType == 'Lady') ? price.toStringAsFixed(2) : totalPricePassenger; } paymentAmount = totalCost; box.write(BoxName.statusDriverLocation, 'off'); // Prepare data for API calls final nowString = DateTime.now().toString(); final basePayload = { 'id': (rideId), 'rideTimeFinish': nowString, 'status': 'Finished', 'price': totalCost, }; final driverOrderPayload = { 'order_id': (rideId.toString()), 'status': 'Finished' }; // List to hold all asynchronous operations List> futures = []; // API calls that can run in parallel futures.add(CRUD().post( link: "${AppLink.seferCairoServer}/ride/rides/update.php", payload: basePayload, )); futures.add(CRUD().post( link: "${AppLink.seferCairoServer}/ride/driver_order/update.php", payload: driverOrderPayload, )); // Wallet transactions (can potentially be parallelized if independent) if (walletChecked == 'true') { paymentToken = await generateTokenPassenger( ((-1) * double.parse(paymentAmount)).toString()); futures .add(CRUD().postWallet(link: AppLink.addPassengersWallet, payload: { 'passenger_id': (passengerId), 'balance': ((-1) * double.parse(paymentAmount)).toString(), 'token': paymentToken, })); } paymentToken = await generateTokenDriver(paymentAmount.toString()); futures.add(CRUD().postWallet(link: AppLink.addDrivePayment, payload: { 'rideId': (rideId), 'amount': paymentAmount, 'payment_method': walletChecked == 'true' ? "${paymentMethod}Ride" : paymentMethod, 'passengerID': (passengerId), 'token': paymentToken, 'driverID': box.read(BoxName.driverID).toString(), })); if (double.parse(passengerWalletBurc) < 0) { final paymentToken1 = await generateTokenPassenger( ((-1) * double.parse(passengerWalletBurc)).toString()); futures .add(CRUD().postWallet(link: AppLink.addPassengersWallet, payload: { 'passenger_id': (passengerId), 'token': paymentToken1, 'balance': ((-1) * double.parse(passengerWalletBurc)).toString() })); } double pointsSubtraction = double.parse(paymentAmount) * (-1) * 0.1; final paymentToken2 = await generateTokenDriver((pointsSubtraction).toStringAsFixed(0)); futures .add(CRUD().postWallet(link: AppLink.addDriversWalletPoints, payload: { 'paymentID': 'rideId${(rideId)}', 'amount': (pointsSubtraction).toStringAsFixed(0), 'paymentMethod': paymentMethod, 'token': paymentToken2, 'driverID': box.read(BoxName.driverID).toString(), })); // Wait for all independent API calls to complete await Future.wait(futures); Get.put(DriverBehaviorController()).sendSummaryToServer(driverId, rideId); // Send notification (this likely depends on previous steps) Get.find().sendNotificationToDriverMAP( "Driver Finish Trip".tr, '${'you will pay to Driver'.tr} $paymentAmount \$', tokenPassenger, [ box.read(BoxName.driverID), rideId, box.read(BoxName.tokenDriver), paymentAmount.toString() ], 'ding.wav'); // Navigate to the next screen (likely depends on previous steps being done) Get.to(() => RatePassenger(), arguments: { 'passengerId': passengerId, 'rideId': rideId, 'price': paymentAmount.toString(), 'walletChecked': walletChecked }); } // void finishRideFromDriver1() async { // // if (carType != 'Comfort' || carType != 'Free Ride') { // isRideFinished = true; // isRideStarted = false; // isPriceWindow = false; // box.write(BoxName.rideStatus, 'Finished'); // // Get.find().changeToAppliedRide('Finished'); // // Get.find().update(); // totalCost = price < 20 // ? carType != 'Comfort' && carType != 'Mishwar Vip' && carType != 'Lady' // ? '20' // : '30' // : price < double.parse(totalPricePassenger) // ? totalPricePassenger // : carType != 'Comfort' && // carType != 'Mishwar Vip' && // carType != 'Lady' // ? totalPricePassenger // : price.toStringAsFixed(2); // paymentAmount = totalCost; // box.write(BoxName.statusDriverLocation, 'off'); // // changeRideToBeginToPassenger(); // await CRUD().post( // link: "${AppLink.seferCairoServer}/ride/rides/update.php", // payload: { // 'id': rideId, // 'rideTimeFinish': DateTime.now().toString(), // 'status': 'Finished', // 'price': totalCost, // }); // CRUD().post( // link: "${AppLink.seferCairoServer}/ride/driver_order/update.php", // payload: { // // 'driver_id': box.read(BoxName.driverID).toString(), // 'order_id': rideId.toString(), // 'status': 'Finished' // }); // if (AppLink.endPoint != AppLink.seferCairoServer) { // CRUD().post( // link: "${AppLink.endPoint}/ride/rides/update.php", // payload: { // 'id': rideId, // 'rideTimeFinish': DateTime.now().toString(), // 'status': 'Finished', // 'price': totalCost, // }, // ); // CRUD().post( // link: "${AppLink.endPoint}/ride/driver_order/update.php", // payload: { // // 'driver_id': box.read(BoxName.driverID).toString(), // 'order_id': rideId.toString(), // 'status': 'Finished' // }); // } // if (walletChecked == 'true') { // paymentToken = await generateTokenPassenger( // ((-1) * double.parse(paymentAmount)).toString()); // await CRUD().post(link: AppLink.addPassengersWallet, payload: { // 'passenger_id': passengerId, // 'balance': ((-1) * double.parse(paymentAmount)).toString(), // 'token': paymentToken, // }); // } // paymentToken = await generateTokenDriver(paymentAmount.toString()); // await CRUD().post(link: AppLink.addDrivePayment, payload: { // 'rideId': rideId, // 'amount': paymentAmount, // 'payment_method': // walletChecked == 'true' ? "${paymentMethod}Ride" : paymentMethod, // 'passengerID': passengerId, // 'token': paymentToken, // 'driverID': box.read(BoxName.driverID).toString(), // }); // if (double.parse(passengerWalletBurc) < 0) { // // for zero passenger // var paymentToken1 = await generateTokenPassenger( // ((-1) * double.parse(passengerWalletBurc)).toString()); // await CRUD().post(link: AppLink.addPassengersWallet, payload: { // 'passenger_id': passengerId, // 'token': paymentToken1, // 'balance': ((-1) * double.parse(passengerWalletBurc)).toString() // }); // } // double pointsSubtraction = 0; // pointsSubtraction = // double.parse(paymentAmount) * (-1) * .08; //for 300 from 3000 // var paymentToken2 = // await generateTokenDriver((pointsSubtraction).toStringAsFixed(0)); // var res = await CRUD().post(link: AppLink.addDriversWalletPoints, payload: { // 'paymentID': 'rideId$rideId', // 'amount': (pointsSubtraction).toStringAsFixed(0), // 'paymentMethod': paymentMethod, // 'token': paymentToken2, // 'driverID': box.read(BoxName.driverID).toString(), // }); // Future.delayed(const Duration(milliseconds: 300)); // Get.find().sendNotificationToDriverMAP( // "Driver Finish Trip".tr, // '${'you will pay to Driver'.tr} $paymentAmount \$', // tokenPassenger, // [ // box.read(BoxName.driverID), // rideId, // box.read(BoxName.tokenDriver), // // carType == 'Comfort' || carType == 'Mishwar Vip' // // ? price.toStringAsFixed(2) // // : totalPassenger // paymentAmount.toString() // ], // 'ding.wav'); // Get.to(() => RatePassenger(), arguments: { // 'passengerId': passengerId, // 'rideId': rideId, // 'price': paymentAmount.toString(), //price // 'walletChecked': walletChecked // }); // // Get.back(); // } void cancelCheckRideFromPassenger() async { var res = await CRUD().get( link: "${AppLink.endPoint}/ride/driver_order/getOrderCancelStatus.php", payload: { 'order_id': (rideId), }); //.then((value) { var response = jsonDecode(res); canelString = response['data']['status']; update(); if (canelString == 'Cancel') { remainingTimeTimerRideBegin = 0; remainingTimeToShowPassengerInfoWindowFromDriver = 0; remainingTimeToPassenger = 0; isRideStarted = false; isRideFinished = false; isPassengerInfoWindow = false; clearPolyline(); update(); MyDialog().getDialog( 'Order Cancelled'.tr, 'Order Cancelled by Passenger'.tr, () { Get.offAll(HomeCaptain()); }, ); } } int rideTimerFromBegin = 0; double price = 0; DateTime currentTime = DateTime.now(); /// أثناء الرحلة: نعرض السعر لحظياً بدون مضاعفة العمولة في كل ثانية. /// - نستخدم سعر الدقيقة حسب الوقت، مع قواعد الرحلات البعيدة: /// >25كم أو >35كم => دقيقة = 600، سقف 60 دقيقة، ومع >35كم عفو 10 دقائق. /// - سرعة طويلة: لو المسافة المخططة > 40كم نستخدم 2600 ل.س/كم للـ Speed، //// ونطبق نفس نسبة التخفيض على Comfort/Electric/Van. /// - نضيف فقط "الزيادة" فوق التسعيرة المقتبسة (وقت زائد + كم زائد). /// - نعكس العمولة kazán مرة واحدة على الزيادة (وليس كل ثانية). void rideIsBeginPassengerTimer() async { // === مراجع عامة === final hc = Get.find(); final loc = Get.find(); // أسعار/كم من السيرفر final double perKmSpeedBase = hc.speedPrice; // مثال: 2900 final double perKmComfortRaw = hc.comfortPrice; // مثال: 3600 final double perKmDelivery = hc.deliveryPrice; // للدليفري final double perKmVanRaw = hc.familyPrice; // فان const double electricUpliftKm = 400; // كهرباء = كومفورت + 400/كم final double perKmElectricRaw = perKmComfortRaw + electricUpliftKm; // أسعار الدقيقة (ل.س/دقيقة) final double perMinNature = hc.naturePrice; // طبيعي final double perMinLate = hc.latePrice; // ليل final double perMinHeavy = hc.heavyPrice; // ذروة // Long/بعيد const double longSpeedThresholdKm = 40.0; const double longSpeedPerKm = 2600.0; const double mediumDistThresholdKm = 25.0; // >25كم const double longDistThresholdKm = 35.0; // >35كم const double longTripPerMin = 600.0; const int minuteCapMedium = 60; // سقف 60 دقيقة const int minuteCapLong = 60; // سقف 60 دقيقة const int freeMinutesLong = 10; // عفو 10 دقائق عند >35كم const double extraReduction100 = 0.07; // +7% فوق تخفيض >40كم عند >100كم const double maxReductionCap = 0.35; // سقف إجمالي للتخفيض // ——— الأساسات المقتبسة من شاشة التسعير ——— final double basePassengerQuote = double.tryParse(totalCost) ?? 0.0; // الدقائق المقتبسة (من نفس المصدر الذي تستخدمه مسبقًا) final int quotedMinutes = int.tryParse(duration) ?? int.tryParse(durationOfRideValue) ?? 0; // ——— المسافة المخططة من Notification/OnInit ——— // حاول أولًا من متغيّر الرحلة (تمريره عند onInit من النوتيفيكيشن): double plannedKm = 0.0; try { // أمثلة أسماء: غيّر وفق ما عندك // 1) إن كانت محفوظة في هذا الكنترولر: this.plannedDistanceKm plannedKm = (int.parse(distance) as num).toDouble(); } catch (_) {} final double startKm = loc.totalDistance; if (plannedKm <= 0) { // fallback ثالث: خذ المسافة الحيّة وقت البدء كأساس plannedKm = (startKm > 0) ? startKm : 0.0; } // ——— سياق المطار (لا نضيف 20,000 هنا مرة ثانية) ——— bool _isAirport(String s) => s.toLowerCase().contains('airport') || s.contains('مطار') || s.contains('المطار'); final bool airportCtx = _isAirport(startNameLocation) || _isAirport(endNameLocation); // عمولة الراكب (kazan ٪) final double kazanPct = (double.tryParse(kazan) ?? 0.0) / 100.0; // ——— أدوات ——— double _perMinuteByTime(DateTime now) { final h = now.hour; if (airportCtx) return perMinLate; // مطار = دقيقة ليل (مثل منطقك القديم) if (h >= 21 || h < 1) return perMinLate; // ليل if (h >= 14 && h <= 17) return perMinHeavy; // ذروة return perMinNature; // طبيعي } bool _isLongSpeed(double km) => km > longSpeedThresholdKm; double _distanceReductionPct(double km) { double r40 = 0.0; if (perKmSpeedBase > 0) { r40 = (1.0 - (longSpeedPerKm / perKmSpeedBase)) .clamp(0.0, maxReductionCap); } if (km > 100.0) return (r40 + extraReduction100).clamp(0.0, maxReductionCap); if (km > 40.0) return r40; return 0.0; } // فلترة اهتزاز GPS const double jitterMeters = 10.0; double lastKmForNoise = startKm; // تحديث كل 5 ثواني final int loopCapSec = (quotedMinutes + 3600) * 60; // أمان for (int sec = 0; sec <= loopCapSec; sec += 5) { await Future.delayed(const Duration(seconds: 5)); final now = DateTime.now(); // كم حي (مع فلترة الاهتزاز) double liveKm = loc.totalDistance; final double deltaMeters = (liveKm - lastKmForNoise) * 1000.0; if (deltaMeters < jitterMeters) { liveKm = lastKmForNoise; } else { lastKmForNoise = liveKm; } // كم معتمد لقواعد التسعير final double plannedForRulesKm = (plannedKm > 0) ? plannedKm : liveKm; // LongSpeed لسعر/كم Speed final bool isLong = _isLongSpeed(plannedForRulesKm); // نسبة التخفيض للفئات الأخرى (Comfort/Electric/Van) في كل الأوقات final double reductionPct = _distanceReductionPct(plannedForRulesKm); // سعر/كم فعّال لكل فئة final double perKmSpeed = isLong ? longSpeedPerKm : perKmSpeedBase; final double perKmBalash = (perKmSpeed - 500).clamp(0, double.infinity); final double perKmComfort = (perKmComfortRaw * (1.0 - reductionPct)).clamp(0, double.infinity); final double perKmElectric = (perKmElectricRaw * (1.0 - reductionPct)).clamp(0, double.infinity); final double perKmVan = (perKmVanRaw * (1.0 - reductionPct)).clamp(0, double.infinity); final double perKmForType = () { switch (carType) { case 'Speed': return perKmSpeed; case 'Awfar Car': return perKmBalash; case 'Comfort': case 'Mishwar Vip': case 'RayehGaiComfort': case 'Lady': return perKmComfort; case 'Electric': return perKmElectric; case 'Van': return perKmVan; case 'Delivery': return perKmDelivery; default: return perKmSpeed; } }(); // قواعد الدقيقة (تطبّق على كل الأوقات بما فيها المطار) double perMinEff = _perMinuteByTime(now); int capMinutes = 1 << 30; // لا سقف افتراضيًا int freeMinutes = 0; if (plannedForRulesKm > longDistThresholdKm) { perMinEff = longTripPerMin; // 600/د capMinutes = minuteCapLong; // سقف 60 freeMinutes = freeMinutesLong; // عفو 10 } else if (plannedForRulesKm > mediumDistThresholdKm) { perMinEff = longTripPerMin; // 600/د capMinutes = minuteCapMedium; // سقف 60 } // دقائق منقضية final int elapsedMinutes = (sec ~/ 60); // دقائق إضافية فوق المقتبس (مع السقف/العفو) int extraMinutes = 0; final int rawExtra = elapsedMinutes - quotedMinutes - freeMinutes; if (rawExtra > 0) { extraMinutes = rawExtra; if (capMinutes < (1 << 30)) { final int maxBill = (capMinutes - quotedMinutes - freeMinutes); if (extraMinutes > maxBill) extraMinutes = maxBill.clamp(0, extraMinutes); } } // كيلومترات إضافية: إن لم يكن لدينا plannedKm «حقيقي»، نقيس على لحظة البدء double extraKm = liveKm - plannedForRulesKm; if (plannedKm <= 0) { extraKm = (liveKm - startKm); } if (extraKm < 0) extraKm = 0; // زيادات قبل العمولة (بدون إضافة مطار ثابتة — فهي مضافة في التسعير الأولي) final double extraBefore = (extraMinutes * perMinEff) + (extraKm * perKmForType); // السعر المعروض = المقتبس + الزيادات * (1 + kazan) price = basePassengerQuote + (extraBefore * (1.0 + kazanPct)); // تحديث واجهة speed = loc.speed * 3.6; remainingTimeTimerRideBegin = (quotedMinutes * 60 - sec).clamp(0, 1 << 30); progressTimerRideBegin = (quotedMinutes == 0) ? 0 : (sec / (quotedMinutes * 60)).clamp(0.0, 1.0); remainingTimeTimerRideBegin < 60 ? driverEndPage = 160 : 100; updateMarker(); update(); } } // int durationOfRide = int.parse(durationOfRideValue); // double latePrice = Get.find().latePrice; // update(); // int infinity = 40000; // if (carType == 'Comfort' || // carType == 'Mishwar Vip' || // carType == 'RayehGaiComfort') { // durationOfRide = infinity; // update(); // } // price = double.parse(totalCost); // for (int i = 0; i <= durationOfRide; i++) { // await Future.delayed(const Duration(seconds: 1)); // recentDistanceToDash = Get.find().totalDistance; // // rideTimerFromBegin = i; // if (int.parse(duration) + 300 > i) { // price = double.parse(totalCost); // } else { // if (startNameLocation.toLowerCase().contains('airport') || // endNameLocation.toLowerCase().contains('airport') || // startNameLocation.contains('مطار') || // startNameLocation.contains('المطار') || // endNameLocation.contains('مطار') || // endNameLocation.contains('المطار')) { // price = carType == 'Comfort' // || carType == 'Free Ride' // ? price + ((i ~/ 60) - int.parse(duration)) * latePrice // : carType == 'Lady' // ? price + ((i ~/ 60) - int.parse(duration)) * latePrice // : carType == 'RayehGaiComfort' // ? (i ~/ 60) * latePrice + // (recentDistanceToDash * // Get.find().comfortPrice) // : (i ~/ 60) * latePrice + // (recentDistanceToDash * // Get.find().mashwariPrice); // } else if (currentTime.hour >= 21 && currentTime.hour < 0) { // price = carType == 'Comfort' // || carType == 'Free Ride' // ? price + ((i ~/ 60) - int.parse(duration)) * latePrice // : carType == 'Lady' // ? price + ((i ~/ 60) - int.parse(duration)) * latePrice // : carType == 'RayehGaiComfort' // ? (i ~/ 60) * latePrice + // (recentDistanceToDash * // Get.find().comfortPrice) // : (i ~/ 60) * latePrice + // (recentDistanceToDash * // Get.find().mashwariPrice); // } else if (currentTime.hour >= 1 && currentTime.hour < 5) { // if (startNameLocation.contains('club') || // startNameLocation.contains('nightclub') || // startNameLocation.contains('ديسكو') || // startNameLocation.contains('ملهى ليلي') || // startNameLocation.contains('Night club')) { // price = carType == 'Comfort' // || carType == 'Free Ride' // ? (i ~/ 60) * (latePrice + .5) * 2 + // (price) - // int.parse(duration) * (latePrice + .5) * 2 // : carType == 'Lady' // ? (i ~/ 60) * (latePrice + .5) * 2 + // (price) - // int.parse(duration) * (latePrice + .5) * 2 // : carType == 'RayehGaiComfort' // ? (i ~/ 60) * (latePrice + .5) * 2 + (price) // : (i ~/ 60) * (latePrice + .5) * 2 + (price); // } // price = carType == 'Comfort' // || carType == 'Free Ride' // ? (latePrice + 0.5) * ((i ~/ 60) - int.parse(duration)) + price // : carType == 'Lady' // ? (latePrice + 0.5) * ((i ~/ 60) - int.parse(duration)) + // price // : carType == 'RayehGaiComfort' // ? (latePrice + 0.5) * ((i ~/ 60) - int.parse(duration)) + // price // : price; // } else if (currentTime.hour >= 14 && currentTime.hour <= 17) { // price = carType == 'Comfort' // || carType == 'Free Ride' // ? Get.find().heavyPrice * // ((i ~/ 60) - int.parse(duration)) + // price - // (0.5 * int.parse(duration)) // : carType == 'Lady' // ? Get.find().heavyPrice * // ((i ~/ 60) - int.parse(duration)) + // price - // (0.5 * int.parse(duration)) // : carType == 'RayehGaiComfort' // ? (i ~/ 60) * // (Get.find().heavyPrice) + // (recentDistanceToDash * // Get.find().comfortPrice) // : (i ~/ 60) * // (Get.find().heavyPrice) + // (recentDistanceToDash * // Get.find().mashwariPrice); // } else { // price = carType == 'Comfort' // || carType == 'Free Ride' // ? (i ~/ 60) + (price) - int.parse(duration) // : carType == 'Lady' // ? (i ~/ 60) + (price) - int.parse(duration) // : carType == 'RayehGaiComfort' // ? (i ~/ 60) + // (recentDistanceToDash * // Get.find().comfortPrice) // : (i ~/ 60) + // (recentDistanceToDash * // Get.find().mashwariPrice); // } // } // // $1 for each minute + $4 for each km // price = (price * double.parse(kazan)) + price; // Add 10% tax // speed = Get.find().speed * 3.6; // progressTimerRideBegin = i / durationOfRide; // remainingTimeTimerRideBegin = durationOfRide - i; // remainingTimeTimerRideBegin < 60 ? driverEndPage = 160 : 100; // updateMarker(); // if (remainingTimeTimerRideBegin < 60) { // // to make driver available on last 2 minute in his trip // box.write(BoxName.statusDriverLocation, 'off'); // } // int minutes = (remainingTimeTimerRideBegin / 60).floor(); // int seconds = remainingTimeTimerRideBegin % 60; // stringRemainingTimeRideBegin = // '$minutes:${seconds.toString().padLeft(2, '0')}'; // int minutes1 = (i / 60).floor(); // int seconds1 = i % 60; // stringRemainingTimeRideBegin1 = // '$minutes1:${seconds1.toString().padLeft(2, '0')}'; // update(); // } // } double recentDistanceToDash = 0; double recentAngelToMarker = 0; double speed = 0; void updateMarker() async { // Remove the existing marker with the ID `MyLocation`. markers.remove(MarkerId('MyLocation')); // Add a new marker with the ID `MyLocation` at the current location of the user. LocationController locationController = Get.find(); myLocation = locationController.myLocation; markers.add( Marker( markerId: MarkerId('MyLocation'.tr), position: myLocation, draggable: true, icon: carIcon, rotation: locationController.heading, // infoWindow: const InfoWindow( // title: 'Time', // ), ), ); // Animate camera only once after updating the marker // mapController!.animateCamera( // CameraUpdate.newLatLng(myLocation), // ); update(); } void addCustomCarIcon() { ImageConfiguration config = ImageConfiguration( size: const Size(30, 35), devicePixelRatio: Get.pixelRatio); BitmapDescriptor.asset( config, 'assets/images/car.png', // mipmaps: false, ).then((value) { carIcon = value; update(); }); } void addCustomStartIcon() async { // Create the marker with the resized image ImageConfiguration config = ImageConfiguration( size: const Size(30, 30), devicePixelRatio: Get.pixelRatio); BitmapDescriptor.asset( config, 'assets/images/A.png', ).then((value) { startIcon = value; update(); }); } void addCustomEndIcon() { ImageConfiguration config = ImageConfiguration( size: const Size(25, 25), devicePixelRatio: Get.pixelRatio); BitmapDescriptor.asset( config, 'assets/images/b.png', ).then((value) { endIcon = value; update(); }); } void addCustomPassengerIcon() { ImageConfiguration config = ImageConfiguration( size: const Size(30, 30), devicePixelRatio: Get.pixelRatio // scale: 1.0, ); BitmapDescriptor.asset( config, 'assets/images/picker.png', ).then((value) { passengerIcon = value; update(); }); } var activeRouteSteps = >[]; var traveledPathPoints = []; // المسار المقطوع (رمادي) var upcomingPathPoints = []; // المسار المتبقي (أزرق/أحمر) // --- متغيرات الأيقونات والمواقع --- var heading = 0.0; // ... يمكنك إضافة أيقونات البداية والنهاية هنا // --- متغيرات قياس الأداء الذكي --- final List _performanceReadings = []; final int _readingsToCollect = 10; // اجمع 10 قراءات bool _hasMadeDecision = false; var updateInterval = 5.obs; // القيمة الافتراضية // --- متغيرات داخلية للملاحة --- var _stepBounds = []; var _stepEndPoints = []; var _allPointsForActiveRoute = []; // دالة موحّدة وقوية لجلب أي مسار Future getRoute({ required LatLng origin, required LatLng destination, required Color routeColor, }) async { // إظهار مؤشر التحميل لو رغبت // isLoading.value = true; var url = ('${AppLink.googleMapsLink}directions/json?language=ar&destination=${destination.latitude},${destination.longitude}&origin=${origin.latitude},${origin.longitude}&key=${AK.mapAPIKEY}'); var response = await CRUD().getGoogleApi(link: url, payload: {}); if (response == null || response['routes'].isEmpty) { // isLoading.value = false; // أظهر رسالة خطأ return; } _resetRouteState(); // تنظيف الحالة القديمة قبل رسم الجديد final route = response['routes'][0]; final leg = route['legs'][0]; // استخراج النقاط ورسم المسار المبدئي بالكامل final pointsString = route["overview_polyline"]["points"]; _allPointsForActiveRoute = decodePolylineToLatLng(pointsString); upcomingPathPoints.assignAll(_allPointsForActiveRoute); // استخراج خطوات الملاحة activeRouteSteps.assignAll(List>.from(leg['steps'])); _prepareStepData(activeRouteSteps); // تحديث التعليمات الأولية if (activeRouteSteps.isNotEmpty) { currentInstruction = _parseInstruction(activeRouteSteps[0]['html_instructions']); Get.find().speakText(currentInstruction); } // تحديث الكاميرا لتناسب المسار الجديد final boundsData = route["bounds"]; _fitToBounds(LatLngBounds( northeast: LatLng( boundsData['northeast']['lat'], boundsData['northeast']['lng']), southwest: LatLng( boundsData['southwest']['lat'], boundsData['southwest']['lng']), )); // isLoading.value = false; update(); // تحديث الواجهة مرة واحدة بعد كل العمليات } // الدالة التي يتم استدعاؤها من خدمة الموقع كل 5 ثوان (أو حسب الفترة المحددة) void onLocationUpdated(Position newPosition) { myLocation = LatLng(newPosition.latitude, newPosition.longitude); heading = newPosition.heading; // -->> منطق قياس الأداء يبدأ هنا <<-- final stopwatch = Stopwatch()..start(); // -->> منطق الملاحة وتحديث المسار <<-- _onLocationTick(myLocation); stopwatch.stop(); // -->> تحليل الأداء واتخاذ القرار <<-- if (!_hasMadeDecision) { _performanceReadings.add(stopwatch.elapsedMilliseconds); if (_performanceReadings.length >= _readingsToCollect) { _analyzePerformance(); _hasMadeDecision = true; } } } // ================================================================= // 3. منطق الملاحة الداخلي (Internal Navigation Logic) // ================================================================= void _onLocationTick(LatLng pos) { if (activeRouteSteps.isEmpty || currentStepIndex >= _stepBounds.length) { return; } final double dToEnd = _distanceMeters(pos, _stepEndPoints[currentStepIndex]); if (dToEnd <= 35) { // 35 متر عتبة للوصول لنهاية الخطوة _advanceStep(); } } void _advanceStep() { if (currentStepIndex >= _stepBounds.length - 1) { // وصل للنهاية currentInstruction = "لقد وصلت إلى وجهتك"; return; } currentStepIndex++; currentInstruction = _parseInstruction( activeRouteSteps[currentStepIndex]['html_instructions']); Get.find().speakText(currentInstruction); // -->> هنا يتم تحديث لون المسار <<-- _updateTraveledPath(); _fitToBounds(_stepBounds[currentStepIndex], padding: 80); // تقريب الكاميرا على الخطوة التالية update(); } void _updateTraveledPath() { // استخراج كل النقاط للخطوات التي تم اجتيازها List pointsForTraveledSteps = []; for (int i = 0; i < currentStepIndex; i++) { final stepPolyline = activeRouteSteps[i]['polyline']['points']; pointsForTraveledSteps.addAll(decodePolylineToLatLng(stepPolyline)); } traveledPathPoints.assignAll(pointsForTraveledSteps); } void _prepareStepData(List> steps) { _stepBounds.clear(); _stepEndPoints.clear(); for (final s in steps) { // 1. استخراج نقطة النهاية (الكود الحالي سليم) final end = s['end_location']; _stepEndPoints.add(LatLng( (end['lat'] as num).toDouble(), (end['lng'] as num).toDouble(), )); // 2. فك تشفير البوليلاين الخاص بالخطوة وتحويله إلى LatLng // -->> هنا تم التصحيح <<-- List pts = decodePolyline(s['polyline']['points']) .map((point) => LatLng(point[0].toDouble(), point[1].toDouble())) .toList(); // أضف نقاط البداية والنهاية إذا لم تكن موجودة في البوليلاين لضمان دقة الحدود if (pts.isNotEmpty) { final start = s['start_location']; final startLatLng = LatLng( (start['lat'] as num).toDouble(), (start['lng'] as num).toDouble()); if (pts.first != startLatLng) { pts.insert(0, startLatLng); } } _stepBounds.add(_boundsFromPoints(pts)); } } // A helper function to decode and convert the polyline string List decodePolylineToLatLng(String polylineString) { // 1. Decode the string into a list of number lists (e.g., [[lat, lng], ...]) List> decodedPoints = decodePolyline(polylineString); // 2. Map each [lat, lng] pair to a LatLng object, ensuring conversion to double List latLngPoints = decodedPoints .map((point) => LatLng(point[0].toDouble(), point[1].toDouble())) .toList(); return latLngPoints; } // ================================================================= // 4. منطق الأداء الذكي (Smart Performance Logic) // ================================================================= void _analyzePerformance() { final int sum = _performanceReadings.reduce((a, b) => a + b); final double averageTime = sum / _performanceReadings.length; if (averageTime > 1000) { // إذا كانت العملية تستغرق أكثر من ثانية _suggestOptimization(); } } void _suggestOptimization() { Get.snackbar( "تحسين أداء التطبيق", "لضمان أفضل تجربة، نقترح تعديل الإعدادات لتناسب جهازك. هل تود المتابعة؟", duration: const Duration(seconds: 15), mainButton: TextButton( child: const Text("نعم، قم بالتحسين"), onPressed: () { updateInterval.value = 8; // غير الفترة إلى 8 ثوانٍ // save setting to shared_preferences box.write(BoxName.updateInterval, 8); Get.back(); }, ), ); } // ================================================================= // 5. دوال مساعدة (Helper Functions) // ================================================================= void _resetRouteState() { activeRouteSteps.clear(); traveledPathPoints.clear(); upcomingPathPoints.clear(); _allPointsForActiveRoute.clear(); currentStepIndex = 0; } String _parseInstruction(String html) => html.replaceAll(RegExp(r'<[^>]*>'), ''); Future _fitToBounds(LatLngBounds b, {double padding = 60}) async { await mapController ?.animateCamera(CameraUpdate.newLatLngBounds(b, padding)); } double distanceBetweenDriverAndPassengerWhenConfirm = 0; getMap(String origin, destination) async { isLoading = false; update(); var url = ('${AppLink.googleMapsLink}directions/json?&language=${box.read(BoxName.lang)}&avoid=tolls|ferries&destination=$destination&origin=$origin&key=${AK.mapAPIKEY}'); var response = await CRUD().getGoogleApi(link: url, payload: {}); Log.print('response: ${response}'); data = response['routes'][0]['legs']; distanceBetweenDriverAndPassengerWhenConfirm = (data[0]['distance']['value']) / 1000; final points = decodePolyline(response["routes"][0]["overview_polyline"]["points"]); for (int i = 0; i < points.length; i++) { double lat = points[i][0].toDouble(); double lng = points[i][1].toDouble(); polylineCoordinates.add(LatLng(lat, lng)); } if (polyLines.isNotEmpty) { clearPolyline(); var polyline = Polyline( polylineId: PolylineId(response["routes"][0]["summary"]), points: polylineCoordinates, width: 10, color: AppColor.blueColor, ); polyLines.add(polyline); // rideConfirm = false; update(); } else { var polyline = Polyline( polylineId: PolylineId(response["routes"][0]["summary"]), points: polylineCoordinates, width: 10, color: AppColor.blueColor, ); // final dataBounds = response["routes"][0]["bounds"]; // updateCameraFromBoundsAfterGetMap(dataBounds); // Fit the camera to the bounds polyLines.add(polyline); // rideConfirm = false; // Define the northeast and southwest coordinates final bounds = response["routes"][0]["bounds"]; LatLng northeast = LatLng(bounds['northeast']['lat'], bounds['northeast']['lng']); LatLng southwest = LatLng(bounds['southwest']['lat'], bounds['southwest']['lng']); // Create the LatLngBounds object boundsData = LatLngBounds(northeast: northeast, southwest: southwest); // Fit the camera to the bounds var cameraUpdate = CameraUpdate.newLatLngBounds(boundsData!, 140); mapController!.animateCamera(cameraUpdate); update(); } } void checkForNextStep(LatLng currentPosition) { if (currentStepIndex >= routeSteps.length) return; final step = routeSteps[currentStepIndex]; final endLocation = step['end_location']; final endLatLng = LatLng(endLocation['lat'], endLocation['lng']); final distance = calculateDistance( currentPosition.latitude, currentPosition.longitude, endLatLng.latitude, endLatLng.longitude, ); if (distance < 50) { // 50 متر قبل النقطة currentStepIndex++; if (currentStepIndex < routeSteps.length) { currentInstruction = _parseInstruction( routeSteps[currentStepIndex]['html_instructions']); Get.isRegistered() ? Get.find().speakText(currentInstruction) : Get.put(TextToSpeechController()).speakText(currentInstruction); Log.print('Current Instruction: $currentInstruction'); update(); } } } /// Calculates the distance in meters between two latitude/longitude points. double calculateDistance(double lat1, double lon1, double lat2, double lon2) { const double earthRadius = 6371000; // meters double dLat = _degreesToRadians(lat2 - lat1); double dLon = _degreesToRadians(lon2 - lon1); double a = (sin(dLat / 2) * sin(dLat / 2)) + cos(_degreesToRadians(lat1)) * cos(_degreesToRadians(lat2)) * (sin(dLon / 2) * sin(dLon / 2)); double c = 2 * atan2(sqrt(a), sqrt(1 - a)); double distance = earthRadius * c; return distance; } double _degreesToRadians(double degrees) { return degrees * (3.1415926535897932 / 180.0); } // String _parseInstruction(String htmlInstruction) { // return htmlInstruction.replaceAll(RegExp(r'<[^>]*>'), ''); // } void checkDestinationProximity() { final distance = calculateDistance( myLocation.latitude, myLocation.longitude, latLngPassengerDestination.latitude, latLngPassengerDestination.longitude, ); if (distance < 300) { // 300 متر قبل الوجهة Get.find().sendNotificationToDriverMAP( "You are near the destination".tr, "You are near the destination".tr, tokenPassenger, [ box.read(BoxName.driverID), rideId, box.read(BoxName.tokenDriver), paymentAmount.toString() ], 'ding.wav', ); // يمكن إضافة أي إجراء آخر هنا عند الاقتراب من الوجهة } } List stepBounds = []; List stepEndPoints = []; List stepInstructions = []; StreamSubscription? _posSub; DateTime _lastCameraUpdateTs = DateTime.fromMillisecondsSinceEpoch(0); getMapDestination(String origin, destination) async { var url = ('${AppLink.googleMapsLink}directions/json?&language=${box.read(BoxName.lang)}&avoid=tolls|ferries&destination=$destination&origin=$origin&key=${AK.mapAPIKEY}'); var response = await CRUD().getGoogleApi(link: url, payload: {}); dataDestination = response['routes'][0]['legs']; final points = decodePolyline(response["routes"][0]["overview_polyline"]["points"]); polylineCoordinatesDestination.clear(); for (int i = 0; i < points.length; i++) { double lat = points[i][0].toDouble(); double lng = points[i][1].toDouble(); polylineCoordinatesDestination.add(LatLng(lat, lng)); } // الخطوات من أول leg routeSteps = List>.from(dataDestination[0]['steps']); // حضّر بيانات كل step _prepareStepData(routeSteps); currentStepIndex = 0; if (stepInstructions.isNotEmpty) { currentInstruction = _parseInstruction(stepInstructions[0]); Log.print('currentInstruction: $currentInstruction'); Get.isRegistered() ? Get.find().speakText(currentInstruction) : Get.put(TextToSpeechController()).speakText(currentInstruction); } // ارسم الـ polyline الرئيسي (مثل ما لديك) final summary = response["routes"][0]["summary"]; final polyline = Polyline( polylineId: PolylineId(summary.isEmpty ? 'route' : summary), points: polylineCoordinatesDestination, width: 10, color: AppColor.redColor, ); polyLinesDestination.add(polyline); // fit للخريطة على أول step if (stepBounds.isNotEmpty) { _fitToBounds(stepBounds[0]); } update(); // // ابدأ التتبع الحيّ لخطوات الملاحة // await startListeningStepNavigation(); } // ———————————————————————————————————————————————————————————————— // يُنادى عند كل تحديث للموقع // void _onLocationTick(LatLng pos) async { // if (routeSteps.isEmpty || currentStepIndex >= stepBounds.length) return; // // إذا تجاوزت نهاية الـ step الحالية بمسافة صغيرة -> انتقل // final double dToEnd = _distanceMeters(pos, stepEndPoints[currentStepIndex]); // final bool nearEnd = dToEnd <= 25; // عتبة 25م (عدّلها حسب حاجتك) // // إذا دخلت نطاق الـ step التالية -> انتقل // bool insideNext = false; // if (currentStepIndex < stepBounds.length - 1) { // insideNext = _contains(stepBounds[currentStepIndex + 1], pos); // } // if (nearEnd || insideNext) { // _advanceStep(); // return; // } // // تحديث الكاميرا بشكل خفيف أثناء الحركة (كل 2 ثانية مثلاً) // final now = DateTime.now(); // if (now.difference(_lastCameraUpdateTs).inMilliseconds > 2000) { // _lastCameraUpdateTs = now; // // ركّز على bounds الحالية مع الحفاظ على تتبّع عام // _fitToBounds(stepBounds[currentStepIndex], padding: 60); // } // } LatLngBounds _boundsFromPoints(List pts) { double? minLat, maxLat, minLng, maxLng; for (final p in pts) { minLat = (minLat == null) ? p.latitude : math.min(minLat, p.latitude); maxLat = (maxLat == null) ? p.latitude : math.max(maxLat, p.latitude); minLng = (minLng == null) ? p.longitude : math.min(minLng, p.longitude); maxLng = (maxLng == null) ? p.longitude : math.max(maxLng, p.longitude); } return LatLngBounds( southwest: LatLng(minLat ?? 0, minLng ?? 0), northeast: LatLng(maxLat ?? 0, maxLng ?? 0), ); } bool _contains(LatLngBounds b, LatLng p) { final south = math.min(b.southwest.latitude, b.northeast.latitude); final north = math.max(b.southwest.latitude, b.northeast.latitude); final west = math.min(b.southwest.longitude, b.northeast.longitude); final east = math.max(b.southwest.longitude, b.northeast.longitude); return (p.latitude >= south && p.latitude <= north && p.longitude >= west && p.longitude <= east); } double _distanceMeters(LatLng a, LatLng b) { // هافرساين مبسطة const R = 6371000.0; // m final dLat = _deg2rad(b.latitude - a.latitude); final dLng = _deg2rad(b.longitude - a.longitude); final s1 = math.sin(dLat / 2); final s2 = math.sin(dLng / 2); final aa = s1 * s1 + math.cos(_deg2rad(a.latitude)) * math.cos(_deg2rad(b.latitude)) * s2 * s2; final c = 2 * math.atan2(math.sqrt(aa), math.sqrt(1 - aa)); return R * c; } double _deg2rad(double d) => d * math.pi / 180.0; // تحريك الكاميرا لباوند معيّن // Future _fitToBounds(LatLngBounds b, {double padding = 40}) async { // if (mapController == null) return; // try { // // أحياناً يلزم انتظار فريم حتى تكون الخريطة مرسومة // await Future.delayed(const Duration(milliseconds: 50)); // await mapController!.animateCamera( // CameraUpdate.newLatLngBounds(b, padding), // ); // } catch (_) { // // fallback لو حصلت مشكلة الحجم // final center = LatLng( // (b.northeast.latitude + b.southwest.latitude) / 2, // (b.northeast.longitude + b.southwest.longitude) / 2, // ); // await mapController!.animateCamera(CameraUpdate.newLatLng(center)); // } // } // الانتقال للخطوة التالية وتحديث التعليمات والكاميرا // void _advanceStep() { // if (currentStepIndex >= stepBounds.length - 1) return; // currentStepIndex++; // currentInstruction = _parseInstruction(stepInstructions[currentStepIndex]); // // نطق التعليمات // if (Get.isRegistered()) { // Get.find().speakText(currentInstruction); // } else { // Get.put(TextToSpeechController()).speakText(currentInstruction); // } // // تركيز الكاميرا على باوند الخطوة الجديدة // _fitToBounds(stepBounds[currentStepIndex]); // update(); // } // void _prepareStepData(List> steps) { // stepBounds.clear(); // stepEndPoints.clear(); // stepInstructions.clear(); // for (final s in steps) { // // 1) instruction // final html = (s['html_instructions'] ?? '').toString(); // stepInstructions.add(html); // // 2) end point // final end = s['end_location']; // final endLatLng = LatLng( // (end['lat'] as num).toDouble(), (end['lng'] as num).toDouble()); // stepEndPoints.add(endLatLng); // // 3) bounds من الـ polyline (إن لم يوجد bounds جاهز من الـ API) // List pts = []; // if (s['polyline'] != null && s['polyline']['points'] != null) { // final decoded = decodePolyline(s['polyline']['points']); // for (var p in decoded) { // pts.add(LatLng((p[0] as num).toDouble(), (p[1] as num).toDouble())); // } // } else { // // fallback: استخدم start/end فقط // final start = s['start_location']; // pts.add(LatLng((start['lat'] as num).toDouble(), // (start['lng'] as num).toDouble())); // pts.add(endLatLng); // } // stepBounds.add(_boundsFromPoints(pts)); // } // } // getMapDestination(String origin, destination) async { // var url = // ('${AppLink.googleMapsLink}directions/json?&language=${box.read(BoxName.lang)}&avoid=tolls|ferries&destination=$destination&origin=$origin&key=${AK.mapAPIKEY}'); // var response = await CRUD().getGoogleApi(link: url, payload: {}); // dataDestination = response['routes'][0]['legs']; // final points = // decodePolyline(response["routes"][0]["overview_polyline"]["points"]); // for (int i = 0; i < points.length; i++) { // double lat = points[i][0].toDouble(); // double lng = points[i][1].toDouble(); // polylineCoordinatesDestination.add(LatLng(lat, lng)); // } // // استخراج الخطوات // routeSteps = List>.from(dataDestination[0]['steps']); // Log.print('routeSteps: ${routeSteps}'); // currentStepIndex = 0; // if (routeSteps.isNotEmpty) { // currentInstruction = // _parseInstruction(routeSteps[0]['html_instructions']); // Log.print('currentInstruction: ${currentInstruction}'); // Get.isRegistered() // ? Get.find().speakText(currentInstruction) // : Get.put(TextToSpeechController()).speakText(currentInstruction); // } // update(); // // دالة مساعدة لتنظيف التعليمات // if (polyLinesDestination.isNotEmpty) { // // clearPolyline(); // var polyline = Polyline( // polylineId: PolylineId(response["routes"][0]["summary"]), // points: polylineCoordinatesDestination, // width: 10, // color: AppColor.redColor, // ); // polyLinesDestination.add(polyline); // // rideConfirm = false; // update(); // } else { // var polyline = Polyline( // polylineId: PolylineId(response["routes"][0]["summary"]), // points: polylineCoordinatesDestination, // width: 10, // color: AppColor.redColor, // ); // // final dataBounds = response["routes"][0]["bounds"]; // // updateCameraFromBoundsAfterGetMap(dataBounds); // // polyLinesDestination.add(polyline); // // rideConfirm = false; // // Define the northeast and southwest coordinates // update(); // } // } void updateCameraFromBoundsAfterGetMap(dynamic response) { final bounds = response["routes"][0]["bounds"]; LatLng northeast = LatLng(bounds['northeast']['lat'], bounds['northeast']['lng']); LatLng southwest = LatLng(bounds['southwest']['lat'], bounds['southwest']['lng']); // Create the LatLngBounds object LatLngBounds boundsData = LatLngBounds(northeast: northeast, southwest: southwest); // Fit the camera to the bounds var cameraUpdate = CameraUpdate.newLatLngBounds(boundsData, 140); mapController!.animateCamera(cameraUpdate); } void changePassengerInfoWindow() { isPassengerInfoWindow = !isPassengerInfoWindow; passengerInfoWindowHeight = isPassengerInfoWindow == true ? 200 : 0; update(); } double mpg = 0; calculateConsumptionFuel() { mpg = Get.find().fuelPrice / 12; //todo in register car add mpg in box update(); } argumentLoading() async { passengerLocation = Get.arguments['passengerLocation']; passengerDestination = Get.arguments['passengerDestination']; duration = Get.arguments['Duration']; totalCost = Get.arguments['totalCost']; passengerId = Get.arguments['passengerId']; driverId = Get.arguments['driverId']; distance = Get.arguments['Distance']; passengerName = Get.arguments['name']; passengerEmail = Get.arguments['email']; totalPricePassenger = Get.arguments['totalPassenger']; passengerPhone = Get.arguments['phone']; walletChecked = Get.arguments['WalletChecked']; tokenPassenger = Get.arguments['tokenPassenger']; direction = Get.arguments['direction']; durationToPassenger = Get.arguments['DurationToPassenger']; rideId = Get.arguments['rideId']; durationOfRideValue = Get.arguments['durationOfRideValue']; paymentAmount = Get.arguments['paymentAmount']; paymentMethod = Get.arguments['paymentMethod']; isHaveSteps = Get.arguments['isHaveSteps']; step0 = Get.arguments['step0']; step1 = Get.arguments['step1']; step2 = Get.arguments['step2']; step3 = Get.arguments['step3']; step4 = Get.arguments['step4']; passengerWalletBurc = Get.arguments['passengerWalletBurc']; timeOfOrder = Get.arguments['timeOfOrder']; carType = Get.arguments['carType']; kazan = Get.arguments['kazan']; startNameLocation = Get.arguments['startNameLocation']; endNameLocation = Get.arguments['endNameLocation']; // Parse to double latlng(passengerLocation, passengerDestination); String lat = Get.find().myLocation.latitude.toString(); String lng = Get.find().myLocation.longitude.toString(); String origin = '$lat,$lng'; // Set the origin and destination coordinates for the Google Maps directions request. Future.delayed(const Duration(seconds: 1)); getRoute( origin: Get.find().myLocation, destination: latLngPassengerLocation, routeColor: Colors.yellow // أو أي لون ); // getRoute( // origin: latLngPassengerLocation, // destination: latLngPassengerDestination, // routeColor: Colors.blue // أو أي لون // ); // getMap(origin, passengerLocation); // isHaveSteps == 'haveSteps' // ? ( // await getMapDestination(step0, step1), // await getMapDestination(step1, step2), // step3 == '' ? await getMapDestination(step2, step3) : () {}, // step4 == '' ? await getMapDestination(step3, step4) : () {}, // ) // : await getMapDestination(passengerLocation, passengerDestination); update(); } latlng(String passengerLocation, passengerDestination) { double latPassengerLocation = double.parse(passengerLocation.toString().split(',')[0]); double lngPassengerLocation = double.parse(passengerLocation.toString().split(',')[1]); double latPassengerDestination = double.parse(passengerDestination.toString().split(',')[0]); double lngPassengerDestination = double.parse(passengerDestination.toString().split(',')[1]); latLngPassengerLocation = LatLng(latPassengerLocation, lngPassengerLocation); latLngPassengerDestination = LatLng(latPassengerDestination, lngPassengerDestination); } late Duration durationToAdd; int hours = 0; int minutes = 0; late String carType; late String kazan; late String startNameLocation; late String endNameLocation; Future runGoogleMapDirectly() async { if (box.read(BoxName.googlaMapApp) == true) { if (Platform.isAndroid) { Bubble().startBubbleHead(sendAppToBackground: true); } await openGoogleMapFromDriverToPassenger(); } } @override void onInit() async { mapAPIKEY = await storage.read(key: BoxName.mapAPIKEY); // Get the passenger location from the arguments. await argumentLoading(); Get.put(FirebaseMessagesController()); runGoogleMapDirectly(); addCustomCarIcon(); addCustomPassengerIcon(); addCustomStartIcon(); addCustomEndIcon(); // updateMarker(); // updateLocation(); startTimerToShowPassengerInfoWindowFromDriver(); durationToAdd = Duration(seconds: int.parse(duration)); hours = durationToAdd.inHours; minutes = (durationToAdd.inMinutes % 60).round(); calculateConsumptionFuel(); updateLocation(); // cancelCheckRidefromPassenger(); // checkIsDriverNearPassenger(); super.onInit(); } Timer? _navigationTimer; Future startListeningStepNavigation() async { _posSub?.cancel(); _navigationTimer?.cancel(); Position? pos; _posSub = Geolocator.getPositionStream( locationSettings: LocationSettings( accuracy: LocationAccuracy.high, distanceFilter: 5, // حدّث كل ~5 متر لتقليل الاهتزاز) ), ).listen((position) { // خزّن آخر موقع معروف، لكن لا تعالجه فوراً myLocation = LatLng(position.latitude, position.longitude); heading = position.heading; update(); }); _navigationTimer = Timer.periodic( Duration( seconds: box.read(BoxName.updateInterval) ?? 5, ), (timer) { // تأكد أن لدينا موقع صالح قبل المعالجة if (myLocation.latitude != 0) { // استدعِ دالة المعالجة الثقيلة هنا onLocationUpdated(pos!); } }); // _posSub = Geolocator.getPositionStream( // locationSettings: const LocationSettings( // accuracy: LocationAccuracy.high, // distanceFilter: 5, // حدّث كل ~5 متر لتقليل الاهتزاز // ), // ).listen((pos) => _onLocationTick(LatLng(pos.latitude, pos.longitude))); } void stopListeningStepNavigation() { _posSub?.cancel(); _posSub = null; } }