إصلاح مسار الإشعار→الطلب عند الكابتن + توحيد إعادة اتصال سوكيت الراكب
سوكيت الراكب: - setReconnectionAttempts: من 20 إلى لا نهائي، توحيداً مع تطبيق السائق. بعد 20 محاولة كان يستسلم **نهائياً** فيصمت للأبد بعد دقائق من شبكة سيئة ويبقى الراكب على الـ polling بلا أن يدري — وخطره أكبر الآن لأن الاتصال صار يعيش من فتح الخريطة لا من إنشاء الرحلة. - الـ heartbeat من 15ث إلى 30ث: Socket.IO يعمل ping/pong على مستوى البروتوكول ومستمع 'heartbeat' في passenger_socket.php فارغ أصلاً. البدء البارد على iOS (سبب "التطبيق يغلق ونرجع من أول"): - didChangeAppLifecycleState لا يُنفَّذ عند إطلاق التطبيق (يبدأ في resumed بلا انتقال حالة)، وهو المُنادي الوحيد لـ _checkPendingTrip ⇒ الضغط على الإشعار والتطبيق مقتول لا يوجّه لصفحة الطلب إطلاقاً. - وحتى لو نُفِّذ، كان يستسلم على '/' ويحوّل المسؤولية لـ HomeCaptainController الذي لا يملك أي منطق كهذا ⇒ الطلب يبقى في التخزين للأبد. صار addPostFrameCallback + مُجدوِل يعيد المحاولة حتى خروج التطبيق من شاشة البداية (سقف ~30ث)، ولا يحذف الطلب قبل التوجيه. - حرس إضافي: لا اقتحام لرحلة نشطة بطلب قديم، لا توجيه مكرر إذا كنا على OrderRequestPage، وتنظيف الحمولة التالفة. TripOverlayPlugin أندرويد فقط بلا أي حرس منصّة: - كل دالة كانت تنادي invokeMethod مباشرة ⇒ MissingPluginException على iOS (لا يوجد ios/ ولا مدخل في pubspec.plugin.platforms). - الأخطر: backgroundMessageHandler كان ينادي showOverlay **قبل** حفظ pending_driver_list، فيسقط الاستثناء ويُلغي الحفظ كلياً على iOS. عُكس الترتيب — الحفظ أولاً دائماً، وهو الحدّ الأدنى الذي لا يتوقف على منصّة أو صلاحية. - أُضيف isSupported وحُصِّنت كل الدوال (ترجع false/no-op ولا ترمي أبداً)، فلا يُقطع أيضاً _initApp قبل _listenToOverlayEvents. أخطاء index في حمولة add_ride.php (أربع نقاط قبول): - 'Duration' كان index 4 وهو end_lng لا المدة ⇒ صار 15 (duration_text). أحد المواضع كان فيه تعليق "انتبه: تأكد من الإندكس الصحيح للوقت". - 'passengerWalletBurc' كان 26 وهو price_for_driver (أرباح السائق) لا رصيد الراكب ⇒ صار 27 (passenger_wallet). - الموضعان مكرران في main.dart و local_notification.dart و order_over_lay.dart و order_request_controller.dart — وُحِّدت كلها. - حرس الطول في backgroundMessageHandler: كان length > 29 مع قراءة index 30 ⇒ RangeError على قائمة طولها 30 بالضبط. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
This commit is contained in:
co-authored by
Claude Opus 5
parent
ab3be7e2e3
commit
a26472ed9f
@@ -359,7 +359,7 @@ class NotificationController extends GetxController {
|
|||||||
return {
|
return {
|
||||||
'passengerLocation': '${_getVal(data, 0)},${_getVal(data, 1)}',
|
'passengerLocation': '${_getVal(data, 0)},${_getVal(data, 1)}',
|
||||||
'passengerDestination': '${_getVal(data, 3)},${_getVal(data, 4)}',
|
'passengerDestination': '${_getVal(data, 3)},${_getVal(data, 4)}',
|
||||||
'Duration': _getVal(data, 4), // انتبه: تأكد من الإندكس الصحيح للوقت
|
'Duration': _getVal(data, 15), // 15 = duration_text في حمولة add_ride.php (كان 4 = end_lng)
|
||||||
'totalCost': _getVal(data, 26),
|
'totalCost': _getVal(data, 26),
|
||||||
'Distance': _getVal(data, 5),
|
'Distance': _getVal(data, 5),
|
||||||
'name': _getVal(data, 8),
|
'name': _getVal(data, 8),
|
||||||
@@ -382,7 +382,7 @@ class NotificationController extends GetxController {
|
|||||||
'step2': _getVal(data, 23),
|
'step2': _getVal(data, 23),
|
||||||
'step3': _getVal(data, 24),
|
'step3': _getVal(data, 24),
|
||||||
'step4': _getVal(data, 25),
|
'step4': _getVal(data, 25),
|
||||||
'passengerWalletBurc': _getVal(data, 26),
|
'passengerWalletBurc': _getVal(data, 27), // 27 = passenger_wallet (كان 26 = price_for_driver)
|
||||||
'timeOfOrder': DateTime.now().toString(),
|
'timeOfOrder': DateTime.now().toString(),
|
||||||
'totalPassenger': _getVal(data, 2),
|
'totalPassenger': _getVal(data, 2),
|
||||||
'carType': _getVal(data, 31),
|
'carType': _getVal(data, 31),
|
||||||
|
|||||||
@@ -755,7 +755,7 @@ class OrderRequestController extends GetxController
|
|||||||
'step2': _safeGet(23),
|
'step2': _safeGet(23),
|
||||||
'step3': _safeGet(24),
|
'step3': _safeGet(24),
|
||||||
'step4': _safeGet(25),
|
'step4': _safeGet(25),
|
||||||
'passengerWalletBurc': _safeGet(26),
|
'passengerWalletBurc': _safeGet(27), // 27 = passenger_wallet (كان 26 = price_for_driver)
|
||||||
'timeOfOrder': DateTime.now().toString(),
|
'timeOfOrder': DateTime.now().toString(),
|
||||||
'totalPassenger': _safeGet(2),
|
'totalPassenger': _safeGet(2),
|
||||||
'carType': _safeGet(31),
|
'carType': _safeGet(31),
|
||||||
|
|||||||
+89
-15
@@ -207,7 +207,9 @@ Future<void> backgroundMessageHandler(RemoteMessage message) async {
|
|||||||
double pLat = 0.0;
|
double pLat = 0.0;
|
||||||
double pLng = 0.0;
|
double pLng = 0.0;
|
||||||
|
|
||||||
if (myList.isNotEmpty && myList.length > 29) {
|
// نقرأ حتى index 30، فالطول المطلوب 31 لا 30 (كان > 29 ⇒ RangeError
|
||||||
|
// على قائمة طولها 30 بالضبط).
|
||||||
|
if (myList.length > 30) {
|
||||||
fare = double.tryParse(myList[26].toString()) ?? 0.0;
|
fare = double.tryParse(myList[26].toString()) ?? 0.0;
|
||||||
distance = double.tryParse(myList[11].toString()) ?? 0.0;
|
distance = double.tryParse(myList[11].toString()) ?? 0.0;
|
||||||
passengerName = myList[8].toString();
|
passengerName = myList[8].toString();
|
||||||
@@ -238,11 +240,15 @@ Future<void> backgroundMessageHandler(RemoteMessage message) async {
|
|||||||
currentRideStatus == 'Arrived');
|
currentRideStatus == 'Arrived');
|
||||||
|
|
||||||
if (!hasActiveRide) {
|
if (!hasActiveRide) {
|
||||||
if (!isAppInForeground) {
|
// 🔴 الحفظ **أولاً** ثم النافذة. كان العكس: showOverlay ترمي
|
||||||
|
// MissingPluginException على iOS (الإضافة أندرويد فقط) فيُلغى الحفظ
|
||||||
|
// تماماً ⇒ يضغط الكابتن الإشعار فلا يجد طلباً ويبدأ التطبيق من الصفر.
|
||||||
|
// الحفظ هو الحدّ الأدنى الذي لا يجوز أن يتوقف على أي منصّة أو صلاحية.
|
||||||
|
await storage.write(key: 'pending_driver_list', value: myListString);
|
||||||
|
|
||||||
|
if (!isAppInForeground && TripOverlayPlugin.isSupported) {
|
||||||
await TripOverlayPlugin.showOverlay(tripData, autoCloseSeconds: 15);
|
await TripOverlayPlugin.showOverlay(tripData, autoCloseSeconds: 15);
|
||||||
}
|
}
|
||||||
// 🔴 الكتابة على القرص مباشرة بدلاً من الرام لضمان انتقالها للتطبيق 🔴
|
|
||||||
await storage.write(key: 'pending_driver_list', value: myListString);
|
|
||||||
} else {
|
} else {
|
||||||
print("⛔ [Background FCM] Ignoring new order overlay - driver is in active ride ($currentRideStatus)");
|
print("⛔ [Background FCM] Ignoring new order overlay - driver is in active ride ($currentRideStatus)");
|
||||||
}
|
}
|
||||||
@@ -299,6 +305,15 @@ class _MyAppState extends State<MyApp> with WidgetsBindingObserver {
|
|||||||
super.initState();
|
super.initState();
|
||||||
WidgetsBinding.instance.addObserver(this);
|
WidgetsBinding.instance.addObserver(this);
|
||||||
_initApp();
|
_initApp();
|
||||||
|
|
||||||
|
// 🔴 البدء البارد: didChangeAppLifecycleState **لا يُنفَّذ** عند إطلاق
|
||||||
|
// التطبيق (يبدأ في resumed بلا انتقال حالة)، فكان _checkPendingTrip لا
|
||||||
|
// يُنادى إطلاقاً حين يفتح الكابتن التطبيق من الإشعار وهو مقتول. والتعليق
|
||||||
|
// القديم كان يُحوّل المسؤولية لـ HomeCaptainController وهو لا يملك أي
|
||||||
|
// منطق كهذا ⇒ يبقى الطلب في التخزين للأبد ويهبط الكابتن على الرئيسية.
|
||||||
|
WidgetsBinding.instance.addPostFrameCallback((_) {
|
||||||
|
_schedulePendingTripCheck();
|
||||||
|
});
|
||||||
// 🔥 التقاط رسالة القبول من الأندرويد بشكل مباشر 100% 🔥
|
// 🔥 التقاط رسالة القبول من الأندرويد بشكل مباشر 100% 🔥
|
||||||
const platformAppControl = MethodChannel('com.siro_driver/app_control');
|
const platformAppControl = MethodChannel('com.siro_driver/app_control');
|
||||||
platformAppControl.setMethodCallHandler((call) async {
|
platformAppControl.setMethodCallHandler((call) async {
|
||||||
@@ -405,18 +420,74 @@ class _MyAppState extends State<MyApp> with WidgetsBindingObserver {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
void _checkPendingTrip() async {
|
/// ينتظر خروج التطبيق من شاشة البداية ثم يفحص الطلب المعلّق.
|
||||||
// 🔴 القراءة من القرص مباشرة 🔴
|
/// كان الفحص يستسلم فوراً إذا كان على '/' فيُهدر الطلب — الآن يعيد المحاولة.
|
||||||
String? savedTrip = await storage.read(key: 'pending_driver_list');
|
bool _pendingCheckScheduled = false;
|
||||||
|
|
||||||
if (savedTrip != null && savedTrip.isNotEmpty) {
|
Future<void> _schedulePendingTripCheck() async {
|
||||||
if (Get.currentRoute == '/') {
|
if (_pendingCheckScheduled) return;
|
||||||
print(
|
_pendingCheckScheduled = true;
|
||||||
'⏳ App is still on Splash screen. Postponing notification trip navigation to HomeCaptainController.');
|
try {
|
||||||
|
// سقف ~30 ثانية: يكفي لأبطأ إقلاع بلا أن يدور للأبد
|
||||||
|
for (int attempt = 0; attempt < 60; attempt++) {
|
||||||
|
final String? savedTrip = await storage.read(key: 'pending_driver_list');
|
||||||
|
if (savedTrip == null || savedTrip.isEmpty) {
|
||||||
|
// لا شيء معلّق بعد — الإشعار قد يكون ما زال يُحفَظ من
|
||||||
|
// getInitialMessage، فنكمل الانتظار قليلاً.
|
||||||
|
if (attempt > 10) return;
|
||||||
|
await Future.delayed(const Duration(milliseconds: 500));
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
if (Get.currentRoute == '/' || Get.currentRoute.isEmpty) {
|
||||||
|
await Future.delayed(const Duration(milliseconds: 500));
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
await _checkPendingTrip();
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
print('⚠️ Pending trip check timed out waiting for app to leave splash.');
|
||||||
|
} finally {
|
||||||
|
_pendingCheckScheduled = false;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
Future<void> _checkPendingTrip() async {
|
||||||
|
// 🔴 القراءة من القرص مباشرة 🔴
|
||||||
|
String? savedTrip = await storage.read(key: 'pending_driver_list');
|
||||||
|
if (savedTrip == null || savedTrip.isEmpty) return;
|
||||||
|
|
||||||
|
if (Get.currentRoute == '/' || Get.currentRoute.isEmpty) {
|
||||||
|
// ما زلنا على شاشة البداية — لا نحذف الطلب، ندع المُجدوِل يعيد المحاولة
|
||||||
|
print('⏳ Still on splash — deferring pending trip navigation (kept).');
|
||||||
|
_schedulePendingTripCheck();
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
// لا نقتحم رحلة نشطة بطلب قديم
|
||||||
|
final String currentRideStatus = box.read(BoxName.rideStatus)?.toString() ?? '';
|
||||||
|
if (currentRideStatus == 'Begin' ||
|
||||||
|
currentRideStatus == 'Apply' ||
|
||||||
|
currentRideStatus == 'Arrived') {
|
||||||
|
print('⛔ Active ride ($currentRideStatus) — discarding stale pending order.');
|
||||||
|
await storage.delete(key: 'pending_driver_list');
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (Get.currentRoute == '/OrderRequestPage') {
|
||||||
|
print('⚠️ Already on OrderRequestPage — skipping duplicate navigation.');
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
List<dynamic> driverList;
|
||||||
|
try {
|
||||||
|
driverList = jsonDecode(savedTrip) as List<dynamic>;
|
||||||
|
} catch (e) {
|
||||||
|
print('❌ Corrupt pending_driver_list, discarding: $e');
|
||||||
|
await storage.delete(key: 'pending_driver_list');
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
await storage.delete(key: 'pending_driver_list');
|
await storage.delete(key: 'pending_driver_list');
|
||||||
List<dynamic> driverList = jsonDecode(savedTrip);
|
|
||||||
|
|
||||||
print('👀 التطبيق فتح من الإشعار أو يدوياً، توجيه لصفحة الطلب...');
|
print('👀 التطبيق فتح من الإشعار أو يدوياً، توجيه لصفحة الطلب...');
|
||||||
Get.toNamed('/OrderRequestPage', arguments: {
|
Get.toNamed('/OrderRequestPage', arguments: {
|
||||||
@@ -425,7 +496,6 @@ class _MyAppState extends State<MyApp> with WidgetsBindingObserver {
|
|||||||
'body': 'طلب جديد'
|
'body': 'طلب جديد'
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
}
|
|
||||||
|
|
||||||
// ==============================================================================
|
// ==============================================================================
|
||||||
// 🟢 دوال معالجة قبول الطلب وتجهيز المتغيرات 🟢
|
// 🟢 دوال معالجة قبول الطلب وتجهيز المتغيرات 🟢
|
||||||
@@ -521,7 +591,9 @@ class _MyAppState extends State<MyApp> with WidgetsBindingObserver {
|
|||||||
return {
|
return {
|
||||||
'passengerLocation': '${_getVal(data, 0)},${_getVal(data, 1)}',
|
'passengerLocation': '${_getVal(data, 0)},${_getVal(data, 1)}',
|
||||||
'passengerDestination': '${_getVal(data, 3)},${_getVal(data, 4)}',
|
'passengerDestination': '${_getVal(data, 3)},${_getVal(data, 4)}',
|
||||||
'Duration': _getVal(data, 4),
|
// كان index 4 وهو end_lng لا المدة! ترتيب الحمولة في add_ride.php:
|
||||||
|
// 15 = duration_text. (راجع $payload هناك قبل تعديل أي index)
|
||||||
|
'Duration': _getVal(data, 15),
|
||||||
'totalCost': _getVal(data, 26),
|
'totalCost': _getVal(data, 26),
|
||||||
'Distance': _getVal(data, 5),
|
'Distance': _getVal(data, 5),
|
||||||
'name': _getVal(data, 8),
|
'name': _getVal(data, 8),
|
||||||
@@ -544,7 +616,9 @@ class _MyAppState extends State<MyApp> with WidgetsBindingObserver {
|
|||||||
'step2': _getVal(data, 23),
|
'step2': _getVal(data, 23),
|
||||||
'step3': _getVal(data, 24),
|
'step3': _getVal(data, 24),
|
||||||
'step4': _getVal(data, 25),
|
'step4': _getVal(data, 25),
|
||||||
'passengerWalletBurc': _getVal(data, 26),
|
// كان index 26 وهو price_for_driver (أرباح السائق) لا رصيد الراكب.
|
||||||
|
// 27 = passenger_wallet في حمولة add_ride.php.
|
||||||
|
'passengerWalletBurc': _getVal(data, 27),
|
||||||
'timeOfOrder': DateTime.now().toString(),
|
'timeOfOrder': DateTime.now().toString(),
|
||||||
'totalPassenger': _getVal(data, 2),
|
'totalPassenger': _getVal(data, 2),
|
||||||
'carType': _getVal(data, 31),
|
'carType': _getVal(data, 31),
|
||||||
|
|||||||
@@ -230,7 +230,7 @@ class _OrderOverlayState extends State<OrderOverlay>
|
|||||||
'status': 'Apply',
|
'status': 'Apply',
|
||||||
'passengerLocation': '${_getData(0)},${_getData(1)}',
|
'passengerLocation': '${_getData(0)},${_getData(1)}',
|
||||||
'passengerDestination': '${_getData(3)},${_getData(4)}',
|
'passengerDestination': '${_getData(3)},${_getData(4)}',
|
||||||
'Duration': _getData(4),
|
'Duration': _getData(15), // 15 = duration_text (كان 4 = end_lng)
|
||||||
'totalCost': _getData(26),
|
'totalCost': _getData(26),
|
||||||
'Distance': _getData(5),
|
'Distance': _getData(5),
|
||||||
'name': _getData(8),
|
'name': _getData(8),
|
||||||
@@ -251,7 +251,7 @@ class _OrderOverlayState extends State<OrderOverlay>
|
|||||||
'step2': myList[23].toString(),
|
'step2': myList[23].toString(),
|
||||||
'step3': myList[24].toString(),
|
'step3': myList[24].toString(),
|
||||||
'step4': myList[25].toString(),
|
'step4': myList[25].toString(),
|
||||||
'passengerWalletBurc': myList[26].toString(),
|
'passengerWalletBurc': myList[27].toString(), // 27 = passenger_wallet (كان 26 = price_for_driver)
|
||||||
'carType': myList[31].toString(),
|
'carType': myList[31].toString(),
|
||||||
'kazan': myList[32].toString(),
|
'kazan': myList[32].toString(),
|
||||||
'startNameLocation': myList[29].toString(),
|
'startNameLocation': myList[29].toString(),
|
||||||
|
|||||||
@@ -1,5 +1,7 @@
|
|||||||
import 'dart:async';
|
import 'dart:async';
|
||||||
import 'dart:convert';
|
import 'dart:convert';
|
||||||
|
import 'dart:io' show Platform;
|
||||||
|
import 'package:flutter/foundation.dart' show debugPrint;
|
||||||
import 'package:flutter/services.dart';
|
import 'package:flutter/services.dart';
|
||||||
|
|
||||||
/// Model for trip data passed to the overlay
|
/// Model for trip data passed to the overlay
|
||||||
@@ -119,45 +121,96 @@ class TripOverlayPlugin {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// هذه الإضافة **أندرويد فقط** — لا يوجد تنفيذ iOS ولا مجلد ios/ ولا مدخل
|
||||||
|
/// في pubspec.plugin.platforms. iOS لا يسمح بنوافذ فوق التطبيقات أصلاً.
|
||||||
|
///
|
||||||
|
/// بلا هذا الحرس كان أي نداء يرمي MissingPluginException على iOS، وأخطر
|
||||||
|
/// موضع: backgroundMessageHandler ينادي showOverlay **قبل** حفظ
|
||||||
|
/// pending_driver_list، فيسقط الاستثناء ويُلغي الحفظ ⇒ يفتح الكابتن الإشعار
|
||||||
|
/// فلا يجد طلباً وتبدأ الشاشة من الصفر.
|
||||||
|
static bool get isSupported => Platform.isAndroid;
|
||||||
|
|
||||||
/// Check if SYSTEM_ALERT_WINDOW permission is granted
|
/// Check if SYSTEM_ALERT_WINDOW permission is granted
|
||||||
static Future<bool> isPermissionGranted() async {
|
static Future<bool> isPermissionGranted() async {
|
||||||
|
if (!isSupported) return false;
|
||||||
|
try {
|
||||||
final result = await _channel.invokeMethod<bool>('isPermissionGranted');
|
final result = await _channel.invokeMethod<bool>('isPermissionGranted');
|
||||||
return result ?? false;
|
return result ?? false;
|
||||||
|
} on PlatformException catch (e) {
|
||||||
|
debugPrint('[TripOverlay] isPermissionGranted failed: ${e.message}');
|
||||||
|
return false;
|
||||||
|
} on MissingPluginException {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Open system settings to grant SYSTEM_ALERT_WINDOW permission
|
/// Open system settings to grant SYSTEM_ALERT_WINDOW permission
|
||||||
static Future<void> requestPermission() async {
|
static Future<void> requestPermission() async {
|
||||||
|
if (!isSupported) return;
|
||||||
|
try {
|
||||||
await _channel.invokeMethod('requestPermission');
|
await _channel.invokeMethod('requestPermission');
|
||||||
|
} on PlatformException catch (e) {
|
||||||
|
debugPrint('[TripOverlay] requestPermission failed: ${e.message}');
|
||||||
|
} on MissingPluginException {
|
||||||
|
// لا شيء — غير مدعوم على هذه المنصة
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Show the trip overlay with the given [tripData]
|
/// Show the trip overlay with the given [tripData]
|
||||||
/// [autoCloseSeconds] — how long before auto-dismiss (default 30s)
|
/// [autoCloseSeconds] — how long before auto-dismiss (default 30s)
|
||||||
|
///
|
||||||
|
/// يرجع false ولا يرمي أبداً: المُنادي يجب أن يكمل مساره (حفظ الطلب المعلّق،
|
||||||
|
/// الإشعار المحلي) حتى لو تعذّرت النافذة.
|
||||||
static Future<bool> showOverlay(
|
static Future<bool> showOverlay(
|
||||||
TripData tripData, {
|
TripData tripData, {
|
||||||
int autoCloseSeconds = 30,
|
int autoCloseSeconds = 30,
|
||||||
}) async {
|
}) async {
|
||||||
|
if (!isSupported) return false;
|
||||||
|
|
||||||
final granted = await isPermissionGranted();
|
final granted = await isPermissionGranted();
|
||||||
if (!granted) {
|
if (!granted) {
|
||||||
await requestPermission();
|
await requestPermission();
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
try {
|
||||||
final result = await _channel.invokeMethod<bool>('showOverlay', {
|
final result = await _channel.invokeMethod<bool>('showOverlay', {
|
||||||
'tripData': tripData.toJson(),
|
'tripData': tripData.toJson(),
|
||||||
'autoCloseSeconds': autoCloseSeconds,
|
'autoCloseSeconds': autoCloseSeconds,
|
||||||
});
|
});
|
||||||
return result ?? false;
|
return result ?? false;
|
||||||
|
} on PlatformException catch (e) {
|
||||||
|
debugPrint('[TripOverlay] showOverlay failed: ${e.message}');
|
||||||
|
return false;
|
||||||
|
} on MissingPluginException {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Programmatically close the overlay (e.g. if trip was cancelled)
|
/// Programmatically close the overlay (e.g. if trip was cancelled)
|
||||||
static Future<void> hideOverlay() async {
|
static Future<void> hideOverlay() async {
|
||||||
|
if (!isSupported) return;
|
||||||
|
try {
|
||||||
await _channel.invokeMethod('hideOverlay');
|
await _channel.invokeMethod('hideOverlay');
|
||||||
|
} on PlatformException catch (e) {
|
||||||
|
debugPrint('[TripOverlay] hideOverlay failed: ${e.message}');
|
||||||
|
} on MissingPluginException {
|
||||||
|
// لا شيء
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Check if the overlay is currently visible
|
/// Check if the overlay is currently visible
|
||||||
static Future<bool> isOverlayActive() async {
|
static Future<bool> isOverlayActive() async {
|
||||||
|
if (!isSupported) return false;
|
||||||
|
try {
|
||||||
final result = await _channel.invokeMethod<bool>('isOverlayActive');
|
final result = await _channel.invokeMethod<bool>('isOverlayActive');
|
||||||
return result ?? false;
|
return result ?? false;
|
||||||
|
} on PlatformException catch (e) {
|
||||||
|
debugPrint('[TripOverlay] isOverlayActive failed: ${e.message}');
|
||||||
|
return false;
|
||||||
|
} on MissingPluginException {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Dispose streams — call in app's dispose()
|
/// Dispose streams — call in app's dispose()
|
||||||
|
|||||||
@@ -87,7 +87,11 @@ class MapSocketController extends GetxController {
|
|||||||
.setTransports(['websocket'])
|
.setTransports(['websocket'])
|
||||||
.disableAutoConnect()
|
.disableAutoConnect()
|
||||||
.setQuery({'id': passengerId, 'jwt': jwt})
|
.setQuery({'id': passengerId, 'jwt': jwt})
|
||||||
.setReconnectionAttempts(20)
|
// محاولات لا نهائية، توحيداً مع تطبيق السائق (background_service.dart).
|
||||||
|
// كانت 20: بعدها يستسلم السوكيت **نهائياً** فيصمت للأبد بعد بضع دقائق
|
||||||
|
// من شبكة سيئة، ويبقى الراكب على الـ polling بلا أن يدري. صار الاتصال
|
||||||
|
// يعيش من فتح الخريطة لا من إنشاء الرحلة، فالسقف الثابت خطر أكبر الآن.
|
||||||
|
.setReconnectionAttempts(double.infinity)
|
||||||
.setReconnectionDelay(2000)
|
.setReconnectionDelay(2000)
|
||||||
.setReconnectionDelayMax(10000)
|
.setReconnectionDelayMax(10000)
|
||||||
.enableReconnection()
|
.enableReconnection()
|
||||||
@@ -266,9 +270,14 @@ class MapSocketController extends GetxController {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// ⚠️ زائدة عن الحاجة عملياً: Socket.IO يعمل ping/pong على مستوى البروتوكول،
|
||||||
|
/// ومستمع 'heartbeat' في passenger_socket.php فارغ ولا يفعل شيئاً. أبقيناها
|
||||||
|
/// بفاصل 30ث بدل 15ث لتنصيف حركتها — الاتصال صار يعيش من فتح الخريطة لا من
|
||||||
|
/// إنشاء الرحلة، فعدد الاتصالات الخاملة أعلى بكثير. حذفها كاملاً ممكن لاحقاً
|
||||||
|
/// بعد التأكد أن لا شيء على السيرفر يعتمد عليها لقياس الحضور.
|
||||||
void _startHeartbeat() {
|
void _startHeartbeat() {
|
||||||
_heartbeatTimer?.cancel();
|
_heartbeatTimer?.cancel();
|
||||||
_heartbeatTimer = Timer.periodic(const Duration(seconds: 15), (timer) {
|
_heartbeatTimer = Timer.periodic(const Duration(seconds: 30), (timer) {
|
||||||
if (isSocketConnected && socket.connected) {
|
if (isSocketConnected && socket.connected) {
|
||||||
socket.emit('heartbeat',
|
socket.emit('heartbeat',
|
||||||
{'passenger_id': box.read(BoxName.passengerID).toString()});
|
{'passenger_id': box.read(BoxName.passengerID).toString()});
|
||||||
|
|||||||
Reference in New Issue
Block a user