إصلاح أخطاء شاشة الكباتن من تحديث الذكاء الاصطناعي

استرجاع شبكة أمان موقع السائق (انحدارة من الالتزام ab3be7e2):
- كان شرط "Smart Mode" يتخطّى _startDriverLocationPollingWithTimer كلياً إذا
  جاء القبول من السوكيت. قبل إصلاح ترتيب الاتصال كان السوكيت يخسر دائماً
  فالـ polling يعمل ويحمل الموقع؛ وبعد أن صار يفوز تعطّلت شبكة الأمان.
- وصول حدث القبول لا يعني أن تدفّق المواقع يعمل: التدفّق له شرط مستقل —
  تطبيق السائق يحقن passenger_id في update_location فقط إذا كان rideStatus
  نشطاً و BoxName.passengerID مكتوباً، وهو لا يُكتب إلا بعد فتح صفحة خريطة
  السائق (map_driver_controller.dart:2444).
- الآن الـ polling يعمل دائماً، و handleDriverLocationUpdate يوقفه تلقائياً
  بعد 3 تحديثات ناجحة من السوكيت — وهذا المنطق كان موجوداً أصلاً وهو التصميم
  المقصود. السوكيت يبقى المسار السريع بلا فقدان شبكة الأمان.

عدم تعطيل إنشاء الرحلة بانتظار سوكيت غير قابل للوصول:
- ensureConnectedBeforeRide كانت تنتظر 4 ثوانٍ قبل كل طلب. سوكيتات
  Workerman تستمع نصّاً صريحاً على 2020/3030 بينما التطبيقان يستخدمان
  https:// عليها (nginx لا يمرّرها — انظر docker/nginx/default.conf)، فمصافحة
  TLS ضد منفذ غير TLS تتجمّد حتى المهلة ⇒ 4 ثوانٍ مهدورة في كل رحلة.
- المهلة صارت 2ث، وأُضيف cooldown دقيقتين: بعد فشل قريب لا ننتظر إطلاقاً بل
  نحاول الاتصال في الخلفية ونكمل الطلب فوراً.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
This commit is contained in:
Hamza-Ayed
2026-07-27 00:51:18 +03:00
co-authored by Claude Opus 5
parent b76eccb763
commit 6fec88251c
2 changed files with 33 additions and 9 deletions
@@ -225,11 +225,26 @@ class MapSocketController extends GetxController {
/// (الحدث يُفقد نهائياً في تلك الحالة — لا طابور في غرف Socket.IO). /// (الحدث يُفقد نهائياً في تلك الحالة — لا طابور في غرف Socket.IO).
/// لا يُفشل إنشاء الرحلة أبداً: عند انتهاء المهلة نكمل ونتّكل على /// لا يُفشل إنشاء الرحلة أبداً: عند انتهاء المهلة نكمل ونتّكل على
/// الـ FCM/polling كشبكة احتياطية. /// الـ FCM/polling كشبكة احتياطية.
/// آخر مرة فشل فيها الاتصال — حتى لا نُبطئ كل طلب رحلة بانتظارٍ عقيم إذا كان
/// السوكيت غير قابل للوصول أصلاً (منفذ مغلق/TLS غير مُنهى/سيرفر واقف).
static DateTime? _lastConnectFailure;
static const Duration _failureCooldown = Duration(minutes: 2);
Future<bool> ensureConnectedBeforeRide({ Future<bool> ensureConnectedBeforeRide({
Duration timeout = const Duration(seconds: 4), Duration timeout = const Duration(seconds: 2),
}) async { }) async {
if (isReadyForEvents) return true; if (isReadyForEvents) return true;
final lastFail = _lastConnectFailure;
if (lastFail != null &&
DateTime.now().difference(lastFail) < _failureCooldown) {
// فشل قريباً — نبدأ محاولة الاتصال في الخلفية ولا نُعطّل الطلب إطلاقاً
initConnectionWithSocket();
Log.print(
"⏭️ Skipping socket wait (recent failure). Ride proceeds on FCM/polling.");
return false;
}
await initConnectionWithSocket(); await initConnectionWithSocket();
final deadline = DateTime.now().add(timeout); final deadline = DateTime.now().add(timeout);
@@ -238,11 +253,13 @@ class MapSocketController extends GetxController {
} }
if (isSocketConnected) { if (isSocketConnected) {
_lastConnectFailure = null;
Log.print("✅ Socket ready before ride creation."); Log.print("✅ Socket ready before ride creation.");
return true; return true;
} }
_lastConnectFailure = DateTime.now();
Log.print( Log.print(
"⚠️ Socket not ready within ${timeout.inSeconds}s — continuing; FCM/polling will cover."); "⚠️ Socket not ready within ${timeout.inMilliseconds}ms — continuing; FCM/polling will cover.");
return false; return false;
} }
@@ -3917,13 +3917,20 @@ class RideLifecycleController extends GetxController {
PipService.enablePip(); PipService.enablePip();
if (source == "Socket" && mapSocket.isSocketConnected) { // ⚠️ لا نثق بالسوكيت قبل أن **يُثبت** أنه يوصّل المواقع فعلاً.
Log.print( // كان هنا شرط "Smart Mode" يتخطّى الـ polling كلياً إذا جاء القبول من
"🧠 Smart Mode: Socket accepted ride. Skipping polling, relying on WebSocket."); // السوكيت — وهو خطأ: وصول حدث القبول لا يعني أن تدفّق المواقع يعمل.
} else { // تدفّق المواقع له شرط مستقل تماماً: تطبيق السائق يحقن passenger_id في
Log.print("🔄 Fallback Mode: $source accepted ride. Starting polling."); // update_location فقط إذا كان rideStatus نشطاً و BoxName.passengerID
_startDriverLocationPollingWithTimer(); // مكتوباً — وهو لا يُكتب إلا بعد فتح صفحة خريطة السائق. فبين القبول وفتح
} // الصفحة لا يوجّه driver_socket أي موقع، وبلا polling لا يظهر الماركر أبداً.
//
// نشغّل الـ polling دائماً، و handleDriverLocationUpdate يوقفه تلقائياً
// بعد 3 تحديثات ناجحة من السوكيت (المنطق موجود هناك أصلاً). هكذا يبقى
// السوكيت هو المسار السريع بلا أن نفقد شبكة الأمان.
Log.print("🔄 Starting driver-location polling (source: $source). "
"Socket will take over and stop it once it proves delivery.");
_startDriverLocationPollingWithTimer();
} }
void _fillDriverDataLocally(Map<String, dynamic> data) { void _fillDriverDataLocally(Map<String, dynamic> data) {