import 'dart:async'; import 'dart:convert'; import 'dart:io'; import 'package:flutter/material.dart'; import 'package:flutter/services.dart'; import 'package:get/get.dart'; import 'package:get_storage/get_storage.dart'; import 'package:intl/date_symbol_data_local.dart'; import 'package:siro_driver/constant/box_name.dart'; import 'package:trip_overlay_plugin/trip_overlay_plugin.dart'; import 'package:wakelock_plus/wakelock_plus.dart'; import 'package:firebase_core/firebase_core.dart'; import 'package:firebase_messaging/firebase_messaging.dart'; import 'package:flutter_local_notifications/flutter_local_notifications.dart'; import 'package:flutter_secure_storage/flutter_secure_storage.dart'; import 'package:permission_handler/permission_handler.dart'; import 'package:device_info_plus/device_info_plus.dart'; import 'constant/api_key.dart'; import 'constant/info.dart'; import 'constant/links.dart'; import 'controller/firebase/firbase_messge.dart'; import 'controller/firebase/local_notification.dart'; import 'controller/functions/background_service.dart'; import 'controller/functions/crud.dart'; import 'controller/home/captin/home_captain_controller.dart'; import 'controller/local/local_controller.dart'; import 'controller/local/translations.dart'; import 'firebase_options.dart'; import 'models/db_sql.dart'; import 'print.dart'; import 'splash_screen_page.dart'; import 'views/home/Captin/orderCaptin/order_request_page.dart'; import 'views/home/Captin/driver_map_page.dart'; import 'controller/profile/setting_controller.dart'; import 'controller/voice_call_controller.dart'; final box = GetStorage(); const storage = FlutterSecureStorage(); DbSql sql = DbSql.instance; final GlobalKey navigatorKey = GlobalKey(); const platform = MethodChannel('com.siro_driver/app_control'); const String backgroundServiceChannelId = 'driver_service_channel'; const String locationServiceChannelId = 'location_service_channel'; const String geolocatorChannelId = 'geolocator_channel'; Future initFirebaseIfNeeded() async { if (Firebase.apps.isEmpty) { await Firebase.initializeApp( options: DefaultFirebaseOptions.currentPlatform); } else { Firebase.app(); } } Future requestNotificationPermission() async { if (Platform.isAndroid) { try { final androidInfo = await DeviceInfoPlugin().androidInfo; if (androidInfo.version.sdkInt >= 33) { final status = await Permission.notification.request(); if (status.isGranted) return true; if (status.isDenied) return false; if (status.isPermanentlyDenied) { await openAppSettings(); return false; } } else { return true; } } catch (e) { print('❌ Error requesting notification permission: $e'); return false; } } return true; } Future createAllNotificationChannels() async { if (!Platform.isAndroid) return; final FlutterLocalNotificationsPlugin flutterLocalNotificationsPlugin = FlutterLocalNotificationsPlugin(); const AndroidNotificationChannel backgroundChannel = AndroidNotificationChannel(backgroundServiceChannelId, 'خدمة السائق', description: 'استقبال الطلبات في الخلفية', importance: Importance.low, playSound: false, enableVibration: false, showBadge: false); const AndroidNotificationChannel locationChannel = AndroidNotificationChannel( locationServiceChannelId, 'خدمة الموقع', description: 'تتبع موقع السائق', importance: Importance.low, playSound: false, enableVibration: false, showBadge: false); const AndroidNotificationChannel geolocatorChannel = AndroidNotificationChannel(geolocatorChannelId, 'تحديد الموقع', description: 'خدمة تحديد الموقع الجغرافي', importance: Importance.low, playSound: false, enableVibration: false, showBadge: false); try { await flutterLocalNotificationsPlugin .resolvePlatformSpecificImplementation< AndroidFlutterLocalNotificationsPlugin>() ?.createNotificationChannel(backgroundChannel); await flutterLocalNotificationsPlugin .resolvePlatformSpecificImplementation< AndroidFlutterLocalNotificationsPlugin>() ?.createNotificationChannel(locationChannel); await flutterLocalNotificationsPlugin .resolvePlatformSpecificImplementation< AndroidFlutterLocalNotificationsPlugin>() ?.createNotificationChannel(geolocatorChannel); } catch (e) { print('❌ Error creating notification channels: $e'); } } // ============================================================================== // 🔴 دوال مساعدة 🔴 // ============================================================================== String _getVal(List data, int index) { if (data.length > index && data[index] != null) { return data[index].toString(); } return ''; } Future _processRejectOrderBackground(List data) async { try { final box = GetStorage(); final driverId = box.read(BoxName.driverID); String orderId = _getVal(data, 16); if (driverId != null && orderId.isNotEmpty) { print("📤 Rejecting Order: $orderId"); await CRUD().post(link: AppLink.addDriverOrder, payload: { 'driver_id': driverId, 'order_id': orderId, 'status': 'Refused' }); print("✅ Order Rejected Silently!"); } } catch (e) { print("❌ Error rejecting order: $e"); } } // ============================================================================== // 🔴 التعامل مع الإشعارات في الخلفية 🔴 // ============================================================================== @pragma('vm:entry-point') Future backgroundMessageHandler(RemoteMessage message) async { WidgetsFlutterBinding.ensureInitialized(); await GetStorage.init(); await TripOverlayPlugin.initialize(); // 🔴 الاستماع لزر الرفض في الخلفية باستخدام storage 🔴 TripOverlayPlugin.onTripRejected.listen((tripId) async { print("❌ [Background] Overlay Reject Clicked!"); String? savedTrip = await storage.read(key: 'pending_driver_list'); if (savedTrip != null) { await storage.delete(key: 'pending_driver_list'); List driverList = jsonDecode(savedTrip); await _processRejectOrderBackground(driverList); } }); final NotificationController notificationController = NotificationController(); await notificationController.initNotifications(); String? title = message.data['title'] ?? message.notification?.title; String? body = message.data['body'] ?? message.notification?.body; String? myListString = message.data['DriverList']; String? category = message.data['category'] ?? message.data['type']; if (title != null && body != null && myListString != null) { notificationController.showOrderNotification( title, body, 'ding.wav', myListString); if (category == 'Order' || category == 'OrderSpeed' || category == null) { List myList = []; try { myList = jsonDecode(myListString); } catch (e) {} String orderId = message.data['order_id']?.toString() ?? ''; if (orderId.isEmpty && myList.isNotEmpty) orderId = myList[0].toString(); double fare = 0.0; double distance = 0.0; String passengerName = title; String pickup = 'موقع الانطلاق'; String dropoff = 'موقع الوصول'; double pLat = 0.0; double pLng = 0.0; if (myList.isNotEmpty && myList.length > 29) { fare = double.tryParse(myList[26].toString()) ?? 0.0; distance = double.tryParse(myList[11].toString()) ?? 0.0; passengerName = myList[8].toString(); pickup = myList[29].toString(); dropoff = myList[30].toString(); // 🔴 استخراج الإحداثيات للخريطة (تأكد من الاندكس الخاص بخطوط الطول والعرض في مصفوفتك) pLat = double.tryParse(myList[0].toString()) ?? 0.0; pLng = double.tryParse(myList[1].toString()) ?? 0.0; } final tripData = TripData( tripId: orderId, passengerName: passengerName, pickupAddress: pickup, dropoffAddress: dropoff, distanceKm: distance, estimatedFare: fare, estimatedMinutes: 0, pickupLat: pLat, // تمرير خط العرض pickupLng: pLng, // تمرير خط الطول ); bool isAppInForeground = box.read(BoxName.isAppInForeground) ?? false; if (!isAppInForeground) { await TripOverlayPlugin.showOverlay(tripData, autoCloseSeconds: 15); } // 🔴 الكتابة على القرص مباشرة بدلاً من الرام لضمان انتقالها للتطبيق 🔴 await storage.write(key: 'pending_driver_list', value: myListString); } } } @pragma('vm:entry-point') void notificationTapBackground(NotificationResponse notificationResponse) { NotificationController().handleNotificationResponse(notificationResponse); } void main() { runZonedGuarded(() async { WidgetsFlutterBinding.ensureInitialized(); await initFirebaseIfNeeded(); await WakelockPlus.enable(); await GetStorage.init(); await initializeDateFormatting(); await SystemChrome.setPreferredOrientations( [DeviceOrientation.portraitUp, DeviceOrientation.portraitDown]); await requestNotificationPermission(); await TripOverlayPlugin.initialize(); await createAllNotificationChannels(); await BackgroundServiceHelper.initialize(); FirebaseMessaging.onBackgroundMessage(backgroundMessageHandler); runApp(const MyApp()); }, (error, stack) { final errorString = error.toString(); if (!errorString.contains('PERMISSION_DENIED') && !errorString.contains('FormatException') && !errorString.contains('Null check operator')) { CRUD.addError(error.toString(), stack.toString(), 'main'); } }); } class MyApp extends StatefulWidget { const MyApp({Key? key}) : super(key: key); @override _MyAppState createState() => _MyAppState(); } class _MyAppState extends State with WidgetsBindingObserver { @override void initState() { super.initState(); WidgetsBinding.instance.addObserver(this); _initApp(); // 🔥 التقاط رسالة القبول من الأندرويد بشكل مباشر 100% 🔥 const platformAppControl = MethodChannel('com.siro_driver/app_control'); platformAppControl.setMethodCallHandler((call) async { if (call.method == 'onOverlayTripAccepted') { print("✅ [Native Intent] تم التقاط زر القبول بنجاح من الأندرويد!"); // 🔴 القراءة من القرص مباشرة (يعمل بنجاح بين الـ Isolates) 🔴 String? savedTrip = await storage.read(key: 'pending_driver_list'); if (savedTrip != null && savedTrip.isNotEmpty) { await storage.delete(key: 'pending_driver_list'); List driverList = jsonDecode(savedTrip); // تنفيذ دالة القبول فوراً await _processAcceptOrder(driverList); } else { print("⚠️ خطأ: لم يتم العثور على بيانات الطلب في الذاكرة!"); } } }); } @override void dispose() { WidgetsBinding.instance.removeObserver(this); super.dispose(); } Future _initApp() async { try { if (!Get.isRegistered()) { Get.put(NotificationController()); } if (!Get.isRegistered()) { Get.put(FirebaseMessagesController()).getToken(); } if (!Get.isRegistered()) { Get.put(SettingController()); } if (!Get.isRegistered()) { Get.lazyPut(() => VoiceCallController(), fenix: true); } await FirebaseMessaging.instance.requestPermission(); await NotificationController().initNotifications(); await TripOverlayPlugin.initialize(); bool isOverlayGranted = await TripOverlayPlugin.isPermissionGranted(); if (!isOverlayGranted) { await TripOverlayPlugin.requestPermission(); } // 🔴 أعدنا الاستماع لأزرار النافذة إلى هنا (المكان الصحيح الذي لا يموت) 🔴 _listenToOverlayEvents(); } catch (e) { Log.print("Error during _initApp: $e"); } } // ============================================================================== // 🔴 الاستماع الحي لأزرار النافذة 🔴 // ============================================================================== void _listenToOverlayEvents() { TripOverlayPlugin.onTripAccepted.listen((result) async { Log.print('✅ [Main Isolate] Trip ACCEPTED via Overlay: ${result.tripId}'); final savedTrip = box.read('pending_overlay_trip'); if (savedTrip != null) { List driverList = jsonDecode(savedTrip['driverList']); box.remove('pending_overlay_trip'); // مسح حتى لا يتكرر // تنفيذ قبول الطلب فوراً! await _processAcceptOrder(driverList); } }); TripOverlayPlugin.onTripRejected.listen((tripId) async { Log.print('❌ [Main Isolate] Trip REJECTED via Overlay: $tripId'); final savedTrip = box.read('pending_overlay_trip'); if (savedTrip != null) { List driverList = jsonDecode(savedTrip['driverList']); box.remove('pending_overlay_trip'); // تنفيذ الرفض الصامت await _processRejectOrderBackground(driverList); } }); } // ============================================================================== // 🔴 التحكم بتوجيه التطبيق عند فتحه 🔴 // ============================================================================== @override void didChangeAppLifecycleState(AppLifecycleState state) { if (state == AppLifecycleState.resumed) { box.write(BoxName.isAppInForeground, true); // تأخير بسيط جداً، لكي نعطي فرصة لـ _listenToOverlayEvents لتعمل أولاً إذا كان الفتح بسبب زر "قبول" Future.delayed(const Duration(milliseconds: 500), () { _checkPendingTrip(); }); } else if (state == AppLifecycleState.paused || state == AppLifecycleState.inactive) { box.write(BoxName.isAppInForeground, false); } } void _checkPendingTrip() async { // 🔴 القراءة من القرص مباشرة 🔴 String? savedTrip = await storage.read(key: 'pending_driver_list'); if (savedTrip != null && savedTrip.isNotEmpty) { if (Get.currentRoute == '/') { print('⏳ App is still on Splash screen. Postponing notification trip navigation to HomeCaptainController.'); return; } await storage.delete(key: 'pending_driver_list'); List driverList = jsonDecode(savedTrip); print('👀 التطبيق فتح من الإشعار أو يدوياً، توجيه لصفحة الطلب...'); Get.toNamed('/OrderRequestPage', arguments: { 'myListString': savedTrip, 'DriverList': driverList, 'body': 'طلب جديد' }); } } // ============================================================================== // 🟢 دوال معالجة قبول الطلب وتجهيز المتغيرات 🟢 // ============================================================================== bool _isProcessingAccept = false; // ============================================================================== // 🟢 دوال معالجة قبول الطلب وتجهيز المتغيرات 🟢 // ============================================================================== Future _processAcceptOrder(List data) async { if (_isProcessingAccept) { print("⏳ _processAcceptOrder: Already accepting order, skipping duplicate request."); return; } _isProcessingAccept = true; Get.dialog( WillPopScope( onWillPop: () async => false, child: const Center(child: CircularProgressIndicator(color: Colors.white)), ), barrierDismissible: false, ); try { final driverId = box.read(BoxName.driverID); String orderId = _getVal(data, 16); String passengerToken = _getVal(data, 9); print("🚀 Sending Accept Request for Order: $orderId"); var res = await CRUD().post( link: "${AppLink.ride}/rides/acceptRide.php", payload: { 'id': orderId, 'rideTimeStart': DateTime.now().toString(), 'status': 'Apply', 'passengerToken': passengerToken, 'driver_id': driverId, }, ); print("📥 Server Response: $res"); // إغلاق أي ديالوج مفتوح باستخدام المفتاح العالمي لضمان الموثوقية if (Get.isDialogOpen == true) { navigatorKey.currentState?.pop(); } bool isFailure = false; if (res is Map && res['status'] == 'failure') { isFailure = true; } else if (res == 'failure') isFailure = true; if (isFailure) { Get.defaultDialog( title: "تنبيه", middleText: "عذراً، الطلب أخذه سائق آخر.", confirmTextColor: Colors.white, onConfirm: () => Get.back(), textConfirm: "حسناً", ); return; } if (!Get.isRegistered()) { Get.put(HomeCaptainController(), permanent: true); } else { Get.find().changeRideId(); } box.write(BoxName.statusDriverLocation, 'on'); box.write(BoxName.rideStatus, 'Apply'); var rideArgs = _buildRideArgs(data); box.write(BoxName.rideArguments, rideArgs); Get.offAll(() => PassengerLocationMapPage(), arguments: rideArgs); } catch (e) { if (Get.isDialogOpen == true) { navigatorKey.currentState?.pop(); } print("❌ Error in accept process: $e"); Get.snackbar("خطأ", "حدث خطأ غير متوقع"); } finally { _isProcessingAccept = false; } } Map _buildRideArgs(List data) { return { 'passengerLocation': '${_getVal(data, 0)},${_getVal(data, 1)}', 'passengerDestination': '${_getVal(data, 3)},${_getVal(data, 4)}', 'Duration': _getVal(data, 4), 'totalCost': _getVal(data, 26), 'Distance': _getVal(data, 5), 'name': _getVal(data, 8), 'phone': _getVal(data, 10), 'email': _getVal(data, 28), 'WalletChecked': _getVal(data, 13), 'tokenPassenger': _getVal(data, 9), 'direction': 'https://www.google.com/maps/dir/${_getVal(data, 0)}/${_getVal(data, 1)}/', 'DurationToPassenger': _getVal(data, 15), 'rideId': _getVal(data, 16), 'passengerId': _getVal(data, 7), 'driverId': _getVal(data, 18), 'durationOfRideValue': _getVal(data, 19), 'paymentAmount': _getVal(data, 2), 'paymentMethod': _getVal(data, 13) == 'true' ? 'visa' : 'cash', 'isHaveSteps': _getVal(data, 20), 'step0': _getVal(data, 21), 'step1': _getVal(data, 22), 'step2': _getVal(data, 23), 'step3': _getVal(data, 24), 'step4': _getVal(data, 25), 'passengerWalletBurc': _getVal(data, 26), 'timeOfOrder': DateTime.now().toString(), 'totalPassenger': _getVal(data, 2), 'carType': _getVal(data, 31), 'kazan': _getVal(data, 32), 'startNameLocation': _getVal(data, 29), 'endNameLocation': _getVal(data, 30), }; } @override Widget build(BuildContext context) { final LocaleController localController = Get.put(LocaleController()); final SettingController settingController = Get.put(SettingController()); return GetMaterialApp( navigatorKey: navigatorKey, title: AppInformation.appName, translations: MyTranslation(), debugShowCheckedModeBanner: false, locale: localController.language, theme: localController.lightTheme, darkTheme: localController.darkTheme, themeMode: settingController.isDarkMode ? ThemeMode.dark : ThemeMode.light, initialRoute: '/', getPages: [ GetPage(name: '/', page: () => SplashScreen()), GetPage(name: '/OrderRequestPage', page: () => OrderRequestPage()), GetPage( name: '/passenger-location-map', page: () => PassengerLocationMapPage()), ], ); } }