diff --git a/siro_rider/lib/controller/home/map/map_socket_controller.dart b/siro_rider/lib/controller/home/map/map_socket_controller.dart index 3f090f88..fc4daa58 100644 --- a/siro_rider/lib/controller/home/map/map_socket_controller.dart +++ b/siro_rider/lib/controller/home/map/map_socket_controller.dart @@ -18,6 +18,12 @@ class MapSocketController extends GetxController { late io_client.Socket socket; bool isSocketConnected = false; bool _isSocketInitialized = false; + // يغطّي نافذة الـ await على قراءة الـ JWT: بلا هذا يقدر نداءان متزامنان + // (initializeDataAfterLogin + startSearchingForDriver) يفتحان سوكيتين. + bool _isConnecting = false; + + /// جاهز لاستقبال الأحداث — أي منضمّ فعلياً لغرفة passenger_{id} على السيرفر. + bool get isReadyForEvents => isSocketConnected && _isSocketInitialized; Timer? _heartbeatTimer; DateTime? _lastSocketLocationTime; int _socketLocationUpdatesCount = 0; @@ -53,16 +59,26 @@ class MapSocketController extends GetxController { return ''; } + /// يجب أن يُنادى **قبل** إنشاء الرحلة: غرف Socket.IO لا تخزّن الأحداث، فإن + /// قبل كابتن قبل أن ينضم الراكب لغرفته فإن ride_status_change يُرمى نهائياً + /// ولا يبقى إلا الـ FCM/polling. النداء آمن ومتكرر (idempotent). Future initConnectionWithSocket() async { - if (isSocketConnected) return; + if (isSocketConnected || _isSocketInitialized || _isConnecting) return; + _isConnecting = true; String passengerId = box.read(BoxName.passengerID).toString(); final String jwt = await _getJwtForSocket(); if (jwt.isEmpty) { + _isConnecting = false; Log.print( "⚠️ No JWT available — passenger socket would be rejected by the server. Aborting connect."); return; } + if (passengerId.isEmpty || passengerId == 'null') { + _isConnecting = false; + Log.print("⚠️ No passengerID yet — deferring socket connect."); + return; + } Log.print("🔌 Initializing Socket for Passenger: $passengerId"); socket = io_client.io( @@ -80,6 +96,7 @@ class MapSocketController extends GetxController { .build(), ); _isSocketInitialized = true; + _isConnecting = false; socket.connect(); @@ -199,6 +216,32 @@ class MapSocketController extends GetxController { }); } + /// يوصل السوكيت وينتظر انضمامه فعلياً لغرفة الراكب، بسقف زمني. + /// يُنادى قبل إنشاء الرحلة حتى لا يقبل كابتن والراكب ما زال خارج غرفته + /// (الحدث يُفقد نهائياً في تلك الحالة — لا طابور في غرف Socket.IO). + /// لا يُفشل إنشاء الرحلة أبداً: عند انتهاء المهلة نكمل ونتّكل على + /// الـ FCM/polling كشبكة احتياطية. + Future ensureConnectedBeforeRide({ + Duration timeout = const Duration(seconds: 4), + }) async { + if (isReadyForEvents) return true; + + await initConnectionWithSocket(); + + final deadline = DateTime.now().add(timeout); + while (!isSocketConnected && DateTime.now().isBefore(deadline)) { + await Future.delayed(const Duration(milliseconds: 100)); + } + + if (isSocketConnected) { + Log.print("✅ Socket ready before ride creation."); + return true; + } + Log.print( + "⚠️ Socket not ready within ${timeout.inSeconds}s — continuing; FCM/polling will cover."); + return false; + } + // ── مواصلاتي: اشتراك/إلغاء اشتراك ببثّ موقع خط ───────────── // يُستدعى عند فتح/إغلاق شاشة تتبع الباص الحي. يضمن السوكيت متصلاً أولاً. Future subscribeToTransitRoute(int routeId) async { diff --git a/siro_rider/lib/controller/home/map/ride_lifecycle_controller.dart b/siro_rider/lib/controller/home/map/ride_lifecycle_controller.dart index efeaa5fe..0fd1a236 100644 --- a/siro_rider/lib/controller/home/map/ride_lifecycle_controller.dart +++ b/siro_rider/lib/controller/home/map/ride_lifecycle_controller.dart @@ -728,6 +728,13 @@ class RideLifecycleController extends GetxController { _currentSearchPhase = 0; update(); + // ⚠️ الترتيب مقصود: نضمن انضمام الراكب لغرفته على السوكيت **قبل** إنشاء + // الرحلة. add_ride.php يوزّع الطلب على الكباتن فوراً، وكابتن ينظر لقائمة + // السوق يقدر يقبل في أقل من ثانية. وغرف Socket.IO لا تخزّن شيئاً: لو قبل + // قبل أن ينضم الراكب فإن ride_status_change يُرمى نهائياً ويبقى الاعتماد + // على FCM/polling وحدهما. كان السوكيت يُوصَل بعد الإنشاء فيخسر السباق دائماً. + await mapSocket.ensureConnectedBeforeRide(); + bool rideCreated = await postRideDetailsToServer(); if (!rideCreated) { @@ -739,7 +746,6 @@ class RideLifecycleController extends GetxController { } _addRideToWaitingTable(); - mapSocket.initConnectionWithSocket(); } void _showAiNegotiatorDialog() { @@ -4444,6 +4450,12 @@ class RideLifecycleController extends GetxController { getLocationArea(passengerLocation.latitude, passengerLocation.longitude); await _stagePricingAndState(); await _stageNiceToHave(); + + // نوصل السوكيت عند فتح الخريطة لا عند إنشاء الرحلة، حتى تكون العضوية في + // الغرفة جاهزة سلفاً فيصير انتظار ensureConnectedBeforeRide صفراً عملياً، + // ويسبق السوكيت الـ FCM والـ polling كما هو مفروض بالتصميم. + mapSocket.initConnectionWithSocket(); + startMasterTimer(); }