Files
intaleq/lib/controller/home/map_passenger_controller.dart
Hamza-Ayed 11dfe94bbb 25-12-1/1
2025-12-01 07:53:52 +03:00

7847 lines
285 KiB
Dart

import 'dart:async';
import 'dart:convert';
import 'dart:io';
import 'dart:math' show Random, atan2, cos, max, min, pi, pow, sin, sqrt;
import 'dart:math' as math;
import 'dart:ui';
import 'dart:convert';
import 'package:crypto/crypto.dart';
import 'package:Intaleq/views/Rate/rate_captain.dart';
import 'package:Intaleq/views/Rate/rating_driver_bottom.dart';
import 'package:device_info_plus/device_info_plus.dart';
import 'package:flutter/foundation.dart';
import 'package:http/http.dart' as http;
import 'package:Intaleq/constant/univeries_polygon.dart';
import 'package:Intaleq/controller/firebase/local_notification.dart';
import 'package:Intaleq/controller/functions/encrypt_decrypt.dart';
import 'package:flutter/cupertino.dart';
import 'package:flutter_confetti/flutter_confetti.dart';
import 'package:vector_math/vector_math.dart' show radians, degrees;
import 'package:Intaleq/controller/functions/tts.dart';
import 'package:Intaleq/views/home/map_page_passenger.dart';
import 'package:Intaleq/views/widgets/my_textField.dart';
import 'package:flutter/material.dart';
import 'package:geolocator/geolocator.dart';
import 'package:get/get.dart';
import 'package:google_maps_flutter/google_maps_flutter.dart';
// import 'package:google_polyline_algorithm/google_polyline_algorithm.dart';
import 'package:intl/intl.dart';
import 'package:location/location.dart';
import 'package:Intaleq/constant/colors.dart';
import 'package:Intaleq/constant/style.dart';
import 'package:Intaleq/controller/home/points_for_rider_controller.dart';
import 'package:Intaleq/views/home/map_widget.dart/form_serch_multiy_point.dart';
import '../../constant/api_key.dart';
import '../../constant/box_name.dart';
import '../../constant/country_polygons.dart';
import '../../constant/info.dart';
import '../../constant/links.dart';
import '../../constant/table_names.dart';
import '../../env/env.dart';
import '../../main.dart';
import '../../models/model/locations.dart';
import '../../models/model/painter_copoun.dart';
import '../../print.dart';
import '../../views/home/map_widget.dart/cancel_raide_page.dart';
import '../../views/home/map_widget.dart/car_details_widget_to_go.dart';
import '../../views/home/map_widget.dart/searching_captain_window.dart';
import '../../views/home/map_widget.dart/select_driver_mishwari.dart';
import '../../views/widgets/elevated_btn.dart';
import '../../views/widgets/error_snakbar.dart';
import '../../views/widgets/mydialoug.dart';
import '../firebase/firbase_messge.dart';
import '../firebase/notification_service.dart';
import '../functions/audio_record1.dart';
import '../functions/crud.dart';
import '../functions/launch.dart';
import '../functions/package_info.dart';
import '../functions/secure_storage.dart';
import '../payment/payment_controller.dart';
import '../rate/rate_conroller.dart';
import 'decode_polyline_isolate.dart';
import 'deep_link_controller.dart';
import 'device_tier.dart';
import 'vip_waitting_page.dart';
enum RideState {
noRide, // لا يوجد رحلة جارية، عرض واجهة البحث
cancelled, // تم إلغاء الرحلة
preCheckReview, // يوجد رحلة منتهية، تحقق من التقييم
searching, // جاري البحث عن كابتن
driverApplied, // تم قبول الطلب
driverArrived, // وصل السائق
inProgress, // الرحلة بدأت بالفعل
finished, // انتهت الرحلة (سيتم تحويلها إلى preCheckReview)
}
class MapPassengerController extends GetxController {
bool isLoading = true;
TextEditingController placeDestinationController = TextEditingController();
TextEditingController increasFeeFromPassenger = TextEditingController();
TextEditingController placeStartController = TextEditingController();
TextEditingController wayPoint0Controller = TextEditingController();
TextEditingController wayPoint1Controller = TextEditingController();
TextEditingController wayPoint2Controller = TextEditingController();
TextEditingController wayPoint3Controller = TextEditingController();
TextEditingController wayPoint4Controller = TextEditingController();
TextEditingController sosPhonePassengerProfile = TextEditingController();
TextEditingController whatsAppLocationText = TextEditingController();
TextEditingController messageToDriver = TextEditingController();
final sosFormKey = GlobalKey<FormState>();
final promoFormKey = GlobalKey<FormState>();
final messagesFormKey = GlobalKey<FormState>();
final increaseFeeFormKey = GlobalKey<FormState>();
List data = [];
List<LatLng> bounds = [];
List placesStart = [];
List<String> driversToken = [];
LatLng previousLocationOfDrivers = const LatLng(0, 0);
double angleDegrees = 0;
LatLng currentLocationOfDrivers = const LatLng(0, 0);
List<TextEditingController> allTextEditingPlaces = [];
List placesDestination = [];
List wayPoint0 = [];
List wayPoint1 = [];
List wayPoint2 = [];
List wayPoint3 = [];
List wayPoint4 = [];
final firebaseMessagesController =
Get.isRegistered<FirebaseMessagesController>()
? Get.find<FirebaseMessagesController>()
: Get.put(FirebaseMessagesController());
List<List<dynamic>> placeListResponseAll = [];
List<Widget> placeListResponse = [
formSearchPlaces(0),
formSearchPlaces(1),
formSearchPlaces(2),
formSearchPlaces(3),
];
LatLngBounds? boundsdata;
List<Marker> markers = [];
List<Polyline> polyLines = [];
late LatLng passengerLocation = const LatLng(32, 34);
late LatLng newMyLocation = const LatLng(32.115295, 36.064773);
late LatLng newStartPointLocation = const LatLng(32.115295, 36.064773);
late LatLng newPointLocation0 = const LatLng(32.115295, 36.064773);
late LatLng newPointLocation1 = const LatLng(32.115295, 36.064773);
late LatLng newPointLocation2 = const LatLng(32.115295, 36.064773);
late LatLng newPointLocation3 = const LatLng(32.115295, 36.064773);
late LatLng newPointLocation4 = const LatLng(32.115295, 36.064773);
late LatLng myDestination;
List<LatLng> polylineCoordinates = [];
List<LatLng> polylineCoordinates0 = [];
List<LatLng> polylineCoordinates1 = [];
List<LatLng> polylineCoordinates2 = [];
List<LatLng> polylineCoordinates3 = [];
List<LatLng> polylineCoordinates4 = [];
List<List<LatLng>> polylineCoordinatesPointsAll = [];
List carsLocationByPassenger = [];
List<LatLng> driverCarsLocationToPassengerAfterApplied = [];
BitmapDescriptor markerIcon = BitmapDescriptor.defaultMarker;
BitmapDescriptor tripIcon = BitmapDescriptor.defaultMarker;
BitmapDescriptor startIcon = BitmapDescriptor.defaultMarker;
BitmapDescriptor endIcon = BitmapDescriptor.defaultMarker;
BitmapDescriptor carIcon = BitmapDescriptor.defaultMarker;
BitmapDescriptor motoIcon = BitmapDescriptor.defaultMarker;
BitmapDescriptor ladyIcon = BitmapDescriptor.defaultMarker;
double height = 150;
DateTime currentTime = DateTime.now();
final location = Location();
late LocationData currentLocation;
double heightMenu = 0;
double widthMenu = 0;
double heightPickerContainer = 90;
double heightPointsPageForRider = 0;
double mainBottomMenuMapHeight = Get.height * .2;
double wayPointSheetHeight = 0;
String stringRemainingTimeToPassenger = '';
String stringRemainingTimeDriverWaitPassenger5Minute = '';
bool isDriverInPassengerWay = false;
bool isDriverArrivePassenger = false;
bool startLocationFromMap = false;
bool isAnotherOreder = false;
bool isWhatsAppOrder = false;
bool passengerStartLocationFromMap = false;
bool workLocationFromMap = false;
bool homeLocationFromMap = false;
bool isPassengerRideLocationWidget = false;
bool startLocationFromMap0 = false;
bool startLocationFromMap1 = false;
bool startLocationFromMap2 = false;
bool startLocationFromMap3 = false;
bool startLocationFromMap4 = false;
List startLocationFromMapAll = [];
double latePrice = 0;
double fuelPrice = 0;
double heavyPrice = 0;
double naturePrice = 0;
bool heightMenuBool = false;
String statusRide = 'wait';
String statusRideVip = 'wait';
bool statusRideFromStart = false;
bool isPickerShown = false;
bool isPointsPageForRider = false;
bool isBottomSheetShown = false;
bool mapType = false;
bool mapTrafficON = false;
bool isCancelRidePageShown = false;
bool isCashConfirmPageShown = false;
bool isPaymentMethodPageShown = false;
bool isRideFinished = false;
bool rideConfirm = false;
bool isMarkersShown = false;
bool isMainBottomMenuMap = true;
Timer? markerReloadingTimer2 = Timer(Duration.zero, () {});
Timer? markerReloadingTimer1 = Timer(Duration.zero, () {});
int durationToPassenger = 0;
bool isWayPointSheet = false;
bool isWayPointStopsSheet = false;
bool isWayPointStopsSheetUtilGetMap = false;
double heightBottomSheetShown = 0;
double cashConfirmPageShown = 250;
late String driverId = '';
late String gender = '';
double widthMapTypeAndTraffic = 50;
double paymentPageShown = Get.height * .6;
late LatLng southwest;
late LatLng northeast;
List<CarLocationModel> carLocationsModels = <CarLocationModel>[];
var dataCarsLocationByPassenger;
var datadriverCarsLocationToPassengerAfterApplied;
CarLocation? nearestCar;
late Timer? markerReloadingTimer = Timer(Duration.zero, () {});
bool shouldFetch = true; // Flag to determine if fetch should be executed
int selectedPassengerCount = 1;
double progress = 0;
double progressTimerToPassengerFromDriverAfterApplied = 0;
double progressTimerDriverWaitPassenger5Minute = 0;
int durationTimer = 9;
int durationToRide = 0;
int remainingTime = 25;
int remainingTimeToPassengerFromDriverAfterApplied = 60;
int remainingTimeDriverWaitPassenger5Minute = 60;
int timeToPassengerFromDriverAfterApplied = 0;
Timer? timerToPassengerFromDriverAfterApplied;
bool rideTimerBegin = false;
double progressTimerRideBegin = 0;
int remainingTimeTimerRideBegin = 60;
String stringRemainingTimeRideBegin = '';
String hintTextStartPoint = 'Search for your Start point'.tr;
String hintTextwayPoint0 = 'Search for waypoint'.tr;
String hintTextwayPoint1 = 'Search for waypoint'.tr;
String hintTextwayPoint2 = 'Search for waypoint'.tr;
String hintTextwayPoint3 = 'Search for waypoint'.tr;
String hintTextwayPoint4 = 'Search for waypoint'.tr;
String currentLocationString = 'Current Location'.tr;
String currentLocationString0 = 'Current Location'.tr;
String currentLocationString1 = 'Add Location 1'.tr;
String currentLocationString2 = 'Add Location 2'.tr;
String currentLocationString3 = 'Add Location 3'.tr;
String currentLocationString4 = 'Add Location 4'.tr;
String placesCoordinate0 = ''.tr;
String placesCoordinate1 = ''.tr;
String placesCoordinate2 = ''.tr;
String placesCoordinate3 = ''.tr;
String placesCoordinate4 = ''.tr;
List<String> currentLocationStringAll = [];
List<String> hintTextwayPointStringAll = [];
var placesCoordinate = <String>[];
String hintTextDestinationPoint = 'Select your destination'.tr;
late String rideId = 'yet';
bool noCarString = false;
bool isCashSelectedBeforeConfirmRide = false;
bool isPassengerChosen = false;
bool isSearchingWindow = false;
bool currentLocationToFormPlaces = false;
bool currentLocationToFormPlaces0 = false;
bool currentLocationToFormPlaces1 = false;
bool currentLocationToFormPlaces2 = false;
bool currentLocationToFormPlaces3 = false;
bool currentLocationToFormPlaces4 = false;
List currentLocationToFormPlacesAll = [];
late String driverToken = '';
int carsOrder = 0;
int wayPointIndex = 0;
late double kazan = 8;
String? mapAPIKEY;
late double totalME = 0;
late double tax = 0;
late double totalPassenger = 0;
late double totalCostPassenger = 0;
late double totalPassengerComfort = 0;
late double totalPassengerComfortDiscount = 0;
late double totalPassengerElectricDiscount = 0;
late double totalPassengerLadyDiscount = 0;
late double totalPassengerSpeedDiscount = 0;
late double totalPassengerBalashDiscount = 0;
late double totalPassengerRaihGaiDiscount = 0;
late double totalPassengerScooter = 0;
late double totalPassengerVan = 0;
late double totalDriver = 0;
late double averageDuration = 0;
late double costDuration = 0;
late double costDistance = 0;
late double distance = 0;
late double duration = 0;
bool _isDriverAppliedLogicExecuted = false; // فلاج لمنع التنفيذ المتكرر
bool _isDriverArrivedLogicExecuted = false;
bool _isRideBeginLogicExecuted = false;
DateTime? _searchStartTime; // لتتبع مدة البحث
DateTime? _lastDriversNotifyTime; // لتتبع آخر مرة تم إرسال إشعار للسائقين
final int _masterTimerIntervalSeconds = 5; // فاصل زمني ثابت للمؤقت الرئيسي
final int _searchTimeoutSeconds = 60; // مهلة البحث قبل عرض خيار زيادة السعر
final int _notifyDriversIntervalSeconds =
25; // إرسال إشعار للسائقين كل 25 ثانية
// --- إضافة جديدة: متغيرات لإدارة البحث المتوسع ---
int _currentSearchPhase = 0; // لتتبع المرحلة الحالية للبحث
// قائمة بأنصاف الأقطار (بالأمتار) لكل مرحلة
final List<int> _searchRadii = [
3000,
4500,
6000
]; // 0 ثانية، 30 ثانية، 60 ثانية
// المدة الزمنية لكل مرحلة بحث (بالثواني)
final int _searchPhaseDurationSeconds = 30;
// المهلة الإجمالية للبحث قبل عرض خيار زيادة السعر
final int _totalSearchTimeoutSeconds = 90; // 90 ثانية
// --- noRide throttling ---
int _noRideSearchCount = 0;
final int _noRideMaxTries = 3; // نفذ البحث 6 مرات فقط
final int _noRideIntervalSec = 5; // بين كل محاولة وأخرى 5 ثواني
DateTime? _noRideNextAllowed; // متى نسمح بالمحاولة التالية
bool _noRideSearchCapped = false; // وصلنا للحد وتوقفنا
// ============== new design to manage ride state ==============
// === 1. حالة الرحلة والمؤقت الرئيسي (Single Source of Truth) ===
Rx<RideState> currentRideState = RideState.noRide.obs;
Timer? _masterTimer;
final int _pollingIntervalSeconds = 4; // فاصل زمني موحد للاستعلام
void _startMasterTimer() {
// نضمن أن مؤقت واحد فقط يعمل في أي وقت
_masterTimer?.cancel();
_masterTimer =
Timer.periodic(Duration(seconds: _pollingIntervalSeconds), (_) {
_handleRideState(currentRideState.value);
});
}
void stopAllTimers() {
_masterTimer?.cancel();
// إذا كان لديك مؤقتات زمنية (مثل عداد الـ 5 دقائق للانتظار) قم بإلغائها هنا أيضاً
cancelTimerToPassengerFromDriverAfterApplied();
// ...
}
final int _maxNoRideSearch = 3; // عدد المرات القصوى
final int _noRideDelaySeconds = 6; // الفاصل الزمني بين كل بحث
//
//
// !!! يرجى استبدال الدالة القديمة بالكامل بهذه الدالة الجديدة !!!
//
//
Future<void> _handleRideState(RideState state) async {
Log.print('Handling state: $state');
int effectivePollingInterval = _pollingIntervalSeconds;
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));
String currentCarType = box.read(BoxName.carType) ?? 'yet';
getCarsLocationByPassengerAndReloadMarker(currentCarType, 3000);
getNearestDriverByPassengerLocation();
break;
case RideState.cancelled:
Log.print('[handleRideState] Ride cancelled. Stopping polling.');
stopAllTimers();
effectivePollingInterval = 3600;
break;
case RideState.preCheckReview:
stopAllTimers();
_checkLastRideForReview();
break;
case RideState.searching:
effectivePollingInterval = 5;
// 1. التحقق من حالة الطلب (هل قبله أحد؟)
try {
String statusFromServer = await getRideStatus(rideId);
if (statusFromServer == 'Apply' || statusFromServer == 'Applied') {
await processRideAcceptance();
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;
}
// 2. إدارة مراحل البحث (توسيع النطاق)
// السيناريو الجديد: لا نقوم بالقصف العشوائي، نرسل بناء على المرحلة أو مرور وقت كافٍ لدخول سائقين جدد
int targetPhase =
(elapsedSeconds / _searchPhaseDurationSeconds).floor();
if (targetPhase >= _searchRadii.length) {
targetPhase = _searchRadii.length - 1;
}
// هل تغيرت المرحلة (توسع النطاق)؟ أو هل مر 10 ثواني منذ آخر محاولة إرسال؟
// هذا يمنع إرسال الإشعار في كل دورة (كل 5 ثواني) ويقلل الازعاج
bool isNewPhase = targetPhase > _currentSearchPhase;
bool timeToScanForNewDrivers =
(elapsedSeconds % 15 == 0); // كل 15 ثانية نفحص الدخول الجديد
if (isNewPhase || timeToScanForNewDrivers || elapsedSeconds < 5) {
_currentSearchPhase = targetPhase;
int currentRadius = _searchRadii[_currentSearchPhase];
Log.print(
'[Search Logic] Scanning for drivers. Phase: $_currentSearchPhase, Radius: $currentRadius');
// استدعاء دالة الإشعار الذكية
_findAndNotifyNearestDrivers(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:
effectivePollingInterval = 10;
if (!_isDriverAppliedLogicExecuted) {
Log.print('[handleRideState] Execution driverApplied logic.');
rideAppliedFromDriver(true);
_isDriverAppliedLogicExecuted = true;
}
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');
}
getDriverCarsLocationToPassengerAfterApplied();
break;
case RideState.driverArrived:
if (!_isDriverArrivedLogicExecuted) {
_isDriverArrivedLogicExecuted = true;
startTimerDriverWaitPassenger5Minute();
driverArrivePassengerDialoge();
}
effectivePollingInterval = 8;
break;
case RideState.inProgress:
effectivePollingInterval = 11;
try {
String statusFromServer = await getRideStatus(rideId);
if (statusFromServer == 'Finished' ||
statusFromServer == 'finished') {
if (currentRideState.value == RideState.inProgress) {
currentRideState.value = RideState.finished;
break;
}
}
} catch (e) {
Log.print('Error polling for Finished status: $e');
}
if (!_isRideBeginLogicExecuted) {
_isRideBeginLogicExecuted = true;
_executeBeginRideLogic();
}
getDriverCarsLocationToPassengerAfterApplied();
break;
case RideState.finished:
tripFinishedFromDriver();
stopAllTimers();
effectivePollingInterval = 3600;
break;
}
if (_masterTimer?.tick != effectivePollingInterval) {
_masterTimer?.cancel();
_masterTimer =
Timer.periodic(Duration(seconds: effectivePollingInterval), (_) {
_handleRideState(currentRideState.value);
});
}
}
// Future<void> _handleRideState(RideState state) async {
// Log.print('Handling state: $state');
// int effectivePollingInterval = _pollingIntervalSeconds;
// switch (state) {
// // 1. تم فصل "noRide" عن "cancelled"
// 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));
// String currentCarType = box.read(BoxName.carType) ?? 'yet';
// getCarsLocationByPassengerAndReloadMarker(currentCarType, 3000);
// getNearestDriverByPassengerLocation();
// break;
// // 2. "cancelled" أصبحت حالة منفصلة توقف كل شيء
// case RideState.cancelled:
// Log.print('[handleRideState] Ride cancelled. Stopping polling.');
// stopAllTimers(); // أوقف المؤقت الرئيسي
// effectivePollingInterval = 3600; // إيقاف فعلي للمؤقت
// break;
// case RideState.preCheckReview:
// stopAllTimers();
// _checkLastRideForReview();
// break;
// case RideState.searching:
// // ... (منطق هذا الكيس سيبقى كما هو تماماً - لا تغيير هنا)
// effectivePollingInterval = 3;
// try {
// String statusFromServer = await getRideStatus(rideId);
// if (statusFromServer == 'Apply' || statusFromServer == 'Applied') {
// await processRideAcceptance();
// 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 needsNewSearch = false;
// if (targetPhase > _currentSearchPhase) {
// _currentSearchPhase = targetPhase;
// needsNewSearch = true;
// Log.print(
// '[Search] Expanding to Phase $_currentSearchPhase (Radius: ${_searchRadii[_currentSearchPhase]}m)');
// } else if (elapsedSeconds < effectivePollingInterval) {
// needsNewSearch = true;
// Log.print('[Search] Starting Phase 0 (Radius: ${_searchRadii[0]}m)');
// }
// // if (needsNewSearch) {
// int currentRadius = _searchRadii[_currentSearchPhase];
// // _findAndNotifyNearestDrivers(currentRadius);
// // }
// // (3) [!! التعديل الجوهري !!]
// // احذف 'if (needsNewSearch)' وقم بالاستدعاء مباشرة
// // هذا سيضمن أنه في كل دورة (كل 4 ثوانٍ)، يتم إعادة جلب السائقين
// Log.print(
// '[Search Polling] Finding new drivers in Phase $_currentSearchPhase (Radius: ${currentRadius}m)');
// _findAndNotifyNearestDrivers(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:
// effectivePollingInterval = 10; // فاصل زمني مناسب
// if (!_isDriverAppliedLogicExecuted) {
// Log.print('[handleRideState] تنفيذ منطق "driverApplied" لأول مرة.');
// rideAppliedFromDriver(true); // هذا يشغل عداد وصول السائق
// _isDriverAppliedLogicExecuted = true;
// }
// // [!! إضافة مهمة !!]
// // استمر في التحقق من حالة الرحلة (Status)
// try {
// String statusFromServer = await getRideStatus(rideId);
// if (statusFromServer == 'Arrived') {
// Log.print('[Polling] اكتشف "Arrived". تغيير الحالة.');
// currentRideState.value = RideState.driverArrived;
// break; // اخرج (سيتم تفعيل case driverArrived في الدورة القادمة)
// } else if (statusFromServer == 'Begin' ||
// statusFromServer == 'inProgress') {
// Log.print('[Polling] اكتشف "Begin" (تجاوز حالة الوصول).');
// // استدعاء حارس البوابة الآمن
// processRideBegin(); // هذه الدالة ستغير الحالة إلى inProgress
// break;
// }
// } catch (e) {
// Log.print('Error polling for Arrived/Begin status: $e');
// }
// // [!! نهاية الإضافة !!]
// getDriverCarsLocationToPassengerAfterApplied(); // استمر بجلب الموقع أيضاً
// break;
// // ...
// case RideState.driverArrived:
// if (!_isDriverArrivedLogicExecuted) {
// _isDriverArrivedLogicExecuted = true;
// Log.print(
// '[handleRideState] اكتشف "driverArrived". بدء عداد 5 دقائق.');
// startTimerDriverWaitPassenger5Minute();
// driverArrivePassengerDialoge();
// }
// effectivePollingInterval = 8; // يبقى يعمل لاكتشاف "inProgress"
// break;
// case RideState.inProgress:
// effectivePollingInterval = 11;
// // 3. إضافة "بحث دوري" (Polling) لاكتشاف إنهاء الرحلة
// try {
// String statusFromServer = await getRideStatus(rideId);
// if (statusFromServer == 'Finished' ||
// statusFromServer == 'finished') {
// if (currentRideState.value == RideState.inProgress) {
// Log.print('[Polling] اكتشف "Finished". تغيير الحالة.');
// currentRideState.value = RideState.finished; // غيّر الحالة
// break; // اخرج (سيتم تفعيل case finished في الدورة القادمة)
// }
// }
// } catch (e) {
// Log.print('Error polling for Finished status: $e');
// }
// // --- نهاية الإضافة ---
// if (!_isRideBeginLogicExecuted) {
// _isRideBeginLogicExecuted = true;
// _executeBeginRideLogic();
// }
// getDriverCarsLocationToPassengerAfterApplied(); // استمر في تتبع الموقع
// break;
// // 4. "finished" أصبحت حالة نهائية آمنة
// case RideState.finished:
// // إشعار FCM (الذي يملك بيانات السعر) هو المسؤول عن الانتقال للتقييم
// // هذه الحالة هي مجرد "نقطة توقف" للمؤقت.
// Log.print('[handleRideState] Ride is Finished. Stopping all activity.');
// // نستدعي هذه لضمان تصفير الواجهة، حتى لو فاز البحث الدوري
// tripFinishedFromDriver(); // (سنقوم بتعديل هذه الدالة في الخطوة 3)
// stopAllTimers();
// effectivePollingInterval = 3600; // إيقاف فعلي
// break;
// }
// if (_masterTimer?.tick != effectivePollingInterval) {
// _masterTimer?.cancel();
// _masterTimer =
// Timer.periodic(Duration(seconds: effectivePollingInterval), (_) {
// _handleRideState(currentRideState.value);
// });
// }
// }
// // === 4. المنطق الجديد: فحص الرحلة الأخيرة للتقييم ===
Future<void> _checkInitialRideStatus() async {
// 1. جلب الحالة من السيرفر (باستخدام getRideStatusFromStartApp)
await getRideStatusFromStartApp();
String _status = rideStatusFromStartApp['data']['status'];
// Log.print('rideStatusFromStartApp: ${rideStatusFromStartApp}');
// Log.print('_status: ${_status}');
if (_status == 'waiting' || _status == 'Apply' || _status == 'Begin') {
// رحلة جارية
rideId = rideStatusFromStartApp['data']['rideId'].toString();
currentRideState.value = _status == 'waiting'
? RideState.searching
: _status == 'Apply'
? RideState.driverApplied
: _status == 'Begin'
? RideState.inProgress
: _status == 'Cancel'
? RideState.cancelled
: RideState.noRide;
} else if (_status == 'Finished') {
// رحلة منتهية/ملغاة
if (rideStatusFromStartApp['data']['needsReview'] == 1) {
currentRideState.value = RideState.preCheckReview;
} else {
currentRideState.value = RideState.noRide;
}
} else {
currentRideState.value = RideState.noRide;
}
// بدء المعالجة الفورية
_handleRideState(currentRideState.value);
}
Future<void> _checkLastRideForReview() async {
// (يتم استدعاؤها مرة واحدة عند الدخول لحالة preCheckReview)
// 1. استعلام السيرفر للتأكد من الحاجة للتقييم
getRideStatusFromStartApp();
String needsReview =
rideStatusFromStartApp['data']['needsReview'].toString();
if (needsReview == '1') {
// 2. إظهار Bottom Sheet للتقييم (إجبار الراكب على التقييم)
Get.to(() => RatingDriverBottomSheet(), arguments: {
'driverId': rideStatusFromStartApp['data']['driver_id'].toString(),
'rideId': rideStatusFromStartApp['data']['rideId'].toString(),
'driverName': rideStatusFromStartApp['data']['driverName'],
'price': rideStatusFromStartApp['data']['price'],
});
// showRatingScreen();
print('>>> Showing forced Rating Bottom Sheet for ride: $rideId');
// بمجرد انتهاء التقييم، يتم تعيين الحالة إلى NO_RIDE
// onRatingSubmitted: () {
// currentRideState.value = RideState.noRide;
// _startMasterTimer(); // إعادة تشغيل المؤقت لحالة NO_RIDE
// }
} else {
// لا تحتاج تقييم، العودة لحالة لا رحلة
currentRideState.value = RideState.noRide;
_startMasterTimer(); // إعادة تشغيل المؤقت لحالة NO_RIDE
}
}
// [داخل MapPassengerController]
// (افترض وجود هذه الدالة لديك بالفعل)
// === 5. دوال التغيير في الحالة التي تستدعى من واجهة المستخدم ===
// void startSearchingForDriver() {
// confirmRideForAllDriverAvailable();
// currentRideState.value = RideState.searching;
// _startMasterTimer();
// }
/// هذه هي الدالة التي يتم استدعاؤها من الواجهة عند ضغط المستخدم على زر تأكيد الطلب
void startSearchingForDriver() async {
isSearchingWindow = true;
update();
bool rideCreated = await postRideDetailsToServer();
if (!rideCreated) {
isSearchingWindow = false;
update();
return;
}
await _addRideToWaitingTable();
Log.print('[startSearchingForDriver] Starting fresh search sequence.');
// تصفير القائمة هنا ضروري لأنها رحلة جديدة
notifiedDrivers.clear();
_searchStartTime = DateTime.now();
_lastDriversNotifyTime = null;
_currentSearchPhase = 0;
_isDriverAppliedLogicExecuted = false;
_isDriverArrivedLogicExecuted = false;
_isRideBeginLogicExecuted = false;
// تنفيذ البحث الأولي فوراً
await _findAndNotifyNearestDrivers(_searchRadii[0]);
currentRideState.value = RideState.searching;
isSearchingWindow = true;
update();
_startMasterTimer();
}
/// دالة جديدة للبحث عن السائقين وإرسال الإشعارات لهم
// تم التعديل: الآن تقبل نصف القطر (radius)
/// [!! إصلاح خطأ البيلود !!]
/// تم التعديل: العودة إلى إرسال إشعارات فردية (لكل سائق)
/// لإرسال "البيلود" الصحيح الذي يتوقعه تطبيق السائق
Future<void> _findAndNotifyNearestDrivers(int radius) async {
Log.print('[Notify Logic] Finding drivers within $radius meters...');
// 1. جلب السائقين وتحديث الماركر
// ملاحظة: تأكد أن هذه الدالة تعيد true/false لتدل على النجاح
await getCarsLocationByPassengerAndReloadMarker(
box.read(BoxName.carType), radius);
// التأكد من صحة البيانات
if (dataCarsLocationByPassenger == null ||
dataCarsLocationByPassenger == 'failure' ||
dataCarsLocationByPassenger['message'] == null) {
Log.print('[Notify Logic] No drivers found or API failure.');
return;
}
int newDriversNotified = 0;
var driversList = dataCarsLocationByPassenger['message'];
if (driversList is! List) {
Log.print('[Notify Logic] Drivers data is not a list.');
return;
}
// 2. المرور على كل سائق
for (var driverData in driversList) {
// حماية من البيانات الناقصة
if (driverData['driver_id'] == null || driverData['token'] == null) {
continue;
}
String driverId = driverData['driver_id'].toString();
String driverToken = driverData['token'].toString();
// تجاهل التوكن الفارغ أو غير الصالح
if (driverToken.isEmpty || driverToken == 'null') continue;
// 3. التحقق من القائمة المرجعية (المنع الصارم للتكرار)
if (!notifiedDrivers.contains(driverId)) {
// إضافة السائق للقائمة فوراً لمنع تكرار الإرسال في الدورات القادمة
notifiedDrivers.add(driverId);
newDriversNotified++;
// 4. بناء البيلود وإرسال الإشعار
try {
final body = constructNotificationBody(driverData);
Log.print('[Notify Logic] Sending New Order to Driver ID: $driverId');
NotificationService.sendNotification(
target: driverToken,
title: 'Order',
body: endNameAddress, // عنوان الوجهة كنص مختصر
isTopic: false,
tone: 'tone1',
category: 'Order',
driverList: body);
} catch (e) {
Log.print('[Notify Logic] Error sending to driver $driverId: $e');
// في حال فشل الإرسال، هل تريد حذفه من القائمة ليحاول مرة أخرى؟
// يفضل عدم الحذف لتجنب الإزعاج، إلا إذا كان خطأ شبكة مؤقت.
}
} else {
// Log.print('[Notify Logic] Driver $driverId already notified. Skipping.');
}
}
Log.print(
'[Notify Logic] Cycle finished. Sent to $newDriversNotified NEW drivers.');
}
/// دالة لإظهار النافذة المنبثقة لزيادة السعر
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: const TextStyle(color: AppColor.redColor)),
onPressed: () {
Get.back();
changeCancelRidePageShow();
// cancelRide(); // دالة إلغاء الرحلة
},
),
CupertinoDialogAction(
child: Text("Increase Fare".tr,
style: const TextStyle(color: AppColor.greenColor)),
onPressed: () {
Get.back();
// هنا يمكنك عرض نافذة أخرى لإدخال السعر الجديد
// وبعدها استدعاء الدالة التالية
// كمثال، سنزيد السعر بنسبة 10%
double newPrice = totalPassenger * 1.10;
increasePriceAndRestartSearch(newPrice);
},
),
],
),
barrierDismissible: false,
);
}
/// دالة لتحديث السعر وإعادة بدء البحث
Future<void> increasePriceAndRestartSearch(double newPrice) async {
totalPassenger = newPrice;
update();
await CRUD().post(link: AppLink.updateRides, payload: {
"id": rideId,
"price": newPrice.toStringAsFixed(2),
});
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();
}
/// (دالة جديدة موحدة)
/// هذه هي الدالة الوحيدة المسؤولة عن بدء عملية قبول الرحلة.
/// يتم استدعاؤها إما من إشعار Firebase أو من البحث الدوري (Polling).
/// هي تحتوي على "حارس البوابة" لمنع تضارب السباق.
Future<void> processRideAcceptance(
{String? driverIdFromFCM, String? rideIdFromFCM}) async {
// 1. حارس البوابة (منع السباق)
// هل ما زلنا في حالة البحث؟
if (currentRideState.value != RideState.searching) {
Log.print(
'[processRideAcceptance] تم الحظر: الحالة ليست searching، تم قبول الرحلة مسبقاً.');
return; // اخرج فوراً. العملية تمت بالفعل.
}
// getDriverCarsLocationToPassengerAfterApplied();
// 2. فزنا بالسباق! قم بتغيير الحالة فوراً
// هذا السطر يضمن أن أي استدعاء آخر (من الإشعار أو البحث) سيفشل في الشرط أعلاه
currentRideState.value = RideState.driverApplied;
Log.print('[processRideAcceptance] نجح: تغيير الحالة إلى driverApplied.');
// 3. تحديث البيانات (كما في كود الإشعار الخاص بك)
// إذا كان الاستدعاء من الإشعار، استخدم البيانات الممررة
if (driverIdFromFCM != null) {
driverId = driverIdFromFCM;
}
if (rideIdFromFCM != null) {
rideId = rideIdFromFCM;
}
// (ملاحظة: إذا كان الاستدعاء من البحث الدوري، فإن 'rideId' معروف مسبقاً)
await getUpdatedRideForDriverApply(rideId);
// 4. تحديث واجهة المستخدم (كما في كود الإشعار)
statusRide = 'Apply';
isSearchingWindow = false;
update(); // إخفاء نافذة البحث وتحديث الواجهة
// 5. إظهار الإشعار المحلي (توحيد المنطق)
// (نقلنا هذا من معالج الإشعارات إلى هنا)
if (Get.isRegistered<NotificationController>()) {
Get.find<NotificationController>().showNotification(
'Accepted Ride'.tr, 'Driver Accepted the Ride for You'.tr, 'ding');
}
// 6. !! تم الحذف !!
// await rideAppliedFromDriver(true); <-- (تم حذف هذا السطر)
// الآن هذه الدالة آمنة ولا تشغل أي تايمرات.
}
Future<dynamic> driverArrivePassengerDialoge() {
return Get.defaultDialog(
barrierDismissible: false,
title: 'Hi ,I Arrive your site'.tr,
titleStyle: AppStyle.title,
middleText: 'Please go to Car Driver'.tr,
middleTextStyle: AppStyle.title,
confirm: MyElevatedButton(
title: 'Ok I will go now.'.tr,
onPressed: () {
NotificationService.sendNotification(
target: driverToken.toString(),
title: 'Hi ,I will go now'.tr,
body: 'I will go now'.tr,
isTopic: false, // Important: this is a token
tone: 'ding',
driverList: [],
category: 'Hi ,I will go now',
);
Get.back();
remainingTime = 0;
update();
}));
}
/// (دالة خاصة جديدة)
/// تحتوي على كل المنطق الفعلي لبدء الرحلة.
void _executeBeginRideLogic() {
Log.print('[executeBeginRideLogic] تنفيذ منطق بدء الرحلة...');
// 1. تصفير كل عدادات ما قبل الرحلة
timeToPassengerFromDriverAfterApplied = 0;
remainingTime = 0;
remainingTimeToPassengerFromDriverAfterApplied = 0;
remainingTimeDriverWaitPassenger5Minute = 0;
// 2. تحديث الحالة والواجهة
rideTimerBegin = true;
statusRide = 'Begin';
isDriverInPassengerWay = false;
isDriverArrivePassenger = false; // لإخفاء واجهة "السائق وصل"
// 3. (من كود الإشعار الخاص بك)
box.write(BoxName.passengerWalletTotal, '0');
update(); // تحديث الواجهة قبل بدء المؤقتات
// 4. بدء مؤقتات الرحلة الفعلية
rideIsBeginPassengerTimer(); // مؤقت عداد مدة الرحلة
runWhenRideIsBegin(); // مؤقت تتبع موقع السائق أثناء الرحلة
// 5. إشعار الراكب (من كود الإشعار الخاص بك)
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');
}
/// (دالة جديدة موحدة)
/// حارس البوابة الآمن لبدء الرحلة. تمنع تضارب السباق.
//
//
// !!! يرجى استبدال الدالة القديمة بالكامل بهذه الدالة الجديدة !!!
//
//
/// (دالة جديدة موحدة)
/// حارس البوابة الآمن لبدء الرحلة. تمنع تضارب السباق.
void processRideBegin() {
// [!! تعديل رقم 3: حل مشكلة "تم الحظر" !!]
// اقبل بدء الرحلة إذا كانت الحالة (وصل) أو (قَبِل)
// هذا يضمن أنه إذا فاتنا إشعار "الوصول" واستلمنا "البدء" مباشرة، فإنه سيعمل
if (currentRideState.value != RideState.driverArrived &&
currentRideState.value != RideState.driverApplied) {
Log.print(
'[processRideBegin] تم الحظر: الحالة ليست driverArrived أو driverApplied.');
return;
}
// تأكد من تغيير الحالة فقط إذا لم تكن كذلك بالفعل
if (currentRideState.value != RideState.inProgress) {
currentRideState.value = RideState.inProgress;
Log.print('[processRideBegin] نجح: تغيير الحالة إلى inProgress.');
}
// لا تقم بتشغيل أي منطق هنا
// المنطق الفعلي (_executeBeginRideLogic) سيتم استدعاؤه بواسطة
// المؤقت الرئيسي (_handleRideState) عند اكتشاف حالة inProgress
// هذا يضمن أنه يعمل مرة واحدة فقط وبشكل آمن
}
late Duration durationToAdd;
late DateTime newTime = DateTime.now();
int hours = 0;
int minutes = 0;
// --- إضافة جديدة: للوصول إلى وحدة التحكم بالروابط ---
final DeepLinkController _deepLinkController =
Get.isRegistered<DeepLinkController>()
? Get.find<DeepLinkController>()
: Get.put(DeepLinkController());
// ------------------------------------------------
void onChangedPassengerCount(int newValue) {
selectedPassengerCount = newValue;
update();
}
void onChangedPassengersChoose() {
isPassengerChosen = true;
update();
}
void getCurrentLocationFormString() async {
currentLocationToFormPlaces = true;
currentLocationString = 'Waiting for your location'.tr;
await getLocation();
currentLocationString = passengerLocation.toString();
newStartPointLocation = passengerLocation;
update();
}
List<String> coordinatesWithoutEmpty = [];
void getMapPointsForAllMethods() async {
clearPolyline();
isMarkersShown = false;
isWayPointStopsSheetUtilGetMap = false;
isWayPointSheet = false;
durationToRide = 0;
distanceOfDestination = 0;
wayPointSheetHeight = 0;
remainingTime = 25;
haveSteps = true;
// Filter out empty value
coordinatesWithoutEmpty =
placesCoordinate.where((coord) => coord.isNotEmpty).toList();
latestPosition = LatLng(
double.parse(coordinatesWithoutEmpty.last.split(',')[0]),
double.parse(coordinatesWithoutEmpty.last.split(',')[1]));
for (var i = 0; i < coordinatesWithoutEmpty.length; i++) {
if ((i + 1) < coordinatesWithoutEmpty.length) {
await getMapPoints(
coordinatesWithoutEmpty[i].toString(),
coordinatesWithoutEmpty[i + 1].toString(),
i,
);
if (i == 0) {
startNameAddress = data[0]['start_address'];
}
if (i == coordinatesWithoutEmpty.length) {
endNameAddress = data[0]['end_address'];
}
}
}
// isWayPointStopsSheet = false;
if (haveSteps) {
String latestWaypoint =
placesCoordinate.lastWhere((coord) => coord.isNotEmpty);
latestPosition = LatLng(
double.parse(latestWaypoint.split(',')[0]),
double.parse(latestWaypoint.split(',')[1]),
);
}
updateCameraForDistanceAfterGetMap();
changeWayPointStopsSheet();
bottomSheet();
showBottomSheet1();
update();
}
void convertHintTextStartNewPlaces(int index) {
if (placesStart.isEmpty) {
hintTextStartPoint = 'Search for your Start point'.tr;
update();
} else {
var res = placesStart[index];
hintTextStartPoint = res['displayName']?['text'] ??
res['formattedAddress'] ??
'Unknown Place';
double? lat = res['location']?['latitude'];
double? lng = res['location']?['longitude'];
if (lat != null && lng != null) {
newStartPointLocation = LatLng(lat, lng);
}
update();
}
}
void convertHintTextPlaces(int index, var res) {
if (placeListResponseAll[index].isEmpty) {
placeListResponseAll[index] = res;
hintTextwayPointStringAll[index] = 'Search for your Start point'.tr;
update();
} else {
hintTextwayPointStringAll[index] = res['name'];
currentLocationStringAll[index] = res['name'];
placesCoordinate[index] =
'${res['geometry']['location']['lat']},${res['geometry']['location']['lng']}';
placeListResponseAll[index] = [];
allTextEditingPlaces[index].clear();
// double lat = wayPoint0[index]['geometry']['location']['lat'];
// double lng = wayPoint0[index]['geometry']['location']['lng'];
// newPointLocation0 = LatLng(lat, lng);
update();
Get.back();
}
}
increaseFeeByPassengerAndReOrder() async {
if (increaseFeeFormKey.currentState!.validate()) {
if (double.parse(increasFeeFromPassenger.text) > totalPassenger) {
totalPassenger = double.parse(increasFeeFromPassenger.text);
Get.back();
if (rideId != 'yet') {
Log.print('rideId from increase: $rideId');
notifyAvailableDriversAgain();
await CRUD().post(link: AppLink.updateDriverOrder, payload: {
"order_id": rideId.toString(), // Convert to String
"status": 'waiting'
});
await CRUD().post(link: AppLink.updateRides, payload: {
"id": rideId.toString(), // Convert to String
"status": 'waiting'
});
CRUD().post(link: AppLink.updateWaitingTrip, payload: {
"id": rideId.toString(), // Convert to String
"status": 'wait'
});
CRUD().post(
link: "${AppLink.endPoint}/ride/rides/update.php",
payload: {
"id": rideId.toString(), // Convert to String
"status": 'waiting'
});
// if (AppLink.endPoint != AppLink.IntaleqSyriaServer) {
CRUD().post(
link:
"${AppLink.endPoint}/ride/notificationCaptain/updateWaitingTrip.php",
payload: {
"id": rideId.toString(), // Convert to String
"status": 'wait'
});
}
tick = 0;
// }
await getCarsLocationByPassengerAndReloadMarker(
box.read(BoxName.carType), 4000);
// confirmRideForAllDriverAvailable();
increaseForSameRideAndDelay();
}
}
}
void convertHintTextPlaces1(int index) {
if (wayPoint1.isEmpty) {
hintTextwayPoint1 = 'Search for your Start point'.tr;
update();
} else {
hintTextwayPoint1 = wayPoint1[index]['name'];
currentLocationString1 = wayPoint1[index]['name'];
double lat = wayPoint1[index]['geometry']['location']['lat'];
double lng = wayPoint1[index]['geometry']['location']['lng'];
newPointLocation1 = LatLng(lat, lng);
update();
}
}
void convertHintTextPlaces2(int index) {
if (wayPoint1.isEmpty) {
hintTextwayPoint2 = 'Search for your Start point'.tr;
update();
} else {
hintTextwayPoint2 = wayPoint2[index]['name'];
currentLocationString2 = wayPoint1[index]['name'];
double lat = wayPoint2[index]['geometry']['location']['lat'];
double lng = wayPoint2[index]['geometry']['location']['lng'];
newPointLocation2 = LatLng(lat, lng);
update();
}
}
void convertHintTextPlaces3(int index) {
if (wayPoint1.isEmpty) {
hintTextwayPoint3 = 'Search for your Start point'.tr;
update();
} else {
hintTextwayPoint3 = wayPoint3[index]['name'];
currentLocationString3 = wayPoint1[index]['name'];
double lat = wayPoint3[index]['geometry']['location']['lat'];
double lng = wayPoint3[index]['geometry']['location']['lng'];
newPointLocation3 = LatLng(lat, lng);
update();
}
}
void convertHintTextPlaces4(int index) {
if (wayPoint1.isEmpty) {
hintTextwayPoint4 = 'Search for your Start point'.tr;
update();
} else {
hintTextwayPoint4 = wayPoint4[index]['name'];
currentLocationString4 = wayPoint1[index]['name'];
double lat = wayPoint4[index]['geometry']['location']['lat'];
double lng = wayPoint4[index]['geometry']['location']['lng'];
newPointLocation4 = LatLng(lat, lng);
update();
}
}
void convertHintTextDestinationNewPlaces(int index) {
if (placesDestination.isEmpty) {
hintTextDestinationPoint = 'Search for your destination'.tr;
update();
} else {
var res = placesDestination[index];
// استخراج الاسم من displayName.text أو بديله
hintTextDestinationPoint = res['displayName']?['text'] ??
res['formattedAddress'] ??
'Unknown Place';
// استخراج الإحداثيات
double? lat = res['location']?['latitude'];
double? lng = res['location']?['longitude'];
if (lat != null && lng != null) {
newMyLocation = LatLng(lat, lng);
}
update();
}
}
void convertHintTextDestinationNewPlacesFromRecent(
List recentLocations, int index) {
hintTextDestinationPoint = recentLocations[index]['name'];
double lat = recentLocations[index]['latitude'];
double lng = recentLocations[index]['longitude'];
newMyLocation = LatLng(lat, lng);
update();
}
// final mainBottomMenuMap = GlobalKey<AnimatedContainer>();
void changeBottomSheetShown() {
isBottomSheetShown = !isBottomSheetShown;
heightBottomSheetShown = isBottomSheetShown == true ? 250 : 0;
update();
}
void changeCashConfirmPageShown() {
isCashConfirmPageShown = !isCashConfirmPageShown;
isCashSelectedBeforeConfirmRide = true;
cashConfirmPageShown = isCashConfirmPageShown == true ? 250 : 0;
// to get or sure picker point for origin //todo
// isPickerShown = true;
// clickPointPosition();
update();
}
void changePaymentMethodPageShown() {
isPaymentMethodPageShown = !isPaymentMethodPageShown;
paymentPageShown = isPaymentMethodPageShown == true ? Get.height * .6 : 0;
update();
}
void changeMapType() {
mapType = !mapType;
// heightButtomSheetShown = isButtomSheetShown == true ? 240 : 0;
update();
}
void changeMapTraffic() {
mapTrafficON = !mapTrafficON;
update();
}
void changeisAnotherOreder(bool val) {
isAnotherOreder = val;
update();
}
void changeIsWhatsAppOrder(bool val) {
isWhatsAppOrder = val;
update();
}
void sendSMS(String to) async {
// Get the driver's phone number.
String driverPhone =
(dataCarsLocationByPassenger['message'][carsOrder]['phone'].toString());
// Format the message.
String message =
'Hi! This is ${(box.read(BoxName.name).toString().split(' ')[0]).toString()}.\n I am using ${box.read(AppInformation.appName)} to ride with $passengerName as the driver. $passengerName \nis driving a $model\n with license plate $licensePlate.\n I am currently located at $passengerLocation.\n If you need to reach me, please contact the driver directly at\n\n $driverPhone.';
// Launch the URL to send the SMS.
launchCommunication('sms', to, message);
}
String formatSyrianPhone(String phone) {
// Remove spaces and +
phone = phone.replaceAll(' ', '').replaceAll('+', '');
// If starts with 00963 → remove 00 → 963
if (phone.startsWith('00963')) {
phone = phone.replaceFirst('00963', '963');
}
// If starts with 0963 (common mistake) → fix it
if (phone.startsWith('0963')) {
phone = phone.replaceFirst('0963', '963');
}
// If starts with 963 (already correct)
if (phone.startsWith('963')) {
return phone; // nothing to do
}
// If starts with 09 → remove leading 0 → add 963
if (phone.startsWith('09')) {
return '963' + phone.substring(1); // 9xxxxxxxxx
}
// If starts with 9xxxxxxxxx (no country code)
if (phone.startsWith('9') && phone.length == 9) {
return '963' + phone;
}
// Otherwise return raw phone
return phone;
}
void sendWhatsapp(String to) async {
// Normalize phone number before sending
String formattedPhone = formatSyrianPhone(to);
// Message body
String message =
'${'${'Hi! This is'.tr} ${(box.read(BoxName.name).toString().split(' ')[0]).toString()}.\n${' I am using'.tr}'} ${AppInformation.appName}${' to ride with'.tr} $passengerName${' as the driver.'.tr} $passengerName \n${'is driving a '.tr}$model\n${' with license plate '.tr}$licensePlate.\n${' I am currently located at '.tr} https://www.google.com/maps/place/${passengerLocation.latitude},${passengerLocation.longitude}.\n${' If you need to reach me, please contact the driver directly at'.tr}\n\n $driverPhone.';
// Send WhatsApp message
launchCommunication('whatsapp', formattedPhone, message);
}
void changeCancelRidePageShow() {
showCancelRideBottomSheet();
isCancelRidePageShown = !isCancelRidePageShown;
// : cancelRide();
update();
}
void getDrawerMenu() {
heightMenuBool = !heightMenuBool;
widthMapTypeAndTraffic = heightMenuBool == true ? 0 : 50;
heightMenu = heightMenuBool == true ? 80 : 0;
widthMenu = heightMenuBool == true ? 110 : 0;
update();
}
calcualateDistsanceInMetet(LatLng prev, current) async {
double distance2 = Geolocator.distanceBetween(
prev.latitude,
prev.longitude,
current.latitude,
current.longitude,
);
return distance2;
}
StreamController<int> _timerStreamController = StreamController<int>();
Stream<int> get timerStream => _timerStreamController.stream;
bool isTimerFromDriverToPassengerAfterAppliedRunning = true;
bool isTimerRunning = false; // Flag to track if the timer is running
int beginRideInterval = 4; // Interval in seconds for getBeginRideFromDriver
void startTimerFromDriverToPassengerAfterApplied() {
if (isTimerRunning) return;
isTimerRunning = true;
int secondsElapsed = 0;
// استدعاء فوري لأول مرة
getDriverCarsLocationToPassengerAfterApplied();
Timer.periodic(const Duration(seconds: 1), (timer) {
// شروط الإيقاف
if (secondsElapsed > timeToPassengerFromDriverAfterApplied ||
!isTimerFromDriverToPassengerAfterAppliedRunning) {
timer.cancel();
isTimerRunning = false;
if (!_timerStreamController.isClosed) {
_timerStreamController.close();
}
return;
}
secondsElapsed++;
if (!_timerStreamController.isClosed) {
_timerStreamController.add(secondsElapsed);
}
// حسابات الوقت للواجهة
remainingTimeToPassengerFromDriverAfterApplied =
timeToPassengerFromDriverAfterApplied - secondsElapsed;
// حماية من القيم السالبة
if (remainingTimeToPassengerFromDriverAfterApplied < 0)
remainingTimeToPassengerFromDriverAfterApplied = 0;
int minutes =
(remainingTimeToPassengerFromDriverAfterApplied / 60).floor();
int seconds = remainingTimeToPassengerFromDriverAfterApplied % 60;
stringRemainingTimeToPassenger =
'$minutes:${seconds.toString().padLeft(2, '0')}';
// كل 4 ثواني، نحدث موقع السائق ونعيد الرسم
if (secondsElapsed % beginRideInterval == 0) {
// 1. تحقق من حالة الرحلة (هل بدأت؟)
getBeginRideFromDriver();
// 2. ارفع موقع الراكب
uploadPassengerLocation();
// 3. الأهم: جلب موقع السائق وتحديث الخريطة والمسار
// لا نحتاج لعمل await هنا حتى لا نعطل عداد الثواني
getDriverCarsLocationToPassengerAfterApplied();
} else {
// تحديث الواجهة فقط للعداد في الثواني التي لا نطلب فيها الموقع
update();
}
});
} // void startTimerFromDriverToPassengerAfterApplied() async {
// if (isTimerRunning) return; // Exit if timer is already running
// isTimerRunning = true; // Set the flag to true
// int secondsElapsed = 0;
// while (secondsElapsed <= timeToPassengerFromDriverAfterApplied &&
// isTimerFromDriverToPassengerAfterAppliedRunning) {
// await Future.delayed(const Duration(seconds: 1));
// secondsElapsed++;
// progressTimerToPassengerFromDriverAfterApplied =
// secondsElapsed / timeToPassengerFromDriverAfterApplied;
// remainingTimeToPassengerFromDriverAfterApplied =
// timeToPassengerFromDriverAfterApplied - secondsElapsed;
// if (remainingTimeToPassengerFromDriverAfterApplied < 59) {
// if (rideTimerBegin == false) {
// rideTimerBegin = true;
// }
// }
// // Call getBeginRideFromDriver every 4 seconds
// if (secondsElapsed % beginRideInterval == 0) {
// getBeginRideFromDriver();
// uploadPassengerLocation();
// }
// int minutes =
// (remainingTimeToPassengerFromDriverAfterApplied / 60).floor();
// int seconds = remainingTimeToPassengerFromDriverAfterApplied % 60;
// stringRemainingTimeToPassenger =
// '$minutes:${seconds.toString().padLeft(2, '0')}';
// update();
// }
// isTimerRunning = false; // Reset the flag when timer completes
// }
// Remove the getBeginRideFromDriverForDuration function as it's no longer needed
// Function to stop the timer
void stopTimerFromDriverToPassengerAfterApplied() {
isTimerFromDriverToPassengerAfterAppliedRunning = false;
update();
}
void startTimerDriverWaitPassenger5Minute() async {
// 1. [مهم] إيقاف المؤقت السابق (مؤقت تتبع وصول السائق)
stopTimerFromDriverToPassengerAfterApplied();
isTimerRunning = false; // (إذا كنت تستخدم فلاج للتحكم بالمؤقت السابق)
// 2. تحديث حالة الواجهة فوراً
isDriverArrivePassenger = true;
isDriverInPassengerWay = false;
timeToPassengerFromDriverAfterApplied = 0;
update();
Log.print('[startTimerDriverWaitPassenger5Minute] بدأ عداد الـ 5 دقائق.');
// 3. بدء عداد الخمس دقائق (300 ثانية)
for (int i = 0; i <= 300; i++) {
// 4. [حارس السباق 1] التحقق من الحالة
// إذا فاز إشعار FCM (FCM Winner) وتغيرت الحالة، أوقف هذا العداد فوراً.
if (currentRideState.value != RideState.driverArrived) {
Log.print(
'[startTimerDriverWaitPassenger5Minute] إيقاف العد: الحالة تغيرت (FCM فاز).');
break; // اخرج من loop الخمس دقائق
}
// 5. [البحث الدوري 2] التحقق من السيرفر (كل 6 ثوانٍ)
if (i % 6 == 0 && i > 0) {
// (نتخطى الثانية صفر)
try {
var res = await CRUD().get(
link: AppLink.getRideStatusBegin, payload: {'ride_id': rideId});
if (res != 'failure') {
var decode = jsonDecode(res);
if (decode['data']['status'] == 'Begin') {
Log.print(
'[startTimerDriverWaitPassenger5Minute] اكتشف "Begin" (Polling فاز).');
// 6. استدعاء حارس البوابة الآمن
processRideBegin();
break; // اخرج من loop الخمس دقائق
}
}
} catch (e) {
Log.print('خطأ أثناء البحث الدوري عن بدء الرحلة: $e');
}
}
// 7. تحديث واجهة المستخدم (العداد)
progressTimerDriverWaitPassenger5Minute = i / 300;
remainingTimeDriverWaitPassenger5Minute = 300 - i;
int minutes = (remainingTimeDriverWaitPassenger5Minute / 60).floor();
int seconds = remainingTimeDriverWaitPassenger5Minute % 60;
stringRemainingTimeDriverWaitPassenger5Minute =
'$minutes:${seconds.toString().padLeft(2, '0')}';
update();
// 8. الانتظار ثانية واحدة
await Future.delayed(const Duration(seconds: 1));
}
Log.print(
'[startTimerDriverWaitPassenger5Minute] انتهى (إما بالوقت أو ببدء الرحلة).');
}
// Create a StreamController to manage the timer values
final timerController = StreamController<int>();
// Start the timer when the ride begins
void beginRideTimer() {
// Set up the timer to run every second
Timer.periodic(const Duration(seconds: 1), (timer) {
// Update the timer value and notify listeners
timerController.add(timer.tick);
update();
});
}
// Stop the timer when the ride ends
void stopRideTimer() {
timerController.close();
update();
}
late String arrivalTime = '';
void rideIsBeginPassengerTimer() async {
// Calculate arrival time considering current time and duration
DateTime now = DateTime.now();
DateTime arrivalTime1 = now.add(Duration(seconds: durationToRide));
arrivalTime = DateFormat('hh:mm').format(arrivalTime1);
box.write(BoxName.arrivalTime, arrivalTime);
for (int i = 0; i <= durationToRide; i++) {
await Future.delayed(const Duration(seconds: 1));
progressTimerRideBegin = i / durationToRide;
remainingTimeTimerRideBegin = durationToRide - i;
if (i == (durationToRide / 4).round() && (statusRide == 'Begin')) {
NotificationController().showNotification("Record Your Trip".tr,
"You can call or record audio during this trip.".tr, 'tone1');
}
bool sendSOS = false;
if (speed > 100 && sendSOS == false) {
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,
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();
// Implement sharing trip details logic here
String message = "**Emergency SOS from Passenger:**\n";
// Get trip details from GetX or relevant provider
String origin = passengerLocation.toString();
String destination = myDestination.toString();
String driverName = passengerName;
String driverCarPlate = licensePlate;
// Add trip details to the message
message += "* ${'Origin'.tr}: $origin\n";
message += "* ${'Destination'.tr}: $destination\n";
message += "* ${'Driver Name'.tr}: $driverName\n";
message += "* ${'Driver Car Plate'.tr}: $driverCarPlate\n\n";
message += "* ${'Driver phone'.tr}:$driverPhone\n\n";
// Add any additional information you want to include (optional)
// - Example: current location (using GetX LocationController)
message +=
"${'Current Location'.tr}:https://www.google.com/maps/place/${passengerLocation.latitude},${passengerLocation.longitude} \n";
// Append a call to action
message += "Please help! Contact me as soon as possible.".tr;
// Launch WhatsApp communication with the constructed message
launchCommunication(
'whatsapp', box.read(BoxName.sosPhonePassenger), message);
sendSOS = true;
},
kolor: AppColor.redColor,
),
cancel: MyElevatedButton(
title: "Cancel".tr,
onPressed: () {
Get.back();
},
kolor: AppColor.greenColor,
),
);
}
int minutes = (remainingTimeTimerRideBegin / 60).floor();
int seconds = remainingTimeTimerRideBegin % 60;
stringRemainingTimeRideBegin =
'$minutes:${seconds.toString().padLeft(2, '0')}';
update();
}
// rideTimerBegin = false;
// isRideFinished = true;
// update();
}
int progressTimerRideBeginVip = 0;
int elapsedTimeInSeconds = 0; // Timer starts from 0
String stringElapsedTimeRideBegin = '0:00';
String stringElapsedTimeRideBeginVip = '0:00';
bool rideInProgress = true; // To control when to stop the timer
void rideIsBeginPassengerTimerVIP() async {
rideInProgress = true; // Start the ride timer
bool sendSOS = false;
while (rideInProgress) {
await Future.delayed(const Duration(seconds: 1));
// Increment elapsed time
elapsedTimeInSeconds++;
// Update the time display
int minutes = (elapsedTimeInSeconds / 60).floor();
int seconds = elapsedTimeInSeconds % 60;
stringElapsedTimeRideBeginVip =
'$minutes:${seconds.toString().padLeft(2, '0')}';
// Check for speed and SOS conditions
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();
// Implement sharing trip details logic here
String message = "**Emergency SOS from Passenger:**\n";
// Get trip details from GetX or relevant provider
String origin = passengerLocation.toString();
String destination = myDestination.toString();
String driverName = passengerName;
String driverCarPlate = licensePlate;
// Add trip details to the message
message += "* ${'Origin'.tr}: $origin\n";
message += "* ${'Destination'.tr}: $destination\n";
message += "* ${'Driver Name'.tr}: $driverName\n";
message += "* ${'Driver Car Plate'.tr}: $driverCarPlate\n\n";
message += "* ${'Driver Phone'.tr}: $driverPhone\n\n";
// Add current location
message +=
"${'Current Location'.tr}:https://www.google.com/maps/place/${passengerLocation.latitude},${passengerLocation.longitude} \n";
// Append a call to action
message += "Please help! Contact me as soon as possible.".tr;
// Launch WhatsApp communication
launchCommunication(
'whatsapp', box.read(BoxName.sosPhonePassenger), message);
sendSOS = true;
},
kolor: AppColor.redColor,
),
cancel: MyElevatedButton(
title: "Cancel".tr,
onPressed: () {
Get.back();
},
kolor: AppColor.greenColor,
),
);
}
// Update the UI
update();
}
}
void tripFinishedFromDriver() {
// هذه الدالة الآن مسؤولة فقط عن تصفير الواجهة
// معالج الإشعار (FCM) هو المسؤول عن الانتقال لصفحة التقييم
Log.print('[tripFinishedFromDriver] Resetting UI flags for finished ride.');
isRideFinished = true;
rideTimerBegin = false;
statusRideVip = 'Finished';
statusRide = 'Finished'; // لضمان التناسق
box.write(BoxName.arrivalTime, '');
remainingTimeTimerRideBegin = 0;
// (ملاحظة: لقد أبقيت على منطق المحفظة في معالج الإشعار لأنه مرتبط بالسعر)
// box.write(BoxName.passengerWalletTotal, '0'); // تم نقله إلى FCM
update();
// إرسال إشعار للوالدين (إذا كان مفعلاً)
if (box.read(BoxName.parentTripSelected) == true) {
NotificationService.sendNotification(
category: "Finish Monitor",
target: box.read(BoxName.tokenParent),
title: "Finish Monitor".tr,
body: 'Finish Monitor'.tr,
isTopic: false,
tone: 'tone1',
);
box.write(BoxName.parentTripSelected, false);
box.remove(BoxName.tokenParent);
}
// ---!! تم حذف الانتقال من هنا لمنع الازدواجية !! ---
// Get.to(() => RateDriverFromPassenger(), arguments: {
// 'driverId': driverId.toString(),
// 'rideId': rideId.toString(),
// 'price': costForDriver.toString()
// });
// --- نهاية الحذف ---
}
StreamController<String> _beginRideStreamController =
StreamController<String>.broadcast();
Stream<String> get beginRideStream => _beginRideStreamController.stream;
bool isBeginRideFromDriverRunning = false;
void getBeginRideFromDriver() {
if (isBeginRideFromDriverRunning) return; // Prevent duplicate streams
isBeginRideFromDriverRunning = true;
Timer.periodic(const Duration(seconds: 2), (timer) async {
try {
var res = await CRUD().get(
link: AppLink.getRideStatusBegin, payload: {'ride_id': rideId});
print(res);
print('1002');
if (res != 'failure') {
var decode = jsonDecode(res);
_beginRideStreamController
.add(decode['data']['status']); // Emit the status
if (decode['data']['status'] == 'Begin') {
// Stop the periodic check
timer.cancel();
isBeginRideFromDriverRunning = false;
timeToPassengerFromDriverAfterApplied = 0;
remainingTime = 0;
remainingTimeToPassengerFromDriverAfterApplied = 0;
remainingTimeDriverWaitPassenger5Minute = 0;
rideTimerBegin = true;
statusRide = 'Begin';
isDriverInPassengerWay = false;
isDriverArrivePassenger = false;
update();
// Trigger additional actions
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,
'ding');
}
}
} catch (e) {
// Handle errors
_beginRideStreamController.addError(e);
}
});
}
// Call this method to listen to the stream
void listenToBeginRideStream() {
beginRideStream.listen((status) {
print("Ride status: $status");
// Perform additional actions based on the status
}, onError: (error) {
print("Error in Begin Ride Stream: $error");
});
}
begiVIPTripFromPassenger() async {
timeToPassengerFromDriverAfterApplied = 0;
remainingTime = 0;
isBottomSheetShown = false;
remainingTimeToPassengerFromDriverAfterApplied = 0;
remainingTimeDriverWaitPassenger5Minute = 0;
rideTimerBegin = true;
statusRideVip = 'Begin';
isDriverInPassengerWay = false;
isDriverArrivePassenger = false;
update();
// isCancelRidePageShown = true;
rideIsBeginPassengerTimerVIP();
runWhenRideIsBegin();
}
Map rideStatusFromStartApp = {};
bool isStartAppHasRide = false;
getRideStatusFromStartApp() async {
try {
var res = await CRUD().get(
link: AppLink.getRideStatusFromStartApp,
payload: {'passenger_id': box.read(BoxName.passengerID)});
// print(res);
Log.print('rideStatusFromStartApp: ${res}');
// print('1070');
if (res == 'failure') {
rideStatusFromStartApp = {
'data': {'status': 'NoRide', 'needsReview': false}
};
isStartAppHasRide = false;
print(
"No rides found for the given passenger ID within the last hour.");
}
rideStatusFromStartApp = jsonDecode(res);
if (rideStatusFromStartApp['data']['status'] == 'Begin' ||
rideStatusFromStartApp['data']['status'] == 'Apply' ||
rideStatusFromStartApp['data']['status'] == 'Applied') {
statusRide = rideStatusFromStartApp['data']['status'];
isStartAppHasRide = true;
RideState.inProgress;
driverId = rideStatusFromStartApp['data']['driver_id'];
passengerName = rideStatusFromStartApp['data']['driverName'];
driverRate = rideStatusFromStartApp['data']['rateDriver'].toString();
statusRideFromStart = true;
update();
Map<String, dynamic> tripData =
box.read(BoxName.tripData) as Map<String, dynamic>;
final String pointsString = tripData['polyline'];
List<LatLng> decodedPoints =
await compute(decodePolylineIsolate, pointsString);
// decodePolyline(response["routes"][0]["overview_polyline"]["points"]);
for (int i = 0; i < decodedPoints.length; i++) {
polylineCoordinates.add(decodedPoints[i]);
}
var polyline = Polyline(
polylineId: const PolylineId('begin trip'),
points: polylineCoordinates,
width: 10,
color: Colors.blue,
);
polyLines.add(polyline);
timeToPassengerFromDriverAfterApplied = 0;
remainingTime = 0;
remainingTimeToPassengerFromDriverAfterApplied = 0;
remainingTimeDriverWaitPassenger5Minute = 0;
rideTimerBegin = true;
isDriverInPassengerWay = false;
isDriverArrivePassenger = false;
// update();
// isCancelRidePageShown = true;
durationToAdd = tripData['distance_m'];
rideIsBeginPassengerTimer();
runWhenRideIsBegin();
update();
}
} catch (e) {
// Handle the error or perform any necessary actions
}
}
void driverArrivePassenger() {
timeToPassengerFromDriverAfterApplied = 0;
remainingTime = 0;
// isCancelRidePageShown = true;
update();
rideIsBeginPassengerTimer();
// runWhenRideIsBegin();
}
void cancelTimerToPassengerFromDriverAfterApplied() {
timerToPassengerFromDriverAfterApplied?.cancel();
}
void clearPlacesDestination() {
placesDestination = [];
hintTextDestinationPoint = 'Search for your destination'.tr;
update();
}
void clearPlacesStart() {
placesStart = [];
hintTextStartPoint = 'Search for your Start point'.tr;
update();
}
void clearPlaces(int index) {
placeListResponseAll[index] = [];
hintTextwayPointStringAll[index] = 'Search for waypoint'.tr;
update();
}
void clearPlaces1() {
wayPoint1 = [];
hintTextwayPoint1 = 'Search for waypoint'.tr;
update();
}
void clearPlaces2() {
wayPoint2 = [];
hintTextwayPoint2 = 'Search for waypoint'.tr;
update();
}
void clearPlaces3() {
wayPoint3 = [];
hintTextwayPoint3 = 'Search for waypoint'.tr;
update();
}
void clearPlaces4() {
wayPoint4 = [];
hintTextwayPoint4 = 'Search for waypoint'.tr;
update();
}
int selectedReason = -1;
String? cancelNote;
void selectReason(int index, String note) {
selectedReason = index;
cancelNote = note;
update();
}
void getDialog(String title, String? midTitle, VoidCallback onPressed) {
final textToSpeechController = Get.find<TextToSpeechController>();
Get.defaultDialog(
title: title,
titleStyle: AppStyle.title,
middleTextStyle: AppStyle.title,
content: Column(
children: [
IconButton(
onPressed: () async {
await textToSpeechController.speakText(title ?? midTitle!);
},
icon: const Icon(Icons.headphones)),
Text(
midTitle!,
style: AppStyle.title,
)
],
),
confirm: MyElevatedButton(
title: 'Ok'.tr,
onPressed: onPressed,
kolor: AppColor.greenColor,
),
cancel: MyElevatedButton(
title: 'Cancel',
kolor: AppColor.redColor,
onPressed: () {
Get.back();
}));
}
Map<String, double>? extractCoordinatesFromLink(String link) {
try {
// Extract the URL part from the link by finding the first occurrence of "http"
int urlStartIndex = link.indexOf(RegExp(r'https?://'));
if (urlStartIndex == -1) {
throw const FormatException('No URL found in the provided link.');
}
// Extract the URL and clean it
link = link.substring(urlStartIndex).trim();
Uri uri = Uri.parse(link);
// Common coordinate query parameters
List<String> coordinateParams = ['q', 'cp', 'll'];
// Try to extract coordinates from query parameters
for (var param in coordinateParams) {
String? value = uri.queryParameters[param];
if (value != null && (value.contains(',') || value.contains('~'))) {
List<String> coordinates =
value.contains(',') ? value.split(',') : value.split('~');
if (coordinates.length == 2) {
double? latitude = double.tryParse(coordinates[0].trim());
double? longitude = double.tryParse(coordinates[1].trim());
if (latitude != null && longitude != null) {
return {
'latitude': latitude,
'longitude': longitude,
};
}
}
}
}
// Try to extract coordinates from the path
List<String> pathSegments = uri.pathSegments;
for (var segment in pathSegments) {
if (segment.contains(',')) {
List<String> coordinates = segment.split(',');
if (coordinates.length == 2) {
double? latitude = double.tryParse(coordinates[0].trim());
double? longitude = double.tryParse(coordinates[1].trim());
if (latitude != null && longitude != null) {
return {
'latitude': latitude,
'longitude': longitude,
};
}
}
}
}
} catch (e) {
print('Error parsing location link: $e');
}
return null;
}
double latitudeWhatsApp = 0;
double longitudeWhatsApp = 0;
void handleWhatsAppLink(String link) {
Map<String, double>? coordinates = extractCoordinatesFromLink(link);
if (coordinates != null) {
latitudeWhatsApp = coordinates['latitude']!;
longitudeWhatsApp = coordinates['longitude']!;
print(
'Extracted coordinates: Lat: $latitudeWhatsApp, Long: $longitudeWhatsApp');
// Use these coordinates in your app as needed
} else {
print('Failed to extract coordinates from the link');
}
}
void goToWhatappLocation() async {
if (sosFormKey.currentState!.validate()) {
changeIsWhatsAppOrder(true);
Get.back();
handleWhatsAppLink(whatsAppLocationText.text);
myDestination = LatLng(latitudeWhatsApp, longitudeWhatsApp);
await mapController?.animateCamera(CameraUpdate.newLatLng(
LatLng(passengerLocation.latitude, passengerLocation.longitude)));
changeMainBottomMenuMap();
passengerStartLocationFromMap = true;
isPickerShown = true;
update();
}
}
int currentTimeSearchingCaptainWindow = 0;
late String driverPhone = '';
late String driverRate = '';
late String passengerName = '';
late String carColor = '';
late String colorHex = '';
late String carYear = '';
late String model = '';
late String make = '';
late String licensePlate = '';
String driverOrderStatus = 'yet';
bool isDriversTokensSend = false;
Set<String> notifiedDrivers = {};
/// [إضافة جديدة]
/// دالة مخصصة لإضافة الرحلة إلى جدول الانتظار (waiting_ride)
Future<void> _addRideToWaitingTable() async {
try {
await CRUD().post(link: AppLink.addWaitingRide, payload: {
'id': rideId.toString(),
"start_location":
'${startLocation.latitude},${startLocation.longitude}',
"end_location": '${endLocation.latitude},${endLocation.longitude}',
// 'start_location':
// '${data[0]["start_location"]['lat']},${data[0]["start_location"]['lng']}',
// 'end_location':
// '${data[0]["end_location"]['lat']},${data[0]["end_location"]['lng']}',
"date": DateTime.now().toString(),
"time": DateTime.now().toString(),
"price": totalPassenger.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),
});
Log.print('[WaitingTable] Ride $rideId added to waiting_ride table.');
} catch (e) {
Log.print('Error adding ride to waiting_ride table: $e');
}
}
// Future<void> confirmRideForAllDriverAvailable1() async {
// // Try to fetch car locations up to 4 times with a 2-second delay
// bool driversFound = false;
// for (int attempt = 0; attempt < 8; attempt++) {
// await getCarsLocationByPassengerAndReloadMarker(
// box.read(BoxName.carType), attempt > 5 ? 4500 : 3000);
// // Check if dataCarsLocationByPassenger is valid and contains drivers
// if (dataCarsLocationByPassenger != 'failure' &&
// dataCarsLocationByPassenger != null &&
// dataCarsLocationByPassenger.containsKey('data') &&
// dataCarsLocationByPassenger['message'] != null) {
// driversFound = true;
// break; // Exit loop if drivers are found
// }
// // Wait 2 seconds before next attempt
// await Future.delayed(const Duration(seconds: 2));
// }
// // If no drivers were found after 4 attempts, show a dialog
// if (!driversFound) {
// 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();
// Get.offAll(() => const MapPagePassenger());
// },
// child: Text('OK'.tr,
// style: const TextStyle(color: AppColor.greenColor)),
// ),
// ],
// ),
// ),
// barrierDismissible: false,
// );
// return;
// }
// // Proceed with the rest of the function if drivers are found
// PaymentController paymentController = Get.find<PaymentController>();
// rideConfirm = true;
// shouldFetch = true;
// isBottomSheetShown = false;
// timeToPassengerFromDriverAfterApplied = 60;
// // Add ride to database
// await CRUD()
// .post(link: "${AppLink.IntaleqCairoServer}/ride/rides/add.php", payload: {
// "start_location":
// '${data[0]["start_location"]['lat']},${data[0]["start_location"]['lng']}',
// "end_location":
// '${data[0]["end_location"]['lat']},${data[0]["end_location"]['lng']}',
// "date": DateTime.now().toString(),
// "time": DateTime.now().toString(),
// "endtime": durationToAdd.toString(),
// "price": totalPassenger.toStringAsFixed(2),
// "passenger_id": box.read(BoxName.passengerID).toString(),
// "driver_id": dataCarsLocationByPassenger['message'][carsOrder]['driver_id']
// .toString(),
// "status": "waiting",
// 'carType': box.read(BoxName.carType),
// "price_for_driver": totalPassenger.toString(),
// "price_for_passenger": totalME.toString(),
// "distance": distance.toString(),
// "paymentMethod": paymentController.isWalletChecked.toString(),
// }).then((value) {
// if (value is String) {
// final parsedValue = jsonDecode(value);
// rideId = parsedValue['message'];
// } else if (value is Map) {
// rideId = value['message'];
// } else {
// Log.print('Unexpected response type: ${value.runtimeType}');
// }
// // Timer to notify drivers every 2 seconds for 5 iterations
// int iteration = 0;
// Timer.periodic(const Duration(seconds: 2), (timer) async {
// if (iteration >= 5) {
// timer.cancel();
// return;
// }
// iteration++;
// // Reload driver locations and notify available drivers
// await getCarsLocationByPassengerAndReloadMarker(
// box.read(BoxName.carType), 3000);
// if (dataCarsLocationByPassenger != null &&
// dataCarsLocationByPassenger.containsKey('data') &&
// dataCarsLocationByPassenger['message'] != null) {
// for (var driverData in dataCarsLocationByPassenger['message']) {
// String driverId = driverData['driver_id'].toString();
// if (!notifiedDrivers.contains(driverId)) {
// notifiedDrivers.add(driverId);
// List<String> body = [
// '${data[0]["start_location"]['lat']},${data[0]["start_location"]['lng']}',
// '${data[0]["end_location"]['lat']},${data[0]["end_location"]['lng']}',
// totalPassenger.toStringAsFixed(2),
// totalDriver.toStringAsFixed(2),
// durationToRide.toString(),
// distance.toStringAsFixed(2),
// driverId.toString(),
// box.read(BoxName.passengerID).toString(),
// box.read(BoxName.name).toString(),
// box.read(BoxName.tokenFCM).toString(),
// box.read(BoxName.phone).toString(),
// durationByPassenger.toString(),
// distanceByPassenger.toString(),
// paymentController.isWalletChecked.toString(),
// driverData['token'].toString(),
// durationToPassenger.toString(),
// rideId.toString(),
// rideTimerBegin.toString(),
// driverId.toString(),
// durationToRide.toString(),
// Get.find<WayPointController>().wayPoints.length > 1
// ? 'haveSteps'
// : 'startEnd',
// placesCoordinate[0],
// placesCoordinate[1],
// placesCoordinate[2],
// placesCoordinate[3],
// placesCoordinate[4],
// costForDriver.toStringAsFixed(2),
// (double.parse(box.read(BoxName.passengerWalletTotal)) < 0
// ? double.parse(box.read(BoxName.passengerWalletTotal))
// .toStringAsFixed(2)
// : '0'),
// box.read(BoxName.email).toString(),
// data[0]['start_address'],
// data[0]['end_address'],
// box.read(BoxName.carType),
// kazan.toStringAsFixed(0),
// passengerRate.toStringAsFixed(2),
// ];
// Log.print('body: ${body}');
// firebaseMessagesController.sendNotificationToDriverMAP(
// 'OrderSpeed',
// rideId,
// driverData['token'].toString(),
// body,
// 'order',
// );
// }
// }
// }
// });
// });
// // If an additional endpoint is available, post data there as well
// if (AppLink.endPoint != AppLink.IntaleqCairoServer) {
// CRUD().post(link: '${AppLink.endPoint}/ride/rides/add.php', payload: {
// "start_location":
// '${data[0]["start_location"]['lat']},${data[0]["start_location"]['lng']}',
// "end_location":
// '${data[0]["end_location"]['lat']},${data[0]["end_location"]['lng']}',
// "date": DateTime.now().toString(),
// "time": DateTime.now().toString(),
// "endtime": durationToAdd.toString(),
// "price": totalPassenger.toStringAsFixed(2),
// "passenger_id": box.read(BoxName.passengerID).toString(),
// "driver_id": dataCarsLocationByPassenger['message'][carsOrder]['driver_id']
// .toString(),
// "status": "waiting",
// 'carType': box.read(BoxName.carType),
// "price_for_driver": totalPassenger.toString(),
// "price_for_passenger": totalME.toString(),
// "distance": distance.toString(),
// "paymentMethod": paymentController.isWalletChecked.toString(),
// });
// }
// delayAndFetchRideStatusForAllDriverAvailable(rideId);
// update();
// }
increaseForSameRideAndDelay() async {
reSearchAfterCanceledFromDriver();
// bool driversFound = false;
// for (int attempt = 0; attempt < 8; attempt++) {
// await getCarsLocationByPassengerAndReloadMarker(
// box.read(BoxName.carType), 4500);
// // Check if dataCarsLocationByPassenger is valid and contains drivers
// if (dataCarsLocationByPassenger != 'failure' &&
// dataCarsLocationByPassenger != null &&
// dataCarsLocationByPassenger.containsKey('message') &&
// dataCarsLocationByPassenger['message'] != null) {
// driversFound = true;
// break; // Exit loop if drivers are found
// }
// // Wait 2 seconds before next attempt
// await Future.delayed(const Duration(seconds: 2));
// }
// // If no drivers were found after 4 attempts, show a dialog
// if (!driversFound) {
// 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();
// Get.offAll(() => const MapPagePassenger());
// },
// child: Text('OK'.tr,
// style: const TextStyle(color: AppColor.greenColor)),
// ),
// ],
// ),
// ),
// barrierDismissible: false,
// );
// return;
// }
// PaymentController paymentController = Get.find<PaymentController>();
// rideConfirm = true;
// shouldFetch = true;
// isBottomSheetShown = false;
// timeToPassengerFromDriverAfterApplied = 60;
// // confirmRideForAllDriverAvailable();
// for (var i = 0; i < dataCarsLocationByPassenger['message'].length; i++) {
// List<String> body = [
// '${data[0]["start_location"]['lat']},${data[0]["start_location"]['lng']}',
// '${data[0]["end_location"]['lat']},${data[0]["end_location"]['lng']}',
// totalPassenger.toStringAsFixed(2),
// totalDriver.toStringAsFixed(2),
// durationToRide.toString(),
// distance.toStringAsFixed(2),
// dataCarsLocationByPassenger['message'][i]['driver_id'].toString(),
// box.read(BoxName.passengerID).toString(),
// box.read(BoxName.name).toString(),
// box.read(BoxName.tokenFCM).toString(),
// box.read(BoxName.phone).toString(),
// durationByPassenger.toString(),
// distanceByPassenger.toString(),
// paymentController.isWalletChecked.toString(),
// dataCarsLocationByPassenger['message'][i]['token'].toString(),
// durationToPassenger.toString(),
// rideId.toString(),
// rideTimerBegin.toString(),
// dataCarsLocationByPassenger['message'][i]['driver_id'].toString(),
// durationToRide.toString(),
// Get.find<WayPointController>().wayPoints.length > 1
// ? 'haveSteps'
// : 'startEnd',
// placesCoordinate[0],
// placesCoordinate[1],
// placesCoordinate[2],
// placesCoordinate[3],
// placesCoordinate[4],
// costForDriver.toStringAsFixed(2),
// double.parse(box.read(BoxName.passengerWalletTotal)) < 0
// ? double.parse(box.read(BoxName.passengerWalletTotal))
// .toStringAsFixed(2)
// : '0',
// box.read(BoxName.email).toString(),
// data[0]['start_address'],
// data[0]['end_address'],
// box.read(BoxName.carType),
// kazan.toStringAsFixed(0),
// passengerRate.toStringAsFixed(2),
// ];
// // Log.print('body: ${body}');
// firebaseMessagesController.sendNotificationToDriverMAP(
// 'OrderSpeed',
// rideId.toString(),
// dataCarsLocationByPassenger['message'][i]['token'].toString(),
// body,
// 'order');
// }
}
int tick = 0; // Move tick outside the function to maintain its state
// void delayAndFetchRideStatus(String rideId, carType) {
// Timer.periodic(const Duration(seconds: 1), (timer) async {
// if (shouldFetch) {
// if (remainingTimeToPassengerFromDriverAfterApplied > 0) {
// String res = await getRideStatus(rideId);
// Log.print('tick: $tick');
// String rideStatusDelayed = res.toString();
// if ((rideStatusDelayed == 'waiting' ||
// rideStatusDelayed == 'Refused') &&
// tick >= 15) {
// timer.cancel(); // Stop the current timer
// showAndResearchForCaptain();
// //TODO add to wait
// await getCarsLocationByPassengerAndReloadMarker(carType, 3000);
// // await getNearestDriverByPassengerLocationAPIGOOGLE();
// // getCarForFirstConfirm(carType);
// confirmRideForAllDriverAvailable();
// // delayAndFetchRideStatusForAllDriverAvailable(rideId);
// } else if (rideStatusDelayed == 'Apply' || statusRide == 'Apply') {
// Log.print('rideStatusDelayed == Apply: $rideStatusDelayed');
// // todo play sound
// Get.find<AudioRecorderController>()
// .playSoundFromAssets('assets/start');
// timer.cancel(); // Stop the current timer
// await getUpdatedRideForDriverApply(rideId);
// shouldFetch = false; // Stop further fetches
// statusRide = 'Apply';
// rideConfirm = false;
// isSearchingWindow = false;
// update();
// startTimerFromDriverToPassengerAfterApplied();
// if (box.read(BoxName.carType) == 'Speed' ||
// box.read(BoxName.carType) == 'Awfar Car') {
// NotificationController().showNotification(
// 'The captain is responsible for the route.'.tr,
// 'This price is fixed even if the route changes for the driver.'
// .tr,
// 'ding');
// } else if (box.read(BoxName.carType) == 'Comfort' ||
// box.read(BoxName.carType) == 'Lady') {
// NotificationController().showNotification('Attention'.tr,
// 'The price may increase if the route changes.'.tr, 'ding');
// }
// } else if (rideStatusDelayed == 'Refused') {
// statusRide = 'Refused';
// if (isDriversTokensSend == false) {
// confirmRideForAllDriverAvailable();
// isDriversTokensSend = true;
// } // Start 15-second timer
// }
// //else if (isDriversTokensSend == false) {
// // No need to recall delayAndFetchRideStatus as Timer.periodic is already running
// update();
// // }
// if (tick < 19) {
// tick++;
// } else {
// timer.cancel();
// // Stop the timer if remainingTimeToPassengerFromDriverAfterApplied <= 0
// }
// } else {
// timer.cancel();
// // Stop the timer if remainingTimeToPassengerFromDriverAfterApplied <= 0
// }
// } else {
// timer.cancel(); // Stop the timer if shouldFetch is false
// }
// });
// }
// showAndResearchForCaptain() {
// Get.snackbar(
// "No Captain Accepted Your Order".tr,
// "We are looking for a captain but the price may increase to let a captain accept"
// .tr,
// duration: const Duration(seconds: 5),
// backgroundColor: AppColor.yellowColor,
// );
// isSearchingWindow == true;
// update();
// }
String driversStatusForSearchWindow = '';
// Future<void> confirmRideForAllDriverAvailable() async {
// bool driversFound = false;
// const maxAttempts = 8;
// const attemptDelay = Duration(seconds: 3);
// for (int attempt = 0; attempt < maxAttempts; attempt++) {
// final reloadDuration = attempt > 5 ? 4500 : 3000;
// await getCarsLocationByPassengerAndReloadMarker(
// box.read(BoxName.carType), reloadDuration);
// // await getNearestDriverByPassengerLocation();
// currentTimeSearchingCaptainWindow = 0;
// driversStatusForSearchWindow = 'We are search for nearst driver'.tr;
// if (isDriversDataValid()) {
// driversFound = true;
// break;
// }
// await Future.delayed(attemptDelay);
// }
// if (!driversFound) {
// showNoDriversDialog();
// return;
// }
// driversStatusForSearchWindow = 'Your order is being prepared'.tr;
// Log.print('driversStatusForSearchWindow: $driversStatusForSearchWindow');
// update();
// await postRideDetailsToServer();
// driversStatusForSearchWindow = 'Your order sent to drivers'.tr;
// await notifyAvailableDrivers();
// driversStatusForSearchWindow = 'The drivers are reviewing your request'.tr;
// Log.print('driversStatusForSearchWindow: $driversStatusForSearchWindow');
// update();
// delayAndFetchRideStatusForAllDriverAvailable(rideId);
// // update();
// }
Future<void> updateConfirmRideForAllDriverAvailable() async {
bool driversFound = false;
const maxAttempts = 8;
const attemptDelay = Duration(seconds: 3);
for (int attempt = 0; attempt < maxAttempts; attempt++) {
final reloadDuration = attempt > 5 ? 4500 : 3000;
await getCarsLocationByPassengerAndReloadMarker(
box.read(BoxName.carType), reloadDuration);
// await getNearestDriverByPassengerLocation();
if (isDriversDataValid()) {
driversFound = true;
break;
}
await Future.delayed(attemptDelay);
}
if (!driversFound) {
showNoDriversDialog();
return;
}
// await postRideDetailsToServer();
// delayAndFetchRideStatusForAllDriverAvailable(rideId);
// update();
await notifyAvailableDrivers();
}
bool isDriversDataValid() {
return dataCarsLocationByPassenger != 'failure' &&
dataCarsLocationByPassenger != null &&
dataCarsLocationByPassenger.containsKey('message') &&
dataCarsLocationByPassenger['message'] != null;
}
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();
Get.offAll(() => const MapPagePassenger());
},
child: Text('OK'.tr,
style: const TextStyle(color: AppColor.greenColor)),
),
],
),
),
barrierDismissible: false,
);
}
Future<bool> postRideDetailsToServer() async {
final paymentController = Get.find<PaymentController>();
startLocation = polylineCoordinates.first;
endLocation = polylineCoordinates.last;
// '${startLocation.latitude},${startLocation.longitude}',
// "end_location": '${endLocation.latitude},${endLocation.longitude}',
final payload = constructRidePayload(paymentController);
Log.print('constructRidePayload: ${payload}');
try {
final response = await CRUD()
.post(link: "${AppLink.ride}/ride/rides/add.php", payload: payload);
if (response is String) {
final parsedValue = jsonDecode(response);
rideId = parsedValue['message'];
return true;
} else if (response is Map) {
rideId = response['message'];
CRUD().post(
link: "${AppLink.server}/ride/rides/add.php", payload: payload);
return true;
} else {
Log.print('Unexpected response type: ${response.runtimeType}');
return false;
}
} catch (e) {
Log.print('Error posting ride details: $e');
return false;
}
}
late LatLng endLocation;
// polylineCoordinates.last;
// Log.print('endLocation: ${endLocation}');
late LatLng startLocation;
// polylineCoordinates.first;
// Log.print('startLocation: ${startLocation}');
Map<String, dynamic>? constructRidePayload(
PaymentController paymentController) {
try {
final msg = dataCarsLocationByPassenger?['message'];
final hasDrivers = msg is List &&
msg.isNotEmpty &&
carsOrder >= 0 &&
carsOrder < msg.length;
if (!hasDrivers) {
// لا يوجد سائقين -> عرض دايلوغ وارجاع null
_showNoDriversDialog();
return null;
}
// الآن آمن القراءة
final driverId = msg[carsOrder]['driver_id'] ?? 'new_driver';
return {
"start_location":
'${startLocation.latitude},${startLocation.longitude}',
"end_location": '${endLocation.latitude},${endLocation.longitude}',
"date": DateTime.now().toString(),
"time": DateTime.now().toString(),
"endtime": durationToAdd.toString(),
"price": totalPassenger.toStringAsFixed(2),
"passenger_id": box.read(BoxName.passengerID).toString(),
"driver_id": driverId,
"status": "waiting",
'carType': box.read(BoxName.carType),
"price_for_driver": totalPassenger.toString(),
"price_for_passenger": totalME.toString(),
"distance": distance.toString(),
"paymentMethod": paymentController.isWalletChecked.toString(),
};
} catch (e, st) {
Log.print('Error in constructRidePayload: $e');
Log.print(st.toString());
// في حال أي استثناء، اعتبره كعدم وجود سائقين أو خطأ -> عرض دايلوغ
_showNoDriversDialog();
return null;
}
}
void _showNoDriversDialog() {
// اذا تستخدم GetX:
MyDialog().getDialog(
'Sorry'.tr,
'No cars are available at the moment. Please try again later.'.tr,
() {
Get.back(); // closes the dialog
cancelRide(); // cancels or resets the ride
},
);
}
Future<void> notifyAvailableDrivers() async {
int iteration = 0;
const maxIterations = 5;
const iterationDelay = Duration(seconds: 2);
while (iteration < maxIterations) {
await Future.delayed(iterationDelay);
await getCarsLocationByPassengerAndReloadMarker(
box.read(BoxName.carType), 3000);
if (dataCarsLocationByPassenger != null &&
dataCarsLocationByPassenger.containsKey('message') &&
dataCarsLocationByPassenger['message'] != null) {
for (var driverData in dataCarsLocationByPassenger['message']) {
String driverId = driverData['driver_id'].toString();
if (!notifiedDrivers.contains(driverId)) {
notifiedDrivers.add(driverId);
double driverLat = double.parse(driverData['latitude']);
double driverLng = double.parse(driverData['longitude']);
double distanceToDriverInMeters = Geolocator.distanceBetween(
passengerLocation.latitude,
passengerLocation.longitude,
driverLat,
driverLng,
);
double distanceToDriverInKm = distanceToDriverInMeters *
1.25 / //to approximate to stright distance
1000;
double durationToDriverInHours =
distanceToDriverInKm / 25; // 25 km/h as default speed
double durationToDriverInSeconds = durationToDriverInHours * 3600;
durationToPassenger = durationToDriverInSeconds.toInt();
distanceByPassenger =
(distanceToDriverInMeters * 1.25).toStringAsFixed(0);
Future.delayed(const Duration(microseconds: 10));
final body = constructNotificationBody(driverData);
Log.print('body:ww $body');
// firebaseMessagesController.sendNotificationToDriverMAP(
// 'Order', // without tr since background not valid
// endNameAddress,
// (driverData['token'].toString()),
// body,
// 'order');
NotificationService.sendNotification(
category: 'Order',
target: (driverData['token'].toString()),
title: 'Order'.tr,
body: endNameAddress,
isTopic: false, // Important: this is a token
tone: 'tone1',
driverList: body);
}
}
}
iteration++;
}
}
Future<void> notifyAvailableDriversAgain() async {
Log.print('[DEBUG] Starting notifyAvailableDriversAgain()');
await getCarsLocationByPassengerAndReloadMarker(
box.read(BoxName.carType), 3000);
Log.print(
'[DEBUG] Found drivers to notify: ${dataCarsLocationByPassenger['message']?.length}');
if (dataCarsLocationByPassenger != null &&
dataCarsLocationByPassenger.containsKey('message') &&
dataCarsLocationByPassenger['message'] != null) {
for (var driverData in dataCarsLocationByPassenger['message']) {
String driverId = driverData['driver_id'].toString();
if (!notifiedDrivers.contains(driverId)) {
notifiedDrivers.add(driverId);
double driverLat = double.parse(driverData['latitude']);
double driverLng = double.parse(driverData['longitude']);
double distanceToDriverInMeters = Geolocator.distanceBetween(
passengerLocation.latitude,
passengerLocation.longitude,
driverLat,
driverLng,
);
double distanceToDriverInKm = distanceToDriverInMeters *
1.25 / //to approximate to stright distance
1000;
double durationToDriverInHours =
distanceToDriverInKm / 25; // 25 km/h as default speed
double durationToDriverInSeconds = durationToDriverInHours * 3600;
durationToPassenger = durationToDriverInSeconds.toInt();
distanceByPassenger =
(distanceToDriverInMeters * 1.25).toStringAsFixed(0);
Future.delayed(const Duration(microseconds: 10));
final body = constructNotificationBody(driverData);
Log.print('body:ww ${body}');
Log.print(
'[DEBUG] Sending to driver: ${driverData['driver_id']}, token: ${driverData['token']}');
NotificationService.sendNotification(
category: 'Order',
target: (driverData['token'].toString()),
title: 'Order'.tr,
body: endNameAddress,
isTopic: false, // Important: this is a token
tone: 'tone1',
driverList: body);
}
}
}
}
List<String> constructNotificationBody(var driverData) {
final paymentController = Get.find<PaymentController>();
return [
// '${data[0]['start_location']['lat']},${data[0]['start_location']['lng']}',
// '${data[0]['end_location']['lat']},${data[0]['end_location']['lng']}',
'${startLocation.latitude},${startLocation.longitude}',
'${endLocation.latitude},${endLocation.longitude}',
totalPassenger.toStringAsFixed(2),
totalDriver.toStringAsFixed(2),
durationToRide.toString(),
distance.toStringAsFixed(2),
driverData?['driver_id']?.toString() ?? 'N/A',
box.read(BoxName.passengerID).toString(),
(box.read(BoxName.name).toString().split(' ')[0]).toString(),
(box.read(BoxName.tokenFCM).toString()),
(box.read(BoxName.phone).toString()),
durationToPassenger.toStringAsFixed(0) ?? '120',
distanceByPassenger.toString() ?? '2000',
paymentController.isWalletChecked.toString(),
driverData?['token']?.toString() ?? 'N/A',
durationToPassenger.toString(),
rideId.toString(),
rideTimerBegin.toString(),
driverData?['driver_id']?.toString() ?? 'N/A',
durationToRide.toString(),
Get.find<WayPointController>().wayPoints.length > 1
? 'haveSteps'
: 'startEnd',
placesCoordinate[0],
placesCoordinate[1],
placesCoordinate[2],
placesCoordinate[3],
placesCoordinate[4],
costForDriver.toStringAsFixed(2),
(double.parse(box.read(BoxName.passengerWalletTotal)) < 0
? double.parse(box.read(BoxName.passengerWalletTotal))
.toStringAsFixed(2)
: '0'),
box.read(BoxName.email).toString() ?? 'none',
startNameAddress,
endNameAddress,
box.read(BoxName.carType),
kazan.toStringAsFixed(0),
passengerRate.toStringAsFixed(2),
];
}
StreamController<String> _rideStatusStreamController =
StreamController<String>.broadcast();
Stream<String> get rideStatusStream => _rideStatusStreamController.stream;
int maxAttempts = 28;
// Future<void> delayAndFetchRideStatusForAllDriverAvailable(
// String rideId) async {
// int attemptCounter = 0;
// bool isApplied = false;
// tick = 0;
// await addRideToNotificationDriverAvailable();
// Timer.periodic(const Duration(seconds: 1), (timer) async {
// if (attemptCounter >= maxAttempts || isApplied == true) {
// timer.cancel();
// _rideStatusStreamController.close(); // Close the stream when done
// return;
// }
// attemptCounter++;
// tick++;
// try {
// var res = await getRideStatus(rideId);
// String rideStatusDelayed = res.toString();
// Log.print('rideStatusDelayed: $rideStatusDelayed');
// _rideStatusStreamController
// .add(rideStatusDelayed); // Emit the ride status
// // addRideToNotificationDriverString();
// if (rideStatusDelayed == 'Cancel') {
// timer.cancel();
// NotificationController().showNotification(
// "Order Cancelled".tr, "you canceled order".tr, 'ding');
// _rideStatusStreamController
// .close(); // Close stream after cancellation
// //
// //
// } else if (rideStatusDelayed == 'Apply' ||
// rideStatusDelayed == 'Applied') {
// isApplied = true;
// // timer.cancel();
// rideAppliedFromDriver(isApplied);
// timer.cancel();
// // Close stream after applying
// } else if (attemptCounter >= maxAttempts ||
// rideStatusDelayed == 'waiting') {
// // timer.cancel(); //todo
// // addRideToNotificationDriverString();
// // Show dialog to increase fee...
// // buildTimerForIncrease();
// Get.defaultDialog(
// title: 'Are you want to wait drivers to accept your order'.tr,
// middleText: '',
// onConfirm: () {
// Log.print('[DEBUG] User chose to wait again');
// Get.back();
// notifyAvailableDriversAgain();
// delayAndFetchRideStatusForAllDriverAvailable(rideId);
// // addRideToNotificationDriverAvailable();
// },
// onCancel: () {
// timer.cancel();
// Get.back();
// showCancelRideBottomSheet();
// },
// );
// // MyDialog().getDialog(
// // 'Are you want to wait drivers to accept your order'.tr, '', () {
// // Get.back();
// // addRideToNotificationDriverAvailable();
// // });
// update();
// _rideStatusStreamController
// .close(); // Close stream after max attempts
// }
// } catch (e) {
// _rideStatusStreamController.addError(e); // Handle errors in the stream
// }
// });
// }
Future<void> rideAppliedFromDriver(bool isApplied) async {
await getUpdatedRideForDriverApply(rideId);
NotificationController().showNotification(
'Accepted Ride'.tr,
'$driverName ${'accepted your order at price'.tr} ${totalPassenger.toStringAsFixed(1)} ${'with type'.tr} ${box.read(BoxName.carType)}',
'ding');
if (box.read(BoxName.carType) == 'Speed' ||
box.read(BoxName.carType) == 'Awfar Car') {
NotificationController().showNotification(
'The captain is responsible for the route.'.tr,
'This price is fixed even if the route changes for the driver.'.tr,
'ding');
} else if (box.read(BoxName.carType) == 'Comfort' ||
box.read(BoxName.carType) == 'Lady') {
NotificationController().showNotification('Attention'.tr,
'The price may increase if the route changes.'.tr, 'ding');
}
isApplied = true;
statusRide = 'Apply';
rideConfirm = false;
isSearchingWindow = false;
update();
// تشغيل التايمر لجلب موقع السائق بشكل دوري
startTimerFromDriverToPassengerAfterApplied();
// 1. جلب موقع السائق الأولي فوراً
await getDriverCarsLocationToPassengerAfterApplied();
// 2. رسم المسار مرة واحدة فقط هنا
if (driverCarsLocationToPassengerAfterApplied.isNotEmpty) {
await drawDriverPathOnly(
driverCarsLocationToPassengerAfterApplied.last, passengerLocation);
// ضبط الكاميرا لتشمل السائق والراكب مرة واحدة في البداية
_fitCameraToPoints(
driverCarsLocationToPassengerAfterApplied.last, passengerLocation);
}
startUiCountdown();
_rideStatusStreamController.close();
}
// دالة خفيفة وسريعة لرسم خط المسار فقط (بدون أسعار أو خطوات)
Future<void> 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}';
// استخدام overview=full للدقة، و steps=false للسرعة
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<LatLng> decodedPoints =
await compute(decodePolylineIsolate, pointsString);
// إزالة خط مسار السائق القديم فقط
polyLines
.removeWhere((p) => p.polylineId.value == 'driver_track_line');
// إضافة الخط الجديد
polyLines.add(Polyline(
polylineId: const PolylineId('driver_track_line'),
points: decodedPoints,
color: Colors.black87, // لون مميز لمسار السائق
width: 5,
jointType: JointType.round,
startCap: Cap.roundCap,
endCap: Cap.roundCap,
patterns: [
PatternItem.dash(10),
PatternItem.gap(10)
], // جعله منقطاً
));
// لا تستدعي update هنا، سيتم استدعاؤها في الدالة الأب (getDriverCars...) لتقليل عدد التحديثات
}
}
} catch (e) {
print('Error drawing driver path: $e');
}
}
// دالة مساعدة لضبط الكاميرا
void _fitCameraToPoints(LatLng p1, LatLng p2) {
double minLat = min(p1.latitude, p2.latitude);
double maxLat = max(p1.latitude, p2.latitude);
double minLng = min(p1.longitude, p2.longitude);
double maxLng = max(p1.longitude, p2.longitude);
mapController?.animateCamera(
CameraUpdate.newLatLngBounds(
LatLngBounds(
southwest: LatLng(minLat, minLng),
northeast: LatLng(maxLat, maxLng),
),
100 // Padding
),
);
}
// Listening to the Stream
void listenToRideStatusStream() {
rideStatusStream.listen((rideStatus) {
print("Ride Status: $rideStatus");
// Handle updates based on the ride status
}, onError: (error) {
print("Error in Ride Status Stream: $error");
// Handle stream errors
}, onDone: () {
print("Ride status stream closed.");
});
}
reSearchAfterCanceledFromDriver() async {
shouldFetch = true; // Stop further fetches
statusRide = 'wait';
rideConfirm = true;
isSearchingWindow = true;
update();
updateConfirmRideForAllDriverAvailable();
}
void start15SecondTimer(String rideId) {
Timer(const Duration(seconds: 15), () {
// delayAndFetchRideStatusForAllDriverAvailable(rideId);
});
}
// Replaces void startTimer()
Timer?
_uiCountdownTimer; // Add this variable to your class to manage lifecycle
void startUiCountdown() {
// Cancel any existing timer to avoid duplicates
_uiCountdownTimer?.cancel();
// Reset variables
progress = 0;
remainingTime = durationTimer;
_uiCountdownTimer = Timer.periodic(const Duration(seconds: 1), (timer) {
// Logic from your loop, but non-blocking
int i = timer.tick; // current tick
progress = i / durationTimer;
remainingTime = durationTimer - i;
if (remainingTime <= 0) {
timer.cancel(); // Stop this specific timer
rideConfirm = false;
// Add the duration to the tracking time logic
timeToPassengerFromDriverAfterApplied += durationToPassenger;
// Note: We do NOT call startTimerFromDriverToPassengerAfterApplied() here
// because we already started it in rideAppliedFromDriver!
timerEnded(); // Call your existing completion logic
}
update(); // Update the UI progress bar
});
}
void timerEnded() async {
runEvery30SecondsUntilConditionMet();
isCancelRidePageShown = false;
print('isCancelRidePageShown: $isCancelRidePageShown');
update();
}
Future<String> getRideStatus(String rideId) async {
final response = await CRUD().get(
link: "${AppLink.ride}/ride/rides/getRideStatus.php",
payload: {'id': rideId});
print(response);
print('2176');
return jsonDecode(response)['data'];
}
late String driverCarModel,
driverCarMake,
driverLicensePlate,
driverName = '';
Future<void> 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');
// [هام] التحقق من أن data عبارة عن Map وليست false أو null
// هذا يمنع الخطأ: Class 'bool' has no instance method '[]'
if (response['status'] == 'success' &&
response['data'] != null &&
response['data'] is Map) {
var data = response['data'];
// استخدام ?.toString() ?? '' للحماية من القيم الفارغة (Null Safety)
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';
update(); // تحديث الواجهة بالبيانات الجديدة
} else {
Log.print(
"Warning: Ride data not found or invalid (data is false/null)");
// اختياري: يمكنك هنا التعامل مع حالة عدم العثور على السائق بعد
}
}
} catch (e) {
Log.print("Error in getUpdatedRideForDriverApply: $e");
}
}
late LatLng currentDriverLocation;
late double headingList;
// Future getCarsLocationByPassengerAndReloadMarker() async {
// if (statusRide == 'wait') {
// carsLocationByPassenger = [];
// LatLngBounds bounds = calculateBounds(
// passengerLocation.latitude, passengerLocation.longitude, 7000);
// var res;
// if (box.read(BoxName.carType) == 'Lady') {
// res = await CRUD()
// .get(link: AppLink.getFemalDriverLocationByPassenger, payload: {
// 'southwestLat': bounds.southwest.latitude.toString(),
// 'southwestLon': bounds.southwest.longitude.toString(),
// 'northeastLat': bounds.northeast.latitude.toString(),
// 'northeastLon': bounds.northeast.longitude.toString(),
// });
// } else if (box.read(BoxName.carType) == 'Speed') {
// res = await CRUD().get(
// link: AppLink.getCarsLocationByPassengerSpeed,
// payload: {
// 'southwestLat': bounds.southwest.latitude.toString(),
// 'southwestLon': bounds.southwest.longitude.toString(),
// 'northeastLat': bounds.northeast.latitude.toString(),
// 'northeastLon': bounds.northeast.longitude.toString(),
// },
// );
// } else if (box.read(BoxName.carType) == 'Delivery') {
// res = await CRUD().get(
// link: AppLink.getCarsLocationByPassengerDelivery,
// payload: {
// 'southwestLat': bounds.southwest.latitude.toString(),
// 'southwestLon': bounds.southwest.longitude.toString(),
// 'northeastLat': bounds.northeast.latitude.toString(),
// 'northeastLon': bounds.northeast.longitude.toString(),
// },
// );
// } else {
// res = await CRUD()
// .get(link: AppLink.getCarsLocationByPassenger, payload: {
// 'southwestLat': bounds.southwest.latitude.toString(),
// 'southwestLon': bounds.southwest.longitude.toString(),
// 'northeastLat': bounds.northeast.latitude.toString(),
// 'northeastLon': bounds.northeast.longitude.toString(),
// });
// }
// if (res == 'failure') {
// noCarString = true;
// dataCarsLocationByPassenger = res;
// update();
// } else {
// // Get.snackbar('no car', 'message');
// noCarString = false;
// dataCarsLocationByPassenger = jsonDecode(res);
// // if (dataCarsLocationByPassenger.length > carsOrder) {
// driverId = dataCarsLocationByPassenger['message'][carsOrder]
// ['driver_id']
// .toString();
// gender = dataCarsLocationByPassenger['message'][carsOrder]['gender']
// .toString();
// // }
// carsLocationByPassenger.clear(); // Clear existing markers
// // late LatLng lastDriverLocation; // Initialize a variable for last location
// for (var i = 0;
// i < dataCarsLocationByPassenger['message'].length;
// i++) {
// var json = dataCarsLocationByPassenger['message'][i];
// // CarLocationModel model = CarLocationModel.fromJson(json);
// if (carLocationsModels.length < i + 1) {
// // carLocationsModels.add(model);
// markers.add(
// Marker(
// markerId: MarkerId(json['latitude']),
// position: LatLng(
// double.parse(json['latitude']),
// double.parse(json['longitude']),
// ),
// rotation: double.parse(json['heading']),
// icon: json['model'].toString().contains('دراجة')
// ? motoIcon
// : json['gender'] == 'Male'.tr
// ? carIcon
// : ladyIcon,
// ),
// );
// driversToken.add(json['token']);
// // driversToken = json['token'];
// } else {
// // carLocationsModels[i] = model;
// markers[i] = Marker(
// markerId: MarkerId(json['latitude']),
// position: LatLng(
// double.parse(json['latitude']),
// double.parse(json['longitude']),
// ),
// rotation: double.parse(json['heading']),
// icon: json['model'].contains('دراجة')
// ? motoIcon
// : json['gender'] == 'Male'.tr
// ? carIcon
// : ladyIcon,
// );
// // driversToken = json['token'];
// driversToken.add(json['token']);
// }
// }
// }
// update();
// }
// }
Map<String, Timer> _animationTimers = {};
final int updateIntervalMs = 100; // Update every 100ms
final double minMovementThreshold =
10; // Minimum movement in meters to trigger update
Future getCarForFirstConfirm(String carType) async {
bool foundCars = false;
int attempt = 0;
// Set up the periodic timer
Timer? timer = Timer.periodic(const Duration(seconds: 4), (Timer t) async {
// Attempt to get car location
foundCars = await getCarsLocationByPassengerAndReloadMarker(
carType, attempt * 2000);
Log.print('foundCars: $foundCars');
if (foundCars) {
// If cars are found, cancel the timer and exit the search
t.cancel();
} else if (attempt >= 4) {
// After 4 attempts, stop the search
t.cancel();
// No cars found after 4 attempts
// MyDialog().getDialog(
// "No Car or Driver Found in your area.".tr,
// "No Car or Driver Found in your area.".tr,
// () {
// Get.back();
// },
// );
if (!foundCars) {
noCarString = true;
dataCarsLocationByPassenger = 'failure';
}
update();
}
attempt++; // Increment attempt
});
}
void startCarLocationSearch(String carType) {
int searchInterval = 5; // Interval in seconds
Log.print('searchInterval: $searchInterval');
int boundIncreaseStep = 2500; // Initial bounds in meters
Log.print('boundIncreaseStep: $boundIncreaseStep');
int maxAttempts = 3; // Maximum attempts to increase bounds
int maxBoundIncreaseStep = 6000; // Maximum bounds increase step
int attempt = 0; // Current attempt
Log.print('initial attempt: $attempt');
Timer.periodic(Duration(seconds: searchInterval), (Timer timer) async {
Log.print('Current attempt: $attempt'); // Log current attempt
bool foundCars = false;
if (attempt >= maxAttempts) {
timer.cancel();
if (foundCars == false) {
noCarString = true;
// dataCarsLocationByPassenger = 'failure';
update();
}
// return;
} else if (reloadStartApp == true) {
Log.print('reloadStartApp: $reloadStartApp');
foundCars = await getCarsLocationByPassengerAndReloadMarker(
carType, boundIncreaseStep);
Log.print('foundCars: $foundCars');
if (foundCars) {
timer.cancel();
} else {
attempt++;
if (reloadCount >= 3 || tick > 18 || reloadCount > 15) {
timer.cancel();
}
Log.print(
'Incrementing attempt to: $attempt'); // Log incremented attempt
if (boundIncreaseStep < maxBoundIncreaseStep) {
boundIncreaseStep += 1500; // Increase bounds
if (boundIncreaseStep > maxBoundIncreaseStep) {
boundIncreaseStep =
maxBoundIncreaseStep; // Ensure it does not exceed the maximum
}
Log.print(
'New boundIncreaseStep: $boundIncreaseStep'); // Log new bounds
}
}
}
});
}
// String getLocationArea(double latitude, double longitude) {
// final locations = box.read(BoxName.locationName) ?? [];
// for (final location in locations) {
// final locationData = location as Map<String, dynamic>;
// // Debugging: Print location data
// // print('Location Data: $locationData');
// // Convert string values to double
// final minLatitude =
// double.tryParse(locationData['min_latitude'].toString()) ?? 0.0;
// final maxLatitude =
// double.tryParse(locationData['max_latitude'].toString()) ?? 0.0;
// final minLongitude =
// double.tryParse(locationData['min_longitude'].toString()) ?? 0.0;
// final maxLongitude =
// double.tryParse(locationData['max_longitude'].toString()) ?? 0.0;
// // Debugging: Print converted values
// print(
// 'Converted Values: minLatitude=$minLatitude, maxLatitude=$maxLatitude, minLongitude=$minLongitude, maxLongitude=$maxLongitude');
// if (latitude >= minLatitude &&
// latitude <= maxLatitude &&
// longitude >= minLongitude &&
// longitude <= maxLongitude) {
// box.write(BoxName.serverChosen, (locationData['server_link']));
// // Log.print(
// // 'locationData----server_link: ${(locationData['server_link'])}');
// return locationData['name'];
// }
// }
// // Default case
// box.write(BoxName.serverChosen, AppLink.IntaleqSyriaServer);
// return 'Cairo';
// }
String getLocationArea(double latitude, double longitude) {
LatLng passengerPoint = LatLng(latitude, longitude);
// 1. فحص الأردن
if (isPointInPolygon(passengerPoint, CountryPolygons.jordanBoundary)) {
box.write(BoxName.countryCode, 'Jordan');
// يمكنك تعيين AppLink.endPoint هنا إذا كان منطقك الداخلي لا يزال يعتمد عليه
box.write(BoxName.serverChosen,
AppLink.IntaleqSyriaServer); // مثال: اختر سيرفر سوريا للبيانات
return 'Jordan';
}
// 2. فحص سوريا
if (isPointInPolygon(passengerPoint, CountryPolygons.syriaBoundary)) {
box.write(BoxName.countryCode, 'Syria');
box.write(BoxName.serverChosen, AppLink.IntaleqSyriaServer);
return 'Syria';
}
// 3. فحص مصر
if (isPointInPolygon(passengerPoint, CountryPolygons.egyptBoundary)) {
box.write(BoxName.countryCode, 'Egypt');
box.write(BoxName.serverChosen, AppLink.IntaleqAlexandriaServer);
return 'Egypt';
}
// 4. الافتراضي (إذا كان خارج المناطق المخدومة)
box.write(BoxName.countryCode, 'Jordan');
box.write(BoxName.serverChosen, AppLink.IntaleqSyriaServer);
return 'Unknown Location (Defaulting to Jordan)';
}
// if (latitude >= 29.918901 &&
// latitude <= 30.198857 &&
// longitude >= 31.215009 &&
// longitude <= 31.532186) {
// box.write(BoxName.serverChosen, AppLink.IntaleqCairoServer);
// return 'Cairo';
// } else if (latitude >= 29.904975 &&
// latitude <= 30.143372 &&
// longitude >= 30.787030 &&
// longitude <= 31.215009) {
// box.write(BoxName.serverChosen, AppLink.IntaleqGizaServer);
// return 'Giza';
// } else if (latitude >= 30.396286 &&
// latitude <= 31.654458 &&
// longitude >= 29.041139 &&
// longitude <= 32.626259) {
// box.write(BoxName.serverChosen, AppLink.IntaleqAlexandriaServer);
// return 'Alexandria';
// } else {
// box.write(BoxName.serverChosen, AppLink.IntaleqCairoServer);
// return 'Cairo';
// }
// }
Future<bool> getCarsLocationByPassengerAndReloadMarker(
String carType, int boundIncreaseStep) async {
// if (statusRide == 'wait') {
carsLocationByPassenger = [];
LatLngBounds bounds = calculateBounds(passengerLocation.latitude,
passengerLocation.longitude, boundIncreaseStep.toDouble());
var res;
// await getLocation();
switch (carType) {
case 'Lady':
res = await CRUD()
.get(link: AppLink.getFemalDriverLocationByPassenger, payload: {
'southwestLat': bounds.southwest.latitude.toString(),
'southwestLon': bounds.southwest.longitude.toString(),
'northeastLat': bounds.northeast.latitude.toString(),
'northeastLon': bounds.northeast.longitude.toString(),
});
break;
case 'Comfort':
res = await CRUD()
.get(link: AppLink.getCarsLocationByPassengerComfort, payload: {
'southwestLat': bounds.southwest.latitude.toString(),
'southwestLon': bounds.southwest.longitude.toString(),
'northeastLat': bounds.northeast.latitude.toString(),
'northeastLon': bounds.northeast.longitude.toString(),
});
break;
case 'Speed':
res = await CRUD()
.get(link: AppLink.getCarsLocationByPassengerSpeed, payload: {
'southwestLat': bounds.southwest.latitude.toString(),
'southwestLon': bounds.southwest.longitude.toString(),
'northeastLat': bounds.northeast.latitude.toString(),
'northeastLon': bounds.northeast.longitude.toString(),
});
break;
case 'Scooter':
res = await CRUD()
.get(link: AppLink.getCarsLocationByPassengerDelivery, payload: {
'southwestLat': bounds.southwest.latitude.toString(),
'southwestLon': bounds.southwest.longitude.toString(),
'northeastLat': bounds.northeast.latitude.toString(),
'northeastLon': bounds.northeast.longitude.toString(),
});
break;
case 'Awfar Car':
res = await CRUD()
.get(link: AppLink.getCarsLocationByPassengerBalash, payload: {
'southwestLat': bounds.southwest.latitude.toString(),
'southwestLon': bounds.southwest.longitude.toString(),
'northeastLat': bounds.northeast.latitude.toString(),
'northeastLon': bounds.northeast.longitude.toString(),
});
break;
case 'Electric':
res = await CRUD()
.get(link: AppLink.getCarsLocationByPassengerElectric, payload: {
'southwestLat': bounds.southwest.latitude.toString(),
'southwestLon': bounds.southwest.longitude.toString(),
'northeastLat': bounds.northeast.latitude.toString(),
'northeastLon': bounds.northeast.longitude.toString(),
});
break;
case 'Pink Bike':
res = await CRUD()
.get(link: AppLink.getCarsLocationByPassengerPinkBike, payload: {
'southwestLat': bounds.southwest.latitude.toString(),
'southwestLon': bounds.southwest.longitude.toString(),
'northeastLat': bounds.northeast.latitude.toString(),
'northeastLon': bounds.northeast.longitude.toString(),
});
break;
case 'Van':
res = await CRUD()
.get(link: AppLink.getCarsLocationByPassengerVan, payload: {
'southwestLat': bounds.southwest.latitude.toString(),
'southwestLon': bounds.southwest.longitude.toString(),
'northeastLat': bounds.northeast.latitude.toString(),
'northeastLon': bounds.northeast.longitude.toString(),
});
break;
default:
res = await CRUD()
.get(link: AppLink.getCarsLocationByPassenger, payload: {
'southwestLat': bounds.southwest.latitude.toString(),
'southwestLon': bounds.southwest.longitude.toString(),
'northeastLat': bounds.northeast.latitude.toString(),
'northeastLon': bounds.northeast.longitude.toString(),
});
}
if (res == 'failure') {
noCarString = true;
// dataCarsLocationByPassenger = 'failure';
update();
return false;
} else {
noCarString = false;
dataCarsLocationByPassenger = jsonDecode(res);
// Log.print(
// 'dataCarsLocationByPassenger:getCarsLocationByPassengerAndReloadMarker $dataCarsLocationByPassenger');
// Check if 'message' is present and not null
if (dataCarsLocationByPassenger != null &&
dataCarsLocationByPassenger.isNotEmpty) {
print('carsOrder is in of bounds for message array');
// return false;
// }
} else {
// Get.defaultDialog(title: 'No cars available ');
print('No cars available or message is null');
return false;
}
carsLocationByPassenger.clear(); // Clear existing markers
for (var i = 0; i < dataCarsLocationByPassenger['message'].length; i++) {
var json = dataCarsLocationByPassenger['message'][i];
_updateOrCreateMarker(
MarkerId(json['latitude']).toString(),
LatLng(
double.parse(json['latitude']), double.parse(json['longitude'])),
double.parse(json['heading']),
_getIconForCar(json),
);
driversToken.add((json['token']));
}
// Add fake car markers
_addFakeCarMarkers(passengerLocation, 1);
update();
return true;
}
// }
// return false;
}
final List<Map<String, dynamic>> fakeCarData = [];
void _addFakeCarMarkers(LatLng center, int count) {
if (fakeCarData.isEmpty) {
Random random = Random();
double radiusInKm = 2.5; // 3 km diameter, so 1.5 km radius
for (int i = 0; i < count; i++) {
// Generate a random angle and distance within the circle
double angle = random.nextDouble() * 2 * pi;
double distance = sqrt(random.nextDouble()) * radiusInKm;
// Convert distance to latitude and longitude offsets
double latOffset = (distance / 111.32); // 1 degree lat ≈ 111.32 km
double lonOffset =
(distance / (111.32 * cos(radians(center.latitude))));
// Calculate new position
double lat = center.latitude + (latOffset * cos(angle));
double lon = center.longitude + (lonOffset * sin(angle));
double heading = random.nextDouble() * 360;
fakeCarData.add({
'id': 'fake_$i',
'latitude': lat,
'longitude': lon,
'heading': heading,
'gender': 'Male', // Randomize gender
});
}
}
for (var carData in fakeCarData) {
_updateOrCreateMarker(
MarkerId(carData['id']).toString(),
LatLng(carData['latitude'], carData['longitude']),
carData['heading'],
_getIconForCar(carData),
);
}
}
BitmapDescriptor _getIconForCar(Map<String, dynamic> carData) {
if (carData['model'].toString().contains('دراجة')) {
return motoIcon;
} else if (carData['gender'] == 'Female') {
return ladyIcon;
} else {
return carIcon;
}
}
void _updateOrCreateMarker(String markerId, LatLng newPosition,
double newHeading, BitmapDescriptor icon) {
Marker? existingMarker = markers.cast<Marker?>().firstWhere(
(m) => m?.markerId == MarkerId(markerId),
orElse: () => null,
);
if (existingMarker == null) {
markers.add(Marker(
markerId: MarkerId(markerId),
position: newPosition,
rotation: newHeading,
icon: icon,
));
} else {
double distance =
_calculateDistance(existingMarker.position, newPosition);
if (distance >= minMovementThreshold) {
_smoothlyUpdateMarker(existingMarker, newPosition, newHeading, icon);
}
}
}
double _calculateDistance(LatLng start, LatLng end) {
// Implement distance calculation (e.g., Haversine formula)
// For simplicity, this is a placeholder. Replace with actual implementation.
return 1000 *
sqrt(pow(start.latitude - end.latitude, 2) +
pow(start.longitude - end.longitude, 2));
}
String formatSyrianPhoneNumber(String phoneNumber) {
// Trim any whitespace from the input.
String trimmedPhone = phoneNumber.trim();
// If the number starts with '09', remove the leading '0' and prepend '963'.
if (trimmedPhone.startsWith('09')) {
return '963${trimmedPhone.substring(1)}';
}
// If the number already starts with '963', return it as is to avoid duplication.
if (trimmedPhone.startsWith('963')) {
return trimmedPhone;
}
// For any other case (e.g., number starts with '9' without a '0'),
// prepend '963' to ensure the correct format.
return '963$trimmedPhone';
}
String generateTrackingLink(String rideId, String driverId) {
String cleanRideId = rideId.toString().trim();
String cleanDriverId = driverId.toString().trim();
// الكلمة السرية للمطابقة مع السيرفر
const String secretSalt = "Intaleq_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.html?id=$cleanRideId&token=$token";
}
// 2. الدالة الرئيسية (تم تعديلها لإرسال واتساب بدلاً من الإشعارات)
Future shareTripWithFamily() async {
// التحقق أولاً: هل الرقم موجود؟
String? storedPhone = box.read(BoxName.sosPhonePassenger);
if (storedPhone == null) {
// --- (نفس المنطق القديم: فتح ديالوج لإضافة الرقم) ---
Get.defaultDialog(
title: 'Add SOS Phone'.tr,
titleStyle: AppStyle.title,
content: Form(
key: sosFormKey,
child: MyTextForm(
controller: sosPhonePassengerProfile,
label: 'insert sos phone'.tr,
hint: 'e.g. 0912345678'.tr,
type: TextInputType.phone,
),
),
confirm: MyElevatedButton(
title: 'Add SOS Phone'.tr,
onPressed: () async {
if (sosFormKey.currentState!.validate()) {
Get.back();
// تنسيق الرقم
var numberPhone =
formatSyrianPhoneNumber(sosPhonePassengerProfile.text);
// حفظ في السيرفر
await CRUD().post(
link: AppLink.updateprofile,
payload: {
'id': box.read(BoxName.passengerID),
'sosPhone': numberPhone,
},
);
// حفظ محلياً
box.write(BoxName.sosPhonePassenger, numberPhone);
// استدعاء الدالة مرة أخرى للمتابعة
shareTripWithFamily();
}
}));
return;
}
// --- (المنطق الجديد: إرسال واتساب مباشرة) ---
// 1. التأكد من وجود بيانات للرحلة
if (rideId == 'yet' || driverId.isEmpty) {
Get.snackbar("Alert".tr, "Wait for the trip to start first".tr);
return;
}
// 2. تنسيق الرقم
var numberPhone = formatSyrianPhoneNumber(storedPhone);
// 3. توليد الرابط
String trackingLink = generateTrackingLink(rideId, driverId);
// 4. تجهيز الرسالة (بالإنجليزية وجاهزة للترجمة)
// لاحظ: استخدمت المتغيرات الموجودة في الكنترولر (passengerName هنا عادة يحمل اسم السائق في الكنترولر الخاص بك حسب الكود السابق)
String message = """
مرحباً، تابع رحلتي مباشرة على تطبيق انطلق 🚗
يمكنك تتبع مسار الرحلة من هنا:
$trackingLink
السائق: $passengerName
السيارة: $model - $licensePlate
شكراً لاستخدامك انطلق!
"""
.tr;
String messageEn = """Hello, follow my trip live on Intaleq 🚗
Track my ride here:
$trackingLink
Driver: $passengerName
Car: $model - $licensePlate
Thank you for using Intaleq!
""";
// اختر الرسالة بناءً على اللغة المفضلة (مثال بسيط)
String userLanguage = box.read(BoxName.lang) ?? 'ar';
message = (userLanguage == 'ar') ? message : messageEn;
// وضعنا .tr لكي تتمكن من ترجمتها للعربية في ملفات اللغة إذا أردت، أو تركها إنجليزية
print("Sending WhatsApp to: $numberPhone");
// 5. فتح واتساب
launchCommunication('whatsapp', numberPhone, message);
// (اختياري) حفظ أن التتبع مفعل لتغيير حالة الأيقونة في الواجهة
box.write(BoxName.parentTripSelected, true);
update();
}
Future getTokenForParent() async {
// 1. التحقق أولاً: هل الرقم موجود؟
String? storedPhone = box.read(BoxName.sosPhonePassenger);
if (storedPhone == null) {
// --- حالة الرقم غير موجود: نفتح الديالوج فقط ---
Get.defaultDialog(
title: 'Add SOS Phone'.tr,
titleStyle: AppStyle.title,
content: Form(
key: sosFormKey,
child: MyTextForm(
controller: sosPhonePassengerProfile,
label: 'insert sos phone'.tr,
hint: 'e.g. 0912345678'.tr,
type: TextInputType.phone,
),
),
confirm: MyElevatedButton(
title: 'Add SOS Phone'.tr,
onPressed: () async {
if (sosFormKey.currentState!.validate()) {
// إغلاق الديالوج الحالي
Get.back();
// تنسيق الرقم (تأكد أن هذا التنسيق يطابق ما تم تخزينه عند تسجيل الراكب)
var numberPhone =
formatSyrianPhoneNumber(sosPhonePassengerProfile.text);
// حفظ الرقم في السيرفر (تحديث البروفايل)
await CRUD().post(
link: AppLink.updateprofile,
payload: {
'id': box.read(BoxName.passengerID),
'sosPhone': numberPhone,
},
);
// حفظ الرقم محلياً
box.write(BoxName.sosPhonePassenger, numberPhone);
// استدعاء الدالة مرة أخرى
getTokenForParent();
}
}));
return;
}
generateTrackingLink(rideId, driverId);
// --- حالة الرقم موجود: نكمل التنفيذ ---
var numberPhone = formatSyrianPhoneNumber(storedPhone);
print("Searching for Parent Token with Phone: $numberPhone");
// استدعاء السكريبت (استخدم POST بدلاً من GET)
var res = await CRUD()
.post(link: AppLink.getTokenParent, payload: {'phone': numberPhone});
// التعامل مع الاستجابة
if (res is Map<String, dynamic>) {
handleResponse(res);
} else {
try {
// var jsonRes = jsonDecode(res);
handleResponse(res);
} catch (e) {
print("Error parsing response: $res");
}
}
}
void handleResponse(Map<String, dynamic> res) {
print("Handle Response: $res"); // للتأكد من دخول الدالة
// الحالة 1: الرقم غير مسجل (Failure)
if (res['status'] == 'failure') {
// إذا كان هناك أي ديالوج تحميل مفتوح، نغلقه أولاً، لكن بحذر
if (Get.isDialogOpen ?? false) Get.back();
Get.defaultDialog(
title: "No user found".tr, // اختصرت العنوان ليظهر بشكل أفضل
titleStyle: AppStyle.title,
content: Column(
children: [
Text(
"No passenger found for the given phone number".tr,
style: AppStyle.title, // غيرت الستايل ليكون أصغر قليلاً
textAlign: TextAlign.center,
),
const SizedBox(height: 10),
Text(
"Send Intaleq app to him".tr,
style: AppStyle.title
.copyWith(color: AppColor.greenColor, fontSize: 14),
textAlign: TextAlign.center,
)
],
),
confirm: MyElevatedButton(
title: 'Send Invite'.tr,
onPressed: () {
Get.back(); // إغلاق الديالوج
var rawPhone = box.read(BoxName.sosPhonePassenger);
// تأكد أن rawPhone ليس null
if (rawPhone == null) return;
var phone = formatSyrianPhoneNumber(rawPhone);
// تصحيح نص الرسالة
var message = '''Dear Friend,
🚀 I have just started an exciting trip on Intaleq!
Download the app to track my ride:
👉 Android: https://play.google.com/store/apps/details?id=com.Intaleq.intaleq&hl=en-US
👉 iOS: https://apps.apple.com/st/app/intaleq-rider/id6748075179
See you there!
Intaleq Team''';
launchCommunication('whatsapp', phone, message);
}),
cancel: MyElevatedButton(
title: 'Cancel'.tr,
onPressed: () {
Get.back();
}));
}
// الحالة 2: نجاح (Success)
else if (res['status'] == 'success') {
// إغلاق أي ديالوج سابق (مثل Loading)
if (Get.isDialogOpen ?? false) Get.back();
Get.snackbar("Success".tr, "The invitation was sent successfully".tr,
backgroundColor: AppColor.greenColor, colorText: Colors.white);
List tokensData = res['data'];
for (var device in tokensData) {
String tokenParent = device['token'];
NotificationService.sendNotification(
category: "Trip Monitoring",
target: tokenParent,
title: "Trip Monitoring".tr,
body: "Click to track the trip".tr,
isTopic: false,
tone: 'tone1',
driverList: [rideId, driverId],
);
// حفظ آخر توكن
box.write(BoxName.tokenParent, tokenParent);
}
box.write(BoxName.parentTripSelected, true);
}
}
// Function to check if the point is inside the polygon
bool isPointInPolygon(LatLng point, List<LatLng> polygon) {
int intersections = 0;
for (int i = 0; i < polygon.length; i++) {
LatLng vertex1 = polygon[i];
LatLng vertex2 =
polygon[(i + 1) % polygon.length]; // Loop back to the start
if (_rayIntersectsSegment(point, vertex1, vertex2)) {
intersections++;
}
}
// If the number of intersections is odd, the point is inside
return intersections % 2 != 0;
}
// Helper function to check if a ray from the point intersects with a polygon segment
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;
// Check if the point is outside the vertical bounds of the segment
if ((py < v1y && py < v2y) || (py > v1y && py > v2y)) {
return false;
}
// Calculate the intersection of the ray and the segment
double intersectX = v1x + (py - v1y) * (v2x - v1x) / (v2y - v1y);
// Check if the intersection is to the right of the point
return intersectX > px;
}
bool isInUniversity = false;
// Function to check if the passenger is in any university polygon
// Function to check if the passenger is in any university polygon and return the university name
String checkPassengerLocation(LatLng passengerLocation,
List<List<LatLng>> universityPolygons, List<String> universityNames) {
for (int i = 0; i < universityPolygons.length; i++) {
if (isPointInPolygon(passengerLocation, universityPolygons[i])) {
isInUniversity = true;
return "Passenger is in ${universityNames[i]}";
}
}
return "Passenger is not in any university";
}
String passengerLocationStringUnvirsity = 'unKnown';
void getPassengerLocationUniversity() {
// Check if the passenger is inside any of the university polygons and get the university name
passengerLocationStringUnvirsity = checkPassengerLocation(
passengerLocation,
UniversitiesPolygons.universityPolygons,
UniversitiesPolygons.universityNames,
);
if (passengerLocationStringUnvirsity != 'unKnown') {
// Get.snackbar('you are in $passengerLocationStringUnvirsity', "");
}
print(passengerLocationStringUnvirsity);
}
var polygons = <Polygon>{}.obs;
// Initialize polygons from UniversitiesPolygons
void _initializePolygons() {
List<List<LatLng>> universityPolygons =
UniversitiesPolygons.universityPolygons;
List<String> universityNames = UniversitiesPolygons.universityNames;
for (int i = 0; i < universityPolygons.length; i++) {
Polygon polygon = Polygon(
polygonId: PolygonId(universityNames[i]),
points: universityPolygons[i],
strokeColor: Colors.blueAccent,
fillColor: Colors.blueAccent.withOpacity(0.2),
strokeWidth: 2,
);
polygons.add(polygon); // Add polygon to observable set
}
}
LatLng driverLocationToPassenger = const LatLng(32, 35);
Future getDriverCarsLocationToPassengerAfterApplied() async {
// 1. جلب البيانات من السيرفر
var res = await CRUD().get(
link: AppLink.getDriverCarsLocationToPassengerAfterApplied,
payload: {'driver_id': driverId});
if (res != 'failure') {
datadriverCarsLocationToPassengerAfterApplied = jsonDecode(res);
if (datadriverCarsLocationToPassengerAfterApplied['message'] != null &&
datadriverCarsLocationToPassengerAfterApplied['message'].isNotEmpty) {
var _data = datadriverCarsLocationToPassengerAfterApplied['message'][0];
// تحديث إحداثيات السائق الحالية
LatLng newDriverPos = LatLng(
double.parse(_data['latitude']), double.parse(_data['longitude']));
driverLocationToPassenger = newDriverPos;
driverCarsLocationToPassengerAfterApplied.add(newDriverPos);
// 2. تنظيف الخريطة من السيارات الوهمية والقديمة (الإبقاء فقط على السائق والمسار)
clearMarkersExceptStartEndAndDriver();
// 3. تحديث ماركر السائق (تحريكه)
reloadMarkerDriverCarsLocationToPassengerAfterApplied();
// 4. رسم/تحديث خط المسار بين السائق والراكب
// نتحقق أننا في حالة تستدعي الرسم
// if (rideTimerBegin ||
// statusRide == 'Apply' ||
// currentRideState.value == RideState.driverApplied ||
// currentRideState.value == RideState.inProgress) {
// await drawDriverPathOnly(newDriverPos, passengerLocation);
// }
// // 5. تحديث الكاميرا لتشمل السائق والراكب (اختياري: يمكنك وضع شرط لعدم الازعاج اذا حرك المستخدم الخريطة)
// _fitCameraToPoints(newDriverPos, passengerLocation);
}
}
// 6. تحديث الواجهة بعد انتهاء كل العمليات
update();
}
Future runEvery30SecondsUntilConditionMet() async {
// Calculate the duration of the trip in minutes.
double tripDurationInMinutes = durationToPassenger / 5;
int loopCount = tripDurationInMinutes.ceil();
// If the trip duration is less than or equal to 50 minutes, then break the loop.
for (var i = 0; i < loopCount; i++) {
// Wait for 50 seconds.
await Future.delayed(const Duration(seconds: 5));
if (rideTimerBegin == true || statusRide == 'Apply') {
await getDriverCarsLocationToPassengerAfterApplied();
reloadMarkerDriverCarsLocationToPassengerAfterApplied();
}
}
}
Future runWhenRideIsBegin() async {
// Calculate the duration of the trip in minutes.
double tripDurationInMinutes = durationToRide / 6;
int loopCount = tripDurationInMinutes.ceil();
// If the trip duration is less than or equal to 50 minutes, then break the loop.
clearMarkersExceptStartEnd();
for (var i = 0; i < loopCount; i++) {
// Wait for 50 seconds.
await Future.delayed(const Duration(seconds: 4));
// if (rideTimerBegin == true && statusRide == 'Apply') {
await getDriverCarsLocationToPassengerAfterApplied();
// }
reloadMarkerDriverCarsLocationToPassengerAfterApplied();
}
}
Timer? _timer;
// final int updateIntervalMs = 100; // Update every 100ms
// final double minMovementThreshold =
// 1.0; // Minimum movement in meters to trigger update
void clearMarkersExceptStartEndAndDriver() {
markers.removeWhere((marker) {
String id = marker.markerId.value;
// لا تحذف نقطة البداية
if (id == 'start') return false;
// لا تحذف نقطة النهاية
if (id == 'end') return false;
// لا تحذف السائق الحالي
if (id == currentDriverMarkerId) return false;
// احذف أي شيء آخر (مثل السيارات التي ظهرت وقت البحث)
return true;
});
// ملاحظة: لا نستدعي update() هنا لأننا سنستدعيها في نهاية الدالة الرئيسية
}
void clearMarkersExceptStartEnd() {
Set<Marker> markersToRemove = markers
.where((marker) =>
marker.markerId != const MarkerId("start") &&
marker.markerId != const MarkerId("end"))
.toSet();
for (Marker marker in markersToRemove) {
markers.remove(marker);
}
update();
}
// 1. تعريف ID ثابت للسائق طوال الرحلة
String get currentDriverMarkerId => 'driver_marker_$driverId';
void reloadMarkerDriverCarsLocationToPassengerAfterApplied() {
if (datadriverCarsLocationToPassengerAfterApplied == null ||
datadriverCarsLocationToPassengerAfterApplied['message'] == null ||
datadriverCarsLocationToPassengerAfterApplied['message'].isEmpty) {
return;
}
var driverData =
datadriverCarsLocationToPassengerAfterApplied['message'][0];
// جلب الإحداثيات الجديدة
LatLng newPosition = LatLng(double.parse(driverData['latitude'].toString()),
double.parse(driverData['longitude'].toString()));
double newHeading =
double.tryParse(driverData['heading'].toString()) ?? 0.0;
// تحديد الأيقونة
BitmapDescriptor icon;
if (driverData['model'].toString().contains('دراجة') ||
driverData['make'].toString().contains('دراجة')) {
icon = motoIcon;
} else if (driverData['gender'] == 'Female') {
icon = ladyIcon;
} else {
icon = carIcon;
}
// 2. البحث عن الماركر القديم وتحديثه أو إنشاء جديد
final MarkerId markerId = MarkerId(currentDriverMarkerId);
// التحقق هل الماركر موجود مسبقاً؟
final int existingIndex = markers.indexWhere((m) => m.markerId == markerId);
if (existingIndex != -1) {
// الماركر موجود، نقوم بتحريكه (Animation)
Marker oldMarker = markers[existingIndex];
_smoothlyUpdateMarker(oldMarker, newPosition, newHeading, icon);
} else {
// الماركر غير موجود، نقوم بإنشائه
markers.add(Marker(
markerId: markerId,
position: newPosition,
rotation: newHeading,
icon: icon,
anchor: const Offset(0.5, 0.5), // مهم لكي تدور السيارة حول مركزها
infoWindow: InfoWindow(title: driverName),
));
update(); // تحديث الخريطة
}
}
// التأكد من دالة التحريك السلس
void _smoothlyUpdateMarker(Marker oldMarker, LatLng newPosition,
double newHeading, BitmapDescriptor icon) {
// إذا كانت المسافة صغيرة جداً لا داعي للتحريك (لتقليل الوميض)
double distance = Geolocator.distanceBetween(
oldMarker.position.latitude,
oldMarker.position.longitude,
newPosition.latitude,
newPosition.longitude);
if (distance < 2.0) return;
final String markerIdKey = oldMarker.markerId.value;
// إلغاء أي أنيميشن سابق لنفس الماركر
_animationTimers[markerIdKey]?.cancel();
int ticks = 0;
const int totalSteps = 20; // عدد الخطوات (نعومة الحركة)
const int stepDuration = 50; // سرعة التحديث بالميلي ثانية (المجموع 1 ثانية)
double latStep =
(newPosition.latitude - oldMarker.position.latitude) / totalSteps;
double lngStep =
(newPosition.longitude - oldMarker.position.longitude) / totalSteps;
double headingStep = (newHeading - oldMarker.rotation) / totalSteps;
// معالجة مشكلة الدوران (مثلاً الانتقال من 350 درجة إلى 10 درجات)
if (headingStep.abs() > 180) {
// منطق لضبط الدوران في الاتجاه الأقرب (اختياري)
}
LatLng currentPos = oldMarker.position;
double currentHeading = oldMarker.rotation;
_animationTimers[markerIdKey] =
Timer.periodic(const Duration(milliseconds: stepDuration), (timer) {
ticks++;
currentPos =
LatLng(currentPos.latitude + latStep, currentPos.longitude + lngStep);
currentHeading += headingStep;
// تحديث القائمة
int index = markers.indexWhere((m) => m.markerId.value == markerIdKey);
if (index != -1) {
markers[index] = oldMarker.copyWith(
positionParam: currentPos,
rotationParam: currentHeading,
iconParam: icon, // تحديث الأيقونة في حال تغيرت
);
update(); // تحديث الواجهة في كل خطوة
}
if (ticks >= totalSteps) {
timer.cancel();
_animationTimers.remove(markerIdKey);
}
});
}
void _updateMarkerPosition(
LatLng newPosition, double newHeading, BitmapDescriptor icon) {
const String markerId = 'driverToPassengers';
Marker? existingMarker = markers.cast<Marker?>().firstWhere(
(m) => m?.markerId == const MarkerId(markerId),
orElse: () => null,
);
if (existingMarker == null) {
// If the marker doesn't exist, create it at the new position
markers.add(Marker(
markerId: const MarkerId(markerId),
position: newPosition,
rotation: newHeading,
icon: icon,
));
update();
} else {
// If the marker exists, check if the movement is significant enough to update
double distance =
_calculateDistance(existingMarker.position, newPosition);
if (distance >= minMovementThreshold) {
_smoothlyUpdateMarker(existingMarker, newPosition, newHeading, icon);
}
}
mapController?.animateCamera(CameraUpdate.newLatLng(newPosition));
}
@override
void onClose() {
print(
"--- MapPassengerController: Closing and cleaning up all resources. ---");
// 1. إلغاء المؤقتات الفردية (باستخدام ?. الآمن)
markerReloadingTimer?.cancel();
markerReloadingTimer1?.cancel();
markerReloadingTimer2?.cancel();
timerToPassengerFromDriverAfterApplied?.cancel();
_timer?.cancel();
_masterTimer?.cancel(); // (أضف المؤقت الرئيسي)
_camThrottle?.cancel(); // (أضف مؤقت الكاميرا)
// 2. إلغاء جميع المؤقتات في الخريطة (للتحريكات السلسة)
_animationTimers.forEach((key, timer) {
timer.cancel();
});
_animationTimers.clear();
// 3. إغلاق متحكمات البث (StreamControllers) لمنع تسريب الذاكرة
if (!_timerStreamController.isClosed) {
_timerStreamController.close();
}
if (!_beginRideStreamController.isClosed) {
_beginRideStreamController.close();
}
if (!_rideStatusStreamController.isClosed) {
_rideStatusStreamController.close();
}
if (!timerController.isClosed) {
timerController.close();
}
// 4. التخلص من متحكم الخريطة (ممارسة جيدة)
mapController?.dispose();
print("--- Cleanup complete. ---");
super.onClose();
}
restCounter() {
clearPlacesDestination();
clearPolyline();
data = [];
rideConfirm = false;
shouldFetch = false;
timeToPassengerFromDriverAfterApplied = 0;
update();
}
//driver behaviour
double calculateBearing(double lat1, double lon1, double lat2, double lon2) {
double deltaLon = lon2 - lon1;
double y = sin(deltaLon) * cos(lat2);
double x = cos(lat1) * sin(lat2) - sin(lat1) * cos(lat2) * cos(deltaLon);
double bearing = atan2(y, x);
return (bearing * 180 / pi + 360) % 360; // تحويل إلى درجات
}
void analyzeBehavior(Position currentPosition, List<LatLng> routePoints) {
double actualBearing = currentPosition.heading; // الاتجاه الفعلي من GPS
double expectedBearing = calculateBearing(
routePoints[0].latitude,
routePoints[0].longitude,
routePoints[1].latitude,
routePoints[1].longitude,
);
double bearingDifference = (expectedBearing - actualBearing).abs();
if (bearingDifference > 30) {
print("⚠️ السائق انحرف عن المسار!");
}
}
void detectStops(Position currentPosition) {
if (currentPosition.speed < 0.5) {
print("🚦 السائق توقف في موقع غير متوقع!");
}
}
Future<void> cancelRideAfterRejectFromAll() async {
clearPlacesDestination();
clearPolyline();
data = [];
await CRUD().post(link: AppLink.updateRides, payload: {
"id": rideId.toString(), // Convert to String
"status": 'notApplyFromAnyDriver'
});
CRUD().post(link: "${AppLink.endPoint}/ride/rides/update.php", payload: {
"id": rideId.toString(), // Convert to String
"status": 'notApplyFromAnyDriver'
});
rideConfirm = false;
statusRide == 'Cancel';
isSearchingWindow = false;
shouldFetch = false;
isPassengerChosen = false;
isCashConfirmPageShown = false;
// totalStepDurations = 0;
isCashSelectedBeforeConfirmRide = false;
timeToPassengerFromDriverAfterApplied = 0;
changeCancelRidePageShow();
remainingTime = 0;
update();
}
Future cancelRide() async {
clearPlacesDestination();
clearPolyline();
data = [];
changeCancelRidePageShow();
if (rideId != 'yet') {
Log.print('cancelRide: 1');
await NotificationService.sendNotification(
category: 'Cancel Trip',
target: driverToken.toString(),
title: 'Cancel Trip'.tr,
body: 'Cancel Trip'.tr,
isTopic: false,
tone: 'tone1',
driverList: [],
);
await Future.wait([
CRUD().post(
link: AppLink.updateRides,
payload: {"id": rideId.toString(), "status": 'Cancel'}),
CRUD().post(
link: "${AppLink.server}/ride/rides/update.php",
payload: {"id": rideId.toString(), "status": 'Cancel'}),
CRUD().post(
link: AppLink.updateDriverOrder,
payload: {"order_id": rideId.toString(), "status": 'Cancel'}),
CRUD().post(
link: AppLink.updateWaitingTrip,
payload: {"id": rideId.toString(), "status": 'Cancel'}),
]);
}
// ---!! الإضافة الحاسمة لحل مشكلة "cancelled" !! ---
Log.print(
'[cancelRide] User cancelled. Setting state and stopping timers.');
currentRideState.value = RideState.cancelled; // 1. تعيين الحالة
stopAllTimers(); // 2. إيقاف المؤقت الرئيسي
// --- نهاية الإضافة ---
Get.offAll(() => const MapPagePassenger());
}
void changePickerShown() {
isPickerShown = !isPickerShown;
heightPickerContainer = isPickerShown == true ? 150 : 90;
update();
}
void changeHeightPointsPageForRider() {
isPointsPageForRider = !isPointsPageForRider;
heightPointsPageForRider = isPointsPageForRider == true ? Get.height : 0;
update();
}
getCoordinateFromMapWayPoints(int index) {
placesCoordinate[index] = newStartPointLocation.toString();
update();
}
// --- ابدأ الإضافة هنا ---
// 1. قائمة لتخزين نقاط التوقف
List<Map<String, dynamic>> waypoints = [];
// 2. دالة لإضافة نقطة توقف جديدة
void addWaypoint(Map<String, dynamic> placeDetails) {
// يمكنك إضافة منطق للتحقق من عدد نقاط التوقف المسموح بها هنا
waypoints.add(placeDetails);
update(); // لتحديث الواجهة
// TODO: أضف هنا استدعاء دالة إعادة رسم المسار مع نقاط التوقف الجديدة
// getDirectionMapWithWaypoints();
}
// 3. دالة لحذف نقطة توقف
void removeWaypoint(int index) {
if (index >= 0 && index < waypoints.length) {
waypoints.removeAt(index);
update(); // لتحديث الواجهة
// TODO: أضف هنا استدعاء دالة إعادة رسم المسار بعد حذف النقطة
// getDirectionMapWithWaypoints();
}
}
// --- انتهى ---
void changeMainBottomMenuMap() {
if (isWayPointStopsSheetUtilGetMap == true) {
changeWayPointSheet();
} else {
isMainBottomMenuMap = !isMainBottomMenuMap;
mainBottomMenuMapHeight =
isMainBottomMenuMap == true ? Get.height * .22 : Get.height * .6;
isWayPointSheet = false;
if (heightMenuBool == true) {
getDrawerMenu();
}
initilizeGetStorage();
update();
}
}
void downPoints() {
if (Get.find<WayPointController>().wayPoints.length < 2) {
isWayPointStopsSheetUtilGetMap = false;
isWayPointSheet = false;
wayPointSheetHeight = isWayPointStopsSheet ? Get.height * .45 : 0;
// changeWayPointStopsSheet();
update();
}
// changeWayPointStopsSheet();
// isWayPointSheet = false;
update();
}
void changeWayPointSheet() {
isWayPointSheet = !isWayPointSheet;
wayPointSheetHeight = isWayPointSheet == false ? 0 : Get.height * .45;
// if (heightMenuBool == true) {
// getDrawerMenu();
// }
update();
}
void changeWayPointStopsSheet() {
// int waypointsLength = Get.find<WayPointController>().wayPoints.length;
if (wayPointIndex > -1) {
isWayPointStopsSheet = true;
isWayPointStopsSheetUtilGetMap = true;
}
isWayPointStopsSheet = !isWayPointStopsSheet;
wayPointSheetHeight = isWayPointStopsSheet ? Get.height * .45 : 0;
// if (heightMenuBool == true) {
// getDrawerMenu();
// }
update();
}
changeHeightPlaces() {
if (placesDestination.isEmpty) {
height = 0;
update();
}
height = 150;
update();
}
changeHeightStartPlaces() {
if (placesStart.isEmpty) {
height = 0;
update();
}
height = 150;
update();
}
changeHeightPlacesAll(int index) {
if (placeListResponseAll[index].isEmpty) {
height = 0;
update();
}
height = 150;
update();
}
changeHeightPlaces1() {
if (wayPoint1.isEmpty) {
height = 0;
update();
}
height = 150;
update();
}
changeHeightPlaces2() {
if (wayPoint2.isEmpty) {
height = 0;
update();
}
height = 150;
update();
}
changeHeightPlaces3() {
if (wayPoint3.isEmpty) {
height = 0;
update();
}
height = 150;
update();
}
changeHeightPlaces4() {
if (wayPoint4.isEmpty) {
height = 0;
update();
}
height = 150;
update();
}
hidePlaces() {
height = 0;
update();
}
/// تحويل نصف قطر بالكيلومتر إلى دلتا درجات عرض
// double _haversineKm(double lat1, double lon1, double lat2, double lon2) {
// const R = 6371.0; // km
// final dLat = (lat2 - lat1) * math.pi / 180.0;
// final dLon = (lon2 - lon1) * math.pi / 180.0;
// final a = math.sin(dLat / 2) * math.sin(dLat / 2) +
// math.cos(lat1 * math.pi / 180.0) *
// math.cos(lat2 * math.pi / 180.0) *
// math.sin(dLon / 2) *
// math.sin(dLon / 2);
// final c = 2 * math.atan2(math.sqrt(a), math.sqrt(1 - a));
// return R * c;
// }
/// تحويل نصف قطر بالكيلومتر إلى دلتا درجات عرض
// double _kmToLatDelta(double km) => km / 111.0;
// /// تحويل نصف قطر بالكيلومتر إلى دلتا درجات طول (تعتمد على خط العرض)
// double _kmToLngDelta(double km, double atLat) =>
// km / (111.320 * math.cos(atLat * math.pi / 180.0)).abs().clamp(1e-6, 1e9);
/// حساب درجة التطابق النصي (كل كلمة تبدأ بها الاسم = 2 نقاط، يحتويها = 1 نقطة)
// double _relevanceScore(String name, String query) {
// final n = name.toLowerCase();
// final parts =
// query.toLowerCase().split(RegExp(r'\s+')).where((p) => p.length >= 2);
// double s = 0.0;
// for (final p in parts) {
// if (n.startsWith(p)) {
// s += 2.0;
// } else if (n.contains(p)) {
// s += 1.0;
// }
// }
// return s;
// }
// الدالة الرئيسية لجلب الأماكن من السيرفر وترتيبها
// انسخ هذه الدوال والصقها داخل كلاس الكنترولر الخاص بك
// -----------------------------------------------------------------
// --== الدالة الرئيسية للبحث ==--
// -----------------------------------------------------------------
/// الدالة الرئيسية لجلب الأماكن من السيرفر وترتيبها
// انسخ هذه الدوال والصقها داخل كلاس الكنترولر الخاص بك
// -----------------------------------------------------------------
// --== الدالة الرئيسية للبحث ==--
// -----------------------------------------------------------------
/// الدالة الرئيسية لجلب الأماكن من السيرفر وترتيبها
Future<void> getPlaces() async {
// افترض وجود `placeDestinationController` و `passengerLocation` و `CRUD()` معرفة في الكنترولر
final q = placeDestinationController.text.trim();
if (q.isEmpty || q.length < 3) {
// يفضل عدم البحث قبل 3 أحرف
placesDestination = [];
update(); // افترض أنك تستخدم GetX أو أي State Management آخر
return;
}
final lat = passengerLocation.latitude;
final lng = passengerLocation.longitude;
// نصف قطر البحث بالكيلومتر
const radiusKm = 45.0;
// حساب النطاق الجغرافي (Bounding Box) لإرساله للسيرفر
final latDelta = _kmToLatDelta(radiusKm);
final lngDelta = _kmToLngDelta(radiusKm, lat);
final latMin = lat - latDelta;
final latMax = lat + latDelta;
final lngMin = lng - lngDelta;
final lngMax = lng + lngDelta;
try {
// استدعاء الـ API (تأكد من أن AppLink.getPlacesSyria يشير للسكريبت الجديد)
final response = await CRUD().post(
link: AppLink.getPlacesSyria,
payload: {
'query': q,
'lat_min': latMin.toString(),
'lat_max': latMax.toString(),
'lng_min': lngMin.toString(),
'lng_max': lngMax.toString(),
},
);
// --- [تم الإصلاح هنا] ---
// معالجة الاستجابة من السيرفر بشكل يوافق {"status":"success", "message":[...]}
List list;
if (response is Map) {
if (response['status'] == 'success' && response['message'] is List) {
list = List.from(response['message'] as List);
} else if (response['status'] == 'failure') {
print('Server Error: ${response['message']}');
return;
} else {
print('Unexpected Map shape from server');
return;
}
} else if (response is List) {
// للتعامل مع الحالات التي قد يرجع فيها السيرفر قائمة مباشرة
list = List.from(response);
} else {
print('Unexpected response shape from server');
return;
}
// --- هنا يبدأ عمل فلاتر: الترتيب النهائي الدقيق ---
// دالة مساعدة لاختيار أفضل اسم متاح
String _bestName(Map p) {
return (p['name_ar'] ?? p['name'] ?? p['name_en'] ?? '').toString();
}
// حساب المسافة والصلة والنقاط النهائية لكل نتيجة
for (final p in list) {
final plat = double.tryParse(p['latitude']?.toString() ?? '0.0') ?? 0.0;
final plng =
double.tryParse(p['longitude']?.toString() ?? '0.0') ?? 0.0;
final distance = _haversineKm(lat, lng, plat, plng);
final relevance = _relevanceScore(_bestName(p), q);
// معادلة الترتيب: (الأولوية للمسافة الأقرب) * (ثم الصلة الأعلى)
final score = (1.0 / (1.0 + distance)) * (1.0 + relevance);
p['distanceKm'] = distance;
p['relevance'] = relevance;
p['score'] = score;
}
// ترتيب القائمة النهائية حسب النقاط (الأعلى أولاً)
list.sort((a, b) {
final sa = (a['score'] ?? 0.0) as double;
final sb = (b['score'] ?? 0.0) as double;
return sb.compareTo(sa);
});
placesDestination = list;
print('Updated places: $placesDestination');
update();
} catch (e) {
print('Exception in getPlaces: $e');
}
}
// -----------------------------------------------------------------
// --== دوال مساعدة ==--
// -----------------------------------------------------------------
/// تحسب المسافة بين نقطتين بالكيلومتر (معادلة هافرساين)
double _haversineKm(double lat1, double lon1, double lat2, double lon2) {
const R = 6371.0; // نصف قطر الأرض بالكيلومتر
final dLat = (lat2 - lat1) * (pi / 180.0);
final dLon = (lon2 - lon1) * (pi / 180.0);
final rLat1 = lat1 * (pi / 180.0);
final rLat2 = lat2 * (pi / 180.0);
final a = sin(dLat / 2) * sin(dLat / 2) +
cos(rLat1) * cos(rLat2) * sin(dLon / 2) * sin(dLon / 2);
final c = 2 * atan2(sqrt(a), sqrt(1 - a));
return R * c;
}
/// تحسب درجة تطابق بسيطة بين اسم المكان وكلمة البحث
double _relevanceScore(String placeName, String query) {
if (placeName.isEmpty || query.isEmpty) return 0.0;
final pLower = placeName.toLowerCase();
final qLower = query.toLowerCase();
if (pLower.startsWith(qLower)) return 1.0; // تطابق كامل في البداية
if (pLower.contains(qLower)) return 0.5; // تحتوي على الكلمة
return 0.0;
}
/// تحويل كيلومتر إلى فرق درجات لخط العرض
double _kmToLatDelta(double km) {
const kmInDegree = 111.32;
return km / kmInDegree;
}
/// تحويل كيلومتر إلى فرق درجات لخط الطول (يعتمد على خط العرض الحالي)
double _kmToLngDelta(double km, double latitude) {
const kmInDegree = 111.32;
return km / (kmInDegree * cos(latitude * (pi / 180.0)));
}
// var languageCode;
// // تحديد اللغة حسب الإدخال
// if (RegExp(r'[a-zA-Z]').hasMatch(placeDestinationController.text)) {
// languageCode = 'en';
// } else {
// languageCode = 'ar';
// }
// final bool isTextEmpty = placeDestinationController.text.trim().isEmpty;
// var key = Platform.isAndroid ? AK.mapAPIKEY : AK.mapAPIKEYIOS;
// final Uri url = Uri.parse(
// isTextEmpty
// ? 'https://places.googleapis.com/v1/places:searchNearby?key=$key'
// : 'https://places.googleapis.com/v1/places:searchText?key=$key',
// );
// Log.print('url: $url');
// // بناء الجسم حسب نوع الطلب
// final body = isTextEmpty
// ? jsonEncode({
// "languageCode": languageCode,
// "locationRestriction": {
// "circle": {
// "center": {
// "latitude": passengerLocation.latitude,
// "longitude": passengerLocation.longitude
// },
// "radius": 40000 // 40 كم
// }
// },
// "maxResultCount": 10
// })
// : jsonEncode({
// "textQuery": placeDestinationController.text,
// "languageCode": languageCode,
// "maxResultCount": 10,
// "locationBias": {
// "circle": {
// "center": {
// "latitude": passengerLocation.latitude,
// "longitude": passengerLocation.longitude
// },
// "radius": 40000
// }
// }
// });
// final headers = {
// 'Content-Type': 'application/json',
// 'X-Goog-Api-Key': AK.mapAPIKEY,
// 'X-Goog-FieldMask':
// 'places.displayName,places.formattedAddress,places.location'
// };
// try {
// final response = await http.post(url, headers: headers, body: body);
// print('response: ${response.statusCode} - ${response.body}');
// if (response.statusCode == 200) {
// final data = jsonDecode(response.body);
// placesDestination = data['places'] ?? [];
// update();
// } else {
// print('Error: ${response.statusCode} - ${response.reasonPhrase}');
// }
// } catch (e) {
// print('Exception: $e');
// }
// }
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();
} else {}
}
// Future<void> getPlaces() async {
// var languageCode;
// // Check if `placeDestinationController.text` contains English characters
// if (RegExp(r'[a-zA-Z]').hasMatch(placeDestinationController.text)) {
// languageCode = 'en';
// } else {
// languageCode = 'ar';
// }
// // Construct the URL
// var url = Uri.parse(
// '${AppLink.searcMaps}?q=${Uri.encodeQueryComponent(placeDestinationController.text)}&limit=10&in=circle:${passengerLocation.latitude},${passengerLocation.longitude};r=50000&lang=$languageCode&apiKey=$k',
// );
// // Log the URL for debugging
// print(url);
// // box.remove(BoxName.placesDestination);
// try {
// // Make the API request
// var response = await CRUD().getHereMap(
// link: url.toString(),
// );
// // Log the response for debugging
// // Log.print('response: ${response}');
// // Check if the response is valid
// if (response != null && response['items'] != null) {
// placesDestination = response['items'];
// // Log.print('placesDestination: ${placesDestination}');
// placesDestination = response['items'];
// // box.write(BoxName.placesDestination, placesDestination);
// for (var i = 0; i < placesDestination.length; i++) {
// var res = placesDestination[i];
// // Extract fields with null safety
// var title = res['title']?.toString() ?? 'Unknown Place';
// var position = res['position'];
// var address = res['address']?['label'] ?? 'Unknown Address';
// if (position == null) {
// Log.print('Position is null for place: $title');
// continue; // Skip this place and continue with the next one
// }
// String latitude = position['lat']?.toString() ?? '0.0';
// String longitude = position['lng']?.toString() ?? '0.0';
// try {
// await savePlaceToServer(latitude, longitude, title, address);
// // Log.print('Place saved successfully: $title');
// } catch (e) {
// // Log.print('Failed to save place: $e');
// }
// } // todo save key in env then get key and use it
// } else {
// placesDestination = [];
// }
// } catch (e) {
// // Handle any errors that occur during the API request
// Log.print('Error fetching places: $e');
// placesDestination = [];
// }
// // Notify listeners that the state has changed
// update();
// }
Future<void> getPlacesStart() async {
// افترض وجود `placeDestinationController` و `passengerLocation` و `CRUD()` معرفة في الكنترولر
final q = placeStartController.text.trim();
if (q.isEmpty || q.length < 3) {
// يفضل عدم البحث قبل 3 أحرف
placesStart = [];
update(); // افترض أنك تستخدم GetX أو أي State Management آخر
return;
}
final lat = passengerLocation.latitude;
final lng = passengerLocation.longitude;
// نصف قطر البحث بالكيلومتر
const radiusKm = 200.0;
// حساب النطاق الجغرافي (Bounding Box) لإرساله للسيرفر
final latDelta = _kmToLatDelta(radiusKm);
final lngDelta = _kmToLngDelta(radiusKm, lat);
final latMin = lat - latDelta;
final latMax = lat + latDelta;
final lngMin = lng - lngDelta;
final lngMax = lng + lngDelta;
try {
// استدعاء الـ API (تأكد من أن AppLink.getPlacesSyria يشير للسكريبت الجديد)
final response = await CRUD().post(
link: AppLink.getPlacesSyria,
payload: {
'query': q,
'lat_min': latMin.toString(),
'lat_max': latMax.toString(),
'lng_min': lngMin.toString(),
'lng_max': lngMax.toString(),
},
);
// --- [تم الإصلاح هنا] ---
// معالجة الاستجابة من السيرفر بشكل يوافق {"status":"success", "message":[...]}
List list;
if (response is Map) {
if (response['status'] == 'success' && response['message'] is List) {
list = List.from(response['message'] as List);
} else if (response['status'] == 'failure') {
print('Server Error: ${response['message']}');
return;
} else {
print('Unexpected Map shape from server');
return;
}
} else if (response is List) {
// للتعامل مع الحالات التي قد يرجع فيها السيرفر قائمة مباشرة
list = List.from(response);
} else {
print('Unexpected response shape from server');
return;
}
// --- هنا يبدأ عمل فلاتر: الترتيب النهائي الدقيق ---
// دالة مساعدة لاختيار أفضل اسم متاح
String _bestName(Map p) {
return (p['name_ar'] ?? p['name'] ?? p['name_en'] ?? '').toString();
}
// حساب المسافة والصلة والنقاط النهائية لكل نتيجة
for (final p in list) {
final plat = double.tryParse(p['latitude']?.toString() ?? '0.0') ?? 0.0;
final plng =
double.tryParse(p['longitude']?.toString() ?? '0.0') ?? 0.0;
final distance = _haversineKm(lat, lng, plat, plng);
final relevance = _relevanceScore(_bestName(p), q);
// معادلة الترتيب: (الأولوية للمسافة الأقرب) * (ثم الصلة الأعلى)
final score = (1.0 / (1.0 + distance)) * (1.0 + relevance);
p['distanceKm'] = distance;
p['relevance'] = relevance;
p['score'] = score;
}
// ترتيب القائمة النهائية حسب النقاط (الأعلى أولاً)
list.sort((a, b) {
final sa = (a['score'] ?? 0.0) as double;
final sb = (b['score'] ?? 0.0) as double;
return sb.compareTo(sa);
});
placesStart = list;
print('Updated places: $placesDestination');
update();
} catch (e) {
print('Exception in getPlaces: $e');
}
}
Future getPlacesListsWayPoint(int index) async {
var languageCode = wayPoint0Controller.text;
// Regular expression to check for English alphabet characters
final englishRegex = RegExp(r'[a-zA-Z]');
// Check if text contains English characters
if (englishRegex.hasMatch(languageCode)) {
languageCode = 'en';
} else {
languageCode = 'ar';
}
var url =
'${AppLink.googleMapsLink}place/nearbysearch/json?keyword=${wayPoint0Controller.text}&location=${passengerLocation.latitude},${passengerLocation.longitude}&radius=250000&language=$languageCode&key=${AK.mapAPIKEY.toString()}';
try {
var response = await CRUD().getGoogleApi(link: url, payload: {});
if (response != null && response['results'] != null) {
wayPoint0 = response['results'];
placeListResponseAll[index] = response['results'];
update();
} else {
print('Error: Invalid response from Google Places API');
}
} catch (e) {
print('Error fetching places: $e');
}
}
// داخل MapPassengerController
bool lowPerf = false;
Timer? _camThrottle;
DateTime _lastUiUpdate = DateTime.fromMillisecondsSinceEpoch(0);
Future<void> detectPerfMode() async {
try {
if (GetPlatform.isAndroid) {
final info = await DeviceInfoPlugin().androidInfo;
final sdk = info.version.sdkInt ?? 0;
final ram = info.availableRamSize ?? 0;
lowPerf = (sdk < 28) || (ram > 0 && ram < 3 * 1024 * 1024 * 1024);
} else {
lowPerf = false;
}
} catch (_) {
lowPerf = false;
}
update();
}
// تحديث الكاميرا بثروتل
void onCameraMoveThrottled(CameraPosition pos) {
if (_camThrottle?.isActive ?? false) return;
_camThrottle = Timer(const Duration(milliseconds: 160), () {
// ضع فقط المنطق الضروري هنا لتقليل الحمل
int waypointsLength = Get.find<WayPointController>().wayPoints.length;
int index = wayPointIndex;
if (waypointsLength > 0) {
placesCoordinate[index] =
'${pos.target.latitude},${pos.target.longitude}';
}
newMyLocation = pos.target;
});
}
// تهيئة polylines خفيفة (استدعها بعد جلب المسار)
Set<Polyline> polyLinesLight = {};
List<LatLng> simplifyPolyline(List<LatLng> pts, double epsilonMeters) {
if (pts.length <= 2) return pts;
double _perpDist(LatLng p, LatLng a, LatLng b) {
// مسافة عمودية تقريبية بالأمتار
double _toRad(double d) => d * math.pi / 180.0;
// تحويل بسيط للمتر (تقريبي)
final x1 = a.longitude, y1 = a.latitude;
final x2 = b.longitude, y2 = b.latitude;
final x0 = p.longitude, y0 = p.latitude;
final num = ((y2 - y1) * x0 - (x2 - x1) * y0 + x2 * y1 - y2 * x1).abs();
final den = math.sqrt(math.pow(y2 - y1, 2) + math.pow(x2 - x1, 2));
// تحويل درجات -> أمتار تقريبي (1 درجة ~ 111km)
final degDist = den == 0 ? 0 : num / den;
return degDist * 111000; // متر
}
List<LatLng> dp(int start, int end) {
double maxDist = 0;
int index = start;
for (int i = start + 1; i < end; i++) {
final d = _perpDist(pts[i], pts[start], pts[end]);
if (d > maxDist) {
maxDist = d;
index = i;
}
}
if (maxDist > epsilonMeters) {
final r1 = dp(start, index);
final r2 = dp(index, end);
return [...r1.sublist(0, r1.length - 1), ...r2];
} else {
return [pts[start], pts[end]];
}
}
return dp(0, pts.length - 1);
}
void buildLightPolylines(List<LatLng> originalPoints) {
final simplified = simplifyPolyline(originalPoints, lowPerf ? 12 : 3);
polyLinesLight = {
Polyline(
polylineId: const PolylineId('route_light'),
points: simplified,
width: lowPerf ? 4 : 6,
geodesic: true,
color: AppColor.primaryColor,
endCap: Cap.roundCap,
startCap: Cap.roundCap,
jointType: JointType.round,
),
};
update();
}
Future<void> savePlaceToServer(
String latitude, String longitude, String name, String rate) async {
var data = {
'latitude': latitude,
'longitude': longitude,
'name': name,
'rate': rate,
};
try {
CRUD().post(
link: AppLink.savePlacesServer,
payload: data,
);
} catch (e) {
print('Error: $e');
}
}
// Future getPlacesListsWayPoint(int index) async {
// var url =
// '${AppLink.googleMapsLink}place/nearbysearch/json?keyword=${wayPoint0Controller.text}&location=${passengerLocation.latitude},${passengerLocation.longitude}&radius=80000&language=${}&key=${AK.mapAPIKEY.toString()}';
// var response = await CRUD().getGoogleApi(link: url, payload: {});
// wayPoint0 = response['results'];
// placeListResponseAll[index] = response['results'];
// update();
// }
LatLng fromString(String location) {
List<String> parts = location.split(',');
double lat = double.parse(parts[0]);
double lng = double.parse(parts[1]);
return LatLng(lat, lng);
}
void clearPolyline() {
polyLines = [];
polylineCoordinates.clear();
// polylineCoordinates = [];
polylineCoordinatesPointsAll[0].clear();
polylineCoordinatesPointsAll[1].clear();
polylineCoordinatesPointsAll[2].clear();
polylineCoordinatesPointsAll[3].clear();
polylineCoordinatesPointsAll[4].clear();
isMarkersShown = false;
update();
}
void addCustomPicker() {
ImageConfiguration config = ImageConfiguration(
size: const Size(30, 30), devicePixelRatio: Get.pixelRatio
// scale: 1.0,
);
BitmapDescriptor.asset(
config,
'assets/images/picker.png',
// mipmaps: false,
).then((value) {
markerIcon = value;
update();
});
}
void addCustomStartIcon() async {
// Create the marker with the resized image
ImageConfiguration config = ImageConfiguration(
size: const Size(30, 30), devicePixelRatio: Get.pixelRatio);
BitmapDescriptor.asset(
config,
'assets/images/A.png',
// mipmaps: false,
).then((value) {
startIcon = value;
update();
});
}
void addCustomEndIcon() {
ImageConfiguration config = ImageConfiguration(
size: const Size(30, 30), devicePixelRatio: Get.pixelRatio);
BitmapDescriptor.asset(
config,
'assets/images/b.png',
// mipmaps: false,
).then((value) {
endIcon = value;
update();
});
}
void addCustomCarIcon() {
ImageConfiguration config = ImageConfiguration(
size: const Size(30, 35), devicePixelRatio: Get.pixelRatio);
BitmapDescriptor.asset(
config,
'assets/images/car.png',
// mipmaps: false,
).then((value) {
carIcon = value;
update();
});
}
void addCustomMotoIcon() {
ImageConfiguration config = ImageConfiguration(
size: const Size(30, 30), devicePixelRatio: Get.pixelRatio);
BitmapDescriptor.asset(
config,
'assets/images/moto1.png',
// mipmaps: false,
).then((value) {
motoIcon = value;
update();
});
}
void addCustomLadyIcon() {
ImageConfiguration config = ImageConfiguration(
size: const Size(30, 30), devicePixelRatio: Get.pixelRatio);
BitmapDescriptor.asset(
config,
'assets/images/lady1.png',
// mipmaps: false,
).then((value) {
ladyIcon = value;
update();
});
}
void addCustomStepIcon() {
ImageConfiguration config = ImageConfiguration(
size: const Size(30, 30), devicePixelRatio: Get.pixelRatio);
BitmapDescriptor.asset(
config,
'assets/images/brand.png',
// mipmaps: false,
).then((value) {
tripIcon = value;
update();
});
}
dialoge() {
Get.defaultDialog(
title: 'Location '.tr,
content: Container(
child: Column(
children: [
Text(
'We use location to get accurate and nearest driver for you'.tr,
style: AppStyle.title,
),
TextButton(
onPressed: () async {
// await Permission.location.request();
Get.back();
},
child: Text(
'Grant'.tr,
style: AppStyle.title,
),
)
],
),
),
);
}
double speed = 0;
Future<void> getLocation() async {
isLoading = true;
update();
bool serviceEnabled;
PermissionStatus permissionGranted;
// dialoge();
// Check if location services are enabled
serviceEnabled = await location.serviceEnabled();
if (!serviceEnabled) {
serviceEnabled = await location.requestService();
if (!serviceEnabled) {
// Location services are still not enabled, handle the error
return;
}
}
// Check if the app has permission to access location
permissionGranted = await location.hasPermission();
if (permissionGranted == PermissionStatus.denied) {
permissionGranted = await location.requestPermission();
if (permissionGranted != PermissionStatus.granted) {
// Location permission is still not granted, handle the error
return;
}
}
// Configure location accuracy
// LocationAccuracy desiredAccuracy = LocationAccuracy.high;
// Get the current location
LocationData _locationData = await location.getLocation();
passengerLocation =
(_locationData.latitude != null && _locationData.longitude != null
? LatLng(_locationData.latitude!, _locationData.longitude!)
: null)!;
// getLocationArea(passengerLocation.latitude, passengerLocation.longitude);
// Log.print('AppLink.endPoint: ${AppLink.endPoint}');
// Log.print('BoxName.serverChosen: ${box.read(BoxName.serverChosen)}');
newStartPointLocation = passengerLocation;
// Log.print('passengerLocation: $passengerLocation');
speed = _locationData.speed!;
// //print location details
isLoading = false;
update();
}
LatLngBounds calculateBounds(double lat, double lng, double radiusInMeters) {
const double earthRadius = 6378137.0; // Earth's radius in meters
double latDelta = (radiusInMeters / earthRadius) * (180 / pi);
double lngDelta =
(radiusInMeters / (earthRadius * cos(pi * lat / 180))) * (180 / pi);
double minLat = lat - latDelta;
double maxLat = lat + latDelta;
double minLng = lng - lngDelta;
double maxLng = lng + lngDelta;
// Ensure the latitude is between -90 and 90
minLat = max(-90.0, minLat);
maxLat = min(90.0, maxLat);
// Ensure the longitude is between -180 and 180
minLng = (minLng + 180) % 360 - 180;
maxLng = (maxLng + 180) % 360 - 180;
// Ensure the bounds are in the correct order
if (minLng > maxLng) {
double temp = minLng;
minLng = maxLng;
maxLng = temp;
}
return LatLngBounds(
southwest: LatLng(minLat, minLng),
northeast: LatLng(maxLat, maxLng),
);
}
GoogleMapController? mapController;
void onMapCreated(GoogleMapController controller) {
// myLocation = Get.find<LocationController>().location as LatLng;
// myLocation = myLocation;
mapController = controller;
controller.getVisibleRegion();
controller.animateCamera(
CameraUpdate.newLatLng(passengerLocation),
);
// Future.delayed(const Duration(milliseconds: 500), () {
// markers.forEach((marker) {
// controller.showMarkerInfoWindow(marker.markerId);
// });
// });
update();
}
// void startMarkerReloading() {
// int count = 0;
// markerReloadingTimer = Timer.periodic(const Duration(seconds: 30), (timer) {
// reloadMarkers();
//
// count++;
// if (count == 10) {
// timer.cancel();
// }
// });
// }
bool reloadStartApp = false;
int reloadCount = 0;
startMarkerReloading() async {
if (reloadStartApp == false) {
Timer.periodic(const Duration(seconds: 3), (timer) async {
reloadCount++;
Log.print('reloadCount: $reloadCount');
if (rideConfirm == false) {
clearMarkersExceptStartEnd();
// _smoothlyUpdateMarker();
// startCarLocationSearch(box.read(BoxName.carType));
await getCarsLocationByPassengerAndReloadMarker(
box.read(BoxName.carType), 5000);
await getNearestDriverByPassengerLocation();
// Log.print('reloadMarkers: from startMarkerReloading');
} else {
// runWhenRideIsBegin();
}
if (reloadCount >= 6) {
reloadStartApp = true;
timer.cancel(); // Stop the timer after 5 reloads
}
});
}
}
String durationByPassenger = '';
late DateTime newTime1 = DateTime.now();
late DateTime timeFromDriverToPassenger = DateTime.now();
String distanceByPassenger = '';
late Duration durationFromDriverToPassenger;
double nearestDistance = double.infinity;
// Future<CarLocation?> getNearestDriverByPassengerLocation() async {
// if (polyLines.isEmpty || data.isEmpty) {
// return null; // Early return if data is empty
// }
// if (!rideConfirm) {
// if (dataCarsLocationByPassenger != 'failure') {
// if (dataCarsLocationByPassenger != null) {
// if (dataCarsLocationByPassenger['message'].length > 0) {
// double nearestDistance = double
// .infinity; // Initialize nearest distance to a large number
// CarLocation? nearestCar;
// for (var i = 0;
// i < dataCarsLocationByPassenger['message'].length;
// i++) {
// var carLocation = dataCarsLocationByPassenger['message'][i];
// // Calculate the distance between passenger's location and current driver's location
// final distance = Geolocator.distanceBetween(
// passengerLocation.latitude,
// passengerLocation.longitude,
// double.parse(carLocation['latitude']),
// double.parse(carLocation['longitude']),
// );
// // Calculate duration assuming an average speed of 25 km/h (adjust as needed)
// int durationToPassenger =
// (distance * 25 * (1000 / 3600)).round(); // 25 km/h in m/s
// // Update the UI with the distance and duration for each car
// update();
// // If this distance is smaller than the nearest distance found so far, update nearestCar
// if (distance < nearestDistance) {
// nearestDistance = distance;
// nearestCar = CarLocation(
// distance: distance,
// duration: durationToPassenger.toDouble(),
// id: carLocation['driver_id'],
// latitude: double.parse(carLocation['latitude']),
// longitude: double.parse(carLocation['longitude']),
// );
// // Update the UI with the nearest driver
// update();
// }
// }
// // Return the nearest car found
// return nearestCar;
// }
// }
// }
// }
// // Return null if no drivers are found or if ride is confirmed
// return null;
// }
Future<CarLocation?> getNearestDriverByPassengerLocation() async {
if (!rideConfirm) {
if (dataCarsLocationByPassenger != 'failure' &&
dataCarsLocationByPassenger != null &&
dataCarsLocationByPassenger['message'] != null &&
dataCarsLocationByPassenger['message'].length > 0) {
double nearestDistance = double.infinity; // Initialize nearest distance
CarLocation? nearestCar;
for (var i = 0;
i < dataCarsLocationByPassenger['message'].length;
i++) {
var carLocation = dataCarsLocationByPassenger['message'][i];
// Log.print('carLocation: $carLocation');
try {
// Calculate distance between passenger's location and current driver's location
final distance = Geolocator.distanceBetween(
passengerLocation.latitude,
passengerLocation.longitude,
double.parse(carLocation['latitude']),
double.parse(carLocation['longitude']),
);
// Calculate duration assuming an average speed of 25 km/h (adjust as needed)
int durationToPassenger = (distance / 1000 / 25 * 3600).round();
// Log.print('distance: $distance');
// Log.print('durationToPassenger: $durationToPassenger');
// Log.print('passengerLocation: $passengerLocation');
// Log.print('carLocation: $carLocation');
// Log.print('distance: $distance meters');
// Log.print('durationToPassenger: $durationToPassenger seconds');
// Update the UI with the distance and duration for each car
update();
// If this distance is smaller than the nearest distance found so far, update nearestCar
if (distance < nearestDistance) {
nearestDistance = distance;
nearestCar = CarLocation(
distance: distance,
duration: durationToPassenger.toDouble(),
id: carLocation['driver_id'],
latitude: double.parse(carLocation['latitude']),
longitude: double.parse(carLocation['longitude']),
);
// Log.print('nearestCar: $nearestCar');
// Update the UI with the nearest driver
update();
}
} catch (e) {
Log.print('Error calculating distance/duration: $e');
}
}
// Return the nearest car found
return nearestCar;
}
}
// Return null if no drivers are found or if ride is confirmed
return null;
}
getNearestDriverByPassengerLocationAPIGOOGLE() async {
if (polyLines.isEmpty || data.isEmpty) {
return null; // Early return if data is empty
}
if (!rideConfirm) {
double nearestDistance = double.infinity;
if (dataCarsLocationByPassenger != 'failure') {
if (dataCarsLocationByPassenger['message'].length > 0) {
for (var i = 0;
i < dataCarsLocationByPassenger['message'].length;
i++) {
var carLocation = dataCarsLocationByPassenger['message'][i];
// }
// isloading = true;
update();
// Make API request to get exact distance and duration
String apiUrl =
'${AppLink.googleMapsLink}distancematrix/json?destinations=${carLocation['latitude']},${carLocation['longitude']}&origins=${passengerLocation.latitude},${passengerLocation.longitude}&units=metric&key=${AK.mapAPIKEY}';
var response = await CRUD().getGoogleApi(link: apiUrl, payload: {});
if (response != null && response['status'] == "OK") {
var data = response;
// Extract distance and duration from the response and handle accordingly
int distance1 =
data['rows'][0]['elements'][0]['distance']['value'];
distanceByPassenger =
data['rows'][0]['elements'][0]['distance']['text'];
durationToPassenger =
data['rows'][0]['elements'][0]['duration']['value'];
durationFromDriverToPassenger =
Duration(seconds: durationToPassenger.toInt());
newTime1 = currentTime.add(durationFromDriverToPassenger);
timeFromDriverToPassenger =
newTime1.add(Duration(minutes: 2.toInt()));
durationByPassenger =
data['rows'][0]['elements'][0]['duration']['text'];
update();
if (distance1 < nearestDistance) {
nearestDistance = distance1.toDouble();
nearestCar = CarLocation(
distance: distance1.toDouble(),
duration: durationToPassenger.toDouble(),
id: carLocation['driver_id'],
latitude: double.parse(carLocation['latitude']),
longitude: double.parse(carLocation['longitude']),
);
// isloading = false;
update();
}
}
// Handle the distance and duration as needed
else {
// 'Failed to retrieve distance and duration: ${response['status']}');
Log.print('${response['status']}: ${response['status']}}');
// Handle the failure case
}
}
}
}
}
}
calculateDistanceBetweenPassengerAndDriverBeforeCancelRide() async {
await getDriverCarsLocationToPassengerAfterApplied();
double distance = Geolocator.distanceBetween(
passengerLocation.latitude,
passengerLocation.longitude,
driverCarsLocationToPassengerAfterApplied.last.latitude,
driverCarsLocationToPassengerAfterApplied.last.longitude,
);
if (distance > 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<PaymentController>()
.payToDriverForCancelAfterAppliedAndHeNearYou(rideId);
// isCancelRidePageShown = true;
// update();
},
);
},
),
],
),
);
// cancel: MyElevatedButton(
// title: 'No.Iwant Cancel Trip.'.tr, onPressed: () {}));
}
}
List<double> headingAngles = [];
double calculateAngleBetweenLocations(LatLng start, LatLng end) {
double startLat = start.latitude * math.pi / 180;
double startLon = start.longitude * math.pi / 180;
double endLat = end.latitude * math.pi / 180;
double endLon = end.longitude * math.pi / 180;
double dLon = endLon - startLon;
double y = math.sin(dLon) * cos(endLat);
double x = cos(startLat) * math.sin(endLat) -
math.sin(startLat) * cos(endLat) * cos(dLon);
double angle = math.atan2(y, x);
double angleDegrees = angle * 180 / math.pi;
return angleDegrees;
}
late LatLngBounds boundsData;
late String startNameAddress = '';
late String endNameAddress = '';
List<Map<String, dynamic>> stopPoints = [];
void removeStop(Map<String, dynamic> stop) {
stopPoints.remove(stop);
update(); // Trigger a rebuild of the UI
}
// [تعديل] نحتاج هذه المكتبات لإرسال طلب HTTP مع هيدر
// ... (باقي imports الخاصة بك)
// داخل MapPassengerController
// Future<String> getReverseGeocoding(LatLng location) async {
// final lat = location.latitude;
// final lon = location.longitude;
// // بناء رابط الـ Reverse Geocoding
// final url =
// 'https://geocode.intaleq.xyz/reverse_geocode.php?lat=$lat&lon=$lon';
// try {
// // استخدام دالة CRUD للطلب (نفترض أن CRUD تستخدم http.get أو ما شابه)
// final response = await CRUD().get(link: url, payload: {});
// if (response != 'failure') {
// final data = jsonDecode(response);
// // if (data['status'] == 'ok') {
// // نفضل استخدام display_name أو neighbourhood إذا كان متاحًا
// String name = data['display_name'] ??
// data['neighbourhood'] ??
// 'Unknown Location'.tr;
// return name;
// // }
// }
// } catch (e) {
// Log.print('Error in Reverse Geocoding: $e');
// }
// // العودة لقيمة افتراضية في حالة الفشل
// return 'Unknown Location'.tr;
// }
Future<String> getReverseGeocoding(LatLng location) async {
final lat = location.latitude;
final lon = location.longitude;
final url =
'https://geocode.intaleq.xyz/reverse_geocode.php?lat=$lat&lon=$lon';
try {
final response = await http.get(Uri.parse(url));
// Log raw response for debugging
// Check if the request was successful
if (response.statusCode == 200) {
String body = response.body.trim();
// Validate it's actual JSON
if (body.startsWith('{') && body.endsWith('}')) {
final data = jsonDecode(body);
// Handle case where API returns {"status":"ok", ...}
if (data is Map && data['status'] == 'ok') {
String? name = data['display_name'] ?? data['neighbourhood'];
return name ?? 'Unknown Location'.tr;
} else {
// API returned JSON but not with "status: ok"
return 'Unknown Location'.tr;
}
} else {
// Not valid JSON (e.g., just "ok" or error message)
print('[WARNING] Invalid JSON: $body');
return 'Unknown Location'.tr;
}
} else {
print('[ERROR] HTTP ${response.statusCode}: ${response.body}');
return 'Unknown Location'.tr;
}
} catch (e) {
print('Error in Reverse Geocoding: $e');
return 'Unknown Location'.tr;
}
}
bool isDrawingRoute = false;
showDrawingBottomSheet() {
Get.bottomSheet(
Container(
width: double.infinity,
padding: const EdgeInsets.all(20),
decoration: BoxDecoration(
color: Colors.white,
borderRadius: const BorderRadius.only(
topLeft: Radius.circular(20),
topRight: Radius.circular(20),
),
),
child: Column(
mainAxisSize: MainAxisSize.min,
children: [
CircularProgressIndicator(
color: AppColor.primaryColor,
),
const SizedBox(height: 15),
Text(
'Drawing route on map...'.tr,
style: AppStyle.title.copyWith(fontSize: 16),
),
const SizedBox(height: 5),
Text(
'Please wait while we prepare your trip.'.tr,
style: TextStyle(color: Colors.grey[600]),
),
],
),
),
isDismissible: false,
enableDrag: false,
);
}
String dynamicApiUrl = 'https://routec.intaleq.xyz/route';
Future<void> getDistanceFromDriverAfterAcceptedRide(
String origin, String destination) async {
String apiKey = Env.mapKeyOsm; // مفتاح API الخاص بك
if (origin.isEmpty) {
origin =
'${passengerLocation.latitude.toString().split(',')[0]},${passengerLocation.longitude.toString().split(',')[1]}';
}
// 2. بناء الرابط (URI)
// Waypoints غير مدعومة حالياً في OSRM، لذلك تم تجاهلها
var uri = Uri.parse(
'$dynamicApiUrl?origin=$origin&destination=$destination&steps=false&overview=false');
Log.print('uri: ${uri}');
// 3. إرسال الطلب مع الهيدر
http.Response response;
Map<String, dynamic> responseData;
try {
response = await http.get(
uri,
headers: {
'X-API-KEY': apiKey,
},
).timeout(const Duration(seconds: 20)); // تايم آوت 20 ثانية
if (response.statusCode != 200) {
print('Error from API: ${response.statusCode}');
isLoading = false;
update();
return; // خروج في حالة الخطأ
}
if (Get.isBottomSheetOpen ?? false) {
Get.back(); // لإغلاق شاشة "جاري الرسم"
}
isDrawingRoute = false; // Reset state
responseData = json.decode(response.body);
Log.print('responseData: ${responseData}');
if (responseData['status'] != 'ok') {
print('API returned an error: ${responseData['message']}');
isLoading = false;
update();
return; // خروج في حالة خطأ منطقي (مثل "no path")
}
} catch (e) {
print('Failed to get directions: $e');
isLoading = false;
update();
return; // خروج عند فشل الاتصال
}
}
// (b = 1.5348) هو المعامل الذي تم حسابه من مقارنة 60 رحلة بين Google و OSRM
double kDurationScalar =
1.5348; //this from colab 60 random locations from google and routec
// LatLng endPoint=LatLng(33, 36);
Future<void> getDirectionMap(String origin, String destination,
[List<String> waypoints = const []]) async {
isLoading = true;
isDrawingRoute = true;
update();
if (isDrawingRoute) showDrawingBottomSheet();
// --- هذا الجزء يبقى كما هو (تحميل السيارات، الخ) ---
remainingTime = 25;
await getCarsLocationByPassengerAndReloadMarker(
box.read(BoxName.carType), 5000);
var coordDestination = destination.split(',');
double latPassengerDestination = double.parse(coordDestination[0]);
double lngPassengerDestination = double.parse(coordDestination[1]);
myDestination = LatLng(latPassengerDestination, lngPassengerDestination);
if (origin.isEmpty) {
origin =
'${passengerLocation.latitude.toString().split(',')[0]},${passengerLocation.longitude.toString().split(',')[1]}';
}
isLoading = false;
update();
// --- [تعديل] بناء الرابط والاتصال بالـ API الجديد ---
// 1. تعريف الرابط والمفتاح
// final String countryCode =
// box.read(BoxName.countryCode)?.toString() ?? 'Jordan';
// تحديد رابط API المناسب
String dynamicApiUrl;
dynamicApiUrl = 'https://routec.intaleq.xyz/route';
String apiKey = Env.mapKeyOsm; // مفتاح API الخاص بك
// 2. بناء الرابط (URI)
// Waypoints غير مدعومة حالياً في OSRM، لذلك تم تجاهلها
var uri = Uri.parse(
'$dynamicApiUrl?origin=$origin&destination=$destination&steps=false&overview=full');
Log.print('uri: ${uri}');
// 3. إرسال الطلب مع الهيدر
http.Response response;
Map<String, dynamic> responseData;
try {
response = await http.get(
uri,
headers: {
'X-API-KEY': apiKey,
},
).timeout(const Duration(seconds: 20)); // تايم آوت 20 ثانية
if (response.statusCode != 200) {
print('Error from API: ${response.statusCode}');
isLoading = false;
update();
return; // خروج في حالة الخطأ
}
responseData = json.decode(response.body);
Log.print('responseData: ${responseData}');
if (responseData['status'] != 'ok') {
print('API returned an error: ${responseData['message']}');
isLoading = false;
update();
return; // خروج في حالة خطأ منطقي (مثل "no path")
}
} catch (e) {
print('Failed to get directions: $e');
isLoading = false;
update();
return; // خروج عند فشل الاتصال
}
// --- نهاية جزء [تعديل] الاتصال ---
// --- [تعديل] قراءة البيانات من الـ API الجديد ---
// الـ API الجديد لا يرسل 'legs' أو 'routes'، البيانات مباشرة
box.remove(BoxName.tripData);
box.write(
BoxName.tripData, polylineCoordinate); // [تعديل] حفظ الاستجابة الجديدة
isBottomSheetShown = true;
// [تعديل] قراءة البولي لاين والزمن والمسافة
// durationToRide = ((responseData['duration_s'] as num) * 1.4).toInt();
durationToRide =
((responseData['duration_s'] as num) * kDurationScalar).toInt();
Log.print('durationToRide: ${durationToRide}');
final String pointsString = responseData['polyline'];
double distanceOfTrip = (responseData['distance_m'] as num) / 1000.0;
Log.print('distanceOfTrip: ${distanceOfTrip}');
distance = distanceOfTrip;
Log.print('distance: ${distance}');
Log.print('distance: ${distance.toString()}');
// ... داخل getDirectionMap, بعد التحقق من responseData['status'] == 'ok' ...
data = responseData['steps'];
// ... أكمل باقي الكود ...
List<LatLng> decodedPoints =
await compute(decodePolylineIsolate, pointsString);
// مسح الإحداثيات القديمة وإضافة الجديدة
polylineCoordinates.clear();
// box.remove(BoxName.tripData);
for (int i = 0; i < decodedPoints.length; i++) {
polylineCoordinates.add(decodedPoints[i]);
}
box.write(BoxName.tripData, responseData);
if (polylineCoordinates.isEmpty) {
print("Polyline is empty!");
return;
}
final LatLng startLoc = decodedPoints.first;
final LatLng endLoc = decodedPoints.last;
try {
final results = await Future.wait([
getReverseGeocoding(startLoc), // الطلب الأول (index 0)
getReverseGeocoding(endLoc), // الطلب الثاني (index 1)
]);
startNameAddress = results[0];
endNameAddress = results[1];
} catch (e) {
// في حال حدث خطأ في Future.wait
print("Error in parallel reverse geocoding: $e");
startNameAddress = 'Unknown Location'.tr;
endNameAddress = 'Unknown Location'.tr;
}
// [تعديل] الـ API الجديد لا يرسل 'start_location' أو 'bounds'
// لذلك سنقوم باشتقاقها مباشرة من البولي لاين
if (Get.isBottomSheetOpen ?? false) {
Get.back(); // لإغلاق شاشة "جاري الرسم"
}
isDrawingRoute = false; // Reset state
// 1. تحديد نقطة البداية والنهاية من البولي لاين
newStartPointLocation = polylineCoordinates.first;
LatLng endLocation = polylineCoordinates.last;
// 2. إنشاء 'bounds' يدوياً ليناسب الكاميرا
// Find the northeast and southwest points from polyline coordinates
double? minLat, maxLat, minLng, maxLng;
for (LatLng point in 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);
}
LatLngBounds boundsData = LatLngBounds(
northeast: LatLng(maxLat!, maxLng!),
southwest: LatLng(minLat!, minLng!));
// 3. تحديث الكاميرا (نفس الكود القديم، سيعمل الآن)
var cameraUpdate = CameraUpdate.newLatLngBounds(boundsData, 130);
mapController!.animateCamera(cameraUpdate);
// --- باقي الكود (إضافة العلامات ورسم الخطوط) يبقى كما هو تقريباً ---
durationToAdd = Duration(seconds: durationToRide);
hours = durationToAdd.inHours;
minutes = (durationToAdd.inMinutes % 60).round();
markers.clear();
update();
markers.add(
Marker(
markerId: const MarkerId('start'),
position: newStartPointLocation, // [تعديل] نستخدم النقطة من البولي لاين
icon: startIcon,
infoWindow: InfoWindow(
title: startNameAddress,
snippet: '',
),
),
);
// Add end marker
markers.add(
Marker(
markerId: const MarkerId('end'),
position: endLocation, // [تعديل] نستخدم النقطة من البولي لاين
icon: endIcon,
infoWindow: InfoWindow(
title: endNameAddress,
snippet:
'$distance ${'KM'.tr}${hours > 0 ? '${'Your Ride Duration is '.tr}$hours ${'H and'.tr} $minutes ${'m'.tr}' : '${'Your Ride Duration is '.tr} $minutes ${'m'.tr}'}'),
),
);
if (polyLines.isNotEmpty) {
clearPolyline();
} else {
bool lowEndMode = box.read(BoxName.lowEndMode) ?? false;
if (Platform.isIOS) {
animatePolylineLayered(
polylineCoordinates,
maxPoints: lowEndMode ? 30 : 150,
);
} else {
polyLines.add(Polyline(
polylineId: const PolylineId('route'),
points: polylineCoordinates,
width: 6,
color: AppColor.primaryColor,
endCap: Cap.roundCap,
startCap: Cap.roundCap,
jointType: JointType.round,
));
}
rideConfirm = false;
isMarkersShown = true;
update();
}
}
// getDirectionMap(String origin, destination,
// [List<String> waypoints = const []]) async {
// isLoading = true;
// update();
// remainingTime = 25; //to make cancel every call
// // startCarLocationSearch(box.read(BoxName.carType));
// await getCarsLocationByPassengerAndReloadMarker(
// box.read(BoxName.carType), 5000);
// // await getCarsLocationByPassengerAndReloadMarker();
// var coordDestination = destination.split(',');
// double latPassengerDestination = double.parse(coordDestination[0]);
// double lngPassengerDestination = double.parse(coordDestination[1]);
// myDestination = LatLng(latPassengerDestination, lngPassengerDestination);
// if (origin.isEmpty) {
// origin =
// '${passengerLocation.latitude.toString().split(',')[0]},${passengerLocation.longitude.toString().split(',')[1]}'; //todo
// }
// isLoading = false;
// update();
// var url =
// ('${AppLink.googleMapsLink}directions/json?&language=${box.read(BoxName.lang) ?? 'ar'}&avoid=tolls|ferries&destination=$destination&origin=$origin&key=${AK.mapAPIKEY}');
// if (waypoints.isNotEmpty) {
// String formattedWaypoints = waypoints.join('|');
// url += '&waypoints=$formattedWaypoints';
// }
// var response = await CRUD().getGoogleApi(link: url, payload: {});
// data = response['routes'][0]['legs'];
// box.remove(BoxName.tripData);
// box.write(BoxName.tripData, response);
// startNameAddress = shortenAddress(data[0]['start_address']);
// // print('data[0][start_address]: ${data[0]['start_address']}');
// endNameAddress = shortenAddress(data[0]['end_address']);
// isLoading = false;
// newStartPointLocation = LatLng(
// data[0]["start_location"]['lat'], data[0]["start_location"]['lng']);
// durationToRide = data[0]['duration']['value'];
// final String pointsString =
// response['routes'][0]["overview_polyline"]["points"];
// List<LatLng> decodedPoints =
// await compute(decodePolylineIsolate, pointsString);
// // decodePolyline(response["routes"][0]["overview_polyline"]["points"]);
// for (int i = 0; i < decodedPoints.length; i++) {
// // double lat = decodedPoints[i][0].toDouble();
// // double lng = decodedPoints[i][1].toDouble();
// polylineCoordinates.add(decodedPoints[i]);
// }
// // Define the northeast and southwest coordinates
// // Define the northeast and southwest coordinates
// final bounds = response["routes"][0]["bounds"];
// LatLng northeast =
// LatLng(bounds['northeast']['lat'], bounds['northeast']['lng']);
// LatLng southwest =
// LatLng(bounds['southwest']['lat'], bounds['southwest']['lng']);
// // Create the LatLngBounds object
// LatLngBounds boundsData =
// LatLngBounds(northeast: northeast, southwest: southwest);
// // Fit the camera to the bounds
// var cameraUpdate = CameraUpdate.newLatLngBounds(boundsData, 130);
// mapController!.animateCamera(cameraUpdate);
// // getDistanceFromText(data[0]['distance']['text']);
// double distanceOfTrip = (data[0]['distance']['value']) / 1000;
// distance = distanceOfTrip;
// durationToAdd = Duration(seconds: durationToRide);
// hours = durationToAdd.inHours;
// minutes = (durationToAdd.inMinutes % 60).round();
// // updateCameraForDistanceAfterGetMap();
// markers.clear();
// update();
// markers.add(
// Marker(
// markerId: const MarkerId('start'),
// position: newStartPointLocation,
// icon: startIcon,
// infoWindow: InfoWindow(
// title: startNameAddress,
// snippet: '',
// ),
// ),
// );
// // Add end marker
// markers.add(
// Marker(
// markerId: const MarkerId('end'),
// position: LatLng(
// data[0]["end_location"]['lat'], data[0]["end_location"]['lng']),
// icon: endIcon,
// infoWindow: InfoWindow(
// title: endNameAddress,
// snippet:
// '$distance ${'KM'.tr} ⌛ ${hours > 0 ? '${'Your Ride Duration is '.tr}$hours ${'H and'.tr} $minutes ${'m'.tr}' : '${'Your Ride Duration is '.tr} $minutes ${'m'.tr}'}'),
// ),
// );
// // // Show info windows automatically
// // Future.delayed(const Duration(milliseconds: 500), () {
// // mapController?.showMarkerInfoWindow(const MarkerId('start'));
// // });
// // Future.delayed(const Duration(milliseconds: 500), () {
// // mapController?.showMarkerInfoWindow(const MarkerId('end'));
// // });
// // update();
// if (polyLines.isNotEmpty) {
// clearPolyline();
// } else {
// // الآن نقرأ القيمة ونحدد عدد النقاط بناءً عليها
// bool lowEndMode = box.read(BoxName.lowEndMode) ??
// false; // الأفضل أن يكون الافتراضي هو الجودة العالية
// // نمرر عدد النقاط المناسب هنا
// if (Platform.isIOS) {
// animatePolylineLayered(
// polylineCoordinates,
// maxPoints:
// lowEndMode ? 30 : 150, // 30 نقطة لوضع الأداء، 150 للوضع العادي
// );
// } else {
// polyLines.add(Polyline(
// polylineId: const PolylineId('route'),
// points: polylineCoordinates,
// width: 6,
// color: AppColor.primaryColor,
// endCap: Cap.roundCap,
// startCap: Cap.roundCap,
// jointType: JointType.round,
// ));
// }
// rideConfirm = false;
// isMarkersShown = true;
// update();
// }
// }
// 1) تقليل النقاط إلى حد أقصى 30 نقطة (مع بداية ونهاية محفوظة وتوزيع متساوٍ)
List<LatLng> _downsampleEven(List<LatLng> coords, {int maxPoints = 30}) {
if (coords.isEmpty) return const [];
if (coords.length <= maxPoints) return List<LatLng>.from(coords);
final int n = coords.length;
final int keep = maxPoints.clamp(2, n);
final List<int> idx = [];
for (int i = 0; i < keep; i++) {
final double pos = i * (n - 1) / (keep - 1);
idx.add(pos.round());
}
final seen = <int>{};
final List<LatLng> out = [];
for (final i in idx) {
if (seen.add(i)) out.add(coords[i]);
}
if (out.first != coords.first) out.insert(0, coords.first);
if (out.last != coords.last) out.add(coords.last);
while (out.length > maxPoints) {
out.removeAt(out.length ~/ 2);
}
return out;
}
// 2) رسم متدرّج بطبقات متراكبة (بدون حذف)، برونزي ↔ أخضر، مع zIndex وعرض مختلف
Future<void> animatePolylineLayered(List<LatLng> coordinates,
{int layersCount = 8, int stepDelayMs = 10, int maxPoints = 160}) async {
// امسح أي طبقات قديمة فقط الخاصة بالطريق
polyLines.removeWhere((p) => p.polylineId.value.startsWith('route_layer_'));
update();
final List<LatLng> coords =
_downsampleEven(coordinates, maxPoints: maxPoints);
if (coords.length < 2) return;
// ألوان مع شفافية خفيفة للتمييز
Color bronze([int alpha = 220]) => AppColor.gold;
Color green([int alpha = 220]) => AppColor.primaryColor;
Color _layerColor(int layer) => (layer % 2 == 0) ? bronze() : green();
// عرض الخط: البرونزي أعرض، الأخضر أنحف
int _layerWidth(int layer) => (layer % 2 == 0) ? 6 : 4;
// لكل طبقة: أنشئ Polyline بهوية فريدة وزي إندكس أعلى من السابقة
for (int layer = 0; layer < layersCount; layer++) {
final id = PolylineId('route_layer_$layer');
polyLines.add(Polyline(
polylineId: id,
points: const [],
width: _layerWidth(layer),
color: _layerColor(layer),
zIndex: layer, // مهم لإظهار جميع الطبقات
endCap: Cap.roundCap,
startCap: Cap.roundCap,
geodesic: true,
visible: true,
));
}
update();
// نبني كل طبقة تدريجيًا فوق التي قبلها — بدون مسح الطبقات السابقة
for (int layer = 0; layer < layersCount; layer++) {
final id = PolylineId('route_layer_$layer');
final List<LatLng> growing = [];
for (int i = 0; i < coords.length; i++) {
growing.add(coords[i]);
// حدّث فقط هذه الطبقة
polyLines.removeWhere((p) => p.polylineId == id);
polyLines.add(Polyline(
polylineId: id,
points: List<LatLng>.from(growing),
width: _layerWidth(layer),
color: _layerColor(layer),
zIndex: layer,
endCap: Cap.roundCap,
startCap: Cap.roundCap,
geodesic: true,
visible: true,
));
update();
await Future.delayed(Duration(milliseconds: stepDelayMs));
}
// مهلة خفيفة بين الطبقات عشان يبان التبديل
await Future.delayed(const Duration(milliseconds: 120));
}
}
String shortenAddress(String fullAddress) {
// Split the address into parts
List<String> parts = fullAddress.split('،');
// Remove any leading or trailing whitespace from each part
parts = parts.map((part) => part.trim()).toList();
// Remove any empty parts
parts = parts.where((part) => part.isNotEmpty).toList();
// Initialize the short address
String shortAddress = '';
if (parts.isNotEmpty) {
// Add the first part (usually the most specific location)
shortAddress += parts[0];
}
if (parts.length > 2) {
// Add the district or area name (usually the third part in Arabic format)
shortAddress += '، ${parts[2]}';
} else if (parts.length > 1) {
// Add the second part for English or shorter addresses
shortAddress += '، ${parts[1]}';
}
// Add the country (usually the last part)
if (parts.length > 1) {
shortAddress += '، ${parts.last}';
}
// Remove any part that's just numbers (like postal codes)
shortAddress = shortAddress
.split('،')
.where((part) => !RegExp(r'^[0-9 ]+$').hasMatch(part.trim()))
.join('،');
// Check if the address is in English
bool isEnglish =
RegExp(r'^[a-zA-Z0-9 ]+$').hasMatch(shortAddress.replaceAll('،', ''));
if (isEnglish) {
// Further processing for English addresses
List<String> 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;
late LatLng latestPosition;
getMapPoints(String originSteps, String destinationSteps, int index) async {
isWayPointStopsSheetUtilGetMap = false;
// haveSteps = true;
// startCarLocationSearch(box.read(BoxName.carType));
await getCarsLocationByPassengerAndReloadMarker(
box.read(BoxName.carType), 7000);
// await getCarsLocationByPassengerAndReloadMarker();
// isLoading = true;
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'];
// isLoading = false;
int durationToRide0 = data[0]['duration']['value'];
durationToRide = durationToRide + durationToRide0;
distance = distanceOfDestination + (data[0]['distance']['value']) / 1000;
update();
// final points =
// decodePolyline(response["routes"][0]["overview_polyline"]["points"]);
final String pointsString =
response['routes'][0]["overview_polyline"]["points"];
List<LatLng> decodedPoints =
await compute(decodePolylineIsolate, pointsString);
// decodePolyline(response["routes"][0]["overview_polyline"]["points"]);
for (int i = 0; i < decodedPoints.length; i++) {
polylineCoordinates.add(decodedPoints[i]);
}
// Define the northeast and southwest coordinates
if (polyLines.isNotEmpty) {
// clearPolyline();
} else {
var polyline = Polyline(
polylineId: PolylineId(response["routes"][0]["summary"]),
points: polylineCoordinatesPointsAll[index],
width: 10,
color: Colors.blue,
);
polyLines.add(polyline);
rideConfirm = false;
// isMarkersShown = true;
update();
}
}
void updateCameraForDistanceAfterGetMap() {
LatLng coord1 = LatLng(
double.parse(coordinatesWithoutEmpty.first.split(',')[0]),
double.parse(coordinatesWithoutEmpty.first.split(',')[1]));
LatLng coord2 = LatLng(
double.parse(coordinatesWithoutEmpty.last.split(',')[0]),
double.parse(coordinatesWithoutEmpty.last.split(',')[1]));
LatLng northeast;
LatLng southwest;
if (coord1.latitude > coord2.latitude) {
northeast = coord1;
southwest = coord2;
} else {
northeast = coord2;
southwest = coord1;
}
// Create the LatLngBounds object
LatLngBounds bounds =
LatLngBounds(northeast: northeast, southwest: southwest);
// Fit the camera to the bounds
var cameraUpdate = CameraUpdate.newLatLngBounds(bounds, 180);
mapController!.animateCamera(cameraUpdate);
update();
}
int selectedIndex = -1; // Initialize with no selection
void selectCarFromList(int index) {
selectedIndex = index; // Update selected index
carTypes.forEach(
(element) => element.isSelected = false); // Reset selection flags
carTypes[index].isSelected = true;
update();
}
showBottomSheet1() async {
await bottomSheet();
isBottomSheetShown = true;
heightBottomSheetShown = 250;
update();
}
final promo = TextEditingController();
bool promoTaken = false;
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;
// العتبات بالليرة السورية
const double minPromoLowSYP = 17200; // Speed / Balash
const double minPromoHighSYP = 20000; // Comfort / Electric / Lady
try {
final value = await CRUD().get(
link: AppLink.getPassengersPromo,
payload: {'promo_code': promo.text},
);
if (value == 'failure') {
MyDialog().getDialog(
'Promo Ended'.tr,
'The promotion period has ended.'.tr,
() => Get.back(),
);
return;
}
// هل يوجد فئة مؤهلة أصلاً قبل الخصم؟
final bool eligibleNow = (totalPassengerSpeed >= minPromoLowSYP) ||
(totalPassengerBalash >= minPromoLowSYP) ||
(totalPassengerComfort >= minPromoHighSYP) ||
(totalPassengerElectric >= minPromoHighSYP) ||
(totalPassengerLady >= minPromoHighSYP);
if (!eligibleNow) {
Get.snackbar(
'Lowest Price Achieved'.tr,
'Cannot apply further discounts.'.tr,
backgroundColor: AppColor.yellowColor,
);
return;
}
final decode = jsonDecode(value);
if (decode["status"] != "success") {
MyDialog().getDialog(
'Promo Ended'.tr,
'The promotion period has ended.'.tr,
() => Get.back(),
);
return;
}
Get.snackbar('Promo Code Accepted'.tr, '',
backgroundColor: AppColor.greenColor);
final firstElement = decode["message"][0];
final int discountPercentage =
int.tryParse(firstElement['amount'].toString()) ?? 0;
// قيمة المحفظة - قد تكون سالبة
final double walletVal = double.tryParse(
box.read(BoxName.passengerWalletTotal)?.toString() ?? '0') ??
0.0;
final bool isWalletNegative = walletVal < 0;
// --------------------------
// دالة تُطبّق الخصم دون النزول تحت الحد الأدنى
// --------------------------
double _applyDiscountPerTier({
required double fare,
required double minThreshold,
required bool isWalletNegative,
}) {
if (fare < minThreshold) return fare; // غير مؤهل أصلاً
final double discount = fare * (discountPercentage / 100.0);
double result;
if (isWalletNegative) {
double neg = (-1) * walletVal; // walletVal < 0 => neg positive
result = fare + neg - discount;
} else {
result = fare - discount;
}
// لا نسمح بالنزول دون الحد الأدنى
if (result < minThreshold) {
result = minThreshold;
}
// ولا نسمح بمبلغ سالب
return result.clamp(0.0, double.infinity);
}
// Comfort
totalPassengerComfort = _applyDiscountPerTier(
fare: totalPassengerComfort,
minThreshold: minPromoHighSYP,
isWalletNegative: isWalletNegative,
);
// Electric
totalPassengerElectric = _applyDiscountPerTier(
fare: totalPassengerElectric,
minThreshold: minPromoHighSYP,
isWalletNegative: isWalletNegative,
);
// Lady
totalPassengerLady = _applyDiscountPerTier(
fare: totalPassengerLady,
minThreshold: minPromoHighSYP,
isWalletNegative: isWalletNegative,
);
// Speed
totalPassengerSpeed = _applyDiscountPerTier(
fare: totalPassengerSpeed,
minThreshold: minPromoLowSYP,
isWalletNegative: isWalletNegative,
);
// Balash
totalPassengerBalash = _applyDiscountPerTier(
fare: totalPassengerBalash,
minThreshold: minPromoLowSYP,
isWalletNegative: isWalletNegative,
);
// تعديل دخل السائق وفق نسبة الخصم
totalDriver = totalDriver - (totalDriver * discountPercentage / 100.0);
promoTaken = true;
update();
// مؤثرات
Confetti.launch(
context,
options: const ConfettiOptions(particleCount: 100, spread: 70, y: 0.6),
);
Get.back();
await Future.delayed(const Duration(milliseconds: 120));
} catch (e) {
Get.snackbar('Error'.tr, e.toString(),
backgroundColor: AppColor.redColor);
}
}
double getDistanceFromText(String distanceText) {
// Remove any non-digit characters from the distance text
String distanceValue = distanceText.replaceAll(RegExp(r'[^0-9.]+'), '');
// Parse the extracted numerical value as a double
double distance = double.parse(distanceValue);
return distance;
}
double costForDriver = 0;
double totalPassengerSpeed = 0;
double totalPassengerBalash = 0;
double totalPassengerElectric = 0;
double totalPassengerLady = 0;
double totalPassengerRayehGai = 0;
double totalPassengerRayehGaiComfort = 0;
double totalPassengerRayehGaiBalash = 0;
Future bottomSheet() async {
// if (data.isEmpty) return;
// === إعدادات عامة ===
const double minFareSYP = 16000; // حد أدنى
const double minBillableKm = 0.3; // حد أدنى للمسافة المفوترة
const double ladyFlatAddon = 2000; // إضافة ثابتة لـ Lady
const double airportAddonSYP = 20000; // إضافة المطار
// --- ⬇️ الإضافة الجديدة: إضافة حدود مطار دمشق ⬇️ ---
const double damascusAirportBoundAddon = 140000; // إضافة المطار (حدود)
// --- ⬆️ نهاية الإضافة ⬆️ ---
// كهرباء
const double electricPerKmUplift = 400; // زيادة/كم
const double electricFlatAddon = 1000; // زيادة ثابتة
// Long Speed
const double longSpeedThresholdKm = 40.0;
const double longSpeedPerKm = 2600.0; // Speed عند >40كم
// قواعد الرحلات البعيدة للدقائق (تعمل لكل الأوقات)
const double mediumDistThresholdKm = 25.0; // >25كم
const double longDistThresholdKm = 35.0; // >35كم
const double longTripPerMin = 600.0;
const int minuteCapMedium = 60; // سقف دقائق عند >25كم
const int minuteCapLong = 80; // سقف دقائق عند >35كم
const int freeMinutesLong = 10; // عفو 10 دقائق عند >35كم
// تخفيضات المسافات الكبيرة للفئات غير Speed
const double extraReduction100 = 0.07; // +7% فوق تخفيض >40كم للرحلات >100كم
const double maxReductionCap = 0.35; // سقف 35% كحد أقصى
// ====== زمن الرحلة ======
durationToAdd = Duration(seconds: durationToRide);
hours = durationToAdd.inHours;
minutes = (durationToAdd.inMinutes % 60).round();
final DateTime currentTime = DateTime.now();
newTime = currentTime.add(durationToAdd);
averageDuration = (durationToRide / 60) / distance;
final int totalMinutes = (durationToRide / 60).floor();
// ====== أدوات مساعدة ======
bool _isAirport(String s) {
final t = s.toLowerCase();
return t.contains('airport') ||
s.contains('مطار') ||
s.contains('المطار');
}
bool _isClub(String s) {
final t = s.toLowerCase();
return t.contains('club') ||
t.contains('nightclub') ||
t.contains('night club') ||
s.contains('ديسكو') ||
s.contains('ملهى ليلي');
}
// --- ⬇️ الإضافة الجديدة: دالة التحقق من حدود المطار ⬇️ ---
// (P1: 33.415313, 36.499687) (P2: 33.400265, 36.531505)
bool _isInsideDamascusAirportBounds(double lat, double lng) {
final double northLat = 33.415313;
final double southLat = 33.400265;
final double eastLng = 36.531505;
final double westLng = 36.499687;
// التحقق من خط العرض (بين الشمال والجنوب)
bool isLatInside = (lat <= northLat) && (lat >= southLat);
// التحقق من خط الطول (بين الشرق والغرب)
bool isLngInside = (lng <= eastLng) && (lng >= westLng);
return isLatInside && isLngInside;
}
// --- ⬆️ نهاية الإضافة ⬆️ ---
// أسعار الدقيقة من السيرفر
final double naturePerMin = naturePrice; // طبيعي
final double latePerMin = latePrice; // ليل
final double heavyPerMin = heavyPrice; // ذروة
// سعر الدقيقة حسب الوقت (أساس قبل قواعد المسافة)
double _perMinuteByTime(DateTime now, bool clubCtx) {
final h = now.hour;
if (h >= 21 || h < 1) return latePerMin; // ليل
if (h >= 1 && h < 5) return clubCtx ? (latePerMin * 2) : latePerMin;
if (h >= 14 && h <= 17) return heavyPerMin; // ذروة
return naturePerMin; // طبيعي
}
// حد أدنى
double _applyMinFare(double fare) =>
(fare < minFareSYP) ? minFareSYP : fare;
// عمولة الراكب (kazan من السيرفر)
double _withCommission(double base) =>
(base * (1 + kazan / 100)).ceilToDouble();
// ====== سياق ======
final bool airportCtx =
_isAirport(startNameAddress) || _isAirport(endNameAddress);
final bool clubCtx = _isClub(startNameAddress) || _isClub(endNameAddress);
// --- ⬇️ الإضافة الجديدة: التحقق من سياق حدود المطار ⬇️ ---
// !! ⚠️ تأكد من أن هذه هي المتغيرات الصحيحة لإحداثيات نقطة النهاية !!
final bool damascusAirportBoundCtx = _isInsideDamascusAirportBounds(
myDestination.latitude, // <-- ⚠️ غيّر هذا للمتغير الصحيح
myDestination.longitude, // <-- ⚠️ غيّر هذا للمتغير الصحيح
);
final bool isInDamascusAirportBoundCtx = _isInsideDamascusAirportBounds(
newMyLocation.latitude, // <-- ⚠️ غيّر هذا للمتغير الصحيح
newMyLocation.longitude, // <-- ⚠️ غيّر هذا للمتغير الصحيح
);
// --- ⬆️ نهاية الإضافة ⬆️ ---
// ====== مسافة مفوترة ======
final double billableDistance =
(distance < minBillableKm) ? minBillableKm : distance;
// ====== Speed (قصير/طويل) ======
final bool isLongSpeed = billableDistance > longSpeedThresholdKm;
final double perKmSpeedBaseFromServer =
speedPrice; // مثال: 2900 يأتي من السيرفر
final double perKmSpeed =
isLongSpeed ? longSpeedPerKm : perKmSpeedBaseFromServer;
// ====== تخفيضات الفئات الأخرى حسب بُعد الرحلة ======
// ... (الكود كما هو) ...
double reductionPct40 = 0.0;
if (perKmSpeedBaseFromServer > 0) {
reductionPct40 = (1.0 - (longSpeedPerKm / perKmSpeedBaseFromServer))
.clamp(0.0, maxReductionCap);
}
final double reductionPct100 =
(reductionPct40 + extraReduction100).clamp(0.0, maxReductionCap);
double distanceReduction = 0.0;
if (billableDistance > 100.0) {
distanceReduction = reductionPct100;
} else if (billableDistance > 40.0) {
distanceReduction = reductionPct40;
}
// ====== منطق الدقيقة يعمل لكل الأوقات ويتكيّف مع المسافة ======
// ... (الكود كما هو) ...
double effectivePerMin = _perMinuteByTime(currentTime, clubCtx);
int billableMinutes = totalMinutes;
if (billableDistance > longDistThresholdKm) {
effectivePerMin = longTripPerMin;
final int capped =
(billableMinutes > minuteCapLong) ? minuteCapLong : billableMinutes;
billableMinutes = capped - freeMinutesLong;
if (billableMinutes < 0) billableMinutes = 0;
} else if (billableDistance > mediumDistThresholdKm) {
effectivePerMin = longTripPerMin;
billableMinutes = (billableMinutes > minuteCapMedium)
? minuteCapMedium
: billableMinutes;
}
// ====== أسعار/كم قبل التخفيض ======
// ... (الكود كما هو) ...
final double perKmComfortRaw = comfortPrice;
final double perKmDelivery = deliveryPrice;
final double perKmVanRaw =
(familyPrice > 0 ? familyPrice : (speedPrice + 1300));
final double perKmElectricRaw = perKmComfortRaw + electricPerKmUplift;
// ====== تطبيق التخفيضات على الفئات (نفس نسبة Speed للبعيد) ======
// ... (الكود كما هو) ...
double perKmComfort = perKmComfortRaw * (1.0 - distanceReduction);
double perKmElectric = perKmElectricRaw * (1.0 - distanceReduction);
double perKmVan = perKmVanRaw * (1.0 - distanceReduction);
perKmComfort = perKmComfort.clamp(0, double.infinity);
perKmElectric = perKmElectric.clamp(0, double.infinity);
perKmVan = perKmVan.clamp(0, double.infinity);
final double perKmBalash = (perKmSpeed - 500).clamp(0, double.infinity);
// ====== دوال الاحتساب ======
double _oneWayFare({
required double perKm,
required bool isLady,
double flatAddon = 0,
}) {
double fare = billableDistance * perKm;
fare +=
billableMinutes * effectivePerMin; // دقائق بعد السقف/العفو إن وُجد
fare += flatAddon;
if (isLady) fare += ladyFlatAddon;
if (airportCtx) fare += airportAddonSYP;
// --- ⬇️ الإضافة الجديدة: تطبيق إضافة حدود المطار ⬇️ ---
if (damascusAirportBoundCtx || isInDamascusAirportBoundCtx) {
fare += damascusAirportBoundAddon;
}
// --- ⬆️ نهاية الإضافة ⬆️ ---
return _applyMinFare(fare);
}
double _roundTripFare({required double perKm}) {
// خصم 40% لمسافة إياب واحدة + زمن مضاعف (بنفس قواعد الدقيقة المعدّلة)
double distPart =
(billableDistance * 2 * perKm) - ((billableDistance * perKm) * 0.4);
double timePart = (billableMinutes * 2) * effectivePerMin;
double fare = distPart + timePart;
if (airportCtx) fare += airportAddonSYP;
// --- ⬇️ الإضافة الجديدة: تطبيق إضافة حدود المطار ⬇️ ---
// تنطبق أيضاً على رحلات الذهاب والعودة لأنها "تصل" إلى الوجهة
if (damascusAirportBoundCtx || isInDamascusAirportBoundCtx) {
fare += damascusAirportBoundAddon;
}
// --- ⬆️ نهاية الإضافة ⬆️ ---
return _applyMinFare(fare);
}
// ====== حساب كل الفئات (Base قبل العمولة) ======
final double costSpeed = _oneWayFare(perKm: perKmSpeed, isLady: false);
final double costBalash = _oneWayFare(perKm: perKmBalash, isLady: false);
final double costComfort = _oneWayFare(perKm: perKmComfort, isLady: false);
final double costElectric = _oneWayFare(
perKm: perKmElectric, isLady: false, flatAddon: electricFlatAddon);
final double costDelivery =
_oneWayFare(perKm: perKmDelivery, isLady: false);
final double costLady = _oneWayFare(
perKm: perKmComfort,
isLady: true); // Lady تعتمد Comfort بعد التخفيض + إضافة ثابتة
final double costVan = _oneWayFare(perKm: perKmVan, isLady: false);
final double costRayehGai = _roundTripFare(perKm: perKmSpeed);
final double costRayehGaiComfort = _roundTripFare(perKm: perKmComfort);
final double costRayehGaiBalash = _roundTripFare(perKm: perKmBalash);
// ====== أسعار الراكب بعد العمولة (kazan من السيرفر) ======
totalPassengerSpeed = _withCommission(costSpeed);
totalPassengerBalash = _withCommission(costBalash);
totalPassengerComfort = _withCommission(costComfort);
totalPassengerElectric = _withCommission(costElectric);
totalPassengerLady = _withCommission(costLady);
totalPassengerScooter = _withCommission(costDelivery);
totalPassengerVan = _withCommission(costVan);
totalPassengerRayehGai = _withCommission(costRayehGai);
totalPassengerRayehGaiComfort = _withCommission(costRayehGaiComfort);
totalPassengerRayehGaiBalash = _withCommission(costRayehGaiBalash);
// افتراضي للعرض
totalPassenger = totalPassengerSpeed;
totalCostPassenger = totalPassenger;
// ====== دعم رصيد محفظة سلبي ======
try {
final walletStr = box.read(BoxName.passengerWalletTotal).toString();
final walletVal = double.tryParse(walletStr) ?? 0.0;
if (walletVal < 0) {
final neg = (-1) * walletVal;
totalPassenger += neg;
totalPassengerComfort += neg;
totalPassengerElectric += neg;
totalPassengerLady += neg;
totalPassengerBalash += neg;
totalPassengerScooter += neg;
totalPassengerRayehGai += neg;
totalPassengerRayehGaiComfort += neg;
totalPassengerRayehGaiBalash += neg;
totalPassengerVan += neg;
}
} catch (_) {}
update();
changeBottomSheetShown();
}
// addToken() async {
// String fingerPrint = await DeviceHelper.getDeviceFingerprint();
// await CRUD()
// .post(link: "${AppLink.server}/ride/firebase/add.php", payload: {
// 'token': (box.read(BoxName.tokenFCM.toString())),
// 'passengerID': box.read(BoxName.passengerID).toString(),
// "fingerPrint": fingerPrint
// });
// CRUD().postWallet(
// link: "${AppLink.seferPaymentServer}/ride/firebase/add.php",
// payload: {
// 'token': (box.read(BoxName.tokenFCM.toString())),
// 'passengerID': box.read(BoxName.passengerID).toString(),
// "fingerPrint": fingerPrint
// });
// }
List<LatLng> polylineCoordinate = [];
String? cardNumber;
void readyWayPoints() {
hintTextwayPointStringAll = [
hintTextwayPoint0,
hintTextwayPoint1,
hintTextwayPoint2,
hintTextwayPoint3,
hintTextwayPoint4,
];
polylineCoordinatesPointsAll = [
polylineCoordinates0,
polylineCoordinates1,
polylineCoordinates2,
polylineCoordinates3,
polylineCoordinates4,
];
allTextEditingPlaces = [
wayPoint0Controller,
wayPoint1Controller,
wayPoint2Controller,
wayPoint3Controller,
wayPoint4Controller,
];
currentLocationToFormPlacesAll = [
currentLocationToFormPlaces0,
currentLocationToFormPlaces1,
currentLocationToFormPlaces2,
currentLocationToFormPlaces3,
currentLocationToFormPlaces4,
];
placeListResponseAll = [
wayPoint0,
wayPoint1,
wayPoint2,
wayPoint3,
wayPoint4
];
startLocationFromMapAll = [
startLocationFromMap0,
startLocationFromMap1,
startLocationFromMap2,
startLocationFromMap3,
startLocationFromMap4,
];
currentLocationStringAll = [
currentLocationString0,
currentLocationString1,
currentLocationString2,
currentLocationString3,
currentLocationString4,
];
placesCoordinate = [
placesCoordinate0,
placesCoordinate1,
placesCoordinate2,
placesCoordinate3,
placesCoordinate4,
];
update();
}
List driversForMishwari = [];
Future selectDriverAndCarForMishwariTrip() async {
// Calculate the bounds for 20km
double latitudeOffset = 0.1795; // 20km range in latitude
double longitudeOffset = 0.2074; // 20km range in longitude
// Calculate bounding box based on passenger's location
double southwestLat = passengerLocation.latitude - latitudeOffset;
double northeastLat = passengerLocation.latitude + latitudeOffset;
double southwestLon = passengerLocation.longitude - longitudeOffset;
double northeastLon = passengerLocation.longitude + longitudeOffset;
// Create the payload with calculated bounds
var payload = {
'southwestLat': southwestLat.toString(),
'northeastLat': northeastLat.toString(),
'southwestLon': southwestLon.toString(),
'northeastLon': northeastLon.toString(),
};
try {
// Fetch data from the API
var res = await CRUD().get(
link: AppLink.selectDriverAndCarForMishwariTrip, payload: payload);
if (res != 'failure') {
// Check if response is valid JSON
try {
var d = jsonDecode(res);
driversForMishwari = d['message'];
Log.print('driversForMishwari: $driversForMishwari');
update();
} catch (e) {
// Handle invalid JSON format
print("Error decoding JSON: $e");
return 'Server returned invalid data. Please try again later.';
}
} else {
return 'No driver available now, try again later. Thanks for using our app.'
.tr;
}
} catch (e) {
// Handle network or other exceptions
print("Error fetching data: $e");
return 'There was an issue connecting to the server. Please try again later.'
.tr;
}
}
final Rx<DateTime> selectedDateTime = DateTime.now().obs;
void updateDateTime(DateTime newDateTime) {
selectedDateTime.value = newDateTime;
}
Future mishwariOption() async {
isLoading = true;
update();
// add dialoug for select driver and car
await selectDriverAndCarForMishwariTrip();
Future.delayed(Duration.zero);
isLoading = false;
update();
Get.to(() => CupertinoDriverListWidget());
// changeCashConfirmPageShown();
}
var driverIdVip = '';
Future<void> saveTripData(
Map<String, dynamic> driver, DateTime tripDateTime) async {
try {
// Prepare trip data
Map<String, dynamic> tripData = {
'id': driver['driver_id'].toString(), // Ensure the id is a string
'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(), // Convert age to String
'education': driver['education'] ?? 'UnKnown', //startlocationname
'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(), // Convert year to String
'color': driver['color'],
'color_hex': driver['color_hex'],
'displacement': driver['displacement'],
'fuel': driver['fuel'],
'token': driver['token'],
'rating': driver['rating'].toString(), // Convert rating to String
'countRide':
driver['ride_count'].toString(), // Convert countRide to String
'passengerId': box.read(BoxName.passengerID),
'timeSelected': tripDateTime.toIso8601String(),
'status': 'pending',
'startNameAddress': startNameAddress.toString(),
'locationCoordinate':
'${data[0]["start_location"]['lat']},${data[0]["start_location"]['lng']}',
};
Log.print('tripData: $tripData');
// Send data to server
var response =
await CRUD().post(link: AppLink.addMishwari, payload: tripData);
// Log.print('response: $response');
if (response != 'failure') {
// Trip saved successfully
// Get.snackbar('Success'.tr, 'Trip booked successfully'.tr);
var id = response['message']['id'].toString();
await CRUD().post(
link: '${AppLink.IntaleqSyriaServer}/ride/rides/add.php',
payload: {
"start_location":
'${data[0]["start_location"]['lat']},${data[0]["start_location"]['lng']}',
"end_location":
'${data[0]["start_location"]['lat']},${data[0]["start_location"]['lng']}',
"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'];
} else {
Log.print('Unexpected response type: ${value.runtimeType}');
}
});
if (AppLink.endPoint != AppLink.IntaleqSyriaServer) {
await CRUD().post(
link: "${AppLink.endPoint}/ride/mishwari/add.php",
payload: tripData);
CRUD().post(link: '${AppLink.endPoint}/ride/rides/add.php', payload: {
"start_location":
'${data[0]["start_location"]['lat']},${data[0]["start_location"]['lng']}',
"end_location":
'${data[0]["start_location"]['lat']},${data[0]["start_location"]['lng']}',
"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',
});
}
driverIdVip = driver['driver_id'].toString();
driverId = driver['driver_id'].toString();
DateTime timeSelected = DateTime.parse(tripDateTime.toIso8601String());
Get.find<NotificationController>().scheduleNotificationsForTimeSelected(
"Your trip is scheduled".tr,
"Don't forget your ride!".tr,
"tone1",
timeSelected);
// Optionally, set up local notification or send a push notification
// await firebaseMessagesController.sendNotificationToDriverMAP(
// 'OrderVIP',
// rideId.toString(),
// (driver['token'].toString()),
// [
// 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()),
// ],
// 'order');
await NotificationService.sendNotification(
category: 'OrderVIP',
target: driver['token'].toString(),
title: 'OrderVIP'.tr,
body: '$rideId - VIP Trip',
isTopic: false, // Important: this is a token
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);
Log.print(
'previous_driver_token: ${response['previous_driver_token']}');
// firebaseMessagesController.sendNotificationToDriverMAP(
// 'Order VIP Canceld'.tr,
// 'Passenger cancel order'.tr,
// response['previous_driver_token'].toString(),
// [],
// 'cancel',
// );
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, // Important: this is a token
tone: 'cancel',
driverList: [],
);
}
// data = [];
isBottomSheetShown = false;
update();
Get.to(() => VipWaittingPage());
} else {
throw Exception('Failed to save trip');
}
} catch (e) {
// Show error message with more details for debugging
Get.snackbar('Error'.tr, 'Failed to book trip: $e'.tr,
backgroundColor: AppColor.redColor);
Log.print('Error: $e');
}
}
cancelVip(String token, tripId) async {
// firebaseMessagesController.sendNotificationToDriverMAP(
// 'Order VIP Canceld'.tr,
// 'Passenger cancel order'.tr,
// token,
// [],
// 'cancel',
// );
var res = await CRUD()
.post(link: AppLink.cancelMishwari, payload: {'id': tripId});
if (res != 'failur') {
Get.back();
mySnackbarSuccess('You canceled VIP trip'.tr);
}
}
sendToDriverAgain(String token) {
NotificationService.sendNotification(
category: 'Order VIP Canceld',
target: token.toString(),
title: 'Order VIP Canceld'.tr,
body: 'Passenger cancel order'.tr,
isTopic: false, // Important: this is a token
tone: 'cancel',
driverList: [],
);
}
initilizeGetStorage() async {
if (box.read(BoxName.addWork) == null) {
box.write(BoxName.addWork, 'addWork');
}
if (box.read(BoxName.addHome) == null) {
box.write(BoxName.addHome, 'addHome');
}
}
late List recentPlaces = [];
getFavioratePlaces0() async {
recentPlaces = await sql.getCustomQuery(
'SELECT DISTINCT latitude, longitude, name, rate FROM ${TableName.recentLocations}');
}
getFavioratePlaces() async {
recentPlaces = await sql.getCustomQuery(
'SELECT * FROM ${TableName.recentLocations} ORDER BY createdAt DESC');
// Log.print('recentPlaces: ${recentPlaces}');
}
double passengerRate = 5;
double comfortPrice = 8;
double speedPrice = 4;
double mashwariPrice = 4;
double familyPrice = 4;
double deliveryPrice = 1.2;
double minFareSYP = 16000; // حد أدنى للأجرة (سوريا)
double minBillableKm = 1.0; // حد أدنى للمسافة المفوترة
double commissionPct = 15; // عمولة التطبيق % (راكب)
getKazanPercent() async {
var res = await CRUD().get(
link: AppLink.getKazanPercent,
payload: {'country': box.read(BoxName.countryCode).toString()},
);
if (res != 'failure') {
var json = jsonDecode(res);
kazan = double.parse(json['message'][0]['kazan']);
naturePrice = double.parse(json['message'][0]['naturePrice']);
heavyPrice = double.parse(json['message'][0]['heavyPrice']);
latePrice = double.parse(json['message'][0]['latePrice']);
comfortPrice = double.parse(json['message'][0]['comfortPrice']);
speedPrice = double.parse(json['message'][0]['speedPrice']);
deliveryPrice = double.parse(json['message'][0]['deliveryPrice']);
mashwariPrice = double.parse(json['message'][0]['freePrice']);
familyPrice = double.parse(json['message'][0]['familyPrice']);
fuelPrice = double.parse(json['message'][0]['fuelPrice']);
}
}
getPassengerRate() async {
var res = await CRUD().get(
link: AppLink.getPassengerRate,
payload: {'passenger_id': box.read(BoxName.passengerID)});
if (res != 'failure') {
var message = jsonDecode(res)['message'];
if (message['rating'] == null) {
passengerRate = 5.0; // Default rating
} else {
// Safely parse the rating to double
var rating = message['rating'];
if (rating is String) {
passengerRate =
double.tryParse(rating) ?? 5.0; // Default if parsing fails
} else if (rating is num) {
passengerRate =
rating.toDouble(); // Already a number, convert to double
} else {
passengerRate = 5.0; // Default for unexpected data types
}
}
} else {
passengerRate = 5.0; // Default rating for failure
}
}
addFingerPrint() async {
String fingerPrint = await DeviceHelper.getDeviceFingerprint();
await CRUD().postWallet(link: AppLink.addFingerPrint, payload: {
'token': (box.read(BoxName.tokenFCM.toString())),
'passengerID': box.read(BoxName.passengerID).toString(),
"fingerPrint": fingerPrint
});
}
firstTimeRunToGetCoupon() async {
// Check if it's the first time and the app is installed and gift token is available
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');
// Show a full-screen modal styled as an ad
Get.dialog(
AlertDialog(
contentPadding:
EdgeInsets.zero, // Removes the padding around the content
content: SizedBox(
width: 300, // Match the width of PromoBanner
// height: 250, // Match the height of PromoBanner
child: PromoBanner(
promoCode: promo,
discountPercentage: discount,
validity: validity,
),
),
),
);
}
}
// --- دالة جديدة للاستماع ومعالجة الرابط ---
void _listenForDeepLink() {
// استمع إلى أي تغيير في الإحداثيات القادمة من الرابط
ever(_deepLinkController.deepLinkLatLng, (LatLng? latLng) {
if (latLng != null) {
print('MapPassengerController detected deep link LatLng: $latLng');
// عندما يتم استلام إحداثيات جديدة، عينها كوجهة
myDestination = latLng;
// قم بتشغيل المنطق الخاص بك لعرض المسار
// (تأكد من أن `passengerLocation` لديها قيمة أولاً)
if (passengerLocation != null) {
getDirectionMap(
'${passengerLocation.latitude},${passengerLocation.longitude}',
'${myDestination.latitude},${myDestination.longitude}');
} else {
// يمكنك إظهار رسالة للمستخدم لتمكين الموقع أولاً
print(
'Cannot process deep link route yet, passenger location is null.');
}
// إعادة تعيين القيمة إلى null لمنع التشغيل مرة أخرى عند إعادة بناء الواجهة
_deepLinkController.deepLinkLatLng.value = null;
}
});
}
@override
void onInit() async {
super.onInit();
// // --- إضافة جديدة: تهيئة وحدة التحكم في الروابط العميقة ---
Get.put(DeepLinkController(), permanent: true);
// // ----------------------------------------------------
// مرحلة 0: الضروري جداً لعرض الخريطة سريعاً
// mapAPIKEY = await storage.read(key: BoxName.mapAPIKEY);
await initilizeGetStorage(); // إعداد سريع
await _initMinimalIcons(); // start/end فقط
// await addToken(); // لو لازم للمصادقة
_listenForDeepLink();
await getLocation(); // لتحديد الكاميرا
box.write(BoxName.carType, 'yet');
box.write(BoxName.tipPercentage, '0');
await detectAndCacheDeviceTier();
// لا تُنشئ Controllers الثقيلة الآن:
Get.lazyPut<TextToSpeechController>(() => TextToSpeechController(),
fenix: true);
Get.lazyPut<FirebaseMessagesController>(() => FirebaseMessagesController(),
fenix: true);
Get.lazyPut<AudioRecorderController>(() => AudioRecorderController(),
fenix: true);
// ابدأ الخريطة الآن (الشاشة ظهرت للمستخدم)
// لاحقاً: استخدم SchedulerBinding.instance.addPostFrameCallback إذا احتجت.
// مرحلة 1: مهام ضرورية للتسعير لكن غير حرجة لظهور UI
unawaited(_stagePricingAndState());
// مرحلة 2: تحسينات/كماليات بالخلفية
unawaited(_stageNiceToHave());
// ابدأ إعادة تحميل الماركر لكن بثروتل داخلي
// startMarkerReloading(); // تأكد أنه مَخنوق التحديث (throttled)
_startMasterTimer();
}
// === Helpers ===
Future<void> _initMinimalIcons() async {
addCustomStartIcon();
addCustomEndIcon();
// أجّل باقي الأيقونات:
// addCustomCarIcon(), addCustomLadyIcon(), addCustomMotoIcon(), addCustomStepIcon()
}
Future<void> _stagePricingAndState() async {
try {
await getKazanPercent(); // أسعار السيرفر
} catch (_) {}
try {
_checkInitialRideStatus(); // تحقق من حالة الرحلة الحالية
} catch (_) {}
// لو عندك ضبط “وضع خفيف” حسب الجهاز:
_applyLowEndModeIfNeeded();
}
Future<void> _stageNiceToHave() async {
// نفّذ بالتوازي
await Future.wait([
Future(() async {
try {
getFavioratePlaces();
} catch (_) {}
}),
Future(() async {
try {
readyWayPoints();
} catch (_) {}
}),
Future(() async {
try {
addCustomPicker();
} catch (_) {}
}),
Future(() async {
try {
addCustomCarIcon();
} catch (_) {}
}),
Future(() async {
try {
addCustomLadyIcon();
} catch (_) {}
}),
Future(() async {
try {
addCustomMotoIcon();
} catch (_) {}
}),
Future(() async {
try {
addCustomStepIcon();
} catch (_) {}
}),
Future(() async {
try {
getPassengerRate();
} catch (_) {}
}),
Future(() async {
try {
firstTimeRunToGetCoupon();
} catch (_) {}
}),
]);
try {
cardNumber = await SecureStorage().readData(BoxName.cardNumber);
} catch (_) {}
}
void _applyLowEndModeIfNeeded() {
// مثال بسيط: يمكنك حفظ فلاج بنظامك (من السيرفر/الإعدادات/الكاش) لتفعيل وضع خفيف
// لاحقاً فعّل: map.trafficEnabled=false, buildingsEnabled=false, تبسيط polylines...
// controller.lowEndMode = true;
}
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()
});
}
}
class CarLocation {
final String id;
final double latitude;
final double longitude;
final double distance;
final double duration;
CarLocation({
required this.id,
required this.latitude,
required this.longitude,
this.distance = 10000,
this.duration = 10000,
});
}