2026-02-28-1
This commit is contained in:
@@ -5,6 +5,7 @@ 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:Intaleq/services/ride_live_notification.dart';
|
||||
import 'package:crypto/crypto.dart';
|
||||
import 'package:Intaleq/views/Rate/rate_captain.dart';
|
||||
import 'package:Intaleq/views/Rate/rating_driver_bottom.dart';
|
||||
@@ -44,6 +45,7 @@ import '../../main.dart';
|
||||
import '../../models/model/locations.dart';
|
||||
import '../../models/model/painter_copoun.dart';
|
||||
import '../../print.dart';
|
||||
import '../../services/ride_tracking_native.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/select_driver_mishwari.dart';
|
||||
@@ -61,6 +63,7 @@ import '../payment/payment_controller.dart';
|
||||
import 'decode_polyline_isolate.dart';
|
||||
import 'deep_link_controller.dart';
|
||||
import 'device_performance.dart';
|
||||
import 'ios_live_activity_service.dart';
|
||||
import 'vip_waitting_page.dart';
|
||||
|
||||
enum RideState {
|
||||
@@ -333,6 +336,7 @@ class MapPassengerController extends GetxController {
|
||||
// ==============================================================================
|
||||
// 1. الدالة الرئيسية لتأسيس الاتصال (تستدعى عند بدء البحث startSearchingForDriver)
|
||||
// ==============================================================================
|
||||
Timer? _heartbeatTimer;
|
||||
void initConnectionWithSocket() {
|
||||
if (isSocketConnected && socket != null) return;
|
||||
|
||||
@@ -345,21 +349,36 @@ class MapPassengerController extends GetxController {
|
||||
.setTransports(['websocket'])
|
||||
.disableAutoConnect()
|
||||
.setQuery({'id': passengerId})
|
||||
.setReconnectionAttempts(5)
|
||||
.setReconnectionDelay(2000)
|
||||
.setReconnectionAttempts(20)
|
||||
.setReconnectionDelay(2400)
|
||||
// ✅ أضف هذا السطر لحل مشكلة الـ Heartbeat مع PHPSocketIO
|
||||
.setExtraHeaders({'Connection': 'Upgrade'})
|
||||
.build(),
|
||||
);
|
||||
|
||||
socket.connect();
|
||||
|
||||
// ✅ إضافة النبضة (Heartbeat) لمنع السيرفر من قطع الاتصال
|
||||
_heartbeatTimer?.cancel(); // إيقاف أي نبضة قديمة
|
||||
_heartbeatTimer = Timer.periodic(const Duration(seconds: 25), (timer) {
|
||||
if (isSocketConnected && socket != null && socket!.connected) {
|
||||
socket!.emit('heartbeat', {'passenger_id': passengerId});
|
||||
// Log.print("💓 Socket Heartbeat sent"); // اختياري للتأكد أنه يعمل
|
||||
} else {
|
||||
timer.cancel(); // إيقاف النبضة إذا انقطع السوكيت
|
||||
}
|
||||
});
|
||||
// ✅ معالج الاتصال
|
||||
|
||||
socket.onConnect((_) {
|
||||
Log.print("✅ Socket Connected Successfully");
|
||||
isSocketConnected = true;
|
||||
_reconnectAttempts = 0; // إعادة تعيين عداد المحاولات
|
||||
_reconnectAttempts = 0;
|
||||
_startHeartbeat(); // ← أضف هذا
|
||||
update();
|
||||
});
|
||||
|
||||
// دالة منفصلة للـ heartbeat
|
||||
|
||||
// ⚠️ معالج الانقطاع
|
||||
socket.onDisconnect((_) {
|
||||
Log.print("⚠️ Socket Disconnected");
|
||||
@@ -391,6 +410,16 @@ class MapPassengerController extends GetxController {
|
||||
});
|
||||
}
|
||||
|
||||
void _startHeartbeat() {
|
||||
_heartbeatTimer?.cancel();
|
||||
_heartbeatTimer = Timer.periodic(const Duration(seconds: 25), (timer) {
|
||||
if (isSocketConnected && socket.connected) {
|
||||
socket.emit('heartbeat',
|
||||
{'passenger_id': box.read(BoxName.passengerID).toString()});
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
// دالة مساعدة
|
||||
bool _isActiveRideState() {
|
||||
return currentRideState.value == RideState.searching ||
|
||||
@@ -515,7 +544,7 @@ class MapPassengerController extends GetxController {
|
||||
// 1. تجهيز الرابط (نفس API الـ Direction)
|
||||
// نستخدم overview=full للحصول على الرسمة، و steps=false لتخفيف البيانات
|
||||
String dynamicApiUrl =
|
||||
'https://routesjo.intaleq.xyz/route/v1/driving/${driverPos.longitude},${driverPos.latitude};${passengerPos.longitude},${passengerPos.latitude}';
|
||||
'${AppLink.routesOsm}/route/v1/driving/${driverPos.longitude},${driverPos.latitude};${passengerPos.longitude},${passengerPos.latitude}';
|
||||
|
||||
var uri = Uri.parse('$dynamicApiUrl?steps=false&overview=full');
|
||||
Log.print('📍 Calculating Driver Route: $uri');
|
||||
@@ -678,13 +707,17 @@ class MapPassengerController extends GetxController {
|
||||
///
|
||||
/// تستدعى من [Socket] أو [FCM] عند قيام السائق بإلغاء الرحلة.
|
||||
/// تضمن عدم تضارب الإشعارات وتوحد تجربة المستخدم.
|
||||
void processRideCancelledByDriver(dynamic data, {String source = "Unknown"}) {
|
||||
Future<void> processRideCancelledByDriver(dynamic data,
|
||||
{String source = "Unknown"}) async {
|
||||
if (_isCancelProcessed) return;
|
||||
|
||||
_isCancelProcessed = true;
|
||||
stopAllTimers();
|
||||
if (Get.isDialogOpen == true) Get.back();
|
||||
|
||||
await RideLiveNotification.cancel();
|
||||
IosLiveActivityService.endRideActivity(); // ✅ أضف هذا السطر
|
||||
if (Get.isDialogOpen == true) Get.back();
|
||||
await RideLiveNotification.cancel();
|
||||
Get.defaultDialog(
|
||||
title: "Sorry 😔".tr, // استخدام المفتاح الإنجليزي
|
||||
titleStyle:
|
||||
@@ -726,8 +759,10 @@ class MapPassengerController extends GetxController {
|
||||
);
|
||||
}
|
||||
|
||||
void handleNoDriverFound() {
|
||||
Future<void> handleNoDriverFound() async {
|
||||
stopAllTimers();
|
||||
await RideLiveNotification.cancel();
|
||||
IosLiveActivityService.endRideActivity(); // ✅ أضف هذا السطر
|
||||
_isCancelProcessed = false;
|
||||
currentRideState.value = RideState.noRide;
|
||||
Get.offAll(() => const MapPagePassenger());
|
||||
@@ -789,7 +824,7 @@ class MapPassengerController extends GetxController {
|
||||
};
|
||||
|
||||
var response = await CRUD().post(
|
||||
link: "${AppLink.ride}/rides/retry_search_drivers.php",
|
||||
link: "${AppLink.rideServerSide}/rides/retry_search_drivers.php",
|
||||
payload: payload,
|
||||
);
|
||||
|
||||
@@ -813,11 +848,12 @@ class MapPassengerController extends GetxController {
|
||||
///
|
||||
/// يبدأ عداداً (مثلاً 90 ثانية). إذا لم يتم قبول الرحلة خلال هذه المدة،
|
||||
/// يتم إنهاء البحث واستدعاء [handleNoDriverFound].
|
||||
void startSearchingTimer() {
|
||||
Future<void> startSearchingTimer() async {
|
||||
_searchTimer?.cancel();
|
||||
int seconds = 0;
|
||||
|
||||
Log.print("⏳ Search Timer Started (90s)...");
|
||||
await RideLiveNotification.showSearching(driversStatusForSearchWindow);
|
||||
|
||||
_searchTimer = Timer.periodic(const Duration(seconds: 1), (timer) {
|
||||
seconds++;
|
||||
@@ -850,7 +886,7 @@ class MapPassengerController extends GetxController {
|
||||
/// 3. **تفعيل الواجهة:** تظهر ديالوج "السائق وصل" وتبدأ عداد الانتظار المجاني (5 دقائق).
|
||||
///
|
||||
/// * [source] : نص يوضح مصدر الاستدعاء (مثل "Socket" أو "FCM") لأغراض التتبع (Logging).
|
||||
void processDriverArrival(String source) {
|
||||
Future<void> processDriverArrival(String source) async {
|
||||
// 1. الحارس: إذا تم التنفيذ سابقاً، توقف
|
||||
if (currentRideState.value == RideState.driverArrived ||
|
||||
_isArrivalProcessed) {
|
||||
@@ -864,6 +900,7 @@ class MapPassengerController extends GetxController {
|
||||
// 2. تحديث الحالة
|
||||
currentRideState.value = RideState.driverArrived;
|
||||
statusRide = 'Arrived';
|
||||
await RideLiveNotification.showDriverArrived(driverName ?? '');
|
||||
|
||||
// 3. تشغيل واجهة الوصول والعداد
|
||||
driverArrivePassengerDialoge();
|
||||
@@ -881,8 +918,8 @@ class MapPassengerController extends GetxController {
|
||||
/// تقوم بإغلاق السوكيت، إيقاف التتبع، والانتقال لشاشة التقييم والدفع.
|
||||
///
|
||||
/// * [driverList]: قائمة البيانات [driverId, rideId, token, price].
|
||||
void processRideFinished(List<dynamic> driverList,
|
||||
{String source = "Unknown"}) {
|
||||
Future<void> processRideFinished(List<dynamic> driverList,
|
||||
{String source = "Unknown"}) async {
|
||||
// 1. الحارس: منع التكرار
|
||||
if (currentRideState.value == RideState.finished || _isFinishProcessed) {
|
||||
Log.print("✋ Ignored Finish Request from $source. Already Finished.");
|
||||
@@ -912,7 +949,8 @@ class MapPassengerController extends GetxController {
|
||||
"Please make sure not to leave any personal belongings in the car.".tr,
|
||||
'tone1',
|
||||
);
|
||||
|
||||
IosLiveActivityService.endRideActivity();
|
||||
await RideLiveNotification.cancel();
|
||||
// 5. استخراج البيانات والانتقال
|
||||
if (driverList.length >= 4) {
|
||||
String price = driverList[3].toString();
|
||||
@@ -1185,9 +1223,24 @@ class MapPassengerController extends GetxController {
|
||||
Log.print("⚠️ No Data in Payload. Fallback to API.");
|
||||
await getUpdatedRideForDriverApply(rideId);
|
||||
}
|
||||
|
||||
// أضف هذا بعد السطر الذي تستدعي فيه RideTrackingNative
|
||||
await IosLiveActivityService.startRideActivity(
|
||||
rideId: rideId,
|
||||
driverName: driverName ?? 'السائق',
|
||||
carDetails: '$make • $carColor',
|
||||
etaText: stringRemainingTimeToPassenger,
|
||||
progress: 0.0,
|
||||
);
|
||||
// إشعارات (الأسعار، الأمان...)
|
||||
_showRideStartNotifications();
|
||||
final etaText = stringRemainingTimeToPassenger; // مثال: "8 دقائق"
|
||||
final carInfo = '$make • $model • $licensePlate';
|
||||
|
||||
await RideLiveNotification.showDriverOnWay(
|
||||
driverName: driverName,
|
||||
etaText: etaText,
|
||||
carInfo: carInfo,
|
||||
);
|
||||
|
||||
update(); // تحديث الواجهة لإظهار بيانات السائق
|
||||
|
||||
@@ -1206,6 +1259,24 @@ class MapPassengerController extends GetxController {
|
||||
// ج) تشغيل التايمر المحلي (للعد التنازلي فقط)
|
||||
startTimerFromDriverToPassengerAfterApplied();
|
||||
}
|
||||
final int timeToPassengerSeconds =
|
||||
timeToPassengerFromDriverAfterApplied; // مثلاً من السيرفر
|
||||
final double distanceDriverToPassengerMeters =
|
||||
double.parse(distanceByPassenger);
|
||||
await RideTrackingNative.updateRideTracking(
|
||||
driverName: driverName,
|
||||
driverPhone: driverPhone,
|
||||
carDetails: '$make • $carColor • $licensePlate',
|
||||
driverLat: driverCarsLocationToPassengerAfterApplied.last.latitude,
|
||||
driverLng: driverCarsLocationToPassengerAfterApplied.last.longitude,
|
||||
passengerLat: passengerLocation.latitude,
|
||||
passengerLng: passengerLocation.longitude,
|
||||
destLat: myDestination.latitude,
|
||||
destLng: myDestination.longitude,
|
||||
rideState: 'waiting', // يعني السائق بالطريق للراكب
|
||||
estimatedTimeMinutes: (timeToPassengerSeconds / 60).round(),
|
||||
totalDistanceMeters: distanceDriverToPassengerMeters,
|
||||
);
|
||||
|
||||
// 6. بدء تتبع الموقع الدوري (Polling Backup + Smart Rerouting)
|
||||
// سيبدأ العمل بعد 6 ثواني
|
||||
@@ -1350,7 +1421,7 @@ class MapPassengerController extends GetxController {
|
||||
break;
|
||||
}
|
||||
_noRideSearchCount++;
|
||||
Log.print('_noRideSearchCount: ${_noRideSearchCount}');
|
||||
Log.print('_noRideSearchCount: $_noRideSearchCount');
|
||||
_noRideNextAllowed = now.add(Duration(seconds: _noRideIntervalSec));
|
||||
String currentCarType = box.read(BoxName.carType) ?? 'yet';
|
||||
getCarsLocationByPassengerAndReloadMarker();
|
||||
@@ -1805,40 +1876,57 @@ class MapPassengerController extends GetxController {
|
||||
///
|
||||
/// تستدعى عند استلام حدث بدء الرحلة سواء من السوكيت أو FCM.
|
||||
/// تضمن انتقال التطبيق لحالة [RideState.inProgress] مرة واحدة فقط.
|
||||
void processRideBegin({String source = "Unknown"}) {
|
||||
// 1. الحارس: إذا بدأت الرحلة مسبقاً، تجاهل
|
||||
Future<void> processRideBegin({String source = "Unknown"}) async {
|
||||
// منطقك الحالي
|
||||
if (currentRideState.value == RideState.inProgress ||
|
||||
_isRideStartedProcessed) {
|
||||
Log.print("✋ Ignored Start Request from $source. Already Started.");
|
||||
return;
|
||||
}
|
||||
|
||||
// شرط إضافي: يجب أن نكون في حالة "وصل السائق" أو "تم القبول" لبدء الرحلة
|
||||
if (currentRideState.value != RideState.driverArrived &&
|
||||
currentRideState.value != RideState.driverApplied) {
|
||||
Log.print(
|
||||
"⚠️ Start Request ignored due to invalid previous state: ${currentRideState.value}");
|
||||
// يمكن السماح بها كحالة استثنائية (Fail-safe) إذا انقطع الاتصال سابقاً
|
||||
// return;
|
||||
}
|
||||
|
||||
_isRideStartedProcessed = true;
|
||||
Log.print("🚀 Ride Started via $source! Processing...");
|
||||
|
||||
// 2. إغلاق أي ديالوج مفتوح (مثل ديالوج السائق وصل)
|
||||
if (Get.isDialogOpen == true) Get.back();
|
||||
|
||||
// 3. تحديث الحالة
|
||||
currentRideState.value = RideState.inProgress;
|
||||
statusRide = 'Begin';
|
||||
|
||||
// 4. تصفير وإيقاف عدادات الانتظار
|
||||
// إيقاف مؤقت الانتظار
|
||||
remainingTimeDriverWaitPassenger5Minute = 0;
|
||||
_stopWaitPassengerTimer(); // دالة إيقاف تايمر الانتظار
|
||||
_stopWaitPassengerTimer();
|
||||
|
||||
// 5. بدء عداد وقت الرحلة الفعلي
|
||||
// 1) بيانات السائق والرحلة
|
||||
final driverName = this.driverName ?? 'السائق';
|
||||
final driverPhone = this.driverPhone ?? '';
|
||||
final carBrand = this.make ?? '';
|
||||
final carColor = this.carColor ?? '';
|
||||
final carPlate = this.licensePlate ?? '';
|
||||
final carDetails = '$carBrand • $carColor • $carPlate';
|
||||
final driverLat = driverCarsLocationToPassengerAfterApplied.last.latitude;
|
||||
final driverLng = driverCarsLocationToPassengerAfterApplied.last.longitude;
|
||||
final passengerLat = passengerLocation.latitude;
|
||||
final passengerLng = passengerLocation.longitude;
|
||||
final destLat = myDestination.latitude ?? 0.0;
|
||||
final destLng = myDestination.longitude ?? 0.0;
|
||||
|
||||
final int timeToDestinationSeconds =
|
||||
durationToRide; // موجود عندك من التايمر
|
||||
final double totalDistanceMeters = double.parse(distanceByPassenger);
|
||||
|
||||
// 2) استدعاء خدمة الأندرويد لتحديث الإشعار لحالة "inProgress"
|
||||
await RideTrackingNative.updateRideTracking(
|
||||
driverName: driverName,
|
||||
driverPhone: driverPhone,
|
||||
carDetails: carDetails,
|
||||
driverLat: driverLat,
|
||||
driverLng: driverLng,
|
||||
passengerLat: passengerLat,
|
||||
passengerLng: passengerLng,
|
||||
destLat: destLat,
|
||||
destLng: destLng,
|
||||
rideState: 'inProgress',
|
||||
estimatedTimeMinutes: (timeToDestinationSeconds / 60).round(),
|
||||
totalDistanceMeters: totalDistanceMeters,
|
||||
);
|
||||
|
||||
// 3) بدء التايمر الداخلي الخاص بك (للـ ETA داخل التطبيق نفسه)
|
||||
rideIsBeginPassengerTimer();
|
||||
|
||||
update();
|
||||
}
|
||||
|
||||
@@ -2057,8 +2145,13 @@ class MapPassengerController extends GetxController {
|
||||
}
|
||||
|
||||
// final mainBottomMenuMap = GlobalKey<AnimatedContainer>();
|
||||
void changeBottomSheetShown() {
|
||||
isBottomSheetShown = !isBottomSheetShown;
|
||||
void changeBottomSheetShown({bool? forceValue}) {
|
||||
if (forceValue != null) {
|
||||
isBottomSheetShown = forceValue;
|
||||
} else {
|
||||
isBottomSheetShown = !isBottomSheetShown;
|
||||
}
|
||||
|
||||
heightBottomSheetShown = isBottomSheetShown == true ? 250 : 0;
|
||||
update();
|
||||
}
|
||||
@@ -2239,7 +2332,28 @@ class MapPassengerController extends GetxController {
|
||||
int seconds = remainingTimeToPassengerFromDriverAfterApplied % 60;
|
||||
stringRemainingTimeToPassenger =
|
||||
'$minutes:${seconds.toString().padLeft(2, '0')}';
|
||||
// تحويل الوقت أو المسافة إلى نسبة من 0.0 إلى 1.0
|
||||
double currentProgress = 1 -
|
||||
(remainingTimeToPassengerFromDriverAfterApplied /
|
||||
timeToPassengerFromDriverAfterApplied);
|
||||
|
||||
// 🔴 التعديل هنا: نحدث الآيفون كل 5 ثواني فقط للحفاظ على البطارية وتجنب حظر أبل
|
||||
if (secondsElapsed % 5 == 0) {
|
||||
double currentProgress = 1 -
|
||||
(remainingTimeToPassengerFromDriverAfterApplied /
|
||||
(timeToPassengerFromDriverAfterApplied == 0
|
||||
? 1
|
||||
: timeToPassengerFromDriverAfterApplied));
|
||||
|
||||
IosLiveActivityService.updateRideActivity(
|
||||
status: 'waiting',
|
||||
driverName: driverName ?? 'السائق',
|
||||
carDetails:
|
||||
'$make • $model • $carColor', // من الأفضل إظهار اللون أيضاً
|
||||
etaText: stringRemainingTimeToPassenger,
|
||||
progress: currentProgress.clamp(0.0, 1.0),
|
||||
);
|
||||
}
|
||||
// جلب موقع السائق كل 4 ثواني (Polling) ما دامت الرحلة نشطة
|
||||
if (secondsElapsed % beginRideInterval == 0) {
|
||||
// 2. تحديث موقع الراكب للسائق
|
||||
@@ -2365,7 +2479,8 @@ class MapPassengerController extends GetxController {
|
||||
Log.print("⏳ Ride Timer Started. Duration: $durationToRide sec");
|
||||
|
||||
// 3. بدء التايمر الدوري
|
||||
_rideProgressTimer = Timer.periodic(const Duration(seconds: 1), (timer) {
|
||||
_rideProgressTimer =
|
||||
Timer.periodic(const Duration(seconds: 1), (timer) async {
|
||||
// أ) شرط الإيقاف الحاسم: إذا انتهت الرحلة أو ألغيت
|
||||
if (currentRideState.value != RideState.inProgress) {
|
||||
timer.cancel();
|
||||
@@ -2392,15 +2507,42 @@ class MapPassengerController extends GetxController {
|
||||
stringRemainingTimeRideBegin =
|
||||
'$minutes:${seconds.toString().padLeft(2, '0')}';
|
||||
|
||||
// د) منطق الإشعارات (ربع الوقت)
|
||||
// نحول progressTimerRideBegin (0..1) إلى نسبة (0..100)
|
||||
final percent = (progressTimerRideBegin * 100).clamp(0, 100).toInt();
|
||||
|
||||
// ==============================================================
|
||||
// 🔔 د) تحديث الإشعارات (هنا تم حل مشكلة الإزعاج)
|
||||
// ==============================================================
|
||||
|
||||
// 1. تحديث الآيفون (Live Activity): يمكن تحديثه كل 5 ثواني لأنه "تحديث صامت" للشاشة فقط ولا يصدر صوتاً
|
||||
if (remainingSeconds % 5 == 0 || remainingSeconds == 0) {
|
||||
IosLiveActivityService.updateRideActivity(
|
||||
status: 'inProgress', // الحالة تتغير هنا إلى جارية
|
||||
driverName: driverName ?? '',
|
||||
carDetails: '$make • $model • $carColor',
|
||||
etaText: stringRemainingTimeRideBegin,
|
||||
progress: progressTimerRideBegin.clamp(0.0, 1.0),
|
||||
);
|
||||
}
|
||||
|
||||
// 2. تحديث إشعار الهاتف العادي (RideLiveNotification):
|
||||
// نحدثه كل دقيقة (60 ثانية) بدلاً من 5 ثواني حتى لا يزعج الراكب بالرنين المستمر!
|
||||
if (remainingSeconds % 60 == 0 || remainingSeconds == 0) {
|
||||
await RideLiveNotification.showTripInProgress(
|
||||
percentage: percent,
|
||||
etaText: stringRemainingTimeRideBegin,
|
||||
);
|
||||
}
|
||||
// ==============================================================
|
||||
|
||||
// هـ) منطق الإشعارات لمنتصف الرحلة (يصدر تنبيه مرة واحدة فقط)
|
||||
if (progressTimerRideBegin >= 0.25 &&
|
||||
progressTimerRideBegin < 0.26 &&
|
||||
!_hasShownSpeedWarning) {
|
||||
// يمكن إضافة منطق إشعار منتصف الرحلة هنا
|
||||
}
|
||||
|
||||
// هـ) مراقبة السرعة (Speed Check)
|
||||
// نستخدم المتغير _hasShownSpeedWarning لمنع تكرار الديالوج بشكل مزعج
|
||||
// و) مراقبة السرعة (Speed Check)
|
||||
if (speed > 100 && !_hasShownSpeedWarning) {
|
||||
_hasShownSpeedWarning = true; // ✅ قفل التنبيه حتى لا يتكرر
|
||||
_triggerSpeedWarning();
|
||||
@@ -2411,7 +2553,7 @@ class MapPassengerController extends GetxController {
|
||||
_hasShownSpeedWarning = false;
|
||||
}
|
||||
|
||||
// و) إنهاء التايمر إذا انتهى الوقت
|
||||
// ز) إنهاء التايمر إذا انتهى الوقت
|
||||
if (remainingSeconds <= 0) {
|
||||
timer.cancel();
|
||||
}
|
||||
@@ -2640,7 +2782,7 @@ class MapPassengerController extends GetxController {
|
||||
link: AppLink.getRideStatusFromStartApp,
|
||||
payload: {'passenger_id': box.read(BoxName.passengerID)});
|
||||
// print(res);
|
||||
Log.print('rideStatusFromStartApp: ${res}');
|
||||
Log.print('rideStatusFromStartApp: $res');
|
||||
// print('1070');
|
||||
if (res == 'failure') {
|
||||
rideStatusFromStartApp = {
|
||||
@@ -2796,60 +2938,66 @@ class MapPassengerController extends GetxController {
|
||||
}));
|
||||
}
|
||||
|
||||
Map<String, double>? extractCoordinatesFromLink(String link) {
|
||||
Future<Map<String, double>?> extractCoordinatesFromLinkAsync(
|
||||
String link) async {
|
||||
try {
|
||||
// Extract the URL part from the link by finding the first occurrence of "http"
|
||||
// 1. استخراج الرابط فقط من النص (في حال كان هناك نص مع الرابط في الواتساب)
|
||||
int urlStartIndex = link.indexOf(RegExp(r'https?://'));
|
||||
if (urlStartIndex == -1) {
|
||||
throw const FormatException('No URL found in the provided link.');
|
||||
}
|
||||
if (urlStartIndex == -1) return null;
|
||||
String cleanLink = link.substring(urlStartIndex).trim();
|
||||
|
||||
// Extract the URL and clean it
|
||||
link = link.substring(urlStartIndex).trim();
|
||||
Uri uri = Uri.parse(cleanLink);
|
||||
String finalUrl = cleanLink;
|
||||
|
||||
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,
|
||||
};
|
||||
}
|
||||
}
|
||||
// 2. فك الروابط المختصرة (Unshorten URLs)
|
||||
if (cleanLink.contains('goo.gl') ||
|
||||
cleanLink.contains('maps.app.goo.gl')) {
|
||||
try {
|
||||
// نقوم بطلب HTTP عادي، وhttp يتبع التوجيه التلقائي (Redirects)
|
||||
var response = await http.get(uri);
|
||||
// نأخذ الرابط النهائي بعد التوجيه
|
||||
finalUrl = response.request?.url.toString() ?? cleanLink;
|
||||
} catch (e) {
|
||||
Log.print('Failed to follow redirect: $e');
|
||||
}
|
||||
}
|
||||
|
||||
// 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,
|
||||
};
|
||||
}
|
||||
}
|
||||
}
|
||||
Log.print('Final Unshortened URL: $finalUrl');
|
||||
|
||||
// 3. استخراج الإحداثيات باستخدام تعبيرات نمطية (Regex) قوية تغطي خرائط جوجل وغيرها
|
||||
|
||||
// النمط الأول: @lat,lng (الأكثر شيوعاً في خرائط جوجل)
|
||||
RegExp regexAt = RegExp(r'@(-?\d+\.\d+),(-?\d+\.\d+)');
|
||||
var matchAt = regexAt.firstMatch(finalUrl);
|
||||
if (matchAt != null) {
|
||||
return {
|
||||
'latitude': double.parse(matchAt.group(1)!),
|
||||
'longitude': double.parse(matchAt.group(2)!),
|
||||
};
|
||||
}
|
||||
|
||||
// النمط الثاني: q=lat,lng أو ll=lat,lng أو query=lat,lng
|
||||
RegExp regexQuery =
|
||||
RegExp(r'(?:q|ll|query)=(-?\d+\.\d+)[,~](-?\d+\.\d+)');
|
||||
var matchQuery = regexQuery.firstMatch(finalUrl);
|
||||
if (matchQuery != null) {
|
||||
return {
|
||||
'latitude': double.parse(matchQuery.group(1)!),
|
||||
'longitude': double.parse(matchQuery.group(2)!),
|
||||
};
|
||||
}
|
||||
|
||||
// النمط الثالث: search/lat,lng (موجود في بعض أشكال خرائط جوجل)
|
||||
RegExp regexSearch = RegExp(r'search/(-?\d+\.\d+),(-?\d+\.\d+)');
|
||||
var matchSearch = regexSearch.firstMatch(finalUrl);
|
||||
if (matchSearch != null) {
|
||||
return {
|
||||
'latitude': double.parse(matchSearch.group(1)!),
|
||||
'longitude': double.parse(matchSearch.group(2)!),
|
||||
};
|
||||
}
|
||||
} catch (e) {
|
||||
print('Error parsing location link: $e');
|
||||
Log.print('Error parsing location link: $e');
|
||||
}
|
||||
|
||||
return null;
|
||||
@@ -2857,8 +3005,9 @@ class MapPassengerController extends GetxController {
|
||||
|
||||
double latitudeWhatsApp = 0;
|
||||
double longitudeWhatsApp = 0;
|
||||
void handleWhatsAppLink(String link) {
|
||||
Map<String, double>? coordinates = extractCoordinatesFromLink(link);
|
||||
void handleWhatsAppLink(String link) async {
|
||||
Map<String, double>? coordinates =
|
||||
await extractCoordinatesFromLinkAsync(link);
|
||||
|
||||
if (coordinates != null) {
|
||||
latitudeWhatsApp = coordinates['latitude']!;
|
||||
@@ -3019,7 +3168,7 @@ class MapPassengerController extends GetxController {
|
||||
"step3": placesCoordinate.length > 3 ? placesCoordinate[3] : "",
|
||||
"step4": placesCoordinate.length > 4 ? placesCoordinate[4] : "",
|
||||
};
|
||||
Log.print('payload add_ride: ${payload}');
|
||||
Log.print('payload add_ride: $payload');
|
||||
|
||||
try {
|
||||
// الاتصال بـ add_ride.php
|
||||
@@ -3412,7 +3561,7 @@ class MapPassengerController extends GetxController {
|
||||
|
||||
Future<String> getRideStatus(String rideId) async {
|
||||
final response = await CRUD().get(
|
||||
link: "${AppLink.ride}/ride/rides/getRideStatus.php",
|
||||
link: "${AppLink.rideServerSide}/ride/rides/getRideStatus.php",
|
||||
payload: {'id': rideId});
|
||||
print(response);
|
||||
print('2176');
|
||||
@@ -3731,27 +3880,27 @@ class MapPassengerController extends GetxController {
|
||||
box.write(BoxName.countryCode, 'Jordan');
|
||||
// يمكنك تعيين AppLink.endPoint هنا إذا كان منطقك الداخلي لا يزال يعتمد عليه
|
||||
box.write(BoxName.serverChosen,
|
||||
AppLink.IntaleqSyriaServer); // مثال: اختر سيرفر سوريا للبيانات
|
||||
AppLink.server); // مثال: اختر سيرفر سوريا للبيانات
|
||||
return 'Jordan';
|
||||
}
|
||||
|
||||
// 2. فحص سوريا
|
||||
if (isPointInPolygon(passengerPoint, CountryPolygons.syriaBoundary)) {
|
||||
box.write(BoxName.countryCode, 'Syria');
|
||||
box.write(BoxName.serverChosen, AppLink.IntaleqSyriaServer);
|
||||
box.write(BoxName.serverChosen, AppLink.server);
|
||||
return 'Syria';
|
||||
}
|
||||
|
||||
// 3. فحص مصر
|
||||
if (isPointInPolygon(passengerPoint, CountryPolygons.egyptBoundary)) {
|
||||
box.write(BoxName.countryCode, 'Egypt');
|
||||
box.write(BoxName.serverChosen, AppLink.IntaleqAlexandriaServer);
|
||||
box.write(BoxName.serverChosen, AppLink.server);
|
||||
return 'Egypt';
|
||||
}
|
||||
|
||||
// 4. الافتراضي (إذا كان خارج المناطق المخدومة)
|
||||
box.write(BoxName.countryCode, 'Jordan');
|
||||
box.write(BoxName.serverChosen, AppLink.IntaleqSyriaServer);
|
||||
box.write(BoxName.serverChosen, AppLink.server);
|
||||
return 'Unknown Location (Defaulting to Jordan)';
|
||||
}
|
||||
|
||||
@@ -4574,7 +4723,7 @@ Intaleq Team''';
|
||||
_timer?.cancel();
|
||||
_masterTimer?.cancel(); // (أضف المؤقت الرئيسي)
|
||||
_camThrottle?.cancel(); // (أضف مؤقت الكاميرا)
|
||||
|
||||
_heartbeatTimer?.cancel();
|
||||
if (isSocketConnected) {
|
||||
socket.emit('unsubscribe_all',
|
||||
{'passenger_id': box.read(BoxName.passengerID).toString()});
|
||||
@@ -4720,9 +4869,12 @@ Intaleq Team''';
|
||||
clearPolyline();
|
||||
data = [];
|
||||
|
||||
// إيقاف جميع التايمرات
|
||||
// إيقاف جميع التايمرات
|
||||
stopAllTimers();
|
||||
currentRideState.value = RideState.cancelled;
|
||||
await RideLiveNotification.cancel(); // إغلاق أندرويد
|
||||
IosLiveActivityService.endRideActivity(); // ✅ إغلاق iOS
|
||||
|
||||
// 4. الاتصال بالسيرفر لإلغاء الرحلة وإبلاغ السائق
|
||||
if (rideId != 'yet' && rideId != null) {
|
||||
@@ -6143,7 +6295,7 @@ Intaleq Team''';
|
||||
// Waypoints غير مدعومة حالياً في OSRM، لذلك تم تجاهلها
|
||||
var uri = Uri.parse(
|
||||
'$dynamicApiUrl?origin=$origin&destination=$destination&steps=false&overview=false');
|
||||
Log.print('uri: ${uri}');
|
||||
Log.print('uri: $uri');
|
||||
|
||||
// 3. إرسال الطلب مع الهيدر
|
||||
http.Response response;
|
||||
@@ -6169,7 +6321,7 @@ Intaleq Team''';
|
||||
isDrawingRoute = false; // Reset state
|
||||
|
||||
responseData = json.decode(response.body);
|
||||
Log.print('responseData: ${responseData}');
|
||||
Log.print('responseData: $responseData');
|
||||
|
||||
if (responseData['status'] != 'ok') {
|
||||
print('API returned an error: ${responseData['message']}');
|
||||
@@ -6225,10 +6377,10 @@ Intaleq Team''';
|
||||
// 2. الاتصال بالسيرفر - New OSRM format
|
||||
var originCoords = origin.split(',');
|
||||
String dynamicApiUrl =
|
||||
'https://routesjo.intaleq.xyz/route/v1/driving/${originCoords[1]},${originCoords[0]};${lngDest},${latDest}';
|
||||
'${AppLink.routesOsm}/route/v1/driving/${originCoords[1]},${originCoords[0]};$lngDest,$latDest';
|
||||
|
||||
var uri = Uri.parse('$dynamicApiUrl?steps=false&overview=full');
|
||||
Log.print('Requesting Route URI (Attempt: ${attemptCount + 1}): ${uri}');
|
||||
Log.print('Requesting Route URI (Attempt: ${attemptCount + 1}): $uri');
|
||||
|
||||
http.Response response;
|
||||
Map<String, dynamic> responseData;
|
||||
@@ -7344,7 +7496,7 @@ Intaleq Team''';
|
||||
} catch (_) {}
|
||||
|
||||
update();
|
||||
changeBottomSheetShown();
|
||||
changeBottomSheetShown(forceValue: true);
|
||||
}
|
||||
|
||||
List<LatLng> polylineCoordinate = [];
|
||||
@@ -7779,27 +7931,59 @@ Intaleq Team''';
|
||||
|
||||
// --- دالة جديدة للاستماع ومعالجة الرابط ---
|
||||
void _listenForDeepLink() {
|
||||
// استمع إلى أي تغيير في الإحداثيات القادمة من الرابط
|
||||
ever(_deepLinkController.deepLinkLatLng, (LatLng? latLng) {
|
||||
if (latLng != null) {
|
||||
print('MapPassengerController detected deep link LatLng: $latLng');
|
||||
// عندما يتم استلام إحداثيات جديدة، عينها كوجهة
|
||||
myDestination = latLng;
|
||||
ever(_deepLinkController.rawDeepLink, (String? link) async {
|
||||
if (link != null && link.isNotEmpty) {
|
||||
Log.print('📍 MapPassengerController processing link: $link');
|
||||
|
||||
// قم بتشغيل المنطق الخاص بك لعرض المسار
|
||||
// (تأكد من أن `passengerLocation` لديها قيمة أولاً)
|
||||
if (passengerLocation != null) {
|
||||
getDirectionMap(
|
||||
'${passengerLocation.latitude},${passengerLocation.longitude}',
|
||||
'${myDestination.latitude},${myDestination.longitude}');
|
||||
// 1. استخراج الإحداثيات باستخدام الدالة الموجودة لديك مسبقاً
|
||||
Map<String, double>? coordinates =
|
||||
await extractCoordinatesFromLinkAsync(link);
|
||||
|
||||
if (coordinates != null) {
|
||||
double destLat = coordinates['latitude']!;
|
||||
double destLng = coordinates['longitude']!;
|
||||
myDestination = LatLng(destLat, destLng);
|
||||
|
||||
// 2. التحقق من موقع الراكب الحالي
|
||||
if (passengerLocation == null ||
|
||||
(passengerLocation.latitude == 0 &&
|
||||
passengerLocation.longitude == 0)) {
|
||||
Log.print('⏳ Waiting for current location to calculate route...');
|
||||
await getLocation(); // جلب موقع الراكب إذا لم يكن متاحاً
|
||||
}
|
||||
|
||||
if (passengerLocation != null) {
|
||||
String originStr =
|
||||
'${passengerLocation.latitude},${passengerLocation.longitude}';
|
||||
String destStr = '$destLat,$destLng';
|
||||
|
||||
Log.print(
|
||||
'🚀 Drawing route from Deep Link: $originStr to $destStr');
|
||||
|
||||
// 3. مسح أي مسارات سابقة
|
||||
clearPolyline();
|
||||
|
||||
// 4. استدعاء دالة رسم المسار وحساب التكلفة التي برمجتها
|
||||
await getDirectionMap(originStr, destStr);
|
||||
|
||||
// 5. إظهار الواجهة السفلية للرحلة ليكون الطلب جاهزاً بنقرة واحدة
|
||||
isBottomSheetShown = true;
|
||||
heightBottomSheetShown = 250;
|
||||
update();
|
||||
|
||||
Get.snackbar(
|
||||
'Location Received'.tr,
|
||||
'Route and prices have been calculated successfully!'.tr,
|
||||
backgroundColor: AppColor.greenColor,
|
||||
colorText: Colors.white,
|
||||
);
|
||||
}
|
||||
} else {
|
||||
// يمكنك إظهار رسالة للمستخدم لتمكين الموقع أولاً
|
||||
print(
|
||||
'Cannot process deep link route yet, passenger location is null.');
|
||||
Log.print('⚠️ Could not extract valid coordinates from link: $link');
|
||||
}
|
||||
|
||||
// إعادة تعيين القيمة إلى null لمنع التشغيل مرة أخرى عند إعادة بناء الواجهة
|
||||
_deepLinkController.deepLinkLatLng.value = null;
|
||||
// تفريغ الرابط بعد معالجته حتى لا يتم استدعاؤه مرة أخرى بالخطأ
|
||||
_deepLinkController.rawDeepLink.value = null;
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user