@@ -28,7 +28,32 @@ class OrderData {
final double ? driverEarningsExtra ;
final String ? driverEarningsCurrency ;
/// نوع الطلب: 'exclusive' خلال السبق الحصري، 'public' بعده، و null في
/// وضع البثّ الحر (عندها لا تُعرض شارة ويعمل المؤقّت بمدته العادية).
final String ? offerType ;
/// ثواني السبق الحصري كما حددها الخادم (DISPATCH_HEAD_START_SECONDS).
final int offerExpiresIn ;
/// رحلة محجوزة مسبقاً — موعد مضبوط لا طلب فوري.
final bool isScheduled ;
/// موعد الانطلاق كما حجزه الراكب. فارغ في الرحلات العادية.
final String scheduledAt ;
/// طلب وصل عبر رسالة نصية — الراكب بلا إنترنت.
///
/// ليست تفصيلاً تجميلياً: هذا الراكب لن يصله شات ولا إشعار ولن يرى
/// السائق على الخريطة. الاتصال الهاتفي هو القناة الوحيدة، والسائق
/// الذي لا يعرف ذلك سينتظر رداً لن يأتي ثم يُلغي.
final bool isSmsRide ;
OrderData ( {
this . offerType ,
this . offerExpiresIn = 0 ,
this . isScheduled = false ,
this . scheduledAt = ' ' ,
this . isSmsRide = false ,
required this . customerName ,
required this . customerToken ,
required this . tripDistanceKm ,
@@ -135,9 +160,95 @@ class OrderData {
list . length > 36 & & list [ 36 ] . toString ( ) . isNotEmpty
? list [ 36 ] . toString ( )
: null ,
// مسار FCM لا يحمل هذين اليوم — يبقيان محايدَين فلا تظهر شارة.
offerType: list . length > 37 & & list [ 37 ] . toString ( ) . isNotEmpty
? list [ 37 ] . toString ( )
: null ,
offerExpiresIn:
list . length > 38 ? ( int . tryParse ( list [ 38 ] . toString ( ) ) ? ? 0 ) : 0 ,
// الفهرسان 39/40 — يضيفهما add_ride.php للرحلات المحجوزة.
isScheduled: list . length > 39 & &
( list [ 39 ] . toString ( ) = = ' 1 ' | | list [ 39 ] . toString ( ) = = ' true ' ) ,
scheduledAt: list . length > 40 ? list [ 40 ] . toString ( ) : ' ' ,
// الفهرس 41 — طلب بالرسائل النصية.
isSmsRide: list . length > 41 & &
( list [ 41 ] . toString ( ) = = ' 1 ' | | list [ 41 ] . toString ( ) = = ' true ' ) ,
) ;
}
/// بناء من حمولة السوكِت (خريطة). أسماء الحقول تطابق buildMarketPayload()
/// في backend/ride/rides/add_ride.php — أي تغيير هناك يجب أن ينعكس هنا.
///
/// ملاحظة: حمولة السوق لا تحمل اسم الراكب ولا توكنه عمداً (تصل سائقين لن
/// يقودوا الرحلة)، فتبقى القيم الافتراضية حتى يُقبل الطلب.
factory OrderData . fromSocketMap ( Map < dynamic , dynamic > map ) {
String str ( String key , [ String fallback = ' ' ] ) = >
map [ key ] ? . toString ( ) . isNotEmpty = = true ? map [ key ] . toString ( ) : fallback ;
final distanceToPassengerMeters =
double . tryParse ( str ( ' distanceToPassenger ' , ' 0 ' ) ) ? ? 0.0 ;
return OrderData (
customerName: str ( ' passengerName ' , ' Unknown Customer ' ) ,
customerToken: str ( ' passengerToken ' , ' Unknown token ' ) ,
tripDistanceKm: double . tryParse ( str ( ' distance ' , ' 0 ' ) ) ? ? 0.0 ,
price: str ( ' price ' , ' 0 ' ) . split ( ' . ' ) [ 0 ] ,
startLocationAddress: str ( ' startName ' , ' Unknown Address ' ) ,
endLocationAddress: str ( ' endName ' , ' Unknown Address ' ) ,
distanceToPassengerKm: distanceToPassengerMeters / 1000.0 ,
tripDurationMinutes: _secondsToRoundedUpMinutes ( str ( ' duration ' , ' 0 ' ) ) ,
durationToPassengerMinutes:
_secondsToRoundedUpMinutes ( str ( ' durationToPassenger ' , ' 0 ' ) ) ,
rideType: _getRideType ( str ( ' carType ' , ' Unknown ' ) ) ,
orderId: str ( ' id ' , ' N/A ' ) ,
passengerId: str ( ' passengerId ' , ' N/A ' ) ,
passengerRate: str ( ' passengerRate ' , ' N/A ' ) ,
rawStartCoordinates: str ( ' start_location ' ) . isNotEmpty
? str ( ' start_location ' )
: ( str ( ' start_lat ' ) . isNotEmpty
? ' ${ str ( ' start_lat ' ) } , ${ str ( ' start_lng ' ) } '
: null ) ,
rawEndCoordinates: str ( ' end_location ' ) . isNotEmpty
? str ( ' end_location ' )
: ( str ( ' end_lat ' ) . isNotEmpty
? ' ${ str ( ' end_lat ' ) } , ${ str ( ' end_lng ' ) } '
: null ) ,
driverEarningsExtra: double . tryParse ( str ( ' driver_earnings_extra ' ) ) ,
driverEarningsCurrency: str ( ' driver_earnings_currency ' ) . isNotEmpty
? str ( ' driver_earnings_currency ' )
: null ,
offerType: str ( ' offer_type ' ) . isNotEmpty ? str ( ' offer_type ' ) : null ,
offerExpiresIn: int . tryParse ( str ( ' offer_expires_in ' , ' 0 ' ) ) ? ? 0 ,
isScheduled: str ( ' is_scheduled ' ) = = ' 1 ' | | str ( ' is_scheduled ' ) = = ' true ' ,
scheduledAt: str ( ' scheduled_at ' ) ,
isSmsRide: str ( ' is_sms ' ) = = ' 1 ' | | str ( ' is_sms ' ) = = ' true ' ,
) ;
}
/// الموحِّد: FCM يصل قائمةً مفهرسة، والسوكِت يصل خريطة JSON. النافذة
/// الواحدة تخدم المسارين، فالتمييز يتم هنا لا في كل مستدعٍ.
/// يرجع null على أي شكل غير معروف بدل أن يرمي — النافذة تبقى صامتة
/// بدل أن تنهار على حمولة غريبة.
static OrderData ? fromAny ( dynamic event ) {
if ( event is List ) return OrderData . fromList ( event ) ;
if ( event is Map ) {
// خريطتان مختلفتان تصلان هنا: حمولة السوكِت (أسماء الخادم: id،
// carType، startName) وخريطة toMap() الداخلية (orderId، rideType).
// نميّز بالمفتاح لا بالنوع، وإلا قرأنا إحداهما بقواعد الأخرى فخرجت
// كل الحقول فارغة بصمت.
if ( event . containsKey ( ' orderId ' ) | | event . containsKey ( ' rideType ' ) ) {
return OrderData . fromMap ( Map < String , dynamic > . from ( event ) ) ;
}
return OrderData . fromSocketMap ( event ) ;
}
return null ;
}
static String _getRideType ( String type ) {
switch ( type ) {
case ' Comfort ' :