207 lines
7.3 KiB
Dart
207 lines
7.3 KiB
Dart
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 '../../constant/links.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<bool> 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(
|
|
AppLink.locationSocketUrl,
|
|
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<void> 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<void> startService() async {
|
|
final service = FlutterBackgroundService();
|
|
if (!await service.isRunning()) {
|
|
await service.startService();
|
|
}
|
|
}
|
|
|
|
static Future<void> stopService() async {
|
|
final service = FlutterBackgroundService();
|
|
service.invoke("stopService");
|
|
}
|
|
}
|