import 'dart:convert'; import 'package:flutter/material.dart'; import 'package:get/get.dart'; import 'package:just_audio/just_audio.dart'; // لتشغيل صوت عند وصول رحلة import '../../constant/box_name.dart'; import '../../constant/links.dart'; import '../../main.dart'; // للوصول لـ box import '../functions/crud.dart'; import '../functions/location_controller.dart'; class RideAvailableController extends GetxController { bool isLoading = false; // RxList: أي تغيير هنا سينعكس فوراً على الشاشة RxList> availableRides = >[].obs; DateTime? _lastFetchTime; static const _cacheDuration = Duration(seconds: 5); // تقليل مدة الكاش قليلاً // مشغل الصوت final AudioPlayer _audioPlayer = AudioPlayer(); @override void onInit() { super.onInit(); // 1. جلب القائمة الأولية من السيرفر (HTTP) getRideAvailable(forceRefresh: true); // 2. تفعيل الاستماع المباشر للتحديثات (Socket) _initSocketListeners(); } @override void onClose() { // تنظيف الموارد عند الخروج var socket = Get.find().socket; socket?.off('market_new_ride'); socket?.off('ride_taken'); // تم توحيد الحدث لـ ride_taken _audioPlayer.dispose(); super.onClose(); } // ======================================================================== // 1. جلب الرحلات (HTTP Request) - الطريقة الجديدة (Lat/Lng) // ======================================================================== Future getRideAvailable({bool forceRefresh = false}) async { // منع الطلبات المتكررة السريعة if (!forceRefresh && _lastFetchTime != null && DateTime.now().difference(_lastFetchTime!) < _cacheDuration) { return; } try { if (forceRefresh) { isLoading = true; update(); } // الحصول على موقع السائق الحالي final location = Get.find().myLocation; // 🔥 التعديل الجوهري: نرسل Lat/Lng فقط بدلاً من Bounds var payload = { 'lat': location.latitude.toString(), 'lng': location.longitude.toString(), 'radius': '50', // نصف القطر بالكيلومتر (كما حددناه في السيرفر) }; var res = await CRUD().get(link: AppLink.getRideWaiting, payload: payload); isLoading = false; _lastFetchTime = DateTime.now(); if (res != 'failure') { final decodedResponse = jsonDecode(res); if (decodedResponse is Map && decodedResponse['status'] == 'success') { final rides = decodedResponse['message']; if (rides is List) { // تحويل البيانات وتخزينها availableRides.value = List>.from(rides); } else { availableRides.clear(); } } else { availableRides.clear(); } } update(); // تحديث الواجهة } catch (e) { isLoading = false; update(); print("Error fetching rides: $e"); } } // ======================================================================== // 2. الاستماع للسوكيت (Real-time Updates) ⚡ // ======================================================================== void _initSocketListeners() { var locationCtrl = Get.find(); var socket = locationCtrl.socket; if (socket == null) { print("⚠️ Socket is null in RideAvailableController"); return; } // A. عند وصول رحلة جديدة للسوق (market_new_ride) socket.on('market_new_ride', (data) { print("🔔 Socket: New Ride Market: $data"); if (data != null && data is Map) { // فلترة: هل نوع السيارة يناسبني؟ if (_isCarTypeMatch(data['carType'])) { // منع التكرار (إذا كانت الرحلة موجودة مسبقاً) bool exists = availableRides .any((r) => r['id'].toString() == data['id'].toString()); if (!exists) { // إضافة الرحلة لأعلى القائمة availableRides.insert(0, Map.from(data)); // تشغيل صوت تنبيه (Bling) 🎵 _playNotificationSound(); } } } }); // B. عند أخذ رحلة من قبل سائق آخر (ride_taken) // هذا الحدث يصل من acceptRide.php عبر السوكيت socket.on('ride_taken', (data) { print("🗑️ Socket: Ride Taken: $data"); if (data != null && data['ride_id'] != null) { // حذف الرحلة من القائمة فوراً availableRides.removeWhere( (r) => r['id'].toString() == data['ride_id'].toString()); } }); } // دالة مساعدة للتحقق من نوع السيارة bool _isCarTypeMatch(String? rideCarType) { if (rideCarType == null) return false; String myDriverType = box.read(BoxName.carTypeOfDriver).toString(); // منطق التوزيع الهرمي switch (myDriverType) { case 'Comfort': return ['Speed', 'Comfort', 'Fixed Price'].contains(rideCarType); case 'Speed': case 'Scooter': case 'Awfar Car': return rideCarType == myDriverType; case 'Lady': return ['Comfort', 'Speed', 'Lady'].contains(rideCarType); default: return true; // احتياطياً } } // تشغيل صوت التنبيه Future _playNotificationSound() async { try { // تأكد من وجود الملف في assets وإضافته في pubspec.yaml await _audioPlayer.setAsset('assets/audio/notification.mp3'); _audioPlayer.play(); } catch (e) { // تجاهل الخطأ إذا لم يوجد ملف صوت } } }