Files
Siro/siro_driver/lib/models/model/order_data.dart
T

318 lines
13 KiB
Dart
Executable File
Raw Blame History

This file contains invisible Unicode characters
This file contains invisible Unicode characters that are indistinguishable to humans but may be processed differently by a computer. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
// 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,
};
}
}