318 lines
13 KiB
Dart
Executable File
318 lines
13 KiB
Dart
Executable File
// lib/models/model/order_data.dart
|
||
|
||
import 'package:get/get.dart';
|
||
|
||
class OrderData {
|
||
final String customerName;
|
||
final String customerToken;
|
||
final double tripDistanceKm; // The total trip distance in kilometers
|
||
final String price;
|
||
final String startLocationAddress;
|
||
final String endLocationAddress;
|
||
|
||
final double
|
||
distanceToPassengerKm; // The distance to the passenger in kilometers
|
||
final int tripDurationMinutes; // Total trip duration in minutes (rounded up)
|
||
final int
|
||
durationToPassengerMinutes; // Duration to reach the passenger in minutes (rounded up)
|
||
|
||
final String rideType;
|
||
final String orderId;
|
||
final String passengerId;
|
||
final String passengerRate;
|
||
|
||
final String? rawStartCoordinates;
|
||
final String? rawEndCoordinates;
|
||
|
||
// 🆕 "أرباحك أعلى" — الفرق الإيجابي بأجرة السائق مقارنة بالمنافس (إن وُجد)
|
||
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,
|
||
required this.price,
|
||
required this.startLocationAddress,
|
||
required this.endLocationAddress,
|
||
required this.distanceToPassengerKm,
|
||
required this.tripDurationMinutes,
|
||
required this.durationToPassengerMinutes,
|
||
required this.rideType,
|
||
required this.orderId,
|
||
required this.passengerId,
|
||
required this.passengerRate,
|
||
this.rawStartCoordinates,
|
||
this.rawEndCoordinates,
|
||
this.driverEarningsExtra,
|
||
this.driverEarningsCurrency,
|
||
});
|
||
|
||
// --- NEW: Factory constructor to create an instance from a Map ---
|
||
// This is the missing method that was causing the error.
|
||
factory OrderData.fromMap(Map<String, dynamic> map) {
|
||
return OrderData(
|
||
// For strings, provide a default value in case the map key is null
|
||
customerName: map['customerName']?.toString() ?? 'Unknown Customer',
|
||
customerToken: map['customerToken']?.toString() ?? 'Unknown token',
|
||
|
||
// For numbers, cast from 'num' to handle both int and double, with a default value
|
||
tripDistanceKm: (map['tripDistanceKm'] as num?)?.toDouble() ?? 0.0,
|
||
|
||
price: map['price']?.toString() ?? '0',
|
||
startLocationAddress:
|
||
map['startLocationAddress']?.toString() ?? 'Unknown Address',
|
||
endLocationAddress:
|
||
map['endLocationAddress']?.toString() ?? 'Unknown Address',
|
||
|
||
distanceToPassengerKm:
|
||
(map['distanceToPassengerKm'] as num?)?.toDouble() ?? 0.0,
|
||
|
||
tripDurationMinutes: (map['tripDurationMinutes'] as num?)?.toInt() ?? 0,
|
||
durationToPassengerMinutes:
|
||
(map['durationToPassengerMinutes'] as num?)?.toInt() ?? 0,
|
||
|
||
rideType: map['rideType']?.toString() ?? 'Unknown',
|
||
orderId: map['orderId']?.toString() ?? 'N/A',
|
||
passengerId: map['passengerId']?.toString() ?? 'N/A',
|
||
passengerRate: map['passengerRate']?.toString() ?? 'N/A',
|
||
|
||
// For nullable strings, direct access is fine as it returns null if the key doesn't exist
|
||
rawStartCoordinates: map['rawStartCoordinates'],
|
||
rawEndCoordinates: map['rawEndCoordinates'],
|
||
driverEarningsExtra: (map['driverEarningsExtra'] as num?)?.toDouble(),
|
||
driverEarningsCurrency: map['driverEarningsCurrency']?.toString(),
|
||
);
|
||
}
|
||
|
||
// A helper function to convert seconds to rounded-up minutes
|
||
static int _secondsToRoundedUpMinutes(String secondsString) {
|
||
final seconds = double.tryParse(secondsString) ?? 0.0;
|
||
if (seconds <= 0) return 0;
|
||
return (seconds / 60)
|
||
.ceil(); // .ceil() rounds up (e.g., 0.1 minutes becomes 1 minute)
|
||
}
|
||
|
||
// Your existing factory for creating an instance from a List
|
||
factory OrderData.fromList(List<dynamic> list) {
|
||
double distanceToPassengerMeters =
|
||
list.length > 12 ? (double.tryParse(list[12].toString()) ?? 0.0) : 0.0;
|
||
|
||
return OrderData(
|
||
customerName: list.length > 8 ? list[8].toString() : 'Unknown Customer',
|
||
customerToken: list.length > 9 ? list[9].toString() : 'Unknown token',
|
||
tripDistanceKm:
|
||
list.length > 5 ? (double.tryParse(list[5].toString()) ?? 0.0) : 0.0,
|
||
price: list.length > 2 ? list[2].toString().split('.')[0] : '0',
|
||
startLocationAddress:
|
||
list.length > 29 ? list[29].toString() : 'Unknown Address',
|
||
endLocationAddress:
|
||
list.length > 30 ? list[30].toString() : 'Unknown Address',
|
||
|
||
distanceToPassengerKm:
|
||
distanceToPassengerMeters / 1000.0, // Convert meters to kilometers
|
||
|
||
tripDurationMinutes:
|
||
list.length > 4 ? _secondsToRoundedUpMinutes(list[4].toString()) : 0,
|
||
durationToPassengerMinutes: list.length > 15
|
||
? _secondsToRoundedUpMinutes(list[15].toString())
|
||
: 0,
|
||
|
||
rideType:
|
||
list.length > 31 ? _getRideType(list[31].toString()) : 'Unknown',
|
||
orderId: list.length > 16 ? list[16].toString() : 'N/A',
|
||
passengerId: list.length > 7 ? list[7].toString() : 'N/A',
|
||
passengerRate: list.length > 33 ? list[33].toString() : 'N/A',
|
||
|
||
rawStartCoordinates: list.isNotEmpty ? list[0].toString() : null,
|
||
rawEndCoordinates: list.length > 1 ? list[1].toString() : null,
|
||
|
||
// 🆕 Index 35/36: "أرباحك أعلى"
|
||
driverEarningsExtra: list.length > 35 && list[35].toString().isNotEmpty
|
||
? double.tryParse(list[35].toString())
|
||
: null,
|
||
driverEarningsCurrency:
|
||
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':
|
||
return 'Comfort ❄️'.tr;
|
||
case 'Lady':
|
||
return 'Lady 👩'.tr;
|
||
case 'Speed':
|
||
return 'Speed 🔻'.tr;
|
||
case 'Mashwari':
|
||
return 'Mashwari'.tr;
|
||
case 'Rayeh Gai':
|
||
return 'Rayeh Gai'.tr;
|
||
default:
|
||
return type.tr;
|
||
}
|
||
}
|
||
|
||
// Getter to parse start coordinates
|
||
Map<String, double?>? get startCoordinates {
|
||
if (rawStartCoordinates == null) return null;
|
||
final parts = rawStartCoordinates!.split(',');
|
||
if (parts.length == 2) {
|
||
return {
|
||
'lat': double.tryParse(parts[0].trim()),
|
||
'lng': double.tryParse(parts[1].trim())
|
||
};
|
||
}
|
||
return null;
|
||
}
|
||
|
||
// Getter to parse end coordinates
|
||
Map<String, double?>? get endCoordinates {
|
||
if (rawEndCoordinates == null) return null;
|
||
final parts = rawEndCoordinates!.split(',');
|
||
if (parts.length == 2) {
|
||
return {
|
||
'lat': double.tryParse(parts[0].trim()),
|
||
'lng': double.tryParse(parts[1].trim())
|
||
};
|
||
}
|
||
return null;
|
||
}
|
||
|
||
// Your existing method to convert the object TO a Map.
|
||
// This is used to pass the data from the overlay to the main app.
|
||
Map<String, dynamic> toMap() {
|
||
return {
|
||
'customerName': customerName,
|
||
'tripDistanceKm': tripDistanceKm,
|
||
'price': price,
|
||
'startLocationAddress': startLocationAddress,
|
||
'endLocationAddress': endLocationAddress,
|
||
'distanceToPassengerKm': distanceToPassengerKm,
|
||
'tripDurationMinutes': tripDurationMinutes,
|
||
'durationToPassengerMinutes': durationToPassengerMinutes,
|
||
'rideType': rideType,
|
||
'orderId': orderId,
|
||
'passengerId': passengerId,
|
||
'passengerRate': passengerRate,
|
||
'rawStartCoordinates': rawStartCoordinates,
|
||
'rawEndCoordinates': rawEndCoordinates,
|
||
'driverEarningsExtra': driverEarningsExtra,
|
||
'driverEarningsCurrency': driverEarningsCurrency,
|
||
};
|
||
}
|
||
}
|