12 KiB
12 KiB
🚶 دورة حياة الرحلة - تطبيق الراكب (Rider Ride Lifecycle)
هذا الملف هو مرجع هندسي شامل ودقيق (Source of Truth) يوضح كافة تفاصيل دورة حياة الرحلة (Ride Lifecycle) في تطبيق الراكب (Siro Rider). تمت كتابته وتوثيقه ليكون دليلاً استرشادياً كاملاً لأي مهندس برمجي أو نموذج ذكاء اصطناعي (AI Agent) يرغب في فهم أو تعديل منطق تتبع السائق، استقبال أحداث الرحلة، أو معالجة الحالات المباشرة.
1. الكنترولر المسؤول عن الرحلة (RideLifecycleController)
يعد
[RideLifecycleController](file:///Users/hamzaaleghwairyeen/development/App/Siro/siro_rider/lib/controller/home/map/ride_lifecycle_controller.dart)
هو عقل تطبيق الراكب الذي يدير الحالات والعدادات ومطابقة حركة السائق.
1.1 التهيئة وفحص البداية (Startup & Restoration)
* فحص حالة الرحلة الأولية عند بدء التشغيل:
Future<void> _checkInitialRideStatus() async
تقوم بالخطوات التالية عند تشغيل التطبيق:
1. استدعاء API جلب الحالة الحالية من الخادم عبر `getRideStatusFromStartApp()`.
2. إذا وجدت رحلة معلقة أو فعالة، تقوم بمطابقة حالة الرحلة النصية وتحويلها إلى قيم الـ Enum الخاص بـ `RideState` كالتالي:
* `"waiting"` -> `RideState.searching` (البحث عن سائق).
* `"apply"`, `"applied"`, `"accepted"` -> `RideState.driverApplied` (السائق قبل وهو في الطريق للراكب).
* `"arrived"` -> `RideState.driverArrived` (السائق وصل لنقطة الالتقاء).
* `"begin"` -> `RideState.inProgress` (الرحلة بدأت بالفعل نحو الوجهة النهائية).
* `"cancel"` -> `RideState.cancelled`.
* `"finished"` -> إذا كانت الرحلة بحاجة لتقييم (`needsReview == 1`) تحال لـ `RideState.preCheckReview` وإلا `RideState.noRide`.
3. استدعاء الموجه المركزي `_handleRideState()` لتوجيه واجهة المستخدم وتنشيط المهام التبعية.
1.2 موجه ومراقب حالة الرحلة (State Machine Coordinator)
* مراقب الحالات الرئيسي:
Future<void> _handleRideState(RideState state) async
يعمل كمركز مراقبة مستمر (Polling loop fallback) يقوم بالتحقق الدوري وتحديث شاشات الراكب ومؤقتات المراقبة عند كل حالة:
1. **`RideState.searching`**: يتحقق من فوات زمن البحث الأقصى (`_totalSearchTimeoutSeconds`). في حال انقضاء الوقت بدون موافقة، يوقف المؤشرات ويظهر نافذة زيادة السعر للراكب عبر `_showIncreaseFeeDialog()`.
2. **`RideState.driverApplied`**: إذا كان الويب سوكيت غير متصل، يعتمد على التحديثات الدورية عبر API `getRideStatus` للتحقق مما إذا كان السائق قد وصل (`Arrived`) أو بدأ الرحلة (`Begin`).
3. **`RideState.driverArrived`**: يقوم بتفعيل نافذة إشعار وصول السائق للراكب وبدء مؤقت الخمس دقائق المخصصة لانتظار الراكب.
4. **`RideState.inProgress`**: في حال غياب اتصال السوكيت، يقوم بعمل استعلام دوري للتحقق مما إذا أنهى السائق الرحلة (`Finished`) لتوجيه الراكب فوراً لصفحة الدفع والتقييم.
1.3 معالجة أحداث السائق التفاعلية (Driver Events Handler)
* قبول الطلب من السائق (Acceptance):
Future<void> processRideAcceptance({Map<String, dynamic>? driverData, required String source})
عند قبول الطلب (سواء عبر الـ Socket أو Polling):
1. نقل حالة التطبيق فوراً لـ `RideState.driverApplied` وتحديث الحالة لـ `'Apply'`.
2. استخراج بيانات السائق الفردية وتخزينها محلياً عبر `_fillDriverDataLocally()`.
3. إيقاظ خدمة الأنشطة الحية على iOS عبر `IosLiveActivityService.startRideActivity()`.
4. إرسال إشعار فوري للراكب عبر `RideLiveNotification.showDriverOnWay()`.
5. جلب موقع السائق الفعلي من الخريطة ورسم المسار بين السائق والراكب عبر `calculateDriverToPassengerRoute()`.
6. تشغيل مؤقت حساب المسافة والزمن التقديري (`startTimerFromDriverToPassengerAfterApplied()`).
* وصول السائق للراكب (Driver Arrival):
Future<void> processDriverArrival(String source)
1. تعيين حالة الرحلة محلياً لـ `RideState.driverArrived` وحفظ الحالة النصية لـ `'Arrived'`.
2. عرض ديالوج وصول السائق للراكب عبر `uiInteractions.driverArrivePassengerDialoge()`.
3. تفعيل إشعار وصول السائق العام عبر النظام.
4. تفعيل مؤقت انتظار السائق للراكب (5 دقائق).
5. الاستعداد المسبق ورسم الخط التقديري لمسار الرحلة الفعلي من موقع السائق إلى الوجهة النهائية للراكب.
* بدء الرحلة الفعلية (Begin Ride):
Future<void> processRideBegin({String source = "Unknown"})
1. نقل حالة التطبيق لـ `RideState.inProgress` وتخزين الحالة لـ `'Begin'`.
2. إيقاف ومسح مؤقتات الانتظار السابقة.
3. تنظيف مسار الخريطة الأصفر (مسار بيك اب الراكب) بالكامل لمنع التداخل والتشوه البصري.
4. رسم المسار الفعلي النهائي باللون **الأزرق** باتجاه وجهة الراكب النهائية (`myDestination`).
5. تفعيل عداد الرحلة التفاعلي `rideIsBeginPassengerTimer()`.
* إنهاء الرحلة (Finish Ride):
Future<void> processRideFinished(List<dynamic> driverList, {String source = "Unknown"})
1. نقل الحالة لـ `RideState.finished` وإيقاف وتصفير كافة عدادات ومؤقتات المراقبة.
2. تدمير وإغلاق سوكيت الرحلة النشط عبر `mapSocket.disposeRideSocket()`.
3. إيقاف وإغلاق خدمة الملاحة الحية على iOS والأنشطة الحية.
4. توجيه الراكب فوراً لصفحة التقييم والدفع المخصصة `RateDriverFromPassenger` وتمرير معرف السائق والرحلة والفاتورة.
2. بنية تحديث الويب سوكيت للمواقع (WebSocket Location Handler)
يتولى الكنترولر
[MapSocketController](file:///Users/hamzaaleghwairyeen/development/App/Siro/siro_rider/lib/controller/home/map/map_socket_controller.dart)
استقبال تحديثات السائق لحظياً عبر الحدث
`driver_location_update`
وتمرير البيانات المعالجة لمحركات الحساب والتحكم كالتالي:
void handleDriverLocationUpdate(dynamic data)
الخطوات التي تنفذها الدالة:
1. استلام وتمرير إحداثيات السائق الحالية وسرعته واتجاهه.
2. إذا تم استلام أكثر من 3 تحديثات ناجحة ومتتالية وموثوقة عبر السوكيت، يقوم بإيقاف استعلامات الـ Polling الدورية (`stopDriverLocationPolling()`) لتوفير استهلاك البيانات والبطارية.
3. استدعاء فحص الانحراف والمطابقة الجغرافية عبر `checkAndRecalculateIfDeviated()`.
4. توجيه كاميرا خريطة الراكب لاتباع إحداثيات السائق مع تعديل نسبة التقريب (Zoom) ديناميكياً بناءً على سرعة السيارة (تقريب الكاميرا عند البطء وتوسيع الرؤية عند السرعة العالية).
5. قراءة الحسابات الجاهزة للوقت المتبقي والمسافة من السيرفر إن وجدت وتمريرها، وإلا يتم استدعاء محرك الحساب المحلي للراكب عبر `updateRemainingRoute()`.
6. تحديث موقع ماركر سيارة السائق على الخريطة بسلاسة وتمرير زاوية الدوران الصحيحة (`updateDriverMarker()`).
3. محرك الحساب المحلي وحماية الانحراف (Local Routing & Deviation Engine)
لتلافي المشاكل الجغرافية وعرض أرقام مضللة للراكب، تم بناء محركات ذكية ومحلية لمعالجة المواقع:
3.1 مطابقة المسار وإعادة الحساب (Deviation Guard)
Future<void> checkAndRecalculateIfDeviated(LatLng driverPos, {double? heading, double? speed})
1. تقوم الدالة بمسح كافة نقاط المسار النشط ومقارنتها بموقع السائق الحالي لحساب أقرب نقطة جغرافية.
2. إذا تجاوزت المسافة الفاصلة بين السائق والمسار المخطط قيمة حد الانحراف (`_deviationThresholdMeters` - عادة 50 متر)، أو عند تطابق انحراف الاتجاه لمرتين متتاليتين، يتم إصدار أمر فوري بإعادة جلب ورسم المسار من إحداثيات السائق الحالية:
* إذا كانت الرحلة جارية نحو الوجهة: يتم رسم مسار جديد باتجاه وجهة الراكب النهائية.
* إذا كان السائق في طريقه للراكب: يتم رسم مسار جديد باتجاه موقع الراكب.
3.2 قص المسار واحتساب الـ ETA محلياً (Dynamic Route Trimming)
void updateRemainingRoute(LatLng driverPos, {bool updateEta = true})
1. عند تقدم السائق، تقوم الدالة بالبحث عن أقرب نقطة على خط المسار المتجه نحو الوجهة وقص (Trim) الأجزاء التي قطعتها السيارة بالفعل (`remainingPoints = route.sublist(closestIdx)`).
2. عند تفعيل `updateEta` (في حال عدم توفر معلومات مباشرة من السيرفر)، تقوم الدالة بحساب النسبة المئوية للمسافة المتبقية مقارنة بالمسافة الكلية الأصلية، وضربها في الزمن الكلي التقديري الأصلي لاستخراج قيمة زمن الوصول التقديري (ETA) المتبقي **محلياً وبشكل لحظي ودقيق جداً** دون الحاجة لإجراء اتصالات إضافية بالسيرفر.
3. تحديث خطوط الـ Polylines النشطة على الخريطة بالمسار المقصوص الجديد لضمان بقائه دقيقاً.