import 'dart:async'; import 'dart:convert'; import 'dart:ui'; import 'dart:io'; import 'package:flutter/material.dart'; import 'package:flutter_background_service/flutter_background_service.dart'; import 'package:flutter_local_notifications/flutter_local_notifications.dart'; import 'package:flutter_overlay_window/flutter_overlay_window.dart'; import 'package:socket_io_client/socket_io_client.dart' as IO; import 'package:flutter_overlay_window/flutter_overlay_window.dart' as Overlay; import 'package:get_storage/get_storage.dart'; import '../../constant/box_name.dart'; import '../firebase/local_notification.dart'; const String notificationChannelId = 'driver_service_channel'; const int notificationId = 888; const String notificationIcon = '@mipmap/launcher_icon'; @pragma('vm:entry-point') Future onStart(ServiceInstance service) async { DartPluginRegistrant.ensureInitialized(); final FlutterLocalNotificationsPlugin flutterLocalNotificationsPlugin = FlutterLocalNotificationsPlugin(); await GetStorage.init(); final box = GetStorage(); IO.Socket? socket; String driverId = box.read(BoxName.driverID) ?? ''; String token = box.read(BoxName.tokenDriver) ?? ''; if (driverId.isNotEmpty) { socket = IO.io( 'https://location.intaleq.xyz', IO.OptionBuilder() .setTransports(['websocket']) .disableAutoConnect() .setQuery({ 'driver_id': driverId, 'token': token, 'EIO': '3', // توافقية مع Workerman }) .setReconnectionAttempts(double.infinity) .build()); socket.connect(); socket.onConnect((_) { print("✅ Background Service: Socket Connected! ID: ${socket?.id}"); if (service is AndroidServiceInstance) { flutterLocalNotificationsPlugin.show( id: notificationId, title: 'أنت متصل الآن', body: 'بانتظار الطلبات...', notificationDetails: const NotificationDetails( android: AndroidNotificationDetails( notificationChannelId, 'خدمة السائق', icon: notificationIcon, ongoing: true, importance: Importance.low, priority: Priority.low, ), ), ); } }); socket.on('new_ride_request', (data) async { print("🔔 Background Service: Received new_ride_request"); // 🔥 قراءة حالة التطبيق مباشرة قبل العرض await GetStorage.init(); // تأكد من تحديث البيانات final box = GetStorage(); bool isAppInForeground = box.read(BoxName.isAppInForeground) ?? false; // 🔥 Check إضافي: هل الـ Overlay مفتوح بالفعل؟ (للأندرويد فقط) bool overlayActive = false; if (Platform.isAndroid) { overlayActive = await Overlay.FlutterOverlayWindow.isActive(); } if (isAppInForeground || overlayActive) { print("🛑 App is FOREGROUND or Overlay already shown. Skipping."); return; } // عرض الـ Overlay (للأندرويد فقط) if (Platform.isAndroid) { print("🚀 App is BACKGROUND. Showing Overlay..."); try { await Overlay.FlutterOverlayWindow.showOverlay( enableDrag: true, overlayTitle: "طلب جديد", overlayContent: "لديك طلب جديد وصل للتو!", flag: OverlayFlag.focusPointer, positionGravity: PositionGravity.auto, height: WindowSize.matchParent, width: WindowSize.matchParent, startPosition: const OverlayPosition(0, -30), ); await Overlay.FlutterOverlayWindow.shareData(data); } catch (e) { print("Overlay Error: $e"); } } else if (Platform.isIOS) { // على iOS، نظهر إشعاراً عادياً لأن الـ Overlay غير موجود flutterLocalNotificationsPlugin.show( id: 1002, title: "طلب رحلة جديد 🚖", body: "لديك طلب رحلة جديد، افتح التطبيق للموافقة عليه", notificationDetails: const NotificationDetails( iOS: DarwinNotificationDetails( presentAlert: true, presentBadge: true, presentSound: true, ), ), payload: jsonEncode(data)); } }); } service.on('stopService').listen((event) { socket?.clearListeners(); socket?.dispose(); service.stopSelf(); }); // 🚫 [Architecture Rule] NO redundant GPS stream in background service! // LocationController is the SINGLE SOURCE OF TRUTH for all location GPS updates. // It already uses location.enableBackgroundMode(enable: true) to keep the GPS // stream alive even when the app is in the background. The main socket in // LocationController handles all emitLocationToSocket() calls including heartbeat. // // The background service is ONLY responsible for: // 1. Keeping the socket connection alive for receiving 'new_ride_request' // and 'cancel_ride' events while the main isolate is paused on Android. // 2. Showing the Android Overlay UI for incoming ride requests. // 3. Notifications for iOS background state. // // Location data is not sent from the background isolate — it would conflict // with LocationController's stream and cause duplicate GPS listeners, // battery drain, and device freeze (as documented in driver_lifecycle.md). Timer.periodic(const Duration(seconds: 30), (timer) async { if (service is AndroidServiceInstance) { if (await service.isForegroundService()) { flutterLocalNotificationsPlugin.show( id: notificationId, title: 'خدمة السائق نشطة', body: 'بانتظار الطلبات...', notificationDetails: const NotificationDetails( android: AndroidNotificationDetails( notificationChannelId, 'خدمة السائق', icon: notificationIcon, ongoing: true, importance: Importance.low, priority: Priority.low, ), ), ); } } }); return true; } class BackgroundServiceHelper { static Future initialize() async { final service = FlutterBackgroundService(); await service.configure( androidConfiguration: AndroidConfiguration( onStart: onStart, autoStart: false, isForegroundMode: true, notificationChannelId: notificationChannelId, initialNotificationTitle: 'تطبيق السائق', initialNotificationContent: 'تجهيز الخدمة...', foregroundServiceNotificationId: notificationId, ), iosConfiguration: IosConfiguration( autoStart: false, onForeground: onStart, onBackground: onStart, ), ); } static Future startService() async { final service = FlutterBackgroundService(); if (!await service.isRunning()) { await service.startService(); } } static Future stopService() async { final service = FlutterBackgroundService(); service.invoke("stopService"); } }