import 'dart:async'; import 'dart:convert'; import 'dart:io'; import 'package:flutter/foundation.dart'; import 'package:flutter/material.dart'; import 'package:flutter_overlay_window/flutter_overlay_window.dart'; import 'package:get/get.dart'; import 'package:google_maps_flutter/google_maps_flutter.dart'; import 'package:geolocator/geolocator.dart'; import 'package:http/http.dart' as http; import 'package:just_audio/just_audio.dart'; import 'dart:math' as math; import '../../../constant/box_name.dart'; import '../../../constant/links.dart'; import '../../../main.dart'; import '../../../print.dart'; import '../../../views/home/Captin/driver_map_page.dart'; import '../../../views/home/Captin/orderCaptin/marker_generator.dart'; import '../../../views/widgets/mydialoug.dart'; import '../../functions/crud.dart'; import '../../functions/location_controller.dart'; import '../../home/captin/home_captain_controller.dart'; import '../../firebase/notification_service.dart'; import '../navigation/decode_polyline_isolate.dart'; class OrderRequestController extends GetxController with WidgetsBindingObserver { // --- متغيرات التايمر --- double progress = 1.0; int duration = 15; int remainingTime = 15; Timer? _timer; bool applied = false; final locationController = Get.put(LocationController()); // 🔥 متغير لمنع تكرار القبول bool _isRideTakenHandled = false; // --- الأيقونات والماركرز --- BitmapDescriptor? driverIcon; Map markersMap = {}; Set get markers => markersMap.values.toSet(); // --- البيانات والتحكم --- // 🔥 تم إضافة myMapData لدعم السوكيت الجديد List? myList; Map? myMapData; GoogleMapController? mapController; // الإحداثيات (أزلنا late لتجنب الأخطاء القاتلة) double latPassenger = 0.0; double lngPassenger = 0.0; double latDestination = 0.0; double lngDestination = 0.0; // --- متغيرات العرض --- String passengerRating = "5.0"; String tripType = "Standard"; String totalTripDistance = "--"; String totalTripDuration = "--"; String tripPrice = "--"; String timeToPassenger = "جاري الحساب..."; String distanceToPassenger = "--"; // --- الخريطة --- Set polylines = {}; // حالة التطبيق والصوت bool isInBackground = false; final AudioPlayer audioPlayer = AudioPlayer(); @override Future onInit() async { // 🛑 حماية من الفتح المتكرر لنفس الطلب if (Get.arguments == null) { print("❌ OrderController Error: No arguments received."); Get.back(); // إغلاق الصفحة فوراً return; } super.onInit(); WidgetsBinding.instance.addObserver(this); _checkOverlay(); // 🔥 تهيئة البيانات هي الخطوة الأولى والأهم _initializeData(); _parseExtraData(); // 1. تجهيز أيقونة السائق await _prepareDriverIcon(); // 2. وضع الماركرز المبدئية _updateMarkers( paxTime: "...", paxDist: "", destTime: totalTripDuration, destDist: totalTripDistance); // 3. رسم مبدئي _initialMapSetup(); // 4. الاستماع للسوكيت _listenForRideTaken(); // 5. حساب المسارين await _calculateFullJourney(); // 6. تشغيل التايمر startTimer(); } // ---------------------------------------------------------------------- // 🔥🔥🔥 Smart Data Handling (List & Map Support) 🔥🔥🔥 // ---------------------------------------------------------------------- void _initializeData() { var args = Get.arguments; print("📦 Order Controller Received Type: ${args.runtimeType}"); print("📦 Order Controller Data: $args"); if (args != null) { // الحالة 1: قائمة مباشرة (Legacy / Some Firebase formats) if (args is List) { myList = args; } // الحالة 2: خريطة (Map) else if (args is Map) { // أ) هل هي قادمة من Firebase وتحتوي على DriverList؟ if (args.containsKey('DriverList')) { var listData = args['DriverList']; if (listData is List) { myList = listData; } else if (listData is String) { // أحياناً تصل كنص مشفر داخل الـ Map try { myList = jsonDecode(listData); } catch (e) { print("Error decoding DriverList: $e"); } } } // ب) هل هي قادمة من Socket بالمفاتيح الرقمية ("0", "1", ...)؟ else { myMapData = args; } } } // تعبئة الإحداثيات باستخدام الدالة الذكية _getValueAt latPassenger = _parseCoord(_getValueAt(0)); lngPassenger = _parseCoord(_getValueAt(1)); latDestination = _parseCoord(_getValueAt(3)); lngDestination = _parseCoord(_getValueAt(4)); print( "📍 Parsed Coordinates: Pax($latPassenger, $lngPassenger) -> Dest($latDestination, $lngDestination)"); } /// 🔥 دالة ذكية تجلب القيمة سواء كانت البيانات في List أو Map dynamic _getValueAt(int index) { // الأولوية للقائمة if (myList != null && index < myList!.length) { return myList![index]; } // ثم الخريطة (السوكيت) - المفاتيح عبارة عن String if (myMapData != null && myMapData!.containsKey(index.toString())) { return myMapData![index.toString()]; } return null; } /// الدالة التي يستخدمها باقي الكود لجلب البيانات كنصوص String _safeGet(int index) { var val = _getValueAt(index); if (val != null) { return val.toString(); } return ""; } double _parseCoord(dynamic val) { if (val == null) return 0.0; String s = val.toString().replaceAll(',', '').trim(); if (s.contains(' ')) s = s.split(' ')[0]; return double.tryParse(s) ?? 0.0; } void _parseExtraData() { passengerRating = _safeGet(33).isEmpty ? "5.0" : _safeGet(33); tripType = _safeGet(31); totalTripDistance = _safeGet(5); totalTripDuration = _safeGet(19); tripPrice = _safeGet(2); } // ---------------------------------------------------------------------- // 🔥🔥🔥 Core Logic: Concurrent API Calls & Bounds 🔥🔥🔥 // ---------------------------------------------------------------------- Future _calculateFullJourney() async { try { Position driverPos = await Geolocator.getCurrentPosition( desiredAccuracy: LocationAccuracy.high); LatLng driverLatLng = LatLng(driverPos.latitude, driverPos.longitude); updateDriverLocation(driverLatLng, driverPos.heading); var pickupFuture = _fetchRouteData( start: driverLatLng, end: LatLng(latPassenger, lngPassenger), color: Colors.amber, id: 'pickup_route'); var tripFuture = _fetchRouteData( start: LatLng(latPassenger, lngPassenger), end: LatLng(latDestination, lngDestination), color: Colors.green, id: 'trip_route', isDashed: true); var results = await Future.wait([pickupFuture, tripFuture]); var pickupResult = results[0]; var tripResult = results[1]; if (pickupResult != null) { distanceToPassenger = pickupResult['distance_text']; timeToPassenger = pickupResult['duration_text']; polylines.add(pickupResult['polyline']); } if (tripResult != null) { totalTripDistance = tripResult['distance_text']; totalTripDuration = tripResult['duration_text']; polylines.add(tripResult['polyline']); } await _updateMarkers( paxTime: timeToPassenger, paxDist: distanceToPassenger, destTime: totalTripDuration, destDist: totalTripDistance); zoomToFitRide(driverLatLng); update(); } catch (e) { print("❌ Error in Journey Calculation: $e"); } } Future?> _fetchRouteData( {required LatLng start, required LatLng end, required Color color, required String id, bool isDashed = false}) async { try { // حماية من الإحداثيات الصفرية if (start.latitude == 0 || end.latitude == 0) return null; String apiUrl = "https://routesjo.intaleq.xyz/route/v1/driving"; String coords = "${start.longitude},${start.latitude};${end.longitude},${end.latitude}"; String url = "$apiUrl/$coords?steps=false&overview=full"; var response = await http.get(Uri.parse(url)); if (response.statusCode == 200) { var json = jsonDecode(response.body); if (json['code'] == 'Ok' && json['routes'].isNotEmpty) { var route = json['routes'][0]; double distM = double.parse(route['distance'].toString()); double durS = double.parse(route['duration'].toString()); String distText = "${(distM / 1000).toStringAsFixed(1)} كم"; String durText = "${(durS / 60).toStringAsFixed(0)} دقيقة"; String geometry = route['geometry']; List points = await compute(decodePolylineIsolate, geometry); Polyline polyline = Polyline( polylineId: PolylineId(id), color: color, width: 5, points: points, patterns: isDashed ? [PatternItem.dash(10), PatternItem.gap(5)] : [], startCap: Cap.roundCap, endCap: Cap.roundCap, ); return { 'distance_text': distText, 'duration_text': durText, 'polyline': polyline }; } } } catch (e) { print("Route Fetch Error: $e"); } return null; } void zoomToFitRide(LatLng driverPos) { if (mapController == null) return; // حماية من النقاط الصفرية if (latPassenger == 0 || latDestination == 0) return; List points = [ driverPos, LatLng(latPassenger, lngPassenger), LatLng(latDestination, lngDestination), ]; double minLat = points.first.latitude; double maxLat = points.first.latitude; double minLng = points.first.longitude; double maxLng = points.first.longitude; for (var p in points) { if (p.latitude < minLat) minLat = p.latitude; if (p.latitude > maxLat) maxLat = p.latitude; if (p.longitude < minLng) minLng = p.longitude; if (p.longitude > maxLng) maxLng = p.longitude; } mapController!.animateCamera(CameraUpdate.newLatLngBounds( LatLngBounds( southwest: LatLng(minLat, minLng), northeast: LatLng(maxLat, maxLng), ), 100.0, )); } // ---------------------------------------------------------------------- // Markers & Setup // ---------------------------------------------------------------------- Future _prepareDriverIcon() async { driverIcon = await MarkerGenerator.createDriverMarker(); } Future _updateMarkers( {required String paxTime, required String paxDist, String? destTime, String? destDist}) async { // حماية إذا لم يتم جلب الإحداثيات if (latPassenger == 0 || latDestination == 0) return; final BitmapDescriptor pickupIcon = await MarkerGenerator.createCustomMarkerBitmap( title: paxTime, subtitle: paxDist, color: Colors.green.shade700, iconData: Icons.person_pin_circle, ); final BitmapDescriptor dropoffIcon = await MarkerGenerator.createCustomMarkerBitmap( title: destTime ?? totalTripDuration, subtitle: destDist ?? totalTripDistance, color: Colors.red.shade800, iconData: Icons.flag, ); markersMap[const MarkerId('pax')] = Marker( markerId: const MarkerId('pax'), position: LatLng(latPassenger, lngPassenger), icon: pickupIcon, anchor: const Offset(0.5, 0.85), ); markersMap[const MarkerId('dest')] = Marker( markerId: const MarkerId('dest'), position: LatLng(latDestination, lngDestination), icon: dropoffIcon, anchor: const Offset(0.5, 0.85), ); update(); } void _initialMapSetup() async { Position driverPos = await Geolocator.getCurrentPosition(); LatLng driverLatLng = LatLng(driverPos.latitude, driverPos.longitude); if (driverIcon != null) { markersMap[const MarkerId('driver')] = Marker( markerId: const MarkerId('driver'), position: driverLatLng, icon: driverIcon!, rotation: driverPos.heading, anchor: const Offset(0.5, 0.5), flat: true, zIndex: 10); } if (latPassenger != 0 && lngPassenger != 0) { polylines.add(Polyline( polylineId: const PolylineId('temp_line'), points: [driverLatLng, LatLng(latPassenger, lngPassenger)], color: Colors.grey, width: 2, patterns: [PatternItem.dash(10), PatternItem.gap(10)], )); zoomToFitRide(driverLatLng); } update(); } void updateDriverLocation(LatLng newPos, double heading) { if (driverIcon != null) { markersMap[const MarkerId('driver')] = Marker( markerId: const MarkerId('driver'), position: newPos, icon: driverIcon!, rotation: heading, anchor: const Offset(0.5, 0.5), flat: true, zIndex: 10, ); update(); } } void onMapCreated(GoogleMapController controller) { mapController = controller; } // --- قبول الطلب وإدارة التايمر --- void startTimer() { _timer?.cancel(); remainingTime = duration; _playAudio(); _timer = Timer.periodic(const Duration(seconds: 1), (timer) { if (remainingTime <= 0) { timer.cancel(); _stopAudio(); if (!applied) Get.back(); } else { remainingTime--; progress = remainingTime / duration; update(); } }); } void endTimer() => _timer?.cancel(); void changeApplied() => applied = true; void _playAudio() async { try { await audioPlayer.setAsset('assets/order.mp3', preload: true); await audioPlayer.setLoopMode(LoopMode.one); await audioPlayer.play(); } catch (e) { print(e); } } void _stopAudio() => audioPlayer.stop(); void _listenForRideTaken() { if (locationController.socket != null) { locationController.socket!.off('ride_taken'); locationController.socket!.on('ride_taken', (data) { if (_isRideTakenHandled) return; String takenRideId = data['ride_id'].toString(); String myCurrentRideId = _safeGet(16); String whoTookIt = data['taken_by_driver_id'].toString(); String myDriverId = box.read(BoxName.driverID).toString(); if (takenRideId == myCurrentRideId && whoTookIt != myDriverId) { _isRideTakenHandled = true; endTimer(); if (Get.isSnackbarOpen) Get.closeCurrentSnackbar(); if (Get.isDialogOpen ?? false) Get.back(); Get.back(); Get.snackbar("تنبيه", "تم قبول الطلب من قبل سائق آخر", backgroundColor: Colors.orange, colorText: Colors.white); } }); } } // Lifecycle @override void didChangeAppLifecycleState(AppLifecycleState state) { super.didChangeAppLifecycleState(state); if (state == AppLifecycleState.paused || state == AppLifecycleState.detached) { isInBackground = true; } else if (state == AppLifecycleState.resumed) { isInBackground = false; FlutterOverlayWindow.closeOverlay(); } } void _checkOverlay() async { if (Platform.isAndroid && await FlutterOverlayWindow.isActive()) { await FlutterOverlayWindow.closeOverlay(); } } // Accept Order Logic Future acceptOrder() async { endTimer(); _stopAudio(); var res = await CRUD() .post(link: "${AppLink.ride}/rides/acceptRide.php", payload: { 'id': _safeGet(16), 'rideTimeStart': DateTime.now().toString(), 'status': 'Apply', 'passengerToken': _safeGet(9), 'driver_id': box.read(BoxName.driverID), }); if (res == 'failure') { MyDialog().getDialog("عذراً، الطلب أخذه سائق آخر.", '', () { Get.back(); Get.back(); }); } else { Get.put(HomeCaptainController()).changeRideId(); box.write(BoxName.statusDriverLocation, 'on'); changeApplied(); var rideArgs = { 'passengerLocation': '${_safeGet(0)},${_safeGet(1)}', 'passengerDestination': '${_safeGet(3)},${_safeGet(4)}', 'Duration': totalTripDuration, 'totalCost': _safeGet(26), 'Distance': totalTripDistance, 'name': _safeGet(8), 'phone': _safeGet(10), 'email': _safeGet(28), 'WalletChecked': _safeGet(13), 'tokenPassenger': _safeGet(9), 'direction': 'https://www.google.com/maps/dir/${_safeGet(0)}/${_safeGet(1)}/', 'DurationToPassenger': timeToPassenger, 'rideId': _safeGet(16), 'passengerId': _safeGet(7), 'driverId': _safeGet(18), 'durationOfRideValue': totalTripDuration, 'paymentAmount': _safeGet(2), 'paymentMethod': _safeGet(13) == 'true' ? 'visa' : 'cash', 'isHaveSteps': _safeGet(20), 'step0': _safeGet(21), 'step1': _safeGet(22), 'step2': _safeGet(23), 'step3': _safeGet(24), 'step4': _safeGet(25), 'passengerWalletBurc': _safeGet(26), 'timeOfOrder': DateTime.now().toString(), 'totalPassenger': _safeGet(2), 'carType': _safeGet(31), 'kazan': _safeGet(32), 'startNameLocation': _safeGet(29), 'endNameLocation': _safeGet(30), }; box.write(BoxName.rideArguments, rideArgs); Get.off(() => PassengerLocationMapPage(), arguments: rideArgs); } } @override void onClose() { locationController.socket?.off('ride_taken'); audioPlayer.dispose(); WidgetsBinding.instance.removeObserver(this); _timer?.cancel(); mapController?.dispose(); super.onClose(); } }