import 'dart:async'; import 'dart:convert'; import 'dart:ui'; import 'dart:math' show cos, max, min, pi, pow, sin, atan2; import 'package:flutter/foundation.dart'; import 'package:flutter/material.dart'; import 'package:flutter/cupertino.dart'; import 'package:flutter/services.dart'; import 'package:geolocator/geolocator.dart'; import 'package:get/get.dart'; import 'package:intaleq_maps/intaleq_maps.dart'; import 'package:http/http.dart' as http; import 'package:intl/intl.dart'; import '../../../constant/api_key.dart'; import '../../../services/offline_map_service.dart'; import '../../../models/model/painter_copoun.dart'; import '../../../views/widgets/mycircular.dart'; import '../deep_link_controller.dart'; import '../../../constant/box_name.dart'; import '../../../constant/links.dart'; import '../../../constant/colors.dart'; import '../../../constant/style.dart'; import '../../../constant/country_polygons.dart'; import '../../../env/env.dart'; import '../../../main.dart'; // contains global 'box', 'sql' import '../../../print.dart'; import '../../../services/pip_service.dart'; import '../../../services/ride_live_notification.dart'; import '../../../views/home/map_page_passenger.dart'; import '../../../views/Rate/rate_captain.dart'; import '../../../views/Rate/rating_driver_bottom.dart'; import '../../../views/widgets/mydialoug.dart'; import '../../../views/widgets/elevated_btn.dart'; import '../../../views/home/map_widget.dart/car_details_widget_to_go.dart'; import '../../../views/home/map_widget.dart/select_driver_mishwari.dart'; import '../../functions/crud.dart'; import '../../functions/launch.dart'; import '../../payment/payment_controller.dart'; import '../points_for_rider_controller.dart'; import 'map_engine_controller.dart'; import 'location_search_controller.dart'; import 'nearby_drivers_controller.dart'; import 'ui_interactions_controller.dart'; import 'map_socket_controller.dart'; import '../decode_polyline_isolate.dart'; import '../ios_live_activity_service.dart'; import '../../firebase/local_notification.dart'; import '../../firebase/notification_service.dart'; import '../../functions/audio_record1.dart'; import '../../functions/package_info.dart'; import '../../functions/secure_storage.dart'; import '../vip_waitting_page.dart'; import '../device_performance.dart'; import 'ride_state.dart'; import '../../../views/widgets/error_snakbar.dart'; import 'package:flutter_confetti/flutter_confetti.dart' hide Circle; import 'package:crypto/crypto.dart'; class RideLifecycleController extends GetxController { // --- Missing variables from monolithic controller --- String currentRideId = ''; bool isDrawingRoute = false; bool isAnotherOreder = false; bool isWhatsAppOrder = false; LatLng startLocation = const LatLng(32, 35); LatLng endLocation = const LatLng(32, 35); String dynamicApiUrl = 'https://routec.intaleq.xyz/route'; String? cardNumber; bool isBeginRideFromDriverRunning = false; bool isDriversTokensSend = false; Map rideData = {}; Map dInfo = {}; List datadriverCarsLocationToPassengerAfterApplied = []; double distanceOfTrip = 0.0; double apiDistanceMeters = 0.0; double tax = 0.0; int selectedPassengerCount = 1; final GlobalKey increaseFeeFormKey = GlobalKey(); final GlobalKey messagesFormKey = GlobalKey(); final GlobalKey promoFormKey = GlobalKey(); String walletStr = '0'; double walletVal = 0.0; bool rideConfirm = false; LatLng driverLocationToPassenger = const LatLng(32, 35); final TextEditingController messageToDriver = TextEditingController(); int carsOrder = 0; Rx currentRideState = RideState.noRide.obs; String statusRide = 'wait'; String statusRideVip = 'wait'; bool statusRideFromStart = false; double distance = 0; double duration = 0; int durationToRide = 0; int remainingTime = 25; int remainingTimeToPassengerFromDriverAfterApplied = 60; int remainingTimeDriverWaitPassenger5Minute = 60; int timeToPassengerFromDriverAfterApplied = 0; Timer? timerToPassengerFromDriverAfterApplied; DateTime? _driverEtaUpdatedAt; int _driverEtaSecondsAtUpdate = 0; int _driverEtaCountdownTicks = 0; bool rideTimerBegin = false; double progressTimerRideBegin = 0; int remainingTimeTimerRideBegin = 60; String stringRemainingTimeRideBegin = ''; late String rideId = 'yet'; late String driverId = ''; late String make = ''; late String model = ''; late String gender = ''; late String carColor = ''; late String licensePlate = ''; late String driverName = ''; late String passengerName = ''; late String driverPhone = ''; late String colorHex = ''; late String carYear = ''; late String driverRate = '5.0'; late String driverRatingCount = '0'; late String driverCompletedRides = '0'; late String driverTier = 'Verified driver'; late String driverToken = ''; String totalPassenger = '0'; double totalDriver = 0; double costDistance = 0; double costDuration = 0; double averageDuration = 0; String totalCostPassenger = '0'; String totalPassengerSpeed = '0'; String totalPassengerBalash = '0'; String totalPassengerComfort = '0'; String totalPassengerElectric = '0'; String totalPassengerLady = '0'; String totalPassengerScooter = '0'; String totalPassengerVan = '0'; String totalPassengerRayehGai = '0'; String totalPassengerRayehGaiComfort = '0'; String totalPassengerRayehGaiBalash = '0'; String priceToken = ''; double latePrice = 0; double fuelPrice = 0; double heavyPrice = 0; double naturePrice = 0; bool isRideFinished = false; String stringRemainingTimeToPassenger = ''; String stringRemainingTimeDriverWaitPassenger5Minute = ''; bool isDriverInPassengerWay = false; bool isDriverArrivePassenger = false; bool isSearchingWindow = false; bool shouldFetch = true; double progressTimerToPassengerFromDriverAfterApplied = 0; double progressTimerDriverWaitPassenger5Minute = 0; bool isCashSelectedBeforeConfirmRide = false; bool isPassengerChosen = false; Timer? _masterTimer; Timer? _searchTimer; Timer? _timer; Timer? _uiCountdownTimer; bool _isArrivalProcessed = false; bool _isFinishProcessed = false; bool _isCancelProcessed = false; bool _isAcceptanceProcessed = false; bool _isRatingScreenOpen = false; bool _isReviewProcessed = false; // ๐Ÿ›ก๏ธ Gatekeeper ู„ู…ู†ุน ูุชุญ ุงู„ุชู‚ูŠูŠู… ู…ุฑุชูŠู† bool _isRecalculatingRoute = false; String _rideAcceptedViaSource = "Unknown"; final double kDurationScalar = 1.5348; // ู…ุณุงูุฉ ุงู„ุงู†ุญุฑุงู ุงู„ู…ุณู…ูˆุญ ุจู‡ุง ุจุงู„ู…ุชุฑ ู‚ุจู„ ุฅุนุงุฏุฉ ุญุณุงุจ ุงู„ู…ุณุงุฑ ุชู„ู‚ุงุฆูŠู‹ุง ู„ู„ุฑุญู„ุฉ. // ุฅุฐุง ุงู†ุญุฑู ุงู„ุณุงุฆู‚ ุนู† ุงู„ู…ุณุงุฑ ุจุฃูƒุซุฑ ู…ู† ู‡ุฐู‡ ุงู„ู…ุณุงูุฉุŒ ูŠูุนุงุฏ ุญุณุงุจ ุงู„ู…ุณุงุฑ. final double _deviationThresholdMeters = 30.0; int _routeHeadingMismatchCount = 0; final Map _pollingIntervals = { RideState.noRide: 6, RideState.searching: 8, RideState.driverApplied: 10, RideState.driverArrived: 15, RideState.inProgress: 15, RideState.cancelled: 3600, RideState.finished: 3600, RideState.preCheckReview: 3600, }; Timer? _locationPollingTimer; List _currentDriverRoutePoints = []; double _currentDriverRouteDistanceMeters = 0.0; int _currentDriverRouteDurationSeconds = 0; int _currentSearchPhase = 0; bool _isFetchingDriverLocation = false; Timer? _watchdogTimer; final List _searchRadii = [2400, 3000, 3100]; final int _searchPhaseDurationSeconds = 30; final int _totalSearchTimeoutSeconds = 90; int _noRideSearchCount = 0; final int _noRideMaxTries = 3; final int _noRideIntervalSec = 5; DateTime? _noRideNextAllowed; bool _noRideSearchCapped = false; int _masterIntervalSeconds = -1; final StreamController _rideStatusStreamController = StreamController.broadcast(); Stream get rideStatusStream => _rideStatusStreamController.stream; final StreamController _beginRideStreamController = StreamController.broadcast(); Stream get beginRideStream => _beginRideStreamController.stream; final StreamController _timerStreamController = StreamController(); Stream get timerStream => _timerStreamController.stream; bool isTimerFromDriverToPassengerAfterAppliedRunning = true; bool isTimerRunning = false; int beginRideInterval = 10; Timer? _rideProgressTimer; bool _hasShownSpeedWarning = false; bool rideInProgress = true; double elapsedTimeInSeconds = 0; String stringElapsedTimeRideBeginVip = '0:00'; Map rideStatusFromStartApp = {}; bool isStartAppHasRide = false; late Duration durationToAdd; late DateTime newTime = DateTime.now(); String durationByPassenger = ''; int hours = 0; int minutes = 0; int selectedReason = -1; String? cancelNote; double latitudeWhatsApp = 0; double longitudeWhatsApp = 0; // Getters for linked controllers LocationSearchController get locSearch => Get.find(); MapEngineController get mapEngine => Get.find(); NearbyDriversController get nearbyDrivers => Get.find(); MapSocketController get mapSocket => Get.find(); UiInteractionsController get uiInteractions => Get.find(); // LocationSearchController pass-throughs LatLng get passengerLocation => locSearch.passengerLocation; set passengerLocation(LatLng val) => locSearch.passengerLocation = val; LatLng get newMyLocation => locSearch.newMyLocation; set newMyLocation(LatLng val) => locSearch.newMyLocation = val; LatLng get newStartPointLocation => locSearch.newStartPointLocation; set newStartPointLocation(LatLng val) => locSearch.newStartPointLocation = val; LatLng get myDestination => locSearch.myDestination; set myDestination(LatLng val) => locSearch.myDestination = val; String get startNameAddress => locSearch.startNameAddress; set startNameAddress(String val) => locSearch.startNameAddress = val; String get endNameAddress => locSearch.endNameAddress; set endNameAddress(String val) => locSearch.endNameAddress = val; List get placesCoordinate => locSearch.placesCoordinate; set placesCoordinate(List val) => locSearch.placesCoordinate = val; int get activeMenuWaypointCount => locSearch.activeMenuWaypointCount; set activeMenuWaypointCount(int val) => locSearch.activeMenuWaypointCount = val; List get menuWaypoints => locSearch.menuWaypoints; set menuWaypoints(List val) => locSearch.menuWaypoints = val; List get menuWaypointNames => locSearch.menuWaypointNames; set menuWaypointNames(List val) => locSearch.menuWaypointNames = val; bool get passengerStartLocationFromMap => locSearch.passengerStartLocationFromMap; set passengerStartLocationFromMap(bool val) => locSearch.passengerStartLocationFromMap = val; List get coordinatesWithoutEmpty => locSearch.coordinatesWithoutEmpty; // MapEngineController pass-throughs Set get markers => mapEngine.markers; set markers(Set val) { mapEngine.markers = val; mapEngine.update(); } Set get polyLines => mapEngine.polyLines; set polyLines(Set val) { mapEngine.polyLines = val; mapEngine.update(); } IntaleqMapController? get mapController => mapEngine.mapController; bool get isStyleLoaded => mapEngine.isStyleLoaded; set isStyleLoaded(bool val) => mapEngine.isStyleLoaded = val; bool get isBottomSheetShown => mapEngine.isBottomSheetShown; set isBottomSheetShown(bool val) => mapEngine.isBottomSheetShown = val; double get heightBottomSheetShown => mapEngine.heightBottomSheetShown; set heightBottomSheetShown(double val) => mapEngine.heightBottomSheetShown = val; bool get isPickerShown => mapEngine.isPickerShown; set isPickerShown(bool val) => mapEngine.isPickerShown = val; bool get isMarkersShown => mapEngine.isMarkersShown; set isMarkersShown(bool val) => mapEngine.isMarkersShown = val; bool get isMainBottomMenuMap => mapEngine.isMainBottomMenuMap; set isMainBottomMenuMap(bool val) => mapEngine.isMainBottomMenuMap = val; double get mainBottomMenuMapHeight => mapEngine.mainBottomMenuMapHeight; set mainBottomMenuMapHeight(double val) => mapEngine.mainBottomMenuMapHeight = val; bool get isWayPointSheet => mapEngine.isWayPointSheet; set isWayPointSheet(bool val) => mapEngine.isWayPointSheet = val; bool get isWayPointStopsSheet => mapEngine.isWayPointStopsSheet; set isWayPointStopsSheet(bool val) => mapEngine.isWayPointStopsSheet = val; bool get isWayPointStopsSheetUtilGetMap => mapEngine.isWayPointStopsSheetUtilGetMap; set isWayPointStopsSheetUtilGetMap(bool val) => mapEngine.isWayPointStopsSheetUtilGetMap = val; double get wayPointSheetHeight => mapEngine.wayPointSheetHeight; set wayPointSheetHeight(double val) => mapEngine.wayPointSheetHeight = val; double get cashConfirmPageShown => mapEngine.cashConfirmPageShown; set cashConfirmPageShown(double val) => mapEngine.cashConfirmPageShown = val; bool get isCashConfirmPageShown => mapEngine.isCashConfirmPageShown; set isCashConfirmPageShown(bool val) => mapEngine.isCashConfirmPageShown = val; bool get isCancelRidePageShown => mapEngine.isCancelRidePageShown; set isCancelRidePageShown(bool val) => mapEngine.isCancelRidePageShown = val; void changeCashConfirmPageShown() => mapEngine.changeCashConfirmPageShown(); void resetNoRideSearch() { _noRideSearchCount = 0; _noRideSearchCapped = false; _noRideNextAllowed = null; } double get paymentPageShown => mapEngine.paymentPageShown; set paymentPageShown(double val) => mapEngine.paymentPageShown = val; void changeCancelRidePageShow() => mapEngine.changeCancelRidePageShow(); // NearbyDriversController pass-throughs List get carsLocationByPassenger => nearbyDrivers.carsLocationByPassenger; set carsLocationByPassenger(List val) => nearbyDrivers.carsLocationByPassenger = val; List get driverCarsLocationToPassengerAfterApplied => nearbyDrivers.driverCarsLocationToPassengerAfterApplied; set driverCarsLocationToPassengerAfterApplied(List val) => nearbyDrivers.driverCarsLocationToPassengerAfterApplied = val; bool get noCarString => nearbyDrivers.noCarString; set noCarString(bool val) => nearbyDrivers.noCarString = val; double get speed => locSearch.speed; set speed(double val) => locSearch.speed = val; Timer? get locationPollingTimer => _locationPollingTimer; bool isActiveRideState() { return currentRideState.value == RideState.searching || currentRideState.value == RideState.driverApplied || currentRideState.value == RideState.driverArrived || currentRideState.value == RideState.inProgress; } void startMasterTimer() { _masterTimer?.cancel(); _masterTimer = Timer.periodic(const Duration(seconds: 13), (_) { _handleRideState(currentRideState.value); }); } void cancelMasterTimer() { _masterTimer?.cancel(); _masterTimer = null; } void startMasterTimerWithInterval(int seconds) { if (_masterTimer != null && _masterIntervalSeconds == seconds) return; _masterIntervalSeconds = seconds; _masterTimer?.cancel(); _masterTimer = Timer.periodic(Duration(seconds: seconds), (_) { _handleRideState(currentRideState.value); }); } void stopAllTimers() { Log.print('๐Ÿ›‘ FORCE STOP: Stopping ALL Timers and Streams ๐Ÿ›‘'); _masterTimer?.cancel(); _masterTimer = null; timerToPassengerFromDriverAfterApplied?.cancel(); timerToPassengerFromDriverAfterApplied = null; _timer?.cancel(); _timer = null; _uiCountdownTimer?.cancel(); _uiCountdownTimer = null; _locationPollingTimer?.cancel(); _locationPollingTimer = null; _watchdogTimer?.cancel(); _watchdogTimer = null; _searchTimer?.cancel(); _searchTimer = null; _rideProgressTimer?.cancel(); _rideProgressTimer = null; isTimerRunning = false; isBeginRideFromDriverRunning = false; _isFetchingDriverLocation = false; update(); } Future _handleRideState(RideState state) async { if (_isRatingScreenOpen) { Log.print('โ›” Rating Screen is Open. Skipping Logic.'); stopAllTimers(); return; } Log.print('Handling state: $state'); int effectivePollingInterval = _pollingIntervals[state] ?? 13; switch (state) { case RideState.noRide: final now = DateTime.now(); if (_noRideSearchCount >= _noRideMaxTries) { if (!_noRideSearchCapped) { _noRideSearchCapped = true; Log.print('[noRide] search capped at $_noRideMaxTries attempts'); } break; } if (_noRideNextAllowed != null && now.isBefore(_noRideNextAllowed!)) { break; } _noRideSearchCount++; Log.print('_noRideSearchCount: $_noRideSearchCount'); _noRideNextAllowed = now.add(Duration(seconds: _noRideIntervalSec)); nearbyDrivers.getCarsLocationByPassengerAndReloadMarker(); nearbyDrivers.getNearestDriverByPassengerLocation(); break; case RideState.cancelled: stopAllTimers(); break; case RideState.preCheckReview: stopAllTimers(); _checkLastRideForReview(); break; case RideState.searching: if (rideId == 'yet' || rideId.isEmpty) break; try { String statusFromServer = await getRideStatus(rideId); if (statusFromServer == 'Apply' || statusFromServer == 'Applied') { await processRideAcceptance(source: "Polling"); break; } } catch (e) { Log.print('Error polling getRideStatus: $e'); } final now = DateTime.now(); final int elapsedSeconds = now.difference(_searchStartTime!).inSeconds; if (elapsedSeconds > _totalSearchTimeoutSeconds) { stopAllTimers(); currentRideState.value = RideState.noRide; isSearchingWindow = false; update(); _showIncreaseFeeDialog(); break; } int targetPhase = (elapsedSeconds / _searchPhaseDurationSeconds).floor(); if (targetPhase >= _searchRadii.length) { targetPhase = _searchRadii.length - 1; } bool isNewPhase = targetPhase > _currentSearchPhase; bool timeToScanForNewDrivers = (elapsedSeconds % 15 == 0); if (isNewPhase || timeToScanForNewDrivers || elapsedSeconds < 5) { _currentSearchPhase = targetPhase; int currentRadius = _searchRadii[_currentSearchPhase]; Log.print( '[Search Logic] Scanning for drivers. Phase: $_currentSearchPhase, Radius: $currentRadius'); } if (elapsedSeconds < 5) { driversStatusForSearchWindow = 'Your order is being prepared'.tr; } else if (elapsedSeconds < 15) { driversStatusForSearchWindow = 'Your order sent to drivers'.tr; } else { driversStatusForSearchWindow = 'The drivers are reviewing your request'.tr; } update(); break; case RideState.driverApplied: if (!_isDriverAppliedLogicExecuted && !_isAcceptanceProcessed) { Log.print('[handleRideState] Execution driverApplied logic.'); rideAppliedFromDriver(true); _isDriverAppliedLogicExecuted = true; } if (!mapSocket.isSocketConnected) { try { String statusFromServer = await getRideStatus(rideId); if (statusFromServer == 'Arrived') { currentRideState.value = RideState.driverArrived; break; } else if (statusFromServer == 'Begin' || statusFromServer == 'inProgress') { processRideBegin(); break; } } catch (e) { Log.print('Error polling for Arrived/Begin status: $e'); } } if (!_isSocketHealthy()) { getDriverCarsLocationToPassengerAfterApplied(); } break; case RideState.driverArrived: if (!_isDriverArrivedLogicExecuted) { _isDriverArrivedLogicExecuted = true; startTimerDriverWaitPassenger5Minute(); uiInteractions.driverArrivePassengerDialoge(); } break; case RideState.inProgress: if (!mapSocket.isSocketConnected) { try { String statusFromServer = await getRideStatus(rideId); if (statusFromServer == 'Finished' || statusFromServer == 'finished') { Log.print( '๐Ÿ DETECTED FINISHED: Killing processes and forcing Review.'); stopAllTimers(); currentRideState.value = RideState.preCheckReview; tripFinishedFromDriver(); _checkLastRideForReview(); return; } } catch (e) { Log.print('Error polling status: $e'); } } if (!_isRideBeginLogicExecuted) { _isRideBeginLogicExecuted = true; _executeBeginRideLogic(); } if (!_isSocketHealthy()) { getDriverCarsLocationToPassengerAfterApplied(); } break; case RideState.finished: tripFinishedFromDriver(); stopAllTimers(); effectivePollingInterval = 3600; break; } startMasterTimerWithInterval(effectivePollingInterval); } bool _isSocketHealthy() { return mapSocket.isSocketHealthy(); } Future _checkInitialRideStatus() async { await getRideStatusFromStartApp(); if (rideStatusFromStartApp['data'] == null) { currentRideState.value = RideState.noRide; _handleRideState(currentRideState.value); return; } String _status = rideStatusFromStartApp['data']['status'] ?? ''; String _lowerStatus = _status.toLowerCase(); if (_lowerStatus == 'waiting' || _lowerStatus == 'apply' || _lowerStatus == 'applied' || _lowerStatus == 'accepted' || _lowerStatus == 'arrived' || _lowerStatus == 'begin') { rideId = rideStatusFromStartApp['data']['rideId'].toString(); currentRideState.value = _lowerStatus == 'waiting' ? RideState.searching : (_lowerStatus == 'apply' || _lowerStatus == 'applied' || _lowerStatus == 'accepted') ? RideState.driverApplied : _lowerStatus == 'arrived' ? RideState.driverArrived : _lowerStatus == 'begin' ? RideState.inProgress : _lowerStatus == 'cancel' ? RideState.cancelled : RideState.noRide; } else if (_lowerStatus == 'finished') { if (rideStatusFromStartApp['data']['needsReview'] == 1) { currentRideState.value = RideState.preCheckReview; } else { currentRideState.value = RideState.noRide; } } else { currentRideState.value = RideState.noRide; } _handleRideState(currentRideState.value); } Future _checkLastRideForReview() async { // ๐Ÿ›ก๏ธ Gatekeeper: ู…ู†ุน ูุชุญ ุงู„ุชู‚ูŠูŠู… ู…ุฑุชูŠู† (Race Condition) if (_isReviewProcessed) { Log.print("โœ‹ _checkLastRideForReview: Already processed. Skipping."); return; } _isReviewProcessed = true; Log.print('โญ FORCE OPEN RATING PAGE (Get.to mode)'); await getRideStatusFromStartApp(); if (rideStatusFromStartApp['data'] == null) { _isReviewProcessed = false; currentRideState.value = RideState.noRide; startMasterTimer(); return; } String needsReview = rideStatusFromStartApp['data']['needsReview'].toString(); if (needsReview == '1') { _isRatingScreenOpen = true; var args = { 'driverId': rideStatusFromStartApp['data']['driver_id'].toString(), 'rideId': rideStatusFromStartApp['data']['rideId'].toString(), 'driverName': rideStatusFromStartApp['data']['driverName'], 'price': rideStatusFromStartApp['data']['price'], }; await Get.to( () => RatingDriverBottomSheet(), arguments: args, preventDuplicates: true, popGesture: false, ); Log.print('โœ… Rating Page Closed. Resetting App.'); _isRatingScreenOpen = false; restCounter(); currentRideState.value = RideState.noRide; startMasterTimer(); } else { currentRideState.value = RideState.noRide; startMasterTimer(); } } DateTime? _searchStartTime; bool _isDriverAppliedLogicExecuted = false; bool _isDriverArrivedLogicExecuted = false; bool _isRideBeginLogicExecuted = false; String driversStatusForSearchWindow = ''; void startSearchingForDriver() async { if (currentRideState.value == RideState.searching) return; isSearchingWindow = true; currentRideState.value = RideState.searching; driversStatusForSearchWindow = 'Searching for nearby drivers...'.tr; _searchStartTime = DateTime.now(); _currentSearchPhase = 0; update(); bool rideCreated = await postRideDetailsToServer(); if (!rideCreated) { isSearchingWindow = false; currentRideState.value = RideState.noRide; mySnackbarWarning("Could not create ride. Please try again.".tr); update(); return; } _addRideToWaitingTable(); mapSocket.initConnectionWithSocket(); } void _showIncreaseFeeDialog() { Get.dialog( CupertinoAlertDialog( title: Text("No drivers accepted your request yet".tr), content: Text( "Increasing the fare might attract more drivers. Would you like to increase the price?" .tr), actions: [ CupertinoDialogAction( child: Text("Cancel Ride".tr, style: TextStyle(color: AppColor.redColor)), onPressed: () { Get.back(); mapEngine.changeCancelRidePageShow(); }, ), CupertinoDialogAction( child: Text("Increase Fare".tr, style: TextStyle(color: AppColor.greenColor)), onPressed: () { Get.back(); double newPrice = double.parse(totalPassenger) * 1.10; increasePriceAndRestartSearch(newPrice); }, ), ], ), barrierDismissible: false, ); } Future increasePriceAndRestartSearch(double newPrice) async { totalPassenger = newPrice.toStringAsFixed(2); update(); await CRUD() .post(link: "${AppLink.server}/ride/rides/update.php", payload: { "id": rideId, "price": newPrice.toStringAsFixed(2), }); Log.print( '[increasePrice] Price changed. Clearing notified list to resend.'); notifiedDrivers.clear(); _searchStartTime = DateTime.now(); _currentSearchPhase = 0; isSearchingWindow = true; update(); startMasterTimer(); } void _stopWaitPassengerTimer({bool resetUI = false}) { _waitPassengerTimer?.cancel(); _waitPassengerTimer = null; if (resetUI) { progressTimerDriverWaitPassenger5Minute = 0.0; remainingTimeDriverWaitPassenger5Minute = 0; stringRemainingTimeDriverWaitPassenger5Minute = '00:00'; update(); } } void _executeBeginRideLogic() { Log.print('[executeBeginRideLogic] execution of ride start logic...'); _stopWaitPassengerTimer(resetUI: true); timeToPassengerFromDriverAfterApplied = 0; remainingTime = 0; remainingTimeToPassengerFromDriverAfterApplied = 0; remainingTimeDriverWaitPassenger5Minute = 0; rideTimerBegin = true; statusRide = 'Begin'; isDriverInPassengerWay = false; isDriverArrivePassenger = false; box.write(BoxName.passengerWalletTotal, '0'); update(); rideIsBeginPassengerTimer(); runWhenRideIsBegin(); NotificationController().showNotification( 'Trip is Begin'.tr, 'The trip has started! Feel free to contact emergency numbers, share your trip, or activate voice recording for the journey' .tr, 'start'); } Future processRideBegin({String source = "Unknown"}) async { if (currentRideState.value == RideState.inProgress || _isRideStartedProcessed) { return; } _isRideStartedProcessed = true; currentRideState.value = RideState.inProgress; statusRide = 'Begin'; box.write(BoxName.passengerWalletTotal, '0'); remainingTimeDriverWaitPassenger5Minute = 0; _stopWaitPassengerTimer(); // ู…ุณุญ ุงู„ุฎุทูˆุท ุงู„ู‚ุฏูŠู…ุฉ (pickup/direct) ู‚ุจู„ ุฑุณู… ุฎุท ุงู„ู…ุฑุญู„ุฉ ุงู„ุฌุฏูŠุฏุฉ polyLines = polyLines .where((p) => p.polylineId.value != 'main_route' && p.polylineId.value != 'route_direct' && !p.polylineId.value.startsWith('driver_route')) .toSet(); // ู…ูˆู‚ุน ุงู„ุณุงุฆู‚ ุงู„ุญุงู„ูŠ ู…ู† ุขุฎุฑ ุชุญุฏูŠุซ LatLng driverPos = passengerLocation; if (driverCarsLocationToPassengerAfterApplied.isNotEmpty) { driverPos = driverCarsLocationToPassengerAfterApplied.last; } // ุฑุณู… ุงู„ู…ุณุงุฑ ู…ู† ู…ูˆู‚ุน ุงู„ุณุงุฆู‚ ุฅู„ู‰ ุงู„ู‡ุฏู ุจุฎุท ุฃุฒุฑู‚ ู…ุณุชู…ุฑ await calculateDriverToPassengerRoute(driverPos, myDestination, isBeginPhase: true); rideIsBeginPassengerTimer(); runWhenRideIsBegin(); _updatePassengerWalkLine(); update(); } bool _isRideStartedProcessed = false; void updateDriverRouteMetrics({int? etaSeconds, double? distanceMeters}) { if (distanceMeters != null && distanceMeters > 0) { distanceByPassenger = distanceMeters.toStringAsFixed(0); } if (etaSeconds == null) return; final int clampedEta = max(0, etaSeconds); timeToPassengerFromDriverAfterApplied = clampedEta; remainingTimeToPassengerFromDriverAfterApplied = clampedEta; durationToPassenger = clampedEta; _driverEtaSecondsAtUpdate = clampedEta; _driverEtaUpdatedAt = DateTime.now(); final int minutes = (clampedEta / 60).floor(); final int seconds = clampedEta % 60; stringRemainingTimeToPassenger = '$minutes:${seconds.toString().padLeft(2, '0')}'; } void startTimerFromDriverToPassengerAfterApplied() { stopTimerFromDriverToPassengerAfterApplied(); if (isTimerRunning) return; isTimerRunning = true; isTimerFromDriverToPassengerAfterAppliedRunning = true; _driverEtaUpdatedAt ??= DateTime.now(); _driverEtaSecondsAtUpdate = timeToPassengerFromDriverAfterApplied; _driverEtaCountdownTicks = 0; timerToPassengerFromDriverAfterApplied = Timer.periodic(const Duration(seconds: 1), (timer) { bool isRideActive = (statusRide == 'Apply' || statusRide == 'Arrived' || statusRide == 'Begin' || currentRideState.value == RideState.driverApplied || currentRideState.value == RideState.driverArrived || currentRideState.value == RideState.inProgress); if (!isRideActive || !isTimerFromDriverToPassengerAfterAppliedRunning) { timer.cancel(); timerToPassengerFromDriverAfterApplied = null; isTimerRunning = false; return; } _driverEtaCountdownTicks++; if (!_timerStreamController.isClosed) { _timerStreamController.add(_driverEtaCountdownTicks); } final int secondsElapsedSinceEta = _driverEtaUpdatedAt == null ? 0 : DateTime.now().difference(_driverEtaUpdatedAt!).inSeconds; remainingTimeToPassengerFromDriverAfterApplied = _driverEtaSecondsAtUpdate - secondsElapsedSinceEta; if (remainingTimeToPassengerFromDriverAfterApplied < 0) { remainingTimeToPassengerFromDriverAfterApplied = 0; } int minutes = (remainingTimeToPassengerFromDriverAfterApplied / 60).floor(); int seconds = remainingTimeToPassengerFromDriverAfterApplied % 60; stringRemainingTimeToPassenger = '$minutes:${seconds.toString().padLeft(2, '0')}'; if (_driverEtaCountdownTicks % 5 == 0) { double currentProgress = 1 - (remainingTimeToPassengerFromDriverAfterApplied / (_driverEtaSecondsAtUpdate == 0 ? 1 : _driverEtaSecondsAtUpdate)); IosLiveActivityService.updateRideActivity( status: 'waiting', driverName: driverName, carDetails: '$make โ€ข $model โ€ข $carColor', etaText: stringRemainingTimeToPassenger, progress: currentProgress.clamp(0.0, 1.0), ); } if (_driverEtaCountdownTicks % beginRideInterval == 0) { uploadPassengerLocation(); } else { update(); } }); } void stopTimerFromDriverToPassengerAfterApplied() { isTimerFromDriverToPassengerAfterAppliedRunning = false; timerToPassengerFromDriverAfterApplied?.cancel(); timerToPassengerFromDriverAfterApplied = null; isTimerRunning = false; update(); } Timer? _waitPassengerTimer; static const int _waitPassengerTotalSeconds = 300; int _waitPassengerElapsedSeconds = 0; void startTimerDriverWaitPassenger5Minute() { if (currentRideState.value != RideState.driverArrived) return; stopTimerFromDriverToPassengerAfterApplied(); isTimerRunning = false; _stopWaitPassengerTimer(); isDriverArrivePassenger = true; isDriverInPassengerWay = false; timeToPassengerFromDriverAfterApplied = 0; _waitPassengerElapsedSeconds = 0; remainingTimeDriverWaitPassenger5Minute = _waitPassengerTotalSeconds; progressTimerDriverWaitPassenger5Minute = 0; int m = (remainingTimeDriverWaitPassenger5Minute / 60).floor(); int s = remainingTimeDriverWaitPassenger5Minute % 60; stringRemainingTimeDriverWaitPassenger5Minute = '$m:${s.toString().padLeft(2, '0')}'; update(); _waitPassengerTimer = Timer.periodic(const Duration(seconds: 1), (t) { if (currentRideState.value != RideState.driverArrived) { _stopWaitPassengerTimer(resetUI: true); if (currentRideState.value == RideState.inProgress) { isDriverArrivePassenger = false; } update(); return; } _waitPassengerElapsedSeconds++; int remaining = _waitPassengerTotalSeconds - _waitPassengerElapsedSeconds; if (remaining < 0) remaining = 0; remainingTimeDriverWaitPassenger5Minute = remaining; progressTimerDriverWaitPassenger5Minute = _waitPassengerElapsedSeconds / _waitPassengerTotalSeconds; int minutes = (remaining / 60).floor(); int seconds = remaining % 60; stringRemainingTimeDriverWaitPassenger5Minute = '$minutes:${seconds.toString().padLeft(2, '0')}'; update(); if (remaining == 0) { _stopWaitPassengerTimer(); } }); } void beginRideTimer() { Timer.periodic(const Duration(seconds: 1), (timer) { if (!timerController.isClosed) { timerController.add(timer.tick); } update(); }); } final timerController = StreamController(); void stopRideTimer() { timerController.close(); update(); } void rideIsBeginPassengerTimer() { _rideProgressTimer?.cancel(); _hasShownSpeedWarning = false; DateTime now = DateTime.now(); DateTime expectedArrivalTime = now.add(Duration(seconds: durationToRide)); var arrivalTime = DateFormat('hh:mm a').format(expectedArrivalTime); box.write(BoxName.arrivalTime, arrivalTime); Log.print("โณ Ride Timer Started. Duration: $durationToRide sec"); _rideProgressTimer = Timer.periodic(const Duration(seconds: 1), (timer) async { if (currentRideState.value != RideState.inProgress) { timer.cancel(); return; } DateTime currentNow = DateTime.now(); int remainingSeconds = expectedArrivalTime.difference(currentNow).inSeconds; if (remainingSeconds < 0) remainingSeconds = 0; remainingTimeTimerRideBegin = remainingSeconds; progressTimerRideBegin = durationToRide > 0 ? 1 - (remainingSeconds / durationToRide) : 1.0; int minutes = (remainingSeconds / 60).floor(); int seconds = remainingSeconds % 60; stringRemainingTimeRideBegin = '$minutes:${seconds.toString().padLeft(2, '0')}'; final percent = (progressTimerRideBegin * 100).clamp(0, 100).toInt(); if (remainingSeconds % 5 == 0 || remainingSeconds == 0) { IosLiveActivityService.updateRideActivity( status: 'ongoing', driverName: driverName, carDetails: '$make โ€ข $model โ€ข $carColor', etaText: stringRemainingTimeRideBegin, progress: progressTimerRideBegin.clamp(0.0, 1.0), ); } if (remainingSeconds % 60 == 0 || remainingSeconds == 0) { await RideLiveNotification.showTripInProgress( percentage: percent, etaText: stringRemainingTimeRideBegin, ); } if (speed > 100 && !_hasShownSpeedWarning) { _hasShownSpeedWarning = true; _triggerSpeedWarning(); } if (speed < 80 && _hasShownSpeedWarning) { _hasShownSpeedWarning = false; } if (remainingSeconds <= 0) { timer.cancel(); } update(); }); } void _triggerSpeedWarning() { NotificationController().showNotification("Warning: Speeding detected!".tr, 'You can call or record audio of this trip'.tr, 'tone1'); Get.defaultDialog( barrierDismissible: false, title: "Warning: Speeding detected!".tr, titleStyle: AppStyle.title.copyWith(color: AppColor.redColor), content: Column( children: [ Icon(Icons.speed, size: 50, color: AppColor.redColor), const SizedBox(height: 10), Text( "We noticed the speed is exceeding 100 km/h. Please slow down for your safety..." .tr, textAlign: TextAlign.center, style: AppStyle.title, ), ], ), confirm: MyElevatedButton( title: "Share Trip Details".tr, kolor: AppColor.redColor, onPressed: () { Get.back(); uiInteractions.sosPassenger(); }, ), cancel: MyElevatedButton( title: "I'm Safe".tr, kolor: AppColor.greenColor, onPressed: () { Get.back(); }, ), ); } void rideIsBeginPassengerTimerVIP() async { rideInProgress = true; bool sendSOS = false; while (rideInProgress) { await Future.delayed(const Duration(seconds: 1)); elapsedTimeInSeconds++; int minutes = (elapsedTimeInSeconds / 60).floor(); int seconds = (elapsedTimeInSeconds % 60).toInt(); stringElapsedTimeRideBeginVip = '$minutes:${seconds.toString().padLeft(2, '0')}'; if (speed > 100 && !sendSOS) { Get.defaultDialog( barrierDismissible: false, title: "Warning: Speeding detected!".tr, titleStyle: AppStyle.title, content: Text( "We noticed the speed is exceeding 100 km/h. Please slow down for your safety. If you feel unsafe, you can share your trip details with a contact or call the police using the red SOS button." .tr, style: AppStyle.title, ), confirm: MyElevatedButton( title: "Share Trip Details".tr, onPressed: () { Get.back(); String message = "**Emergency SOS from Passenger:**\n"; message += "* ${'Origin'.tr}: $passengerLocation\n"; message += "* ${'Destination'.tr}: $myDestination\n"; message += "* ${'Driver Name'.tr}: $passengerName\n"; message += "* ${'Driver Car Plate'.tr}: $licensePlate\n\n"; message += "* ${'Driver Phone'.tr}: $driverPhone\n\n"; message += "${'Current Location'.tr}:https://www.google.com/maps/place/${passengerLocation.latitude},${passengerLocation.longitude} \n"; message += "Please help! Contact me as soon as possible.".tr; launchCommunication( 'whatsapp', box.read(BoxName.sosPhonePassenger), message); sendSOS = true; }, kolor: AppColor.redColor, ), cancel: MyElevatedButton( title: "Cancel".tr, onPressed: () { Get.back(); }, kolor: AppColor.greenColor, ), ); } update(); } } Future tripFinishedFromDriver() async { // ๐Ÿ›‘ Race Condition Guard: ุฅุฐุง ุชู…ุช ู…ุนุงู„ุฌุชู‡ุง ู…ุณุจู‚ุงู‹ุŒ ุชุฎุทู‰ ููˆุฑุงู‹ if (_isFinishProcessed) { Log.print("โœ‹ tripFinishedFromDriver: Already processed. Skipping."); return; } _isFinishProcessed = true; Log.print('๐Ÿงน Cleaning UI for Finish'); if (Get.isDialogOpen == true) Get.back(); if (Get.isBottomSheetOpen == true) Get.back(); statusRide = 'Finished'; currentRideState.value = RideState.finished; isSearchingWindow = false; rideTimerBegin = false; shouldFetch = false; stopAllTimers(); resetAllMapStates(); mapEngine.clearPolyline(); markers = {}; update(); } void listenToBeginRideStream() { beginRideStream.listen((status) { Log.print("Ride status: $status"); }, onError: (error) { Log.print("Error in Begin Ride Stream: $error"); }); } Future begiVIPTripFromPassenger() async { timeToPassengerFromDriverAfterApplied = 0; remainingTime = 0; isBottomSheetShown = false; remainingTimeToPassengerFromDriverAfterApplied = 0; remainingTimeDriverWaitPassenger5Minute = 0; rideTimerBegin = true; statusRideVip = 'Begin'; isDriverInPassengerWay = false; isDriverArrivePassenger = false; update(); rideIsBeginPassengerTimerVIP(); runWhenRideIsBegin(); } Future getRideStatusFromStartApp() async { try { var res = await CRUD().get( link: AppLink.getRideStatusFromStartApp, payload: {'passenger_id': box.read(BoxName.passengerID)}); Log.print('rideStatusFromStartApp: $res'); if (res == 'failure') { rideStatusFromStartApp = { 'data': {'status': 'NoRide', 'needsReview': false} }; isStartAppHasRide = false; } else { var decoded = jsonDecode(res); if (decoded['status'] == 'failure') { rideStatusFromStartApp = { 'data': {'status': 'NoRide', 'needsReview': false} }; isStartAppHasRide = false; } else { rideStatusFromStartApp = decoded; } } String status = (rideStatusFromStartApp['data']['status'] ?? '').toString(); String lowerStatus = status.toLowerCase(); if (lowerStatus == 'begin' || lowerStatus == 'apply' || lowerStatus == 'applied' || lowerStatus == 'accepted' || lowerStatus == 'arrived') { statusRide = status; isStartAppHasRide = true; final bool isBeginStatus = lowerStatus == 'begin'; final bool isPickupStatus = lowerStatus == 'apply' || lowerStatus == 'applied' || lowerStatus == 'accepted' || lowerStatus == 'arrived'; currentRideState.value = lowerStatus == 'begin' ? RideState.inProgress : lowerStatus == 'arrived' ? RideState.driverArrived : RideState.driverApplied; driverId = rideStatusFromStartApp['data']['driver_id']?.toString() ?? ''; driverName = rideStatusFromStartApp['data']['driverName']?.toString() ?? ''; driverRate = rideStatusFromStartApp['data']['rateDriver']?.toString() ?? '5.0'; final LatLng? pickupPoint = _parseLatLng( rideStatusFromStartApp['data']['start_location']?.toString()); final LatLng? destinationPoint = _parseLatLng( rideStatusFromStartApp['data']['end_location']?.toString()); if (pickupPoint != null) { passengerLocation = pickupPoint; } if (destinationPoint != null) { myDestination = destinationPoint; } statusRideFromStart = true; update(); // Safe recovery of trip data Map? tripData; try { var rawTrip = box.read(BoxName.tripData); if (rawTrip is Map) { tripData = Map.from(rawTrip); } } catch (e) { Log.print("Error reading BoxName.tripData: $e"); } String? pointsString = tripData?['polyline']; if (pointsString == null || pointsString.isEmpty) { // No local polyline saved: Re-fetch the route from API final String startLoc = rideStatusFromStartApp['data']['start_location'] ?? ''; final String endLoc = rideStatusFromStartApp['data']['end_location'] ?? ''; if (startLoc.isNotEmpty && endLoc.isNotEmpty) { Log.print("๐Ÿ”„ Re-fetching route from API: $startLoc -> $endLoc"); // Call getDirectionMap to fetch the route asynchronously getDirectionMap(startLoc, endLoc, []); } } else { List decodedPoints = await compute(decodePolylineIsolate, pointsString); mapEngine.clearPolyline(); mapEngine.polylineCoordinates.clear(); mapEngine.polylineCoordinates.addAll(decodedPoints); if (decodedPoints.isNotEmpty) { passengerLocation = pickupPoint ?? decodedPoints.first; myDestination = destinationPoint ?? decodedPoints.last; } var polyline = Polyline( polylineId: const PolylineId('main_route'), points: mapEngine.polylineCoordinates, width: 6, color: const Color(0xFF2196F3), ); polyLines = {...polyLines, polyline}; } timeToPassengerFromDriverAfterApplied = 0; remainingTime = 0; remainingTimeToPassengerFromDriverAfterApplied = 0; remainingTimeDriverWaitPassenger5Minute = 0; rideTimerBegin = isBeginStatus; isDriverInPassengerWay = false; isDriverArrivePassenger = false; // Safe durationToAdd parsing if (tripData != null && tripData['distance_m'] != null) { var distVal = tripData['distance_m']; if (distVal is Duration) { durationToAdd = distVal; } else if (distVal is num) { durationToAdd = Duration(seconds: distVal.toInt()); } } else { durationToAdd = Duration.zero; } mapSocket.initConnectionWithSocket(); if (isBeginStatus) { _isRideBeginLogicExecuted = true; _isRideStartedProcessed = true; await getDriverCarsLocationToPassengerAfterApplied(); if (driverCarsLocationToPassengerAfterApplied.isNotEmpty && myDestination.latitude != 0 && myDestination.longitude != 0) { await calculateDriverToPassengerRoute( driverCarsLocationToPassengerAfterApplied.last, myDestination, isBeginPhase: true, ); } rideIsBeginPassengerTimer(); runWhenRideIsBegin(); } else if (isPickupStatus) { _isAcceptanceProcessed = true; _isDriverAppliedLogicExecuted = true; await getDriverCarsLocationToPassengerAfterApplied(); if (driverCarsLocationToPassengerAfterApplied.isNotEmpty) { await calculateDriverToPassengerRoute( driverCarsLocationToPassengerAfterApplied.last, passengerLocation, ); startTimerFromDriverToPassengerAfterApplied(); } _startSocketWatchdog(); } update(); } } catch (e) { Log.print("Error getRideStatusFromStartApp: $e"); } } void driverArrivePassenger() { timeToPassengerFromDriverAfterApplied = 0; remainingTime = 0; update(); rideIsBeginPassengerTimer(); } void cancelTimerToPassengerFromDriverAfterApplied() { timerToPassengerFromDriverAfterApplied?.cancel(); } Future postRideDetailsToServer() async { if (mapEngine.polylineCoordinates.isEmpty) return false; LatLng startLoc = mapEngine.polylineCoordinates.first; LatLng endLoc = mapEngine.polylineCoordinates.last; Map payload = { "start_location": '${startLoc.latitude},${startLoc.longitude}', "end_location": '${endLoc.latitude},${endLoc.longitude}', "date": DateTime.now().toString(), "time": DateTime.now().toString(), "endtime": "00:00:00", "price": double.parse(totalPassenger.toString()).toStringAsFixed(2), "passenger_id": box.read(BoxName.passengerID).toString(), "driver_id": "0", "status": "waiting", "carType": box.read(BoxName.carType), "price_for_driver": totalPassenger.toString(), "price_for_passenger": totalME.toString(), "distance": distance.toString(), "passenger_name": box.read(BoxName.name).toString(), "passenger_phone": box.read(BoxName.phone).toString(), "passenger_token": box.read(BoxName.tokenFCM).toString(), "passenger_email": box.read(BoxName.email).toString(), "passenger_wallet": box.read(BoxName.passengerWalletTotal).toString(), "passenger_rating": (passengerRate ?? 5.0).toString(), "start_name": startNameAddress, "end_name": endNameAddress, "duration_text": "${(durationToRide / 60).floor()}", "distance_text": "$distance", "is_wallet": Get.find().isWalletChecked.toString(), "has_steps": Get.find().wayPoints.length > 1 ? 'true' : 'false', "step0": placesCoordinate.length > 0 ? placesCoordinate[0] : "", "step1": placesCoordinate.length > 1 ? placesCoordinate[1] : "", "step2": placesCoordinate.length > 2 ? placesCoordinate[2] : "", "step3": placesCoordinate.length > 3 ? placesCoordinate[3] : "", "step4": placesCoordinate.length > 4 ? placesCoordinate[4] : "", "price_token": priceToken, }; Log.print(' ๐Ÿ“ฆ Payload: $payload'); try { var response = await CRUD().post( link: "${AppLink.server}/ride/rides/add_ride.php", payload: payload); var jsonResponse = (response is String) ? jsonDecode(response) : response; if (jsonResponse['status'] == 'success') { rideId = jsonResponse['message'].toString(); Log.print("โœ… Ride Created ID: $rideId"); return true; } else { Log.print("โŒ Ride Creation Failed: $response"); return false; } } catch (e) { Log.print("โŒ Exception in postRide: $e"); return false; } } Future rideAppliedFromDriver(bool isApplied) async { Log.print('[rideAppliedFromDriver] ๐Ÿš€ Starting logic...'); await getUpdatedRideForDriverApply(rideId); if (['Speed', 'Awfar Car'].contains(box.read(BoxName.carType))) { NotificationController().showNotification('Fixed Price'.tr, 'The captain is responsible for the route.'.tr, 'ding'); } else if (['Comfort', 'Lady'].contains(box.read(BoxName.carType))) { NotificationController().showNotification('Attention'.tr, 'The price may increase if the route changes.'.tr, 'ding'); } isApplied = true; statusRide = 'Apply'; rideConfirm = false; isSearchingWindow = false; _isDriverAppliedLogicExecuted = true; update(); // ุฅูŠู‚ุงู ุฌู„ุจ ุงู„ุณูŠุงุฑุงุช ุงู„ู…ุฌุงูˆุฑุฉ ูˆู…ุณุญู‡ุงุŒ ุจุงุณุชุซู†ุงุก ุงู„ุณุงุฆู‚ ุงู„ุฐูŠ ู‚ุจู„ ุงู„ุทู„ุจ mapEngine.reloadStartApp = false; mapEngine.markers .removeWhere((marker) => marker.markerId.value != driverId.toString()); mapEngine.update(); await getDriverCarsLocationToPassengerAfterApplied(); if (driverCarsLocationToPassengerAfterApplied.isNotEmpty) { LatLng driverPos = driverCarsLocationToPassengerAfterApplied.last; Log.print( '[rideAppliedFromDriver] ๐Ÿ“ Driver at: $driverPos, Passenger at: $passengerLocation'); await calculateDriverToPassengerRoute(driverPos, passengerLocation); mapEngine.fitCameraToPoints(driverPos, passengerLocation); } startTimerFromDriverToPassengerAfterApplied(); } Future getInitialDriverDistanceAndDuration( LatLng driverPos, LatLng passengerPos) async { final String apiUrl = 'https://routec.intaleq.xyz/route'; final String apiKey = Env.mapKeyOsm; final String origin = '${driverPos.latitude},${driverPos.longitude}'; final String dest = '${passengerPos.latitude},${passengerPos.longitude}'; final Uri uri = Uri.parse( '$apiUrl?origin=$origin&destination=$dest&steps=false&overview=false'); try { final response = await http.get(uri, headers: {'X-API-KEY': apiKey}); if (response.statusCode == 200) { final data = jsonDecode(response.body); if (data['status'] == 'ok') { double durationSecondsRaw = (data['duration_s'] as num).toDouble(); int finalDurationSeconds = (durationSecondsRaw * kDurationScalar).toInt(); double distanceMeters = (data['distance_m'] as num).toDouble(); updateDriverRouteMetrics( etaSeconds: finalDurationSeconds, distanceMeters: distanceMeters, ); update(); } } } catch (e) { Log.print('Error getInitialDriverDistanceAndDuration: $e'); } } int durationToPassenger = 0; String distanceByPassenger = ''; Future drawDriverPathOnly(LatLng driverPos, LatLng passengerPos) async { final String apiUrl = 'https://routec.intaleq.xyz/route'; final String apiKey = Env.mapKeyOsm; final String origin = '${driverPos.latitude},${driverPos.longitude}'; final String dest = '${passengerPos.latitude},${passengerPos.longitude}'; final Uri uri = Uri.parse( '$apiUrl?origin=$origin&destination=$dest&steps=false&overview=full'); try { final response = await http.get(uri, headers: {'X-API-KEY': apiKey}); if (response.statusCode == 200) { final data = jsonDecode(response.body); if (data['status'] == 'ok' && data['polyline'] != null) { final String pointsString = data['polyline']; List decodedPoints = await compute(decodePolylineIsolate, pointsString); _currentDriverRoutePoints = decodedPoints; final double decodedDistance = _pathDistanceMeters(decodedPoints); if (decodedDistance > 0) { _currentDriverRouteDistanceMeters = decodedDistance; } polyLines = polyLines .where((p) => !p.polylineId.value.startsWith('driver_route') && p.polylineId.value != 'main_route' && p.polylineId.value != 'route_primary' && p.polylineId.value != 'route_direct') .toSet(); // ุฑุณู… ุฎุท ุตู„ุจ (Solid) ู…ู† ุงู„ุณุงุฆู‚ ู„ู„ุฑุงูƒุจ final Polyline driverSolidPolyline = Polyline( polylineId: const PolylineId('driver_route_solid'), points: decodedPoints, color: Colors.amber, // ู…ุณุงุฑ ุงู„ู‚ุฏูˆู… ุจุงู„ู„ูˆู† ุงู„ุฃุตูุฑ width: 5, ); polyLines.add(driverSolidPolyline); update(); } } } catch (e) { Log.print('Error drawing driver path: $e'); } } void listenToRideStatusStream() { rideStatusStream.listen((rideStatus) { Log.print("Ride Status: $rideStatus"); }, onError: (error) { Log.print("Error in Ride Status Stream: $error"); }); } void start15SecondTimer(String rideId) {} void startUiCountdown() { _uiCountdownTimer?.cancel(); progress = 0; remainingTime = durationTimer; _uiCountdownTimer = Timer.periodic(const Duration(seconds: 1), (timer) { int i = timer.tick; progress = i / durationTimer; remainingTime = durationTimer - i; if (remainingTime <= 0) { timer.cancel(); rideConfirm = false; timeToPassengerFromDriverAfterApplied += durationToPassenger; timerEnded(); } update(); }); } double progress = 0; int durationTimer = 9; void timerEnded() async { runEvery30SecondsUntilConditionMet(); isCancelRidePageShown = false; update(); } late String driverCarModel = '', driverCarMake = '', driverLicensePlate = ''; Future getUpdatedRideForDriverApply(String rideId) async { if (rideId == 'yet' || rideId.isEmpty) return; try { final res = await CRUD().get( link: "${AppLink.server}/ride/rides/getRideOrderID.php", payload: {'passengerID': box.read(BoxName.passengerID).toString()}); if (res != 'failure') { var response = jsonDecode(res); Log.print('getUpdatedRideForDriverApply Response: $response'); if (response['status'] == 'success' && response['data'] != null && response['data'] is Map) { var data = response['data']; driverId = data['driver_id']?.toString() ?? ''; driverPhone = data['phone']?.toString() ?? ''; driverCarMake = data['make']?.toString() ?? ''; model = data['model']?.toString() ?? ''; colorHex = data['color_hex']?.toString() ?? ''; carColor = data['color']?.toString() ?? ''; make = data['make']?.toString() ?? ''; licensePlate = data['car_plate']?.toString() ?? ''; String firstName = data['passengerName']?.toString() ?? ''; String lastName = data['last_name']?.toString() ?? ''; passengerName = lastName.isNotEmpty ? "$firstName $lastName" : firstName; driverName = data['driverName']?.toString() ?? ''; driverToken = data['token']?.toString() ?? ''; carYear = data['year']?.toString() ?? ''; driverRate = data['ratingDriver']?.toString() ?? '5.0'; driverRatingCount = data['ratingCount']?.toString() ?? '0'; driverCompletedRides = data['completedRides']?.toString() ?? '0'; driverTier = data['driverTier']?.toString() ?? 'Verified driver'; update(); } } } catch (e) { Log.print("Error in getUpdatedRideForDriverApply: $e"); } } String getLocationArea(double latitude, double longitude) { LatLng passengerPoint = LatLng(latitude, longitude); String previousCountry = box.read(BoxName.countryCode)?.toString() ?? ''; String newCountry = 'Jordan'; if (isPointInPolygon(passengerPoint, CountryPolygons.jordanBoundary)) { newCountry = 'Jordan'; } else if (isPointInPolygon( passengerPoint, CountryPolygons.syriaBoundary)) { newCountry = 'Syria'; } else if (isPointInPolygon( passengerPoint, CountryPolygons.egyptBoundary)) { newCountry = 'Egypt'; } else { newCountry = 'Jordan'; } box.write(BoxName.countryCode, newCountry); box.write(BoxName.serverChosen, AppLink.server); if (newCountry != previousCountry) {} return newCountry; } bool isPointInPolygon(LatLng point, List polygon) { int intersections = 0; for (int i = 0; i < polygon.length; i++) { LatLng vertex1 = polygon[i]; LatLng vertex2 = polygon[(i + 1) % polygon.length]; if (_rayIntersectsSegment(point, vertex1, vertex2)) { intersections++; } } return intersections % 2 != 0; } bool _rayIntersectsSegment(LatLng point, LatLng vertex1, LatLng vertex2) { double px = point.longitude; double py = point.latitude; double v1x = vertex1.longitude; double v1y = vertex1.latitude; double v2x = vertex2.longitude; double v2y = vertex2.latitude; if ((py < v1y && py < v2y) || (py > v1y && py > v2y)) { return false; } double intersectX = v1x + (py - v1y) * (v2x - v1x) / (v2y - v1y); return intersectX > px; } String generateTrackingLink(String rideId, String driverId) { String cleanRideId = rideId.toString().trim(); String cleanDriverId = driverId.toString().trim(); const String secretSalt = "Siro_Secure_Track_2025"; String rawString = "$cleanRideId$cleanDriverId$secretSalt"; var bytes = utf8.encode(rawString); var digest = md5.convert(bytes); String token = digest.toString(); return "https://intaleqapp.com/track/index.php?id=$cleanRideId&token=$token"; } calcualateDistsanceInMetet(LatLng prev, current) async { double distance2 = Geolocator.distanceBetween( prev.latitude, prev.longitude, current.latitude, current.longitude, ); return distance2; } uploadPassengerLocation() async { await CRUD().post(link: AppLink.addpassengerLocation, payload: { "passengerId": box.read(BoxName.passengerID), "lat": passengerLocation.latitude.toString(), "lng": passengerLocation.longitude.toString(), "rideId": rideId.toString() }); } void _showRideStartNotifications() { if (['Speed', 'Awfar Car'].contains(box.read(BoxName.carType))) { NotificationController().showNotification('Fixed Price'.tr, 'The captain is responsible for the route.'.tr, 'ding'); } else if (['Comfort', 'Lady'].contains(box.read(BoxName.carType))) { NotificationController().showNotification('Attention'.tr, 'The price may increase if the route changes.'.tr, 'ding'); } } bool promoTaken = false; final promo = TextEditingController(); void applyPromoCodeToPassenger(BuildContext context) async { if (promoTaken == true) { MyDialog().getDialog('Promo Already Used'.tr, 'You have already used this promo code.'.tr, () => Get.back()); return; } if (!promoFormKey.currentState!.validate()) return; try { final res = await CRUD().post(link: AppLink.getPrices, payload: { 'distance': distance.toString(), 'durationToRide': durationToRide.toString(), 'startNameAddress': startNameAddress, 'endNameAddress': endNameAddress, 'destLat': myDestination.latitude.toString(), 'destLng': myDestination.longitude.toString(), 'passengerLat': newMyLocation.latitude.toString(), 'passengerLng': newMyLocation.longitude.toString(), 'walletVal': box.read(BoxName.passengerWalletTotal)?.toString() ?? '0', 'activeMenuWaypointCount': activeMenuWaypointCount.toString(), 'promo_code': promo.text, 'passenger_id': box.read(BoxName.passengerID), 'country': box.read(BoxName.countryCode) ?? '', }); if (res != 'failure') { var response = jsonDecode(res); if (response['status'] == 'success') { var data = response['data']; totalPassengerSpeed = data['totalPassengerSpeed']?.toString() ?? '0'; totalPassengerBalash = data['totalPassengerBalash']?.toString() ?? '0'; totalPassengerComfort = data['totalPassengerComfort']?.toString() ?? '0'; totalPassengerElectric = data['totalPassengerElectric']?.toString() ?? '0'; totalPassengerLady = data['totalPassengerLady']?.toString() ?? '0'; totalPassengerScooter = data['totalPassengerScooter']?.toString() ?? '0'; totalPassengerVan = data['totalPassengerVan']?.toString() ?? '0'; totalPassengerRayehGai = data['totalPassengerRayehGai']?.toString() ?? '0'; totalPassengerRayehGaiComfort = data['totalPassengerRayehGaiComfort']?.toString() ?? '0'; totalPassengerRayehGaiBalash = data['totalPassengerRayehGaiBalash']?.toString() ?? '0'; promoTaken = true; update(); Confetti.launch( context, options: const ConfettiOptions(particleCount: 100, spread: 70, y: 0.6), ); } else { MyDialog().getDialog( 'Promo Error'.tr, response['message']?.toString() ?? 'Invalid Promo'.tr, () => Get.back()); return; } } Get.back(); await Future.delayed(const Duration(milliseconds: 120)); } catch (e) { Get.snackbar('Error'.tr, e.toString(), backgroundColor: AppColor.redColor); } } double getDistanceFromText(String distanceText) { String distanceValue = distanceText.replaceAll(RegExp(r'[^0-9.]+'), ''); double distance = double.parse(distanceValue); return distance; } double costForDriver = 0; Future bottomSheet() async { durationToAdd = Duration(seconds: durationToRide); hours = durationToAdd.inHours; minutes = (durationToAdd.inMinutes % 60).round(); final DateTime currentTime = DateTime.now(); newTime = currentTime.add(durationToAdd); try { final res = await CRUD().post(link: AppLink.getPrices, payload: { 'distance': distance.toString(), 'durationToRide': durationToRide.toString(), 'startNameAddress': startNameAddress, 'endNameAddress': endNameAddress, 'destLat': myDestination.latitude.toString(), 'destLng': myDestination.longitude.toString(), 'passengerLat': newMyLocation.latitude.toString(), 'passengerLng': newMyLocation.longitude.toString(), 'walletVal': box.read(BoxName.passengerWalletTotal)?.toString() ?? '0', 'activeMenuWaypointCount': activeMenuWaypointCount.toString(), 'passenger_id': box.read(BoxName.passengerID) ?? '', 'country': box.read(BoxName.countryCode) ?? '', }); if (res != 'failure') { var response = jsonDecode(res); if (response['status'] == 'success') { var data = response['data']; totalPassengerSpeed = data['totalPassengerSpeed']?.toString() ?? '0'; totalPassengerBalash = data['totalPassengerBalash']?.toString() ?? '0'; totalPassengerComfort = data['totalPassengerComfort']?.toString() ?? '0'; totalPassengerElectric = data['totalPassengerElectric']?.toString() ?? '0'; totalPassengerLady = data['totalPassengerLady']?.toString() ?? '0'; totalPassengerScooter = data['totalPassengerScooter']?.toString() ?? '0'; totalPassengerVan = data['totalPassengerVan']?.toString() ?? '0'; totalPassengerRayehGai = data['totalPassengerRayehGai']?.toString() ?? '0'; totalPassengerRayehGaiComfort = data['totalPassengerRayehGaiComfort']?.toString() ?? '0'; totalPassengerRayehGaiBalash = data['totalPassengerRayehGaiBalash']?.toString() ?? '0'; // Save price_token from server response priceToken = response['price_token']?.toString() ?? ''; totalPassenger = totalPassengerSpeed; totalCostPassenger = totalPassenger; } } } catch (e) { Log.print("Error fetching prices: $e"); } update(); mapEngine.changeBottomSheetShown(forceValue: true); } // ุญุณุงุจ ุงู„ู…ุณุงุฑ ุจูŠู† ุงู„ุณุงุฆู‚ ูˆุงู„ุฑุงูƒุจ ูˆุนุฑุถู‡ ุนู„ู‰ ุงู„ุฎุฑูŠุทุฉ. // ุชุฑุณู„ ู‡ุฐู‡ ุงู„ุฏุงู„ุฉ ุทู„ุจู‹ุง ู„ู„ุฎุงุฏู… ู„ู„ุญุตูˆู„ ุนู„ู‰ ุฅุญุฏุงุซูŠุงุช ุงู„ู…ุณุงุฑ ูˆุชููƒ ุชุดููŠุฑู‡. // ุซู… ุชู‚ูˆู… ุจุชุญุฏูŠุซ ุงู„ู…ุณุงูุฉ ูˆุงู„ูˆู‚ุช ูˆุนุฑุถ ุงู„ุฎุทูˆุท ุงู„ู…ู†ุงุณุจุฉ ุนู„ู‰ ุงู„ุฎุฑูŠุทุฉ. Future calculateDriverToPassengerRoute( LatLng driverPos, LatLng passengerPos, {bool isBeginPhase = false}) async { final Map queryParams = { 'fromLat': driverPos.latitude.toString(), 'fromLng': driverPos.longitude.toString(), 'toLat': passengerPos.latitude.toString(), 'toLng': passengerPos.longitude.toString(), }; final uri = Uri.parse(AppLink.mapSaasRoute).replace(queryParameters: queryParams); Log.print('๐Ÿ“ Calculating Driver Route: $uri'); try { final response = await http.get(uri, headers: { 'x-api-key': Env.mapSaasKey, }).timeout(const Duration(seconds: 20)); if (response.statusCode == 200) { final responseData = json.decode(response.body); var routeData = responseData['routes'] != null ? responseData['routes'][0] : responseData; double durationSecondsRaw = (routeData['duration'] as num).toDouble(); int finalDurationSeconds = (durationSecondsRaw * kDurationScalar).toInt(); double distanceMeters = (routeData['distance'] as num).toDouble(); updateDriverRouteMetrics( etaSeconds: finalDurationSeconds, distanceMeters: distanceMeters, ); _currentDriverRouteDistanceMeters = distanceMeters; _currentDriverRouteDurationSeconds = finalDurationSeconds; int minutes = (finalDurationSeconds / 60).floor(); int seconds = finalDurationSeconds % 60; stringRemainingTimeToPassenger = '$minutes:${seconds.toString().padLeft(2, '0')}'; Log.print( 'โœ… Driver Route Info: $minutes min, ${distanceMeters.toInt()} m'); String pointsString = routeData['points'] ?? routeData['geometry'] ?? ""; if (pointsString.isNotEmpty) { List decodedPoints = await compute(decodePolylineIsolate, pointsString); _currentDriverRoutePoints = decodedPoints; final double decodedDistance = _pathDistanceMeters(decodedPoints); if (decodedDistance > 0) { _currentDriverRouteDistanceMeters = decodedDistance; } // ู…ุณุญ ูƒู„ ุงู„ุณู„ู…ุงุช ุงู„ุณุงุจู‚ุฉ (ุงู„ุฎุท ุงู„ู…ุณุชู…ุฑ ูˆุงู„ู…ุชู‚ุทุน ุนู„ู‰ ุญุฏ ุณูˆุงุก) polyLines = polyLines .where((p) => !p.polylineId.value.startsWith('driver_route') && p.polylineId.value != 'main_route' && p.polylineId.value != 'route_primary' && p.polylineId.value != 'route_direct') .toSet(); if (isBeginPhase) { // ุญุงู„ุฉ Begin: ู„ุง ู†ุฑุณู… ู…ุณุงุฑ ุงู„ุณุงุฆู‚ ุงู„ู‚ุฏูŠู… ุฅุทู„ุงู‚ุงู‹ ู„ุฃู†ู‡ ูˆุตู„ ูˆุงู„ุขู† ุงู„ุฑุญู„ุฉ ุณุชุจุฏุฃ polyLines = polyLines .where((p) => !p.polylineId.value.startsWith('driver_route')) .toSet(); polyLines = { ...polyLines, Polyline( polylineId: const PolylineId('main_route'), points: decodedPoints, color: const Color(0xFF2196F3), width: 6, ) }; } else { // ู…ุณุญ ุงู„ุณู„ู…ุงุช ุงู„ู‚ุฏูŠู…ุฉ ุฃูˆู„ุงู‹ polyLines = polyLines .where((p) => !p.polylineId.value.startsWith('driver_route') && p.polylineId.value != 'main_route' && p.polylineId.value != 'route_primary' && p.polylineId.value != 'route_direct') .toSet(); // ุญุงู„ุฉ Apply/Arrived: ุฎุท ู…ุชุตู„ ุตู„ุจ ุจุฏู„ ุงู„ู…ุชู‚ุทุน polyLines = { ...polyLines, Polyline( polylineId: const PolylineId('driver_route_solid'), points: decodedPoints, color: Colors.amber, // ู…ุณุงุฑ ุงู„ู‚ุฏูˆู… ุจุงู„ู„ูˆู† ุงู„ุฃุตูุฑ width: 5, ) }; } } mapEngine.fitCameraToPoints(driverPos, passengerPos); _updatePassengerWalkLine(); update(); } } catch (e) { Log.print('โŒ Error calculating driver route: $e'); } } Future checkAndRecalculateIfDeviated( LatLng driverPos, { double? heading, double? speed, }) async { if (_isRecalculatingRoute || _currentDriverRoutePoints.isEmpty) return; double minDistance = 100000.0; int closestIdx = 0; for (int idx = 0; idx < _currentDriverRoutePoints.length; idx++) { final point = _currentDriverRoutePoints[idx]; double dist = Geolocator.distanceBetween(driverPos.latitude, driverPos.longitude, point.latitude, point.longitude); if (dist < minDistance) { minDistance = dist; closestIdx = idx; } } final bool distanceDeviation = minDistance > _deviationThresholdMeters; final bool headingDeviation = _isHeadingAwayFromRoute( heading: heading, speed: speed, closestRouteIndex: closestIdx, distanceFromRouteMeters: minDistance, ); if (!headingDeviation) { _routeHeadingMismatchCount = 0; } else { _routeHeadingMismatchCount++; } if (distanceDeviation || _routeHeadingMismatchCount >= 2) { Log.print( "โš ๏ธ Driver deviated (${minDistance.toStringAsFixed(1)} m, heading mismatch: $_routeHeadingMismatchCount). Recalculating route..."); _routeHeadingMismatchCount = 0; _isRecalculatingRoute = true; if (statusRide == 'Begin' || statusRide == 'Arrived' || currentRideState.value == RideState.inProgress || currentRideState.value == RideState.driverArrived) { await calculateDriverToPassengerRoute(driverPos, myDestination, isBeginPhase: true); } else { await calculateDriverToPassengerRoute(driverPos, passengerLocation); } _isRecalculatingRoute = false; } } // ุชุญุฏูŠุซ ุงู„ุฌุฒุก ุงู„ู…ุชุจู‚ูŠ ู…ู† ุงู„ู…ุณุงุฑ ูˆุงู„ู…ุณุงูุฉ ูˆุงู„ูˆู‚ุช ุจุดูƒู„ ุชูุงุนู„ูŠ. // ุชุญุฏุฏ ุงู„ุฏุงู„ุฉ ุฃู‚ุฑุจ ู†ู‚ุทุฉ ู„ู„ุณุงุฆู‚ ุนู„ู‰ ุงู„ู…ุณุงุฑ ุงู„ุญุงู„ูŠ ูˆุชู‚ูˆู… ุจู‚ุต ุงู„ู†ู‚ุงุท ุงู„ุณุงุจู‚ุฉ. // ุซู… ุชุนูŠุฏ ุญุณุงุจ ุงู„ู…ุณุงูุฉ ูˆุงู„ูˆู‚ุช ุงู„ู…ุชุจู‚ูŠูŠู† ู…ุญู„ูŠุงู‹ ูˆุชุญุฏูŠุซ ุงู„ุฎุทูˆุท ุนู„ู‰ ุงู„ุฎุฑูŠุทุฉ. void updateRemainingRoute(LatLng driverPos, {bool updateEta = true}) { if (_currentDriverRoutePoints.isEmpty) return; int closestIdx = 0; double minDistance = double.infinity; for (int i = 0; i < _currentDriverRoutePoints.length; i++) { double dist = Geolocator.distanceBetween( driverPos.latitude, driverPos.longitude, _currentDriverRoutePoints[i].latitude, _currentDriverRoutePoints[i].longitude); if (dist < minDistance) { minDistance = dist; closestIdx = i; } } if (minDistance < 150.0) { List remainingPoints = _currentDriverRoutePoints.sublist(closestIdx); if (updateEta) { final double remainingDistance = _pathDistanceMeters(remainingPoints); int remainingDuration = _currentDriverRouteDurationSeconds; if (remainingDistance > 0 && _currentDriverRouteDistanceMeters > 0 && _currentDriverRouteDurationSeconds > 0) { remainingDuration = ((_currentDriverRouteDurationSeconds * remainingDistance) / _currentDriverRouteDistanceMeters) .round(); } remainingDuration = max(0, remainingDuration); updateDriverRouteMetrics( etaSeconds: remainingDuration, distanceMeters: remainingDistance, ); } polyLines = polyLines .where((p) => !p.polylineId.value.startsWith('driver_route') && p.polylineId.value != 'route_primary' && p.polylineId.value != 'route_direct') .toSet(); if (statusRide == 'Begin' || currentRideState.value == RideState.inProgress) { polyLines = { ...polyLines.where((p) => !p.polylineId.value.startsWith('driver_route')), Polyline( polylineId: const PolylineId('main_route'), points: remainingPoints, color: const Color(0xFF2196F3), width: 6, ), }; } else { polyLines = { ...polyLines, Polyline( polylineId: const PolylineId('driver_route_solid'), points: remainingPoints, color: Colors.amber, width: 5, ), }; } _updatePassengerWalkLine(); update(); } } Future getDriverCarsLocationToPassengerAfterApplied() async { bool isRideActive = (statusRide == 'Apply' || statusRide == 'Arrived' || statusRide == 'Begin' || currentRideState.value == RideState.driverApplied || currentRideState.value == RideState.driverArrived || currentRideState.value == RideState.inProgress); if (!isRideActive || statusRide == 'Finished' || statusRide == 'Cancel' || currentRideState.value == RideState.finished || currentRideState.value == RideState.noRide || currentRideState.value == RideState.preCheckReview) { return; } if (_isFetchingDriverLocation) return; _isFetchingDriverLocation = true; try { var res = await CRUD().get( link: AppLink.getDriverCarsLocationToPassengerAfterApplied, payload: {'driver_id': driverId}); if (res != 'failure') { var datadriverLocation = jsonDecode(res); if (datadriverLocation['message'] != null && datadriverLocation['message'].isNotEmpty) { var _data = datadriverLocation['message'][0]; LatLng newDriverPos = LatLng( double.parse(_data['latitude'].toString()), double.parse(_data['longitude'].toString())); double newHeading = double.tryParse(_data['heading']?.toString() ?? '0') ?? 0.0; double speed = double.tryParse(_data['speed']?.toString() ?? '0') ?? 0; if (driverCarsLocationToPassengerAfterApplied.length > 10) { driverCarsLocationToPassengerAfterApplied.removeAt(0); } driverCarsLocationToPassengerAfterApplied.add(newDriverPos); checkAndRecalculateIfDeviated( newDriverPos, heading: newHeading, speed: speed, ); updateRemainingRoute(newDriverPos); if (statusRide == 'Begin' || currentRideState.value == RideState.inProgress) { double zoom = 16.5; if (speed > 0) { zoom = 17.0 - ((speed - 10) / 70) * 2.5; zoom = zoom.clamp(14.5, 17.0); } if (mapEngine.mapController != null) { mapEngine.mapController!.animateCamera( CameraUpdate.newLatLngZoom(newDriverPos, zoom)); } } mapEngine.clearMarkersExceptStartEndAndDriver(); reloadMarkerDriverCarsLocationToPassengerAfterApplied( datadriverLocation); } } update(); } catch (e) { Log.print('Error fetching driver location: $e'); } finally { _isFetchingDriverLocation = false; } } void reloadMarkerDriverCarsLocationToPassengerAfterApplied( dynamic datadriverLocation) { if (datadriverLocation == null || datadriverLocation['message'] == null || datadriverLocation['message'].isEmpty) { return; } var driverData = datadriverLocation['message'][0]; LatLng newPosition = LatLng(double.parse(driverData['latitude'].toString()), double.parse(driverData['longitude'].toString())); double newHeading = double.tryParse(driverData['heading'].toString()) ?? 0.0; String icon; if (driverData['model'].toString().contains('ุฏุฑุงุฌุฉ') || driverData['make'].toString().contains('ุฏุฑุงุฌุฉ')) { icon = mapEngine.motoIcon; } else if (driverData['gender'] == 'Female') { icon = mapEngine.ladyIcon; } else { icon = mapEngine.carIcon; } final String markerId = 'assigned_driver_marker'; final mId = MarkerId(markerId); final existingMarker = markers.cast().firstWhere( (m) => m?.markerId == mId, orElse: () => null, ); if (existingMarker != null) { mapEngine.smoothlyUpdateMarker( existingMarker, newPosition, newHeading, icon); } else { markers = { ...markers, Marker( markerId: mId, position: newPosition, rotation: newHeading, icon: InlqBitmap.fromStyleImage(icon), anchor: const Offset(0.5, 0.5), ), }; update(); } } void updateDriverMarker(LatLng position, double heading) { const String markerId = 'assigned_driver_marker'; const mId = MarkerId(markerId); // Choose icon based on vehicle type String icon; if (model.contains('ุฏุฑุงุฌุฉ') || make.contains('ุฏุฑุงุฌุฉ')) { icon = mapEngine.motoIcon; } else if (gender == 'Female') { icon = mapEngine.ladyIcon; } else { icon = mapEngine.carIcon; } final existingMarker = markers.cast().firstWhere( (m) => m?.markerId == mId, orElse: () => null, ); if (existingMarker != null) { mapEngine.smoothlyUpdateMarker(existingMarker, position, heading, icon); } else { markers = { ...markers, Marker( markerId: mId, position: position, icon: InlqBitmap.fromStyleImage(icon), rotation: heading, anchor: const Offset(0.5, 0.5), ), }; update(); } } Future runEvery30SecondsUntilConditionMet() async { double tripDurationInMinutes = durationToPassenger / 5; int loopCount = tripDurationInMinutes.ceil(); for (var i = 0; i < loopCount; i++) { await Future.delayed(const Duration(seconds: 5)); if (rideTimerBegin == true || statusRide == 'Apply') { await getDriverCarsLocationToPassengerAfterApplied(); } } } Future runWhenRideIsBegin() async { double tripDurationInMinutes = durationToRide / 6; int loopCount = tripDurationInMinutes.ceil(); mapEngine.clearMarkersExceptStartEndAndDriver(); for (var i = 0; i < loopCount; i++) { await Future.delayed(const Duration(seconds: 4)); await getDriverCarsLocationToPassengerAfterApplied(); } } // ุจุฏุก ู…ุฑุงู‚ุจ ุงุชุตุงู„ ุงู„ู…ู‚ุจุณ (Socket Watchdog). // ูŠุชุญู‚ู‚ ุฏูˆุฑูŠุงู‹ ูƒู„ 5 ุซูˆุงู†ู ู…ู† ุขุฎุฑ ุชุญุฏูŠุซ ุชู… ุงุณุชู„ุงู…ู‡ ุนุจุฑ ุงู„ู…ู‚ุจุณ. // ููŠ ุญุงู„ ูˆุฌูˆุฏ ุฎู…ูˆู„ ู„ุฃูƒุซุฑ ู…ู† 15 ุซุงู†ูŠุฉุŒ ูŠุฌู„ุจ ุงู„ู…ูˆู‚ุน ุนุจุฑ ูˆุงุฌู‡ุฉ ุงู„ุชุทุจูŠู‚ ูƒุทู„ุจ ู…ูุฑุฏ. // ูˆุฅุฐุง ุฒุงุฏ ุงู„ุฎู…ูˆู„ ุนู† 30 ุซุงู†ูŠุฉุŒ ูŠุจุฏุฃ ุขู„ูŠุฉ ุงู„ุงู‚ุชุฑุงุน ุงู„ุฏูˆุฑูŠ ูƒุฎูŠุงุฑ ุงุญุชูŠุงุทูŠ. void _startSocketWatchdog() { _watchdogTimer?.cancel(); Log.print("๐Ÿ‘€ Starting Socket Watchdog (Hybrid Mode)..."); _watchdogTimer = Timer.periodic(const Duration(seconds: 5), (timer) async { if (currentRideState.value != RideState.driverApplied && currentRideState.value != RideState.driverArrived && currentRideState.value != RideState.inProgress) { timer.cancel(); return; } final lastTime = mapSocket.lastDriverLocationTime ?? DateTime.now().subtract(const Duration(minutes: 1)); final difference = DateTime.now().difference(lastTime).inSeconds; if (difference < 15 && mapSocket.isSocketConnected) { if (_locationPollingTimer != null && _rideAcceptedViaSource == "Socket") { Log.print("โœ… Socket recovered. Stopping polling fallback."); stopDriverLocationPolling(); } } else if (difference >= 15 && difference < 30) { Log.print("โš ๏ธ Socket silent for ${difference}s. Single API Poll..."); await getDriverCarsLocationToPassengerAfterApplied(); try { String statusFromServer = await getRideStatus(rideId); _handleServerStatusTransition(statusFromServer); } catch (e) { Log.print("Error polling ride status in watchdog: $e"); } } else if (difference >= 30) { if (_locationPollingTimer == null) { Log.print( "๐Ÿ”ด Socket dead for ${difference}s. Activating polling fallback!"); _startDriverLocationPollingWithTimer(); } else { await getDriverCarsLocationToPassengerAfterApplied(); try { String statusFromServer = await getRideStatus(rideId); _handleServerStatusTransition(statusFromServer); } catch (e) { Log.print("Error polling ride status in watchdog: $e"); } } } }); } // ุจุฏุก ุงู„ุงู‚ุชุฑุงุน ุงู„ุฏูˆุฑูŠ ู„ู…ูˆู‚ุน ุงู„ุณุงุฆู‚ ูƒุฎูŠุงุฑ ุงุญุชูŠุงุทูŠ ุนู†ุฏ ุชูˆู‚ู ุฃูˆ ูุดู„ ุงู„ู…ู‚ุจุณ. // ูŠู‚ูˆู… ู‡ุฐุง ุงู„ุชูˆู‚ูŠุช ุฏูˆุฑูŠุงู‹ ูƒู„ 6 ุซูˆุงู†ู ุจุฌู„ุจ ู…ูˆู‚ุน ุงู„ุณุงุฆู‚ ูˆุชุญุฏูŠุซ ุญุงู„ุฉ ุงู„ุฑุญู„ุฉ. void _startDriverLocationPollingWithTimer() { Log.print("๐Ÿ“ Starting Driver Location Polling (6s interval)"); _locationPollingTimer?.cancel(); _locationPollingTimer = Timer.periodic(Duration(seconds: 6), (timer) async { if (currentRideState.value == RideState.finished || currentRideState.value == RideState.cancelled || currentRideState.value == RideState.noRide) { timer.cancel(); return; } await getDriverCarsLocationToPassengerAfterApplied(); try { String statusFromServer = await getRideStatus(rideId); _handleServerStatusTransition(statusFromServer); } catch (e) { Log.print("Error polling ride status in fallback timer: $e"); } }); } void _handleServerStatusTransition(String status) { String lowerStatus = status.toLowerCase(); Log.print( "๐Ÿ”„ _handleServerStatusTransition status: $lowerStatus | Current state: ${currentRideState.value}"); if (lowerStatus == 'arrived' && currentRideState.value != RideState.driverArrived) { processDriverArrival("Polling"); } else if ((lowerStatus == 'begin' || lowerStatus == 'started' || lowerStatus == 'inprogress') && currentRideState.value != RideState.inProgress) { processRideBegin(source: "Polling"); } else if ((lowerStatus == 'finished' || lowerStatus == 'ended') && currentRideState.value != RideState.finished && currentRideState.value != RideState.preCheckReview) { Log.print( "๐Ÿ Polling detected Finished. Releasing ride and moving to rating."); stopAllTimers(); currentRideState.value = RideState.preCheckReview; tripFinishedFromDriver(); _checkLastRideForReview(); } else if (lowerStatus == 'cancelled' || lowerStatus == 'cancel') { processRideCancelledByDriver({'reason': 'Cancelled by driver'}, source: "Polling"); } } void stopDriverLocationPolling() { Log.print("๐Ÿ›‘ Stopping Location Polling"); _locationPollingTimer?.cancel(); _locationPollingTimer = null; } Future _addRideToWaitingTable() async { try { LatLng startLoc = mapEngine.polylineCoordinates.first; LatLng endLoc = mapEngine.polylineCoordinates.last; await CRUD().post(link: AppLink.addWaitingRide, payload: { 'id': rideId.toString(), "start_location": '${startLoc.latitude},${startLoc.longitude}', "end_location": '${endLoc.latitude},${endLoc.longitude}', "date": DateTime.now().toString(), "time": DateTime.now().toString(), "price": double.parse(totalPassenger.toString()).toStringAsFixed(2), 'passenger_id': box.read(BoxName.passengerID).toString(), 'status': 'waiting', 'carType': box.read(BoxName.carType), 'passengerRate': passengerRate.toStringAsFixed(2), 'price_for_passenger': totalME.toStringAsFixed(2), 'distance': distance.toStringAsFixed(1), 'duration': duration.toStringAsFixed(1), 'payment_method': Get.find().isWalletChecked ? 'wallet' : 'cash', "passenger_wallet": box.read(BoxName.passengerWalletTotal).toString(), }); Log.print('[WaitingTable] Ride $rideId added to waiting_ride table.'); } catch (e) { Log.print('Error adding ride to waiting_ride table: $e'); } } double totalME = 0; double passengerRate = 5; double comfortPrice = 45; double speedPrice = 40; double mashwariPrice = 40; double familyPrice = 55; double deliveryPrice = 1.2; Future selectDriverAndCarForMishwariTrip() async { try { // Logic for mishwari trip driver selection } catch (e) { Log.print("Error Mishwari select: $e"); } } List driversForMishwari = []; final Rx selectedDateTime = DateTime.now().obs; void updateDateTime(DateTime newDateTime) { selectedDateTime.value = newDateTime; } Future mishwariOption() async { isLoading = true; update(); await selectDriverAndCarForMishwariTrip(); Future.delayed(Duration.zero); isLoading = false; update(); Get.to(() => CupertinoDriverListWidget()); } bool isLoading = false; var driverIdVip = ''; Future saveTripData( Map driver, DateTime tripDateTime) async { try { LatLng startLoc = mapEngine.polylineCoordinates.first; Map tripData = { 'id': driver['driver_id'].toString(), 'phone': driver['phone'], 'gender': driver['gender'], 'name': driver['NAME'], 'name_english': driver['name_english'], 'address': driver['address'], 'religion': driver['religion'] ?? 'UnKnown', 'age': driver['age'].toString(), 'education': driver['education'] ?? 'UnKnown', 'license_type': driver['license_type'] ?? 'UnKnown', 'national_number': driver['national_number'] ?? 'UnKnown', 'car_plate': driver['car_plate'], 'make': driver['make'], 'model': driver['model'], 'year': driver['year'].toString(), 'color': driver['color'], 'color_hex': driver['color_hex'], 'displacement': driver['displacement'], 'fuel': driver['fuel'], 'token': driver['token'], 'rating': driver['rating'].toString(), 'countRide': driver['ride_count'].toString(), 'passengerId': box.read(BoxName.passengerID), 'timeSelected': tripDateTime.toIso8601String(), 'status': 'pending', 'startNameAddress': startNameAddress.toString(), 'locationCoordinate': '${startLoc.latitude},${startLoc.longitude}', }; Log.print('tripData: $tripData'); var response = await CRUD().post(link: AppLink.addMishwari, payload: tripData); if (response != 'failure') { var id = response['message']['id'].toString(); await CRUD() .post(link: '${AppLink.server}/ride/rides/add.php', payload: { "start_location": '${startLoc.latitude},${startLoc.longitude}', "end_location": '${startLoc.latitude},${startLoc.longitude}', "date": DateTime.now().toString(), "time": DateTime.now().toString(), "endtime": DateTime.now().add(const Duration(hours: 2)).toString(), "price": '50', "passenger_id": box.read(BoxName.passengerID).toString(), "driver_id": driver['driver_id'].toString(), "status": "waiting", 'carType': 'vip', "price_for_driver": '50', "price_for_passenger": '50', "distance": '20', "paymentMethod": 'cash', }).then((value) { if (value is String) { final parsedValue = jsonDecode(value); rideId = parsedValue['message']; } else if (value is Map) { rideId = value['message']; } }); driverIdVip = driver['driver_id'].toString(); driverId = driver['driver_id'].toString(); DateTime timeSelected = DateTime.parse(tripDateTime.toIso8601String()); Get.find().scheduleNotificationsForTimeSelected( "Your trip is scheduled".tr, "Don't forget your ride!".tr, "tone1", timeSelected); await NotificationService.sendNotification( category: 'OrderVIP', target: driver['token'].toString(), title: 'OrderVIP'.tr, body: '$rideId - VIP Trip', isTopic: false, tone: 'tone1', driverList: [ id, rideId, driver['id'], passengerLocation.latitude.toString(), startNameAddress.toString(), passengerLocation.longitude.toString(), (box.read(BoxName.name).toString().split(' ')[0]).toString(), box.read(BoxName.passengerID).toString(), box.read(BoxName.phone).toString(), box.read(BoxName.email).toString(), box.read(BoxName.passengerPhotoUrl).toString(), box.read(BoxName.tokenFCM).toString(), (driver['token'].toString()), ], ); if (response['message'] == "Trip updated successfully") { mySnackbarSuccess("Trip updated successfully".tr); await NotificationService.sendNotification( category: 'Order VIP Canceld', target: response['previous_driver_token'].toString(), title: 'Order VIP Canceld'.tr, body: 'Passenger cancel order'.tr, isTopic: false, tone: 'cancel', driverList: [], ); } isBottomSheetShown = false; update(); Get.to(() => VipWaittingPage()); } else { throw Exception('Failed to save trip'); } } catch (e) { Get.snackbar('Error'.tr, 'Failed to book trip: $e'.tr, backgroundColor: AppColor.redColor); } } Future cancelVip(String token, tripId) async { var res = await CRUD() .post(link: AppLink.cancelMishwari, payload: {'id': tripId}); if (res != 'failure') { Get.back(); mySnackbarSuccess('You canceled VIP trip'.tr); } } void sendToDriverAgain(String token) { NotificationService.sendNotification( category: 'Order VIP Canceld', target: token.toString(), title: 'Order VIP Canceld'.tr, body: 'Passenger cancel order'.tr, isTopic: false, tone: 'cancel', driverList: [], ); } Set notifiedDrivers = {}; Future processDriverArrival(String source) async { if (currentRideState.value == RideState.driverArrived || _isArrivalProcessed) { Log.print("โœ‹ Ignored Arrival from $source. Already processed."); return; } _isArrivalProcessed = true; Log.print("๐Ÿš– Driver Arrived via $source! Processing..."); currentRideState.value = RideState.driverArrived; statusRide = 'Arrived'; await RideLiveNotification.showDriverArrived(driverName); uiInteractions.driverArrivePassengerDialoge(); startTimerDriverWaitPassenger5Minute(); if (mapEngine.polylineCoordinates.isNotEmpty) { mapEngine.playRouteAnimation( mapEngine.polylineCoordinates, mapEngine.lastComputedBounds); } if (driverCarsLocationToPassengerAfterApplied.isNotEmpty && myDestination.latitude != 0 && myDestination.longitude != 0) { await calculateDriverToPassengerRoute( driverCarsLocationToPassengerAfterApplied.last, myDestination, isBeginPhase: true, ); } update(); } Future processRideFinished(List driverList, {String source = "Unknown"}) async { if (currentRideState.value == RideState.finished || _isFinishProcessed) { Log.print("โœ‹ Ignored Finish Request from $source. Already Finished."); return; } _isFinishProcessed = true; Log.print("๐Ÿ Ride Finished via $source."); currentRideState.value = RideState.finished; mapSocket.disposeRideSocket(); stopDriverLocationPolling(); if (Get.isRegistered()) { Get.find().stopRecording(); } if (Get.isDialogOpen == true) Get.back(); NotificationController().showNotification( 'Alert'.tr, "Please make sure not to leave any personal belongings in the car.".tr, 'tone1', ); IosLiveActivityService.endRideActivity(); PipService.disablePip(); await RideLiveNotification.cancel(); if (driverList.length >= 4) { String price = driverList[3].toString(); Get.offAll(() => RateDriverFromPassenger(), arguments: { 'driverId': driverList[0].toString(), 'rideId': driverList[1].toString(), 'price': price }); } } Future processRideCancelledByDriver(dynamic data, {String source = "Unknown"}) async { if (_isCancelProcessed) return; _isCancelProcessed = true; stopAllTimers(); if (Get.isDialogOpen == true) Get.back(); await RideLiveNotification.cancel(); IosLiveActivityService.endRideActivity(); PipService.disablePip(); Get.defaultDialog( title: "Sorry ๐Ÿ˜”".tr, titleStyle: const TextStyle(color: Colors.red, fontWeight: FontWeight.bold), barrierDismissible: false, content: Column( children: [ const Icon(Icons.cancel_presentation, size: 50, color: Colors.redAccent), const SizedBox(height: 10), Text( "The driver cancelled the trip for an emergency reason.\nDo you want to search for another driver immediately?" .tr, textAlign: TextAlign.center, ), ], ), actions: [ TextButton( onPressed: () { Get.back(); handleNoDriverFound(); }, child: Text("Cancel Trip".tr, style: const TextStyle(color: Colors.grey)), ), ElevatedButton.icon( style: ElevatedButton.styleFrom(backgroundColor: AppColor.primaryColor), icon: const Icon(Icons.refresh, color: Colors.white), label: Text("Search for another driver".tr, style: const TextStyle(color: Colors.white)), onPressed: () { Get.back(); retrySearchForDrivers(); }, ), ], ); } void showNoDriverDialog() { Get.defaultDialog( title: "No Drivers Found".tr, middleText: "Sorry, there are no cars available of this type right now.".tr, textConfirm: "Refresh Map".tr, textCancel: "Cancel".tr, confirmTextColor: Colors.white, onConfirm: () { Get.back(); restCounter(); stopAllTimers(); clearControllersAndGoHome(); }, ); } Future calculateDistanceBetweenPassengerAndDriverBeforeCancelRide() async { await getDriverCarsLocationToPassengerAfterApplied(); double dist = Geolocator.distanceBetween( passengerLocation.latitude, passengerLocation.longitude, driverCarsLocationToPassengerAfterApplied.last.latitude, driverCarsLocationToPassengerAfterApplied.last.longitude, ); if (dist > 500) { isCancelRidePageShown = true; update(); } else { Get.defaultDialog( barrierDismissible: false, title: 'The Driver Will be in your location soon .'.tr, middleText: 'The distance less than 500 meter.'.tr, confirm: Column( children: [ MyElevatedButton( kolor: AppColor.greenColor, title: 'Ok'.tr, onPressed: () { Get.back(); }, ), MyElevatedButton( kolor: AppColor.redColor, title: 'No, I want to cancel this trip'.tr, onPressed: () { Get.back(); MyDialog().getDialog( 'Attention'.tr, 'You will be charged for the cost of the driver coming to your location.' .tr, () async { Get.back(); Get.find() .payToDriverForCancelAfterAppliedAndHeNearYou(rideId); }, ); }, ), ], ), ); } } Future cancelRideAfterRejectFromAll() async { locSearch.clearPlacesDestination(); mapEngine.clearPolyline(); data = []; await CRUD().post( link: "${AppLink.server}/ride/rides/cancel_ride_by_passenger.php", payload: { "ride_id": rideId.toString(), "reason": 'notApplyFromAnyDriver' }); rideConfirm = false; statusRide = 'Cancel'; isSearchingWindow = false; shouldFetch = false; isPassengerChosen = false; isCashConfirmPageShown = false; isCashSelectedBeforeConfirmRide = false; timeToPassengerFromDriverAfterApplied = 0; mapEngine.changeCancelRidePageShow(); remainingTime = 0; update(); } void selectReason0(int index, String note) { selectedReason = index; cancelNote = note; update(); } int selectedReasonIndex = -1; String selectedReasonText = ""; TextEditingController otherReasonController = TextEditingController(); void selectReason(int index, String reason) { selectedReasonIndex = index; selectedReasonText = reason; update(); } List data = []; void restCounter() { locSearch.clearPlacesDestination(); mapEngine.clearPolyline(); data = []; rideConfirm = false; shouldFetch = false; timeToPassengerFromDriverAfterApplied = 0; update(); } Future _checkAndRefreshMapStyle() async { try { final String styleJson = await rootBundle.loadString('assets/style.json'); final Map decoded = json.decode(styleJson); final String? currentVersion = decoded['metadata'] != null ? decoded['metadata']['version'] : null; if (currentVersion == null) return; final String lastVersion = box.read(BoxName.styleVersion) ?? "0.0.0"; if (currentVersion != lastVersion) { Log.print( "โ™ป๏ธ Map Style Version mismatch ($lastVersion -> $currentVersion). Purging offline cache..."); await OfflineMapService.instance.clearCache(); await Future.delayed(const Duration(milliseconds: 500)); box.write(BoxName.styleVersion, currentVersion); Log.print("โœ… Style Version updated to $currentVersion"); } } catch (e) { Log.print("โš ๏ธ Style version check failed: $e"); } } void reinit() { if (currentRideState.value != RideState.noRide && currentRideState.value != RideState.cancelled) { Log.print('โ„น๏ธ reinit() skipped: ride is active'); return; } Log.print('๐Ÿ”„ reinit() calling resetAllMapStates and restarting timers...'); resetAllMapStates(); stopAllTimers(); currentRideState.value = RideState.noRide; // Restart location search locSearch.getLocation(); // Restart lifecycle timers & stages getLocationArea(passengerLocation.latitude, passengerLocation.longitude); unawaited(_stagePricingAndState()); unawaited(_stageNiceToHave()); startMasterTimer(); } void resetAllMapStates() { Log.print('๐Ÿงน Resetting all map states to prevent sticky location bug'); locSearch.clearPlacesDestination(); locSearch.clearPlacesStart(); locSearch.waypoints.clear(); locSearch.clearAllMenuWaypoints(); if (Get.isRegistered()) { Get.find().reset(); } // Call reset on mapEngine which handles clearing markers, polylines, animation timers and UI states mapEngine.reset(); data = []; locSearch.passengerStartLocationFromMap = false; locSearch.startLocationFromMap = false; isPickerShown = false; locSearch.workLocationFromMap = false; locSearch.homeLocationFromMap = false; isAnotherOreder = false; isWhatsAppOrder = false; myDestination = passengerLocation; locSearch.hintTextDestinationPoint = 'Select your destination'.tr; locSearch.placeDestinationController.clear(); locSearch.placeStartController.clear(); rideConfirm = false; shouldFetch = true; // reset to true by default for next ride search polling isDrawingRoute = false; isLoading = false; // Reset RideLifecycleController specific search and lifecycle states isSearchingWindow = false; currentRideState.value = RideState.noRide; statusRide = 'wait'; statusRideVip = 'wait'; statusRideFromStart = false; isDriverInPassengerWay = false; isDriverArrivePassenger = false; _isArrivalProcessed = false; _isFinishProcessed = false; _isCancelProcessed = false; _isAcceptanceProcessed = false; _isRatingScreenOpen = false; _isRecalculatingRoute = false; _isRideStartedProcessed = false; _isDriverAppliedLogicExecuted = false; _isDriverArrivedLogicExecuted = false; _isRideBeginLogicExecuted = false; _currentDriverRoutePoints = []; _currentDriverRouteDistanceMeters = 0.0; _currentDriverRouteDurationSeconds = 0; _driverEtaUpdatedAt = null; _driverEtaSecondsAtUpdate = 0; _driverEtaCountdownTicks = 0; _routeHeadingMismatchCount = 0; distanceByPassenger = ''; durationToPassenger = 0; stringRemainingTimeToPassenger = ''; update(); } void _handleFatalError(String title, String message) { if (Get.isBottomSheetOpen == true || Get.isDialogOpen == true) { Get.back(); } if (Get.isSnackbarOpen) Get.closeCurrentSnackbar(); isDrawingRoute = false; isLoading = false; update(); Get.defaultDialog( title: title, titleStyle: AppStyle.title.copyWith(color: AppColor.redColor), middleText: message, middleTextStyle: AppStyle.subtitle, barrierDismissible: false, confirm: MyElevatedButton( title: "Close".tr, kolor: AppColor.redColor, onPressed: () { Get.back(); clearControllersAndGoHome(); }, ), ); } String shortenAddress(String fullAddress) { List parts = fullAddress.split('ุŒ'); parts = parts.map((part) => part.trim()).toList(); parts = parts.where((part) => part.isNotEmpty).toList(); String shortAddress = ''; if (parts.isNotEmpty) { shortAddress += parts[0]; } if (parts.length > 2) { shortAddress += 'ุŒ ${parts[2]}'; } else if (parts.length > 1) { shortAddress += 'ุŒ ${parts[1]}'; } if (parts.length > 1) { shortAddress += 'ุŒ ${parts.last}'; } shortAddress = shortAddress .split('ุŒ') .where((part) => !RegExp(r'^[0-9 ]+$').hasMatch(part.trim())) .join('er'); bool isEnglish = RegExp(r'^[a-zA-Z0-9 ]+$').hasMatch(shortAddress.replaceAll('ุŒ', '')); if (isEnglish) { List englishParts = shortAddress.split('ุŒ'); if (englishParts.length > 2) { shortAddress = '${englishParts[0]}ุŒ ${englishParts[1]}ุŒ ${englishParts.last}'; } else if (englishParts.length > 1) { shortAddress = '${englishParts[0]}ุŒ ${englishParts.last}'; } } return shortAddress; } double distanceOfDestination = 0; bool haveSteps = false; Future getMapPoints( String originSteps, String destinationSteps, int index) async { isWayPointStopsSheetUtilGetMap = false; await nearbyDrivers.getCarsLocationByPassengerAndReloadMarker(); update(); var url = ('${AppLink.googleMapsLink}directions/json?&language=${box.read(BoxName.lang)}&avoid=tolls|ferries&destination=$destinationSteps&origin=$originSteps&key=${AK.mapAPIKEY}'); var response = await CRUD().getGoogleApi(link: url, payload: {}); data = response['routes'][0]['legs']; int durationToRide0 = data[0]['duration']['value']; durationToRide = durationToRide + durationToRide0; distance = distanceOfDestination + (data[0]['distance']['value']) / 1000; update(); final String pointsString = response['routes'][0]["overview_polyline"]["points"]; List decodedPoints = await compute(decodePolylineIsolate, pointsString); for (int i = 0; i < decodedPoints.length; i++) { mapEngine.polylineCoordinates.add(decodedPoints[i]); } if (polyLines.isEmpty) { var polyline = Polyline( polylineId: PolylineId('route_$index'), points: locSearch.polylineCoordinatesPointsAll[index], width: 6, color: const Color(0xFF2196F3), ); polyLines = {...polyLines, polyline}; rideConfirm = false; update(); } } void updateCameraForDistanceAfterGetMap() { LatLng coord1 = LatLng( double.parse(locSearch.coordinatesWithoutEmpty.first.split(',')[0]), double.parse(locSearch.coordinatesWithoutEmpty.first.split(',')[1])); LatLng coord2 = LatLng( double.parse(locSearch.coordinatesWithoutEmpty.last.split(',')[0]), double.parse(locSearch.coordinatesWithoutEmpty.last.split(',')[1])); LatLng northeastBound; LatLng southwestBound; if (coord1.latitude > coord2.latitude) { northeastBound = coord1; southwestBound = coord2; } else { northeastBound = coord2; southwestBound = coord1; } LatLngBounds boundsObj = LatLngBounds(northeast: northeastBound, southwest: southwestBound); final latDiff = (northeastBound.latitude - southwestBound.latitude).abs(); final lngDiff = (northeastBound.longitude - southwestBound.longitude).abs(); if (latDiff < 0.0001 || lngDiff < 0.0001) { final center = LatLng( (northeastBound.latitude + southwestBound.latitude) / 2, (northeastBound.longitude + southwestBound.longitude) / 2, ); mapController!.animateCamera(CameraUpdate.newLatLngZoom(center, 17)); } else { try { var cameraUpdate = CameraUpdate.newLatLngBounds(boundsObj, left: 180, top: 180, right: 180, bottom: 180); mapController!.animateCamera(cameraUpdate); } catch (e) { final center = LatLng( (northeastBound.latitude + southwestBound.latitude) / 2, (northeastBound.longitude + southwestBound.longitude) / 2, ); mapController!.animateCamera(CameraUpdate.newLatLngZoom(center, 17)); } } update(); } int selectedIndex = -1; void selectCarFromList(int index) { selectedIndex = index; carTypes.forEach((element) => element.isSelected = false); carTypes[index].isSelected = true; update(); } Future showBottomSheet1() async { await bottomSheet(); isBottomSheetShown = true; heightBottomSheetShown = 250; update(); } double calculateAngleBetweenLocations(LatLng start, LatLng end) { double startLat = start.latitude * pi / 180; double startLon = start.longitude * pi / 180; double endLat = end.latitude * pi / 180; double endLon = end.longitude * pi / 180; double dLon = endLon - startLon; double y = sin(dLon) * cos(endLat); double x = cos(startLat) * sin(endLat) - sin(startLat) * cos(endLat) * cos(dLon); double angle = atan2(y, x); double angleDegrees = angle * 180 / pi; return angleDegrees; } get dataCarsLocationByPassenger { return nearbyDrivers.carsLocationByPassenger; } set dataCarsLocationByPassenger(var val) { nearbyDrivers.carsLocationByPassenger = val; } double calculateBearing(double lat1, double lon1, double lat2, double lon2) { return nearbyDrivers.calculateBearing(lat1, lon1, lat2, lon2); } void analyzeBehavior(Position currentPosition, List routePoints) { nearbyDrivers.analyzeBehavior(currentPosition, routePoints); } void detectStops(Position currentPosition) { nearbyDrivers.detectStops(currentPosition); } Future getDirectionMap(String origin, String destination, [List waypoints = const [], int attemptCount = 0]) async { if (attemptCount == 0) { isDrawingRoute = true; update(); if (isDrawingRoute) showDrawingBottomSheet(); await nearbyDrivers.getCarsLocationByPassengerAndReloadMarker(); } if (origin.isEmpty) { origin = '${passengerLocation.latitude},${passengerLocation.longitude}'; } var coordDestination = destination.split(','); double latDest = double.parse(coordDestination[0]); double lngDest = double.parse(coordDestination[1]); myDestination = LatLng(latDest, lngDest); Uri uri; var originCoords = origin.split(','); final Map queryParams = { 'fromLat': originCoords[0].trim(), 'fromLng': originCoords[1].trim(), 'toLat': latDest.toString(), 'toLng': lngDest.toString(), }; for (int i = 0; i < activeMenuWaypointCount; i++) { final wp = menuWaypoints[i]; if (wp != null) { queryParams['stop${i + 1}Lat'] = wp.latitude.toString(); queryParams['stop${i + 1}Lng'] = wp.longitude.toString(); } } uri = Uri.parse(AppLink.mapSaasRoute).replace(queryParameters: queryParams); Log.print( 'Requesting Route URI (SaaS, Attempt: ${attemptCount + 1}): $uri'); http.Response response; Map responseData; try { response = await http.get(uri, headers: { 'x-api-key': Env.mapSaasKey, }).timeout(const Duration(seconds: 20)); responseData = json.decode(response.body); bool isRequestValid = response.statusCode == 200; if (!isRequestValid) { if (attemptCount < 2) { await _retryProcess(origin, destination, waypoints, attemptCount); return; } _handleFatalError( "Server Error".tr, "Connection failed. Please try again.".tr); return; } double apiDistanceMeters; String pointsString; dynamic routeData; apiDistanceMeters = (responseData['distance'] as num).toDouble(); pointsString = responseData['points'] ?? ""; routeData = responseData; var origCoords = origin.split(','); double startLat = double.parse(origCoords[0]); double startLng = double.parse(origCoords[1]); double aerialDistance = Geolocator.distanceBetween(startLat, startLng, latDest, lngDest); if (apiDistanceMeters < 50.0 && aerialDistance > 200.0) { Log.print( "โš ๏ธ Suspicious Route detected! Server: $apiDistanceMeters m | Aerial: $aerialDistance m"); if (attemptCount < 2) { Log.print("๐Ÿ”„ Retrying request (Attempt ${attemptCount + 2})..."); await Future.delayed(const Duration(seconds: 1)); await getDirectionMap( origin, destination, waypoints, attemptCount + 1); return; } else { Log.print("โŒ All retries failed. Calculating Route is impossible."); _handleFatalError( "Route Not Found".tr, "We couldn't find a valid route to this destination. Please try selecting a different point." .tr); return; } } box.remove(BoxName.tripData); box.write(BoxName.tripData, routeData); durationToRide = ((routeData['duration'] as num) * kDurationScalar).toInt(); double distanceOfTrip = apiDistanceMeters / 1000.0; distance = distanceOfTrip; data = routeData['legs'] != null && routeData['legs'].isNotEmpty ? (routeData['legs'][0]['steps'] ?? []) : []; List decodedPoints = []; if (pointsString.isNotEmpty) { decodedPoints = await compute(decodePolylineIsolate, pointsString); } if (decodedPoints.isEmpty) { _handleFatalError("Map Error".tr, "Received empty route data.".tr); return; } mapEngine.polylineCoordinates.clear(); mapEngine.polylineCoordinates.addAll(decodedPoints); final LatLng startLoc = mapEngine.polylineCoordinates.first; final LatLng endLoc = mapEngine.polylineCoordinates.last; startNameAddress = responseData['startName'] ?? 'Start Point'.tr; endNameAddress = responseData['endName'] ?? 'Destination'.tr; Log.print('๐Ÿ“ ROUTE START: $startNameAddress'); Log.print('๐Ÿ“ ROUTE END: $endNameAddress'); if (responseData['bbox'] != null) { List bbox = responseData['bbox']; if (bbox.length == 4) { mapEngine.lastComputedBounds = LatLngBounds( southwest: LatLng(bbox[1], bbox[0]), northeast: LatLng(bbox[3], bbox[2]), ); } } else { double? minLat, maxLat, minLng, maxLng; for (LatLng point in mapEngine.polylineCoordinates) { minLat = minLat == null ? point.latitude : min(minLat, point.latitude); maxLat = maxLat == null ? point.latitude : max(maxLat, point.latitude); minLng = minLng == null ? point.longitude : min(minLng, point.longitude); maxLng = maxLng == null ? point.longitude : max(maxLng, point.longitude); } if (minLat != null) { mapEngine.lastComputedBounds = LatLngBounds( northeast: LatLng(maxLat!, maxLng!), southwest: LatLng(minLat!, minLng!)); } } if (isDrawingRoute) { Log.print('๐Ÿ”” Finalizing route drawing state'); isDrawingRoute = false; isLoading = false; update(); } durationToAdd = Duration(seconds: durationToRide); hours = durationToAdd.inHours; minutes = (durationToAdd.inMinutes % 60).round(); markers = { Marker( markerId: const MarkerId('start'), position: startLoc, icon: InlqBitmap.fromStyleImage('orange_marker'), infoWindow: const InfoWindow(title: 'A'), anchor: const Offset(0.5, 1.0), ), Marker( markerId: const MarkerId('end'), position: endLoc, icon: InlqBitmap.fromStyleImage('violet_marker'), infoWindow: const InfoWindow(title: 'B'), anchor: const Offset(0.5, 1.0), ), }; for (int i = 0; i < activeMenuWaypointCount; i++) { final wp = menuWaypoints[i]; if (wp != null) { final bool isFirstWaypoint = i == 0; markers.add(Marker( markerId: MarkerId('waypoint_$i'), position: wp, icon: InlqBitmap.fromStyleImage( isFirstWaypoint ? 'orange_marker' : 'violet_marker'), infoWindow: InfoWindow(title: isFirstWaypoint ? 'Stop 1' : 'Stop 2'), anchor: const Offset(0.5, 1.0), )); } } if (polyLines.isNotEmpty) mapEngine.clearPolyline(); rideConfirm = false; isMarkersShown = true; update(); await bottomSheet(); await mapEngine.playRouteAnimation( mapEngine.polylineCoordinates, mapEngine.lastComputedBounds); } catch (e, stackTrace) { if (isDrawingRoute) { isDrawingRoute = false; isLoading = false; update(); } Log.print('๐Ÿšจ CRITICAL ERROR IN getDirectionMap: $e'); Log.print('๐Ÿšจ STACKTRACE: $stackTrace'); if (attemptCount < 2) { await _retryProcess(origin, destination, waypoints, attemptCount); } else { _handleFatalError("Connection Error".tr, "Please check your internet and try again.".tr); } } } Future _retryProcess(String origin, String dest, List waypoints, int currentAttempt) async { Log.print( "๐Ÿ”„ Exception or Error caught. Retrying in 1s... (Attempt ${currentAttempt + 1})"); await Future.delayed(const Duration(seconds: 1)); getDirectionMap(origin, dest, waypoints, currentAttempt + 1); } bool _isUsingFallback = false; void _startPollingFallback() { if (_isUsingFallback) return; Log.print('๐Ÿ”„ Starting Polling Fallback Mode'); _isUsingFallback = true; startMasterTimer(); } Future _restorePolyline(String polylineString) async { try { List points = await compute(decodePolylineIsolate, polylineString); mapEngine.polylineCoordinates.clear(); mapEngine.polylineCoordinates.addAll(points); mapEngine.clearPolyline(); mapEngine.polyLines = { ...mapEngine.polyLines, Polyline( polylineId: const PolylineId('route_direct'), points: mapEngine.polylineCoordinates, color: const Color(0xFF2196F3), width: 6, ) }; update(); } catch (e) { Log.print('Error restoring polyline: $e'); } } Future processRideAcceptance( {Map? driverData, required String source}) async { if (_isAcceptanceProcessed || currentRideState.value == RideState.driverApplied || currentRideState.value == RideState.driverArrived || currentRideState.value == RideState.inProgress) { Log.print("โœ‹ Ignored Acceptance from $source. Already processed."); return; } _rideAcceptedViaSource = source; _isAcceptanceProcessed = true; _isDriverAppliedLogicExecuted = true; Log.print("๐Ÿš€ Winner: $source triggered acceptance! Processing..."); _masterTimer?.cancel(); currentRideState.value = RideState.driverApplied; statusRide = 'Apply'; isSearchingWindow = false; if (driverData != null && driverData.isNotEmpty) { Log.print("๐Ÿ“ฅ Populating Data from $source payload..."); _fillDriverDataLocally(driverData); } else { Log.print("โš ๏ธ No Data in Payload. Fallback to API."); await getUpdatedRideForDriverApply(rideId); } await IosLiveActivityService.startRideActivity( rideId: rideId, driverName: driverName, carDetails: '$make โ€ข $carColor', etaText: stringRemainingTimeToPassenger, progress: 0.0, ); _showRideStartNotifications(); final etaText = stringRemainingTimeToPassenger; final carInfo = '$make โ€ข $model โ€ข $licensePlate'; await RideLiveNotification.showDriverOnWay( driverName: driverName, etaText: etaText, carInfo: carInfo, ); update(); await getDriverCarsLocationToPassengerAfterApplied(); _startSocketWatchdog(); if (driverCarsLocationToPassengerAfterApplied.isNotEmpty) { LatLng driverPos = driverCarsLocationToPassengerAfterApplied.last; await calculateDriverToPassengerRoute(driverPos, passengerLocation); startTimerFromDriverToPassengerAfterApplied(); } PipService.enablePip(); if (source == "Socket" && mapSocket.isSocketConnected) { Log.print( "๐Ÿง  Smart Mode: Socket accepted ride. Skipping polling, relying on WebSocket."); } else { Log.print("๐Ÿ”„ Fallback Mode: $source accepted ride. Starting polling."); _startDriverLocationPollingWithTimer(); } } void _fillDriverDataLocally(Map data) { try { driverId = data['driver_id']?.toString() ?? ''; driverPhone = data['phone']?.toString() ?? ''; String fName = (data['first_name'] ?? data['driver_first_name'] ?? '') .toString() .trim(); String lName = (data['last_name'] ?? data['driver_last_name'] ?? '') .toString() .trim(); final socketDriverName = (data['driverName'] ?? data['driver_name'] ?? '').toString().trim(); driverName = socketDriverName.isNotEmpty ? socketDriverName : [fName, lName].where((part) => part.isNotEmpty).join(' '); make = data['make']?.toString() ?? ''; model = data['model']?.toString() ?? ''; gender = data['gender']?.toString() ?? ''; carColor = data['color']?.toString() ?? ''; colorHex = data['color_hex']?.toString() ?? ''; licensePlate = data['car_plate']?.toString() ?? ''; carYear = data['year']?.toString() ?? ''; // ุงู„ู…ุญุงูˆู„ุฉ ุงู„ููˆุฑูŠุฉ ู„ุฑุณู… ุงู„ุณุงุฆู‚ ุฅุฐุง ุชูˆูุฑุช ุงู„ุฅุญุฏุงุซูŠุงุช ููŠ ุงู„ุจูŠุงู†ุงุช double lat = double.tryParse( data['latitude']?.toString() ?? data['lat']?.toString() ?? '0') ?? 0; double lng = double.tryParse(data['longitude']?.toString() ?? data['lng']?.toString() ?? '0') ?? 0; double heading = double.tryParse(data['heading']?.toString() ?? '0') ?? 0; if (lat != 0 && lng != 0) { LatLng initialPos = LatLng(lat, lng); if (driverCarsLocationToPassengerAfterApplied.isEmpty) { driverCarsLocationToPassengerAfterApplied.add(initialPos); } else { driverCarsLocationToPassengerAfterApplied[0] = initialPos; } // ุชุญุฏูŠุซ ุงู„ู…ุงุฑูƒุฑ ููˆุฑุงู‹ ู„ุถู…ุงู† ุธู‡ูˆุฑู‡ ุจุดูƒู„ ู…ูˆุซูˆู‚ updateDriverMarker(initialPos, heading); } driverRate = data['ratingDriver']?.toString() ?? '5.0'; driverRatingCount = data['ratingCount']?.toString() ?? '0'; driverCompletedRides = data['completedRides']?.toString() ?? '0'; driverTier = data['driverTier']?.toString() ?? 'Verified driver'; driverToken = data['token']?.toString() ?? ''; update(); } catch (e) { Log.print("Error parsing socket driver data: $e"); } } Future cancelRide() async { if (selectedReasonIndex == -1) { Get.snackbar( 'Attention'.tr, 'Please select a reason first'.tr, snackPosition: SnackPosition.BOTTOM, backgroundColor: Colors.orange, colorText: Colors.white, ); return; } String finalReason = selectedReasonText; if (finalReason == "Other".tr) { if (otherReasonController.text.trim().isEmpty) { Get.snackbar("Attention".tr, "Please write the reason...".tr, backgroundColor: Colors.red, colorText: Colors.white); return; } finalReason = otherReasonController.text.trim(); } Get.back(); if (isCancelRidePageShown) { mapEngine.changeCancelRidePageShow(); } resetAllMapStates(); stopAllTimers(); currentRideState.value = RideState.cancelled; await RideLiveNotification.cancel(); IosLiveActivityService.endRideActivity(); PipService.disablePip(); if (rideId != 'yet' && rideId != null) { Log.print( '๐Ÿ“ก Sending Cancel Request to Server with Reason: $finalReason'); try { await CRUD().post( link: "${AppLink.server}/ride/rides/cancel_ride_by_passenger.php", payload: { "ride_id": rideId.toString(), "reason": finalReason, "driver_token": driverToken, }, ); } catch (e) { Log.print("Error cancelling on server: $e"); } } clearControllersAndGoHome(); } Future getAIKey(String key) async { var res = await CRUD().get(link: AppLink.getapiKey, payload: {"keyName": key}); if (res != 'failure') { var d = jsonDecode(res)['message']; return d[key].toString(); } return null; } Future getRideStatus(String rideId) async { final response = await CRUD().get( link: "${AppLink.rideServerSide}/ride/rides/getRideStatus.php", payload: {'id': rideId}); Log.print(response); Log.print('2176'); return jsonDecode(response)['data']; } void handleActiveRideOnStartup(dynamic data) { try { if (data == null || data['has_active_ride'] != true) { Log.print('[Startup] No active ride'); currentRideState.value = RideState.noRide; startMasterTimer(); return; } Log.print('[Startup] โœ… Active ride found!'); var rideData = data['ride']; rideId = rideData['ride_id'].toString(); currentRideId = rideId; driverId = rideData['driver_id']?.toString() ?? ''; String status = rideData['status']?.toString().toLowerCase() ?? ''; if (status == 'waiting' || status == 'searching') { currentRideState.value = RideState.searching; isSearchingWindow = true; } else if (status == 'apply' || status == 'applied') { currentRideState.value = RideState.driverApplied; statusRide = 'Apply'; mapSocket.socket.emit('subscribe_driver_location', { 'ride_id': rideId, 'driver_id': driverId, }); if (rideData['driver_info'] != null) { var dInfo = rideData['driver_info']; passengerName = dInfo['first_name']?.toString() ?? ''; driverPhone = dInfo['phone']?.toString() ?? ''; model = dInfo['model']?.toString() ?? ''; licensePlate = dInfo['license_plate']?.toString() ?? ''; } } else if (status == 'arrived') { currentRideState.value = RideState.driverArrived; statusRide = 'Arrived'; isDriverArrivePassenger = true; } else if (status == 'begin' || status == 'started') { currentRideState.value = RideState.inProgress; statusRide = 'Begin'; rideTimerBegin = true; if (rideData['polyline'] != null) { _restorePolyline(rideData['polyline']); } rideIsBeginPassengerTimer(); } update(); startMasterTimer(); } catch (e) { Log.print('[Startup] Error: $e'); currentRideState.value = RideState.noRide; startMasterTimer(); } } Future handleNoDriverFound() async { stopAllTimers(); await RideLiveNotification.cancel(); IosLiveActivityService.endRideActivity(); PipService.disablePip(); _isCancelProcessed = false; currentRideState.value = RideState.noRide; resetAllMapStates(); clearControllersAndGoHome(); Get.defaultDialog( title: "We apologize ๐Ÿ˜”".tr, middleText: "No drivers found at the moment.\nPlease try again later.".tr, confirm: ElevatedButton( onPressed: () => Navigator.pop(Get.context!), child: Text("Ok".tr), ), ); } bool isDriversDataValid() { return dataCarsLocationByPassenger != 'failure' && dataCarsLocationByPassenger != null && (dataCarsLocationByPassenger is Map) && dataCarsLocationByPassenger.containsKey('message') && dataCarsLocationByPassenger['message'] != null; } void retrySearchForDrivers() async { _isCancelProcessed = false; isSearchingWindow = true; currentRideState.value = RideState.searching; driversStatusForSearchWindow = 'Searching for nearby drivers...'.tr; update(); try { Log.print("๐Ÿ”„ Retrying search for ride ID: $rideId"); var payload = { "ride_id": rideId.toString(), "passenger_id": box.read(BoxName.passengerID).toString(), "passenger_name": box.read(BoxName.name).toString(), "passenger_phone": box.read(BoxName.phone).toString(), "passenger_email": box.read(BoxName.email).toString(), "passenger_token": box.read(BoxName.tokenFCM).toString(), "passenger_wallet": box.read(BoxName.passengerWalletTotal).toString(), "passenger_rating": "5.0", "start_lat": startLocation.latitude.toString(), "start_lng": startLocation.longitude.toString(), "end_lat": endLocation.latitude.toString(), "end_lng": endLocation.longitude.toString(), "start_name": startNameAddress, "end_name": endNameAddress, "distance": distance.toString(), "distance_text": distanceByPassenger, "duration_text": durationToPassenger.toString(), "price": totalPassenger.toString(), "price_for_driver": costForDriver.toString(), "car_type": box.read(BoxName.carType).toString(), "is_wallet": Get.find().isWalletChecked.toString(), "has_steps": Get.find().wayPoints.length > 1 ? "true" : "false", }; var response = await CRUD().post( link: "${AppLink.rideServerSide}/rides/retry_search_drivers.php", payload: payload, ); if (response['status'] == 'success') { Log.print("โœ… Search reset successfully."); startSearchingTimer(); } else { Log.print("โŒ Failed to reset search: $response"); handleNoDriverFound(); } } catch (e) { Log.print("โŒ Exception in retrySearchForDrivers: $e"); handleNoDriverFound(); } } Future startSearchingTimer() async { _searchTimer?.cancel(); int seconds = 0; Log.print("โณ Search Timer Started (90s)..."); await RideLiveNotification.showSearching(driversStatusForSearchWindow); _searchTimer = Timer.periodic(const Duration(seconds: 1), (timer) { seconds++; if (currentRideState.value != RideState.searching) { timer.cancel(); return; } if (seconds >= 90) { timer.cancel(); handleNoDriverFound(); } }); } void showNoDriversDialog() { Get.dialog( BackdropFilter( filter: ImageFilter.blur(sigmaX: 5, sigmaY: 5), child: CupertinoAlertDialog( title: Text("No Car or Driver Found in your area.".tr, style: AppStyle.title .copyWith(fontSize: 20, fontWeight: FontWeight.bold)), content: Text("No Car or Driver Found in your area.".tr, style: AppStyle.title.copyWith(fontSize: 16)), actions: [ CupertinoDialogAction( onPressed: () { Get.back(); clearControllersAndGoHome(); }, child: Text('OK'.tr, style: TextStyle(color: AppColor.greenColor)), ), ], ), ), barrierDismissible: false, ); } Future _stageNiceToHave() async { Log.print('๐Ÿš€ MapPassengerController: Starting _stageNiceToHave'); await Future.wait([ Future(() async { try { Log.print('๐Ÿ” Loading Favorites...'); await locSearch.getFavioratePlaces(); } catch (e) { Log.print("Error: $e"); } }), Future(() async { try { Log.print('๐Ÿ” Loading Waypoints...'); locSearch.readyWayPoints(); } catch (e) { Log.print("Error: $e"); } }), Future(() async { try { Log.print('๐Ÿ” Loading Rate...'); await getPassengerRate(); } catch (e) { Log.print("Error: $e"); } }), Future(() async { try { Log.print('๐Ÿ” Loading Coupons...'); await firstTimeRunToGetCoupon(); } catch (e) { Log.print("Error: $e"); } }), ]); Log.print('โœ… MapPassengerController: _stageNiceToHave complete'); try { cardNumber = await SecureStorage().readData(BoxName.cardNumber); } catch (e) { Log.print("Error: $e"); } } Future _stagePricingAndState() async { try {} catch (e) { Log.print("Error: $e"); } try { await _checkInitialRideStatus(); } catch (e) { Log.print("Error: $e"); } _applyLowEndModeIfNeeded(); } void _applyLowEndModeIfNeeded() { // Placeholder comment from original } void showDrawingBottomSheet() { Log.print( '๐Ÿ”” showDrawingBottomSheet called. isDrawingRoute: $isDrawingRoute'); final context = Get.context; if (context == null) return; WidgetsBinding.instance.addPostFrameCallback((_) { // Close any existing open dialogs first if (Get.isDialogOpen == true) { Get.back(); } Get.dialog( Dialog( backgroundColor: Colors.transparent, elevation: 0, child: Container( padding: const EdgeInsets.all(24), width: 180, decoration: BoxDecoration( color: Colors.white.withOpacity(0.95), borderRadius: BorderRadius.circular(24), boxShadow: [ BoxShadow( color: Colors.black.withOpacity(0.15), blurRadius: 20, spreadRadius: 5, ) ], ), child: Column( mainAxisSize: MainAxisSize.min, mainAxisAlignment: MainAxisAlignment.center, crossAxisAlignment: CrossAxisAlignment.center, children: [ // App Logo Image.asset( 'assets/images/logo.gif', height: 64, errorBuilder: (context, error, stackTrace) => const Icon( Icons.map, size: 64, color: AppColor.primaryColor, ), ), const SizedBox(height: 16), const SizedBox( width: 24, height: 24, child: MyCircularProgressIndicator(), ), const SizedBox(height: 16), Text( 'Drawing route on map...'.tr, style: const TextStyle( fontWeight: FontWeight.bold, fontSize: 14, color: AppColor.primaryColor, ), textAlign: TextAlign.center, ), ], ), ), ), barrierDismissible: false, ); // Auto-dismiss after exactly 2 seconds Future.delayed(const Duration(seconds: 2), () { if (Get.isDialogOpen == true) { Get.back(); } }); }); } @override void onInit() async { super.onInit(); await _checkAndRefreshMapStyle(); Get.put(DeepLinkController(), permanent: true); await initilizeGetStorage(); getLocationArea(passengerLocation.latitude, passengerLocation.longitude); unawaited(_stagePricingAndState()); unawaited(_stageNiceToHave()); startMasterTimer(); } @override void onClose() { stopAllTimers(); if (!_timerStreamController.isClosed) { _timerStreamController.close(); } if (!_beginRideStreamController.isClosed) { _beginRideStreamController.close(); } if (!_rideStatusStreamController.isClosed) { _rideStatusStreamController.close(); } if (!timerController.isClosed) { timerController.close(); } super.onClose(); } void clearControllersAndGoHome() { Get.offAll(() => const MapPagePassenger()); } /// Builds a Set of short [Polyline] segments that simulate a dashed line. /// intaleq_maps (MapLibre) doesn't support `patterns`, so we manually /// split the route into dash/gap alternating segments. Set _buildDashedPolylines({ required List points, required double dashLengthMeters, required double gapLengthMeters, required Color color, required int width, required String idPrefix, }) { final Set result = {}; if (points.length < 2) return result; int segmentIndex = 0; bool isDash = true; double remaining = dashLengthMeters; List currentSegment = [points[0]]; for (int i = 0; i < points.length - 1; i++) { final LatLng a = points[i]; final LatLng b = points[i + 1]; double segLen = _haversineDistance(a, b); double covered = 0.0; while (covered < segLen) { double leftInSeg = segLen - covered; if (remaining <= leftInSeg) { // interpolate the endpoint of this dash/gap double fraction = (covered + remaining) / segLen; LatLng interp = LatLng( a.latitude + fraction * (b.latitude - a.latitude), a.longitude + fraction * (b.longitude - a.longitude), ); currentSegment.add(interp); if (isDash && currentSegment.length >= 2) { result.add(Polyline( polylineId: PolylineId('${idPrefix}_seg_$segmentIndex'), points: List.from(currentSegment), color: color, width: width, )); } final double consumed = remaining; segmentIndex++; isDash = !isDash; remaining = isDash ? dashLengthMeters : gapLengthMeters; currentSegment = [interp]; covered += consumed; } else { currentSegment.add(b); covered = segLen; remaining -= leftInSeg; } } } // Flush last dash segment if (isDash && currentSegment.length >= 2) { result.add(Polyline( polylineId: PolylineId('${idPrefix}_seg_$segmentIndex'), points: List.from(currentSegment), color: color, width: width, )); } return result; } /// Haversine distance in meters between two LatLng points. LatLng? _parseLatLng(String? raw) { if (raw == null || raw.trim().isEmpty) return null; final parts = raw.split(','); if (parts.length < 2) return null; final lat = double.tryParse(parts[0].trim()); final lng = double.tryParse(parts[1].trim()); if (lat == null || lng == null) return null; if (lat == 0 && lng == 0) return null; return LatLng(lat, lng); } bool _isHeadingAwayFromRoute({ required double? heading, required double? speed, required int closestRouteIndex, required double distanceFromRouteMeters, }) { if (heading == null || speed == null || speed < 2.5) return false; if (_currentDriverRoutePoints.length < 2) return false; if (distanceFromRouteMeters < 10) return false; int fromIndex = closestRouteIndex; int toIndex = min(closestRouteIndex + 1, _currentDriverRoutePoints.length - 1); if (fromIndex == toIndex && fromIndex > 0) { fromIndex--; } if (fromIndex == toIndex) return false; final double routeBearing = _bearingBetween( _currentDriverRoutePoints[fromIndex], _currentDriverRoutePoints[toIndex], ); final double angleDiff = _angleDifference(heading, routeBearing); return angleDiff > 110; } double _bearingBetween(LatLng a, LatLng b) { final double lat1 = a.latitude * pi / 180; final double lat2 = b.latitude * pi / 180; final double dLng = (b.longitude - a.longitude) * pi / 180; final double y = sin(dLng) * cos(lat2); final double x = cos(lat1) * sin(lat2) - sin(lat1) * cos(lat2) * cos(dLng); return (atan2(y, x) * 180 / pi + 360) % 360; } double _angleDifference(double a, double b) { final double diff = ((a - b + 540) % 360) - 180; return diff.abs(); } double _pathDistanceMeters(List points) { if (points.length < 2) return 0.0; double total = 0.0; for (int i = 0; i < points.length - 1; i++) { total += _haversineDistance(points[i], points[i + 1]); } return total; } double _haversineDistance(LatLng a, LatLng b) { const R = 6371000.0; final dLat = (b.latitude - a.latitude) * pi / 180; final dLng = (b.longitude - a.longitude) * pi / 180; final sinLat = sin(dLat / 2); final sinLng = sin(dLng / 2); final h = sinLat * sinLat + cos(a.latitude * pi / 180) * cos(b.latitude * pi / 180) * sinLng * sinLng; return 2 * R * atan2(pow(h, 0.5).toDouble(), pow(1 - h, 0.5).toDouble()); } // ุฏุงู„ุฉ ู„ุจู†ุงุก ุงู„ุฎุท ุงู„ู…ู†ู‚ุท List _buildDashedLine(LatLng start, LatLng end, {required Color color, required String prefixId}) { List segments = []; double dist = Geolocator.distanceBetween( start.latitude, start.longitude, end.latitude, end.longitude); const double dashLengthMeters = 8.0; const double gapLengthMeters = 6.0; double latDiff = end.latitude - start.latitude; double lngDiff = end.longitude - start.longitude; double totalLength = 0; int segmentCount = 0; while (totalLength < dist) { double startFraction = totalLength / dist; double endFraction = (totalLength + dashLengthMeters) / dist; if (endFraction > 1.0) { endFraction = 1.0; } double startLat = start.latitude + latDiff * startFraction; double startLng = start.longitude + lngDiff * startFraction; double endLat = start.latitude + latDiff * endFraction; double endLng = start.longitude + lngDiff * endFraction; segments.add( Polyline( polylineId: PolylineId('${prefixId}_dash_$segmentCount'), points: [LatLng(startLat, startLng), LatLng(endLat, endLng)], color: color, width: 4, ), ); segmentCount++; totalLength += dashLengthMeters + gapLengthMeters; } return segments; } // Call this externally when passengerLocation changes to refresh walk line void updatePassengerWalkLine() { _updatePassengerWalkLine(); } // ุชุญุฏูŠุซ ุงู„ุฎุท ุงู„ู…ู†ู‚ุท ูˆู…ูƒุงู† ุฃูŠู‚ูˆู†ุฉ ุงู„ู…ุดูŠ ู„ู„ุฑุงูƒุจ void _updatePassengerWalkLine() { polyLines.removeWhere( (p) => p.polylineId.value.startsWith('passenger_walk_line')); markers.removeWhere((m) => m.markerId.value == 'walk_end_marker'); bool shouldShowWalkPath = (statusRide == 'Apply' || statusRide == 'Arrived') && _currentDriverRoutePoints.isNotEmpty && passengerLocation.latitude != 0; if (shouldShowWalkPath) { final LatLng lastRoadPt = _currentDriverRoutePoints.last; final walkDashes = _buildDashedLine( lastRoadPt, passengerLocation, color: Colors.blueGrey, prefixId: 'passenger_walk_line', ); polyLines.addAll(walkDashes); markers.add( Marker( markerId: const MarkerId('walk_end_marker'), position: lastRoadPt, icon: InlqBitmap.fromStyleImage('walk_icon'), anchor: const Offset(0.5, 0.5), ), ); } mapEngine.update(); update(); } initilizeGetStorage() async { if (box.read(BoxName.addWork) == null) { box.write(BoxName.addWork, 'addWork'); } if (box.read(BoxName.addHome) == null) { box.write(BoxName.addHome, 'addHome'); } } getPassengerRate() async { var res = await CRUD().get( link: AppLink.getPassengerRate, payload: {'passenger_id': box.read(BoxName.passengerID)}); if (res != 'failure') { var json = jsonDecode(res); var message = json['data'] ?? json['message']; if (message['rating'] == null) { passengerRate = 5.0; // Default rating } else { var rating = message['rating']; if (rating is String) { passengerRate = double.tryParse(rating) ?? 5.0; } else if (rating is num) { passengerRate = rating.toDouble(); } else { passengerRate = 5.0; } } } else { passengerRate = 5.0; } } firstTimeRunToGetCoupon() async { if (box.read(BoxName.isFirstTime).toString() == '0' && box.read(BoxName.isInstall).toString() == '1' && box.read(BoxName.isGiftToken).toString() == '0') { var promo, discount, validity; var resPromo = await CRUD().get(link: AppLink.getPromoFirst, payload: { "passengerID": box.read(BoxName.passengerID).toString(), }); if (resPromo != 'failure') { var d1 = jsonDecode(resPromo); promo = d1['message']['promo_code']; discount = d1['message']['amount']; validity = d1['message']['validity_end_date']; } box.write(BoxName.isFirstTime, '1'); Get.dialog( AlertDialog( contentPadding: EdgeInsets.zero, content: SizedBox( width: 300, child: PromoBanner( promoCode: promo ?? '', discountPercentage: discount ?? '', validity: validity ?? '', ), ), ), ); } } }