Update: 2026-06-10 18:11:50
This commit is contained in:
@@ -76,15 +76,22 @@ class FirebaseMessagesController extends GetxController {
|
||||
await fcmToken.subscribeToTopic("drivers"); // أو "users" حسب نوع المستخدم
|
||||
print("Subscribed to 'drivers' topic ✅");
|
||||
|
||||
FirebaseMessaging.instance.getInitialMessage().then((RemoteMessage? message) async {
|
||||
FirebaseMessaging.instance
|
||||
.getInitialMessage()
|
||||
.then((RemoteMessage? message) async {
|
||||
if (message != null && message.data.isNotEmpty) {
|
||||
Log.print("🔔 FCM getInitialMessage payload: ${message.data}");
|
||||
String? category = message.data['category'] ?? message.data['type'];
|
||||
if (category == 'ORDER' || category == 'Order' || category == 'OrderVIP' || message.data.containsKey('DriverList')) {
|
||||
if (category == 'ORDER' ||
|
||||
category == 'Order' ||
|
||||
category == 'OrderVIP' ||
|
||||
message.data.containsKey('DriverList')) {
|
||||
String? myListString = message.data['DriverList'];
|
||||
if (myListString != null && myListString.isNotEmpty) {
|
||||
await storage.write(key: 'pending_driver_list', value: myListString);
|
||||
Log.print("💾 Saved pending driver list to secure storage from getInitialMessage");
|
||||
await storage.write(
|
||||
key: 'pending_driver_list', value: myListString);
|
||||
Log.print(
|
||||
"💾 Saved pending driver list to secure storage from getInitialMessage");
|
||||
}
|
||||
} else {
|
||||
Future.delayed(const Duration(milliseconds: 1500), () {
|
||||
@@ -107,7 +114,6 @@ class FirebaseMessagesController extends GetxController {
|
||||
// fireBaseTitles(message);
|
||||
// }
|
||||
});
|
||||
FirebaseMessaging.onBackgroundMessage((RemoteMessage message) async {});
|
||||
|
||||
FirebaseMessaging.onMessageOpenedApp.listen((RemoteMessage message) {
|
||||
if (message.data.isNotEmpty) {
|
||||
|
||||
@@ -9,7 +9,6 @@ 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 'package:geolocator/geolocator.dart' as geo;
|
||||
import '../../constant/box_name.dart';
|
||||
import '../firebase/local_notification.dart';
|
||||
|
||||
@@ -129,40 +128,21 @@ Future<bool> onStart(ServiceInstance service) async {
|
||||
service.stopSelf();
|
||||
});
|
||||
|
||||
// 🔥 Location management in background isolate (Using Geolocator)
|
||||
geo.Position? latestPos;
|
||||
|
||||
// Listen to location changes continuously in the background
|
||||
geo.Geolocator.getPositionStream(
|
||||
locationSettings: geo.AndroidSettings(
|
||||
accuracy: geo.LocationAccuracy.high,
|
||||
distanceFilter: 10,
|
||||
intervalDuration: const Duration(seconds: 10),
|
||||
),
|
||||
).listen((pos) {
|
||||
latestPos = pos;
|
||||
});
|
||||
|
||||
// 🔥 MERCY HEARTBEAT: Send location every 2 minutes to keep driver active in 'raids'
|
||||
Timer.periodic(const Duration(minutes: 2), (timer) async {
|
||||
if (socket != null && socket.connected && latestPos != null) {
|
||||
try {
|
||||
socket.emit('update_location', {
|
||||
'driver_id': driverId,
|
||||
'lat': latestPos!.latitude,
|
||||
'lng': latestPos!.longitude,
|
||||
'heading': latestPos!.heading,
|
||||
'speed': latestPos!.speed * 3.6,
|
||||
'status': box.read(BoxName.statusDriverLocation) ?? 'on',
|
||||
'source': 'background_heartbeat'
|
||||
});
|
||||
print(
|
||||
"💓 Background Mercy Heartbeat Sent: ${latestPos!.latitude}, ${latestPos!.longitude}");
|
||||
} catch (e) {
|
||||
print("❌ Background Heartbeat Error: $e");
|
||||
}
|
||||
}
|
||||
});
|
||||
// 🚫 [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) {
|
||||
|
||||
@@ -19,6 +19,7 @@ import '../firebase/local_notification.dart';
|
||||
import '../home/captin/home_captain_controller.dart';
|
||||
import '../home/captin/map_driver_controller.dart';
|
||||
import '../home/payment/captain_wallet_controller.dart';
|
||||
import '../home/navigation/navigation_controller.dart';
|
||||
import 'background_service.dart';
|
||||
import 'crud.dart';
|
||||
|
||||
@@ -539,6 +540,16 @@ class LocationController extends GetxController with WidgetsBindingObserver {
|
||||
}
|
||||
}
|
||||
|
||||
if (Get.isRegistered<MapDriverController>()) {
|
||||
final mapCtrl = Get.find<MapDriverController>();
|
||||
mapCtrl.handleLocationUpdateFromCentral(pos, speed, heading);
|
||||
}
|
||||
|
||||
if (Get.isRegistered<NavigationController>()) {
|
||||
final navCtrl = Get.find<NavigationController>();
|
||||
navCtrl.handleLocationUpdateFromCentral(pos, speed, heading);
|
||||
}
|
||||
|
||||
await _saveBehaviorIfMoved(pos, now, currentSpeed: speed);
|
||||
}, onError: (e) => Log.print('❌ Location Stream Error: $e'));
|
||||
}
|
||||
|
||||
@@ -2570,27 +2570,19 @@ class MapDriverController extends GetxController
|
||||
}
|
||||
|
||||
void _startLocationListening() {
|
||||
_locationSubscription?.cancel();
|
||||
_locationSubscription = geo.Geolocator.getPositionStream(
|
||||
locationSettings: const geo.LocationSettings(
|
||||
accuracy: geo.LocationAccuracy.bestForNavigation,
|
||||
distanceFilter: 2,
|
||||
),
|
||||
).listen((geo.Position pos) {
|
||||
_handleLocationUpdate(pos);
|
||||
});
|
||||
// Location stream is now centralized in LocationController to prevent device hanging.
|
||||
// LocationController will call handleLocationUpdateFromCentral directly.
|
||||
}
|
||||
|
||||
/// [Fix C-4] تحديث myLocation في المستمع الأساسي
|
||||
void _handleLocationUpdate(geo.Position pos) {
|
||||
final newLoc = LatLng(pos.latitude, pos.longitude);
|
||||
void handleLocationUpdateFromCentral(LatLng newLoc, double posSpeed, double posHeading) {
|
||||
myLocation = newLoc; // ← [Fix C-4] تحديث الموقع الفوري
|
||||
_oldLoc = smoothedLocation ?? newLoc;
|
||||
_targetLoc = newLoc;
|
||||
|
||||
_oldHeading = smoothedHeading;
|
||||
if (pos.speed > 0.5) {
|
||||
_targetHeading = pos.heading;
|
||||
if (posSpeed > 0.5) {
|
||||
_targetHeading = posHeading;
|
||||
} else {
|
||||
_targetHeading = _oldHeading;
|
||||
}
|
||||
|
||||
@@ -69,6 +69,7 @@ class OrderRequestController extends GetxController
|
||||
|
||||
// --- الخريطة ---
|
||||
Set<Polyline> polylines = {};
|
||||
bool _hasCalculatedFullJourney = false;
|
||||
|
||||
// حالة التطبيق والصوت
|
||||
bool isInBackground = false;
|
||||
@@ -219,6 +220,11 @@ class OrderRequestController extends GetxController
|
||||
// ----------------------------------------------------------------------
|
||||
|
||||
Future<void> _calculateFullJourney() async {
|
||||
if (_hasCalculatedFullJourney) {
|
||||
if (mapController != null) zoomToFitRide();
|
||||
return;
|
||||
}
|
||||
_hasCalculatedFullJourney = true;
|
||||
// Don't block on mapController being null - we'll draw routes
|
||||
// and markers first, then zoom when controller is ready
|
||||
bool canZoom = mapController != null;
|
||||
@@ -281,7 +287,7 @@ class OrderRequestController extends GetxController
|
||||
totalTripDistance = tripResult['distance_text'];
|
||||
totalTripDuration = tripResult['duration_text'];
|
||||
polylines.add(tripResult['polyline']);
|
||||
|
||||
|
||||
// 🔥 تخزين استجابة السيرفر كاملة (بما فيها الـ points والـ instructions)
|
||||
if (tripResult['raw_response'] != null) {
|
||||
box.write('cached_trip_route', tripResult['raw_response']);
|
||||
|
||||
@@ -476,32 +476,18 @@ class NavigationController extends GetxController
|
||||
}
|
||||
|
||||
void _startLocationStream() {
|
||||
_locationStreamSubscription?.cancel();
|
||||
// Listen to location updates with minimum distance filter of 2 meters
|
||||
// This provides real-time updates without the 3-4 second delay
|
||||
_locationStreamSubscription = Geolocator.getPositionStream(
|
||||
locationSettings: const LocationSettings(
|
||||
accuracy: LocationAccuracy.high,
|
||||
distanceFilter: 2, // Update every 2 meters
|
||||
),
|
||||
).listen(
|
||||
(Position position) {
|
||||
_handleLocationUpdate(position);
|
||||
},
|
||||
onError: (error) {
|
||||
Log.print("DEBUG: Location stream error: $error");
|
||||
},
|
||||
);
|
||||
// Location stream is now centralized in LocationController to prevent device hanging.
|
||||
// LocationController will call handleLocationUpdateFromCentral directly.
|
||||
}
|
||||
|
||||
bool _isProcessing = false;
|
||||
Future<void> _handleLocationUpdate(Position position) async {
|
||||
Future<void> handleLocationUpdateFromCentral(LatLng newLoc, double locSpeed, double locHeading) async {
|
||||
if (_isProcessing) return;
|
||||
_isProcessing = true;
|
||||
|
||||
try {
|
||||
final newLoc = LatLng(position.latitude, position.longitude);
|
||||
currentSpeed = position.speed * 3.6; // Convert m/s to km/h
|
||||
currentSpeed = locSpeed; // Convert m/s to km/h already done by location controller if needed, wait location_controller sends raw speed or km/h? It sends raw speed. So we should * 3.6
|
||||
currentSpeed = locSpeed * 3.6;
|
||||
|
||||
// Skip if movement is too small
|
||||
if (_lastProcessedLocation != null) {
|
||||
@@ -544,7 +530,7 @@ class NavigationController extends GetxController
|
||||
_targetLoc!.longitude,
|
||||
);
|
||||
} else {
|
||||
_targetHeading = position.heading;
|
||||
_targetHeading = locHeading;
|
||||
}
|
||||
|
||||
_animController?.forward(from: 0.0);
|
||||
|
||||
Reference in New Issue
Block a user