// 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; OrderData({ this.offerType, this.offerExpiresIn = 0, this.isScheduled = false, this.scheduledAt = '', 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 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 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() : '', ); } /// بناء من حمولة السوكِت (خريطة). أسماء الحقول تطابق buildMarketPayload() /// في backend/ride/rides/add_ride.php — أي تغيير هناك يجب أن ينعكس هنا. /// /// ملاحظة: حمولة السوق لا تحمل اسم الراكب ولا توكنه عمداً (تصل سائقين لن /// يقودوا الرحلة)، فتبقى القيم الافتراضية حتى يُقبل الطلب. factory OrderData.fromSocketMap(Map 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'), ); } /// الموحِّد: 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.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? 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? 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 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, }; } }