2026-04-03-maplibra come next
This commit is contained in:
@@ -42,7 +42,7 @@ class AppLink {
|
||||
|
||||
static String test = "$server/test.php";
|
||||
//===============firebase==========================
|
||||
static String getTokens = "$server/ride/firebase/get.php";
|
||||
static String getTokens = "$server/ride/firebase/getTokensPassenger.php.php";
|
||||
static String getTokenParent = "$server/ride/firebase/getTokenParent.php";
|
||||
static String addTokens = "$server/ride/firebase/add.php";
|
||||
static String addFingerPrint = "$paymentServer/ride/firebase/add.php";
|
||||
|
||||
@@ -4,6 +4,7 @@ import 'dart:math';
|
||||
import 'package:Intaleq/constant/api_key.dart';
|
||||
import 'package:Intaleq/controller/firebase/firbase_messge.dart';
|
||||
import 'package:Intaleq/views/auth/otp_page.dart';
|
||||
import 'package:Intaleq/views/widgets/error_snakbar.dart';
|
||||
import 'package:http/http.dart' as http;
|
||||
|
||||
import 'package:Intaleq/constant/info.dart';
|
||||
@@ -276,37 +277,47 @@ class LoginController extends GetxController {
|
||||
// مهم: تأكد من passengerID في الـ box
|
||||
box.write(BoxName.passengerID, passengerID);
|
||||
|
||||
// 4) نفّذ عمليات مكلفة بالتوازي: getTokens + fingerprint
|
||||
// 4) تنفيذ العمليات بالتوازي: getTokens + fingerprint محلي
|
||||
final results = await Future.wait([
|
||||
CRUD().get(link: AppLink.getTokens, payload: {
|
||||
'passengerID': passengerID, // FIX: لا تستخدم box هنا
|
||||
}),
|
||||
CRUD().get(
|
||||
link: AppLink.getTokens, payload: {'passengerID': passengerID}),
|
||||
DeviceHelper.getDeviceFingerprint(),
|
||||
]);
|
||||
await box.write(BoxName.firstTimeLoadKey, 'false');
|
||||
final tokenResp = results[0];
|
||||
final fingerPrint = (results[1] ?? '').toString();
|
||||
await storage.write(key: BoxName.fingerPrint, value: fingerPrint);
|
||||
|
||||
final tokenResp = results[0];
|
||||
final localFP = (results[1] ?? '').toString();
|
||||
|
||||
await storage.write(key: BoxName.fingerPrint, value: localFP);
|
||||
await box.write(BoxName.firstTimeLoadKey, 'false');
|
||||
|
||||
// ── 5. المقارنة: FCM token + fingerprint ──────────────────────
|
||||
if (email != '962798583052@intaleqapp.com' && tokenResp != 'failure') {
|
||||
final tokenJson = jsonDecode(tokenResp);
|
||||
final serverToken = tokenJson['message']?['token']?.toString() ?? '';
|
||||
// Log.print('serverToken: ${serverToken}');
|
||||
final localFcm = (box.read(BoxName.tokenFCM) ?? '').toString();
|
||||
// Log.print('localFcm: ${localFcm}');
|
||||
final serverData = tokenJson['message'] as Map?; // null = أول تسجيل
|
||||
|
||||
// 5) اختلاف الجهاز -> تحقّق OTP
|
||||
if (serverToken.isNotEmpty && serverToken != localFcm) {
|
||||
final goVerify = await _confirmDeviceChangeDialog();
|
||||
if (goVerify == true) {
|
||||
if (serverData != null) {
|
||||
final serverFCM = serverData['token']?.toString() ?? '';
|
||||
final serverFP = serverData['fingerPrint']?.toString() ?? '';
|
||||
|
||||
final localFCM = (box.read(BoxName.tokenFCM) ?? '').toString();
|
||||
|
||||
// ── اختلاف أي منهما = جهاز مختلف أو تثبيت جديد ─────────
|
||||
final fcmChanged = serverFCM.isNotEmpty && serverFCM != localFCM;
|
||||
final fpChanged = serverFP.isNotEmpty && serverFP != localFP;
|
||||
|
||||
if (fcmChanged || fpChanged) {
|
||||
// final goVerify = await _confirmDeviceChangeDialog();
|
||||
// if (goVerify == true) {
|
||||
mySnackbarInfo('Device Change Detected'.tr);
|
||||
//
|
||||
await Get.to(() => OtpVerificationPage(
|
||||
phone: data['phone'].toString(),
|
||||
deviceToken: fingerPrint,
|
||||
token: tokenResp.toString(),
|
||||
ptoken: serverToken,
|
||||
deviceToken: localFP,
|
||||
token: tokenResp,
|
||||
ptoken: serverFCM, // نمرر FCM القديم للـ OTP controller
|
||||
));
|
||||
// بعد العودة من OTP (نجح/فشل)، أخرج من الميثود كي لا يحصل offAll مرتين
|
||||
return;
|
||||
return; // لا تكمل — الـ OTP controller يتولى الانتقال
|
||||
// }
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -359,18 +370,18 @@ class LoginController extends GetxController {
|
||||
}
|
||||
}
|
||||
|
||||
Future<bool?> _confirmDeviceChangeDialog() {
|
||||
return Get.defaultDialog<bool>(
|
||||
barrierDismissible: false,
|
||||
title: 'Device Change Detected'.tr,
|
||||
middleText: 'Please verify your identity'.tr,
|
||||
textConfirm: 'Verify'.tr,
|
||||
confirmTextColor: Colors.white,
|
||||
onConfirm: () => Get.back(result: true),
|
||||
textCancel: 'Cancel'.tr,
|
||||
onCancel: () => Get.back(result: false),
|
||||
);
|
||||
}
|
||||
// Future<bool?> _confirmDeviceChangeDialog() {
|
||||
// return Get.defaultDialog<bool>(
|
||||
// barrierDismissible: false,
|
||||
// title: 'Device Change Detected'.tr,
|
||||
// middleText: 'Please verify your identity'.tr,
|
||||
// textConfirm: 'Verify'.tr,
|
||||
// confirmTextColor: Colors.white,
|
||||
// onConfirm: () => Get.back(result: true),
|
||||
// textCancel: 'Cancel'.tr,
|
||||
// onCancel: () => Get.back(result: false),
|
||||
// );
|
||||
// }
|
||||
|
||||
void login() async {
|
||||
isloading = true;
|
||||
|
||||
@@ -45,7 +45,7 @@ import '../../main.dart';
|
||||
import '../../models/model/locations.dart';
|
||||
import '../../models/model/painter_copoun.dart';
|
||||
import '../../print.dart';
|
||||
import '../../services/ride_tracking_native.dart';
|
||||
import '../../services/pip_service.dart';
|
||||
import '../../views/home/map_widget.dart/cancel_raide_page.dart';
|
||||
import '../../views/home/map_widget.dart/car_details_widget_to_go.dart';
|
||||
import '../../views/home/map_widget.dart/select_driver_mishwari.dart';
|
||||
@@ -357,49 +357,83 @@ class MapPassengerController extends GetxController {
|
||||
.setTransports(['websocket'])
|
||||
.disableAutoConnect()
|
||||
.setQuery({'id': passengerId})
|
||||
// ✅ [FIX] إعادة اتصال شبه-لانهائية (999 محاولة) بدلاً من 20
|
||||
.setReconnectionAttempts(20)
|
||||
.setReconnectionDelay(2400)
|
||||
// ✅ أضف هذا السطر لحل مشكلة الـ Heartbeat مع PHPSocketIO
|
||||
// ✅ [FIX] تأخير أقل (1.5 ثانية) مع حد أقصى (8 ثواني) للتسريع
|
||||
.setReconnectionDelay(1500)
|
||||
.setReconnectionDelayMax(8000)
|
||||
.enableReconnection()
|
||||
.setExtraHeaders({'Connection': 'Upgrade'})
|
||||
.build(),
|
||||
);
|
||||
|
||||
socket.connect();
|
||||
// ✅ إضافة النبضة (Heartbeat) لمنع السيرفر من قطع الاتصال
|
||||
_heartbeatTimer?.cancel(); // إيقاف أي نبضة قديمة
|
||||
_heartbeatTimer = Timer.periodic(const Duration(seconds: 25), (timer) {
|
||||
if (isSocketConnected && socket != null && socket!.connected) {
|
||||
socket!.emit('heartbeat', {'passenger_id': passengerId});
|
||||
// Log.print("💓 Socket Heartbeat sent"); // اختياري للتأكد أنه يعمل
|
||||
} else {
|
||||
timer.cancel(); // إيقاف النبضة إذا انقطع السوكيت
|
||||
}
|
||||
});
|
||||
// ✅ معالج الاتصال
|
||||
|
||||
// ✅ معالج الاتصال الأول
|
||||
socket.onConnect((_) {
|
||||
Log.print("✅ Socket Connected Successfully");
|
||||
isSocketConnected = true;
|
||||
_reconnectAttempts = 0;
|
||||
_startHeartbeat(); // ← أضف هذا
|
||||
_startHeartbeat();
|
||||
|
||||
// ✅ [FIX] الاشتراك مجدداً في أحداث الرحلة عند كل اتصال
|
||||
if (rideId != null && rideId != 'yet' && driverId.isNotEmpty) {
|
||||
socket.emit('subscribe_driver_location', {
|
||||
'ride_id': rideId,
|
||||
'driver_id': driverId,
|
||||
});
|
||||
Log.print("📡 Re-subscribed to driver location after connect");
|
||||
}
|
||||
|
||||
update();
|
||||
});
|
||||
|
||||
// دالة منفصلة للـ heartbeat
|
||||
|
||||
// ⚠️ معالج الانقطاع
|
||||
socket.onDisconnect((_) {
|
||||
Log.print("⚠️ Socket Disconnected");
|
||||
Log.print("⚠️ Socket Disconnected — Auto-Reconnect will handle it");
|
||||
isSocketConnected = false;
|
||||
|
||||
// تفعيل Polling أسرع كـ Fallback
|
||||
// تفعيل Polling أسرع كـ Fallback مؤقت (سيتم إيقافه عند عودة الاتصال)
|
||||
if (_isActiveRideState()) {
|
||||
Log.print("🔄 Switching to Fast Polling Mode (6s interval)");
|
||||
Log.print("🔄 Enabling Fast Polling Fallback (4s) until reconnect...");
|
||||
_startMasterTimerWithInterval(4);
|
||||
}
|
||||
update();
|
||||
});
|
||||
|
||||
// 🔁 [FIX] معالج إعادة الاتصال الناجحة
|
||||
socket.onReconnect((_) {
|
||||
Log.print("🔁 Socket Reconnected Successfully!");
|
||||
isSocketConnected = true;
|
||||
_reconnectAttempts = 0;
|
||||
|
||||
// استئناف النبضة فوراً
|
||||
_startHeartbeat();
|
||||
|
||||
// إعادة الاشتراك في أحداث الرحلة
|
||||
if (rideId != null && rideId != 'yet' && driverId.isNotEmpty) {
|
||||
socket.emit('subscribe_driver_location', {
|
||||
'ride_id': rideId,
|
||||
'driver_id': driverId,
|
||||
});
|
||||
Log.print("📡 Re-subscribed to driver location after reconnect");
|
||||
}
|
||||
|
||||
// ✅ [FIX] إيقاف الـ Fast Polling لأن السوكيت عاد
|
||||
if (_isActiveRideState()) {
|
||||
Log.print("✅ Socket back online — stopping Fast Polling Fallback");
|
||||
_masterTimer?.cancel();
|
||||
_masterTimer = null;
|
||||
}
|
||||
|
||||
update();
|
||||
});
|
||||
|
||||
// 🔄 [FIX] معالج محاولات إعادة الاتصال (للتشخيص)
|
||||
socket.onReconnectAttempt((attemptNumber) {
|
||||
Log.print("🔄 Socket Reconnect Attempt #$attemptNumber...");
|
||||
});
|
||||
|
||||
// ❌ معالج الأخطاء
|
||||
socket.onError((error) {
|
||||
Log.print("❌ Socket Error: $error");
|
||||
@@ -724,6 +758,7 @@ class MapPassengerController extends GetxController {
|
||||
if (Get.isDialogOpen == true) Get.back();
|
||||
await RideLiveNotification.cancel();
|
||||
IosLiveActivityService.endRideActivity(); // ✅ أضف هذا السطر
|
||||
PipService.disablePip(); // ✅ إيقاف PiP عند انتهاء الرحلة
|
||||
if (Get.isDialogOpen == true) Get.back();
|
||||
await RideLiveNotification.cancel();
|
||||
Get.defaultDialog(
|
||||
@@ -771,6 +806,7 @@ class MapPassengerController extends GetxController {
|
||||
stopAllTimers();
|
||||
await RideLiveNotification.cancel();
|
||||
IosLiveActivityService.endRideActivity(); // ✅ أضف هذا السطر
|
||||
PipService.disablePip(); // ✅ إيقاف PiP
|
||||
_isCancelProcessed = false;
|
||||
currentRideState.value = RideState.noRide;
|
||||
resetAllMapStates();
|
||||
@@ -959,6 +995,7 @@ class MapPassengerController extends GetxController {
|
||||
'tone1',
|
||||
);
|
||||
IosLiveActivityService.endRideActivity();
|
||||
PipService.disablePip(); // ✅ إيقاف PiP
|
||||
await RideLiveNotification.cancel();
|
||||
// 5. استخراج البيانات والانتقال
|
||||
if (driverList.length >= 4) {
|
||||
@@ -1272,20 +1309,24 @@ class MapPassengerController extends GetxController {
|
||||
timeToPassengerFromDriverAfterApplied; // مثلاً من السيرفر
|
||||
final double distanceDriverToPassengerMeters =
|
||||
double.parse(distanceByPassenger);
|
||||
await RideTrackingNative.updateRideTracking(
|
||||
driverName: driverName,
|
||||
driverPhone: driverPhone,
|
||||
carDetails: '$make • $carColor • $licensePlate',
|
||||
driverLat: driverCarsLocationToPassengerAfterApplied.last.latitude,
|
||||
driverLng: driverCarsLocationToPassengerAfterApplied.last.longitude,
|
||||
passengerLat: passengerLocation.latitude,
|
||||
passengerLng: passengerLocation.longitude,
|
||||
destLat: myDestination.latitude,
|
||||
destLng: myDestination.longitude,
|
||||
rideState: 'waiting', // يعني السائق بالطريق للراكب
|
||||
estimatedTimeMinutes: (timeToPassengerSeconds / 60).round(),
|
||||
totalDistanceMeters: distanceDriverToPassengerMeters,
|
||||
);
|
||||
// [PiP] تم تعطيل الإشعار المستمر القديم (Foreground Service) واستبداله بـ PiP
|
||||
// await RideTrackingNative.updateRideTracking(
|
||||
// driverName: driverName,
|
||||
// driverPhone: driverPhone,
|
||||
// carDetails: '$make • $carColor • $licensePlate',
|
||||
// driverLat: driverCarsLocationToPassengerAfterApplied.last.latitude,
|
||||
// driverLng: driverCarsLocationToPassengerAfterApplied.last.longitude,
|
||||
// passengerLat: passengerLocation.latitude,
|
||||
// passengerLng: passengerLocation.longitude,
|
||||
// destLat: myDestination.latitude,
|
||||
// destLng: myDestination.longitude,
|
||||
// rideState: 'waiting',
|
||||
// estimatedTimeMinutes: (timeToPassengerSeconds / 60).round(),
|
||||
// totalDistanceMeters: distanceDriverToPassengerMeters,
|
||||
// );
|
||||
|
||||
// [PiP] تفعيل PiP عند بدء الرحلة (سيدخل وضع النافذة العائمة عند خروج المستخدم)
|
||||
PipService.enablePip();
|
||||
|
||||
// 6. بدء تتبع الموقع الدوري (Polling Backup + Smart Rerouting)
|
||||
// سيبدأ العمل بعد 6 ثواني
|
||||
@@ -1918,21 +1959,21 @@ class MapPassengerController extends GetxController {
|
||||
durationToRide; // موجود عندك من التايمر
|
||||
final double totalDistanceMeters = double.parse(distanceByPassenger);
|
||||
|
||||
// 2) استدعاء خدمة الأندرويد لتحديث الإشعار لحالة "inProgress"
|
||||
await RideTrackingNative.updateRideTracking(
|
||||
driverName: driverName,
|
||||
driverPhone: driverPhone,
|
||||
carDetails: carDetails,
|
||||
driverLat: driverLat,
|
||||
driverLng: driverLng,
|
||||
passengerLat: passengerLat,
|
||||
passengerLng: passengerLng,
|
||||
destLat: destLat,
|
||||
destLng: destLng,
|
||||
rideState: 'inProgress',
|
||||
estimatedTimeMinutes: (timeToDestinationSeconds / 60).round(),
|
||||
totalDistanceMeters: totalDistanceMeters,
|
||||
);
|
||||
// [PiP] تم تعطيل الإشعار المستمر القديم (Foreground Service) واستبداله بـ PiP
|
||||
// await RideTrackingNative.updateRideTracking(
|
||||
// driverName: driverName,
|
||||
// driverPhone: driverPhone,
|
||||
// carDetails: carDetails,
|
||||
// driverLat: driverLat,
|
||||
// driverLng: driverLng,
|
||||
// passengerLat: passengerLat,
|
||||
// passengerLng: passengerLng,
|
||||
// destLat: destLat,
|
||||
// destLng: destLng,
|
||||
// rideState: 'inProgress',
|
||||
// estimatedTimeMinutes: (timeToDestinationSeconds / 60).round(),
|
||||
// totalDistanceMeters: totalDistanceMeters,
|
||||
// );
|
||||
|
||||
// 3) بدء التايمر الداخلي الخاص بك (للـ ETA داخل التطبيق نفسه)
|
||||
rideIsBeginPassengerTimer();
|
||||
@@ -4894,6 +4935,7 @@ Intaleq Team''';
|
||||
currentRideState.value = RideState.cancelled;
|
||||
await RideLiveNotification.cancel(); // إغلاق أندرويد
|
||||
IosLiveActivityService.endRideActivity(); // ✅ إغلاق iOS
|
||||
PipService.disablePip(); // ✅ إيقاف PiP عند الإلغاء
|
||||
|
||||
// 4. الاتصال بالسيرفر لإلغاء الرحلة وإبلاغ السائق
|
||||
if (rideId != 'yet' && rideId != null) {
|
||||
|
||||
@@ -4,7 +4,7 @@ class Log {
|
||||
Log._();
|
||||
|
||||
static void print(String value, {StackTrace? stackTrace}) {
|
||||
developer.log(value, name: 'LOG', stackTrace: stackTrace);
|
||||
// developer.log(value, name: 'LOG', stackTrace: stackTrace);
|
||||
}
|
||||
|
||||
static Object? inspect(Object? object) {
|
||||
|
||||
61
lib/services/pip_service.dart
Normal file
61
lib/services/pip_service.dart
Normal file
@@ -0,0 +1,61 @@
|
||||
import 'dart:io';
|
||||
import 'package:flutter/services.dart';
|
||||
|
||||
/// خدمة التحكم بوضع النافذة العائمة (Picture-in-Picture) على أندرويد.
|
||||
/// تُستدعى عند بدء الرحلة لتفعيل PiP تلقائياً عند خروج المستخدم من التطبيق.
|
||||
class PipService {
|
||||
static const MethodChannel _channel = MethodChannel('intaleq/pip');
|
||||
|
||||
/// هل وضع PiP مدعوم على هذا الجهاز؟
|
||||
static Future<bool> isPipSupported() async {
|
||||
if (!Platform.isAndroid) return false;
|
||||
try {
|
||||
final result = await _channel.invokeMethod<bool>('isPipSupported');
|
||||
return result ?? false;
|
||||
} catch (e) {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
/// تفعيل الدخول التلقائي لوضع PiP عند الخروج (أثناء الرحلة)
|
||||
static Future<void> enablePip() async {
|
||||
if (!Platform.isAndroid) return;
|
||||
try {
|
||||
await _channel.invokeMethod('enablePip');
|
||||
} catch (e) {
|
||||
print('PiP enable error: \$e');
|
||||
}
|
||||
}
|
||||
|
||||
/// تعطيل الدخول التلقائي لوضع PiP (بعد انتهاء الرحلة)
|
||||
static Future<void> disablePip() async {
|
||||
if (!Platform.isAndroid) return;
|
||||
try {
|
||||
await _channel.invokeMethod('disablePip');
|
||||
} catch (e) {
|
||||
print('PiP disable error: \$e');
|
||||
}
|
||||
}
|
||||
|
||||
/// الدخول يدوياً لوضع PiP
|
||||
static Future<bool> enterPip() async {
|
||||
if (!Platform.isAndroid) return false;
|
||||
try {
|
||||
final result = await _channel.invokeMethod<bool>('enterPip');
|
||||
return result ?? false;
|
||||
} catch (e) {
|
||||
print('PiP enter error: \$e');
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
/// الاستماع لتغيير وضع PiP (الدخول/الخروج)
|
||||
static void listenToPipChanges(Function(bool isInPip) onChanged) {
|
||||
_channel.setMethodCallHandler((call) async {
|
||||
if (call.method == 'onPipChanged') {
|
||||
final isInPip = call.arguments as bool;
|
||||
onChanged(isInPip);
|
||||
}
|
||||
});
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user