new backend and more secure 29-04-2026
This commit is contained in:
@@ -9,6 +9,7 @@ class AK {
|
||||
X.r(X.r(X.r(Env.stripePublishableKe, cn), cC), cs);
|
||||
static final String sss_pass = X.r(X.r(X.r(Env.sss_pass, cn), cC), cs);
|
||||
static final String allowed = Env.allowed;
|
||||
static final String allowedWallet = Env.allowedWallet;
|
||||
static final String passnpassenger = X
|
||||
.r(X.r(X.r(Env.passnpassenger, cn), cC), cs)
|
||||
.toString()
|
||||
|
||||
@@ -114,8 +114,11 @@ class LoginController extends GetxController {
|
||||
|
||||
if (response.statusCode == 200) {
|
||||
final decoded = jsonDecode(response.body);
|
||||
final String? jwt =
|
||||
decoded['data'] != null ? decoded['data']['jwt'] : (decoded['message'] != null ? decoded['message']['jwt'] : decoded['jwt']);
|
||||
final String? jwt = decoded['data'] != null
|
||||
? decoded['data']['jwt']
|
||||
: (decoded['message'] != null
|
||||
? decoded['message']['jwt']
|
||||
: decoded['jwt']);
|
||||
|
||||
if (jwt != null) {
|
||||
// نشفر الـ JWT بالتشفير الثلاثي قبل التخزين في GetStorage
|
||||
@@ -144,8 +147,11 @@ class LoginController extends GetxController {
|
||||
Log.print('response: ${response.body}');
|
||||
if (response.statusCode == 200) {
|
||||
final decoded = jsonDecode(response.body);
|
||||
final String? jwt =
|
||||
decoded['data'] != null ? decoded['data']['jwt'] : (decoded['message'] != null ? decoded['message']['jwt'] : decoded['jwt']);
|
||||
final String? jwt = decoded['data'] != null
|
||||
? decoded['data']['jwt']
|
||||
: (decoded['message'] != null
|
||||
? decoded['message']['jwt']
|
||||
: decoded['jwt']);
|
||||
|
||||
if (jwt != null) {
|
||||
box.write(BoxName.jwt, c(jwt));
|
||||
@@ -216,13 +222,12 @@ class LoginController extends GetxController {
|
||||
Future<String?> getJwtWallet() async {
|
||||
dev = Platform.isAndroid ? 'android' : 'ios';
|
||||
|
||||
// await DeviceHelper.initAndStore();
|
||||
final String fp = box.read(BoxName.deviceFpEncrypted) ?? '';
|
||||
|
||||
var payload = {
|
||||
'id': box.read(BoxName.passengerID),
|
||||
'password': AK.passnpassenger,
|
||||
'aud': '${AK.allowed}$dev',
|
||||
'aud': '${AK.allowedWallet}$dev',
|
||||
'fingerPrint': fp,
|
||||
};
|
||||
|
||||
@@ -230,23 +235,26 @@ class LoginController extends GetxController {
|
||||
Uri.parse(AppLink.loginJwtWalletRider),
|
||||
body: payload,
|
||||
);
|
||||
Log.print('AppLink.loginJwtWalletRider: ${AppLink.loginJwtWalletRider}');
|
||||
|
||||
// Log.print('payload: ${payload}');
|
||||
Log.print('AppLink.loginJwtWalletRider: ${AppLink.loginJwtWalletRider}');
|
||||
Log.print('response wallet: ${response.body}');
|
||||
|
||||
if (response.statusCode == 200) {
|
||||
final decoded = jsonDecode(response.body);
|
||||
final String? jwt =
|
||||
decoded['data'] != null ? decoded['data']['jwt'] : decoded['jwt'];
|
||||
final String? hmac =
|
||||
decoded['data'] != null ? decoded['data']['hmac'] : decoded['hmac'];
|
||||
|
||||
// ← الإصلاح: نقرأ من message أو data أو root
|
||||
final inner = decoded['data'] ?? decoded['message'] ?? decoded;
|
||||
|
||||
final String? jwt = inner['jwt'];
|
||||
final String? hmac = inner['hmac'];
|
||||
|
||||
Log.print('jwt extracted: $jwt');
|
||||
Log.print('hmac extracted: $hmac');
|
||||
|
||||
if (hmac != null) {
|
||||
// نخزن الـ hmac للاستخدام في X-HMAC-Auth header
|
||||
box.write(BoxName.hmac, hmac);
|
||||
}
|
||||
|
||||
// wallet JWT يُرجَع مباشرة دون تشفير ثلاثي
|
||||
return jwt;
|
||||
}
|
||||
|
||||
|
||||
@@ -54,7 +54,8 @@ class CRUD {
|
||||
final phone = box.read(BoxName.phone) ?? box.read(BoxName.phoneDriver);
|
||||
|
||||
// طباعة الخطأ في الكونسول للمطور للمتابعة الفورية
|
||||
Log.print("🚨 [ADD_ERROR] Where: $where | Error: $error | Details: $details");
|
||||
Log.print(
|
||||
"🚨 [ADD_ERROR] Where: $where | Error: $error | Details: $details");
|
||||
|
||||
// Fire-and-forget call to prevent infinite loops if the logger itself fails.
|
||||
CRUD().post(
|
||||
@@ -114,6 +115,9 @@ class CRUD {
|
||||
final body = response.body;
|
||||
Log.print('request: ${response.request}');
|
||||
Log.print('body: $body');
|
||||
// Log.print('link: $link');
|
||||
Log.print('headers: $headers');
|
||||
Log.print('payload: $payload');
|
||||
|
||||
// 2xx
|
||||
if (sc >= 200 && sc < 300) {
|
||||
@@ -212,29 +216,34 @@ class CRUD {
|
||||
if (jsonData['error'] == 'Token expired') {
|
||||
print("CRUD.get: Token expired, refreshing and retrying once...");
|
||||
await Get.put(LoginController()).getJWT();
|
||||
|
||||
|
||||
// إعادة المحاولة مرة واحدة فقط بتوكن جديد
|
||||
var retryResponse = await http.post(
|
||||
url,
|
||||
body: payload,
|
||||
headers: {
|
||||
'Content-Type': 'application/x-www-form-urlencoded',
|
||||
'Authorization': 'Bearer ${r(box.read(BoxName.jwt)).toString().split(Env.addd)[0]}',
|
||||
'Authorization':
|
||||
'Bearer ${r(box.read(BoxName.jwt)).toString().split(Env.addd)[0]}',
|
||||
'X-Device-FP': _getFpHeader(),
|
||||
},
|
||||
);
|
||||
|
||||
|
||||
if (retryResponse.statusCode == 200) {
|
||||
return retryResponse.body;
|
||||
return retryResponse.body;
|
||||
}
|
||||
return jsonEncode({'status': 'failure', 'message': 'token_expired_retry_failed'});
|
||||
return jsonEncode(
|
||||
{'status': 'failure', 'message': 'token_expired_retry_failed'});
|
||||
} else {
|
||||
return jsonEncode({'status': 'failure', 'message': '401_unauthorized'});
|
||||
}
|
||||
} else {
|
||||
addError('Non-200 response code: ${response.statusCode}',
|
||||
'crud().get - Other', url.toString());
|
||||
return jsonEncode({'status': 'failure', 'message': 'server_error_${response.statusCode}'});
|
||||
return jsonEncode({
|
||||
'status': 'failure',
|
||||
'message': 'server_error_${response.statusCode}'
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
@@ -257,6 +266,10 @@ class CRUD {
|
||||
'X-HMAC-Auth': hmac.toString(),
|
||||
'X-Device-FP': _getFpHeader(), // ← إثبات الجهاز
|
||||
};
|
||||
// add print debug
|
||||
Log.print('headers: $headers');
|
||||
Log.print('payload: $payload');
|
||||
Log.print('link: $link');
|
||||
|
||||
return await _makeRequest(
|
||||
link: link,
|
||||
@@ -711,7 +724,8 @@ class CRUD {
|
||||
if (response.statusCode == 200 || response.statusCode == 201) {
|
||||
return jsonDecode(response.body);
|
||||
}
|
||||
Log.print('MapSaas Post Error: ${response.statusCode} - ${response.body}');
|
||||
Log.print(
|
||||
'MapSaas Post Error: ${response.statusCode} - ${response.body}');
|
||||
return null;
|
||||
} catch (e) {
|
||||
Log.print('MapSaas Post Exception: $e');
|
||||
|
||||
@@ -210,8 +210,7 @@ class MapPassengerController extends GetxController {
|
||||
bool rideConfirm = false;
|
||||
bool isMarkersShown = false;
|
||||
bool isMainBottomMenuMap = true;
|
||||
Timer? markerReloadingTimer2 = Timer(Duration.zero, () {});
|
||||
Timer? markerReloadingTimer1 = Timer(Duration.zero, () {});
|
||||
|
||||
int durationToPassenger = 0;
|
||||
bool isWayPointSheet = false;
|
||||
bool isWayPointStopsSheet = false;
|
||||
@@ -228,7 +227,7 @@ class MapPassengerController extends GetxController {
|
||||
var dataCarsLocationByPassenger;
|
||||
var datadriverCarsLocationToPassengerAfterApplied;
|
||||
CarLocation? nearestCar;
|
||||
late Timer? markerReloadingTimer = Timer(Duration.zero, () {});
|
||||
|
||||
bool shouldFetch = true; // Flag to determine if fetch should be executed
|
||||
int selectedPassengerCount = 1;
|
||||
double progress = 0;
|
||||
@@ -335,12 +334,17 @@ class MapPassengerController extends GetxController {
|
||||
// لتخزين نقاط مسار السائق الحالية للمقارنة
|
||||
List<LatLng> _currentDriverRoutePoints = [];
|
||||
|
||||
// متغير لتتبع مصدر القبول — Socket أم غيره
|
||||
String _rideAcceptedViaSource = "Unknown";
|
||||
// عدّاد تحديثات الموقع المستلمة من السوكيت (لقياس الصحة)
|
||||
int _socketLocationUpdatesCount = 0;
|
||||
|
||||
final Map<RideState, int> _pollingIntervals = {
|
||||
RideState.noRide: 6,
|
||||
RideState.searching: 5,
|
||||
RideState.searching: 8,
|
||||
RideState.driverApplied: 10,
|
||||
RideState.driverArrived: 8,
|
||||
RideState.inProgress: 6,
|
||||
RideState.driverArrived: 15,
|
||||
RideState.inProgress: 15,
|
||||
RideState.cancelled: 3600,
|
||||
RideState.finished: 3600,
|
||||
RideState.preCheckReview: 3600,
|
||||
@@ -485,6 +489,49 @@ class MapPassengerController extends GetxController {
|
||||
currentRideState.value == RideState.inProgress;
|
||||
}
|
||||
|
||||
/// فحص سريع: هل السوكيت يعمل ويرسل بيانات؟
|
||||
bool _isSocketHealthy() {
|
||||
if (!isSocketConnected) return false;
|
||||
if (_lastSocketLocationTime == null) return false;
|
||||
final diff = DateTime.now().difference(_lastSocketLocationTime!).inSeconds;
|
||||
return diff < 20; // إذا آخر تحديث قبل أقل من 20 ثانية
|
||||
}
|
||||
|
||||
/// 🧠 خوارزمية ذكية: حساب أقصر مسافة بين موقع السائق والـ Polyline (بدون API)
|
||||
double _calculateDistanceToPolyline(LatLng point, List<LatLng> polyline) {
|
||||
if (polyline.isEmpty) return 999.0;
|
||||
double minDistance = double.infinity;
|
||||
|
||||
for (int i = 0; i < polyline.length - 1; i++) {
|
||||
double d = _distToSegment(point, polyline[i], polyline[i + 1]);
|
||||
if (d < minDistance) minDistance = d;
|
||||
}
|
||||
return minDistance;
|
||||
}
|
||||
|
||||
double _distToSegment(LatLng p, LatLng v, LatLng w) {
|
||||
double l2 = _dist2(v, w);
|
||||
if (l2 == 0) return _distanceBetween(p, v);
|
||||
double t = ((p.latitude - v.latitude) * (w.latitude - v.latitude) +
|
||||
(p.longitude - v.longitude) * (w.longitude - v.longitude)) /
|
||||
l2;
|
||||
t = max(0, min(1, t));
|
||||
return _distanceBetween(
|
||||
p,
|
||||
LatLng(v.latitude + t * (w.latitude - v.latitude),
|
||||
v.longitude + t * (w.longitude - v.longitude)));
|
||||
}
|
||||
|
||||
double _dist2(LatLng v, LatLng w) {
|
||||
return pow(v.latitude - w.latitude, 2).toDouble() +
|
||||
pow(v.longitude - w.longitude, 2).toDouble();
|
||||
}
|
||||
|
||||
double _distanceBetween(LatLng p1, LatLng p2) {
|
||||
return Geolocator.distanceBetween(
|
||||
p1.latitude, p1.longitude, p2.latitude, p2.longitude);
|
||||
}
|
||||
|
||||
// ==============================================================================
|
||||
// 2. العقل المدبر: توجيه الحالات
|
||||
// ==============================================================================
|
||||
@@ -841,7 +888,7 @@ class MapPassengerController extends GetxController {
|
||||
title: "We apologize 😔".tr,
|
||||
middleText: "No drivers found at the moment.\nPlease try again later.".tr,
|
||||
confirm: ElevatedButton(
|
||||
onPressed: () => Get.back(),
|
||||
onPressed: () => Navigator.pop(Get.context!),
|
||||
child: Text("Ok".tr),
|
||||
),
|
||||
);
|
||||
@@ -976,6 +1023,11 @@ class MapPassengerController extends GetxController {
|
||||
driverArrivePassengerDialoge();
|
||||
startTimerDriverWaitPassenger5Minute();
|
||||
|
||||
// 4. إزالة مسار السائق واستعادة مسار الراكب للوجهة
|
||||
if (polylineCoordinates.isNotEmpty) {
|
||||
_playRouteAnimation(polylineCoordinates, lastComputedBounds);
|
||||
}
|
||||
|
||||
update();
|
||||
}
|
||||
|
||||
@@ -1083,6 +1135,13 @@ class MapPassengerController extends GetxController {
|
||||
if (!isSocketConnected || data == null) return;
|
||||
// 🔥 1. تسجيل وقت استلام البيانات (تغذية الـ Watchdog)
|
||||
_lastSocketLocationTime = DateTime.now();
|
||||
_socketLocationUpdatesCount++;
|
||||
|
||||
// 🧠 إذا وصلتنا 3 تحديثات ناجحة من السوكيت، أوقف البولينج (إن كان يعمل)
|
||||
if (_socketLocationUpdatesCount >= 3 && _locationPollingTimer != null) {
|
||||
Log.print("✅ Socket delivering locations reliably. Stopping polling.");
|
||||
_stopDriverLocationPolling();
|
||||
}
|
||||
try {
|
||||
double lat = double.tryParse(data['latitude']?.toString() ?? '0') ?? 0;
|
||||
double lng = double.tryParse(data['longitude']?.toString() ?? '0') ?? 0;
|
||||
@@ -1101,6 +1160,19 @@ class MapPassengerController extends GetxController {
|
||||
|
||||
currentLocationOfDrivers = newPos;
|
||||
// ------------------------------------------------------------------
|
||||
// 🔥 2. Smart Rerouting Logic ( deviation > 50m )
|
||||
// ------------------------------------------------------------------
|
||||
if (_currentDriverRoutePoints.isNotEmpty) {
|
||||
double deviation =
|
||||
_calculateDistanceToPolyline(newPos, _currentDriverRoutePoints);
|
||||
if (deviation > 50.0) {
|
||||
Log.print(
|
||||
"⚠️ Driver deviated by ${deviation.toStringAsFixed(1)}m. Smart Rerouting triggered.");
|
||||
// إعادة رسم المسار محلياً (لا يتم استدعاؤه إلا عند الانحراف الحقيقي)
|
||||
calculateDriverToPassengerRoute(newPos, passengerLocation);
|
||||
}
|
||||
}
|
||||
|
||||
// 🔥 تحديث الكاميرا: ضمان بقاء السيارة في منتصف الخريطة
|
||||
// ------------------------------------------------------------------
|
||||
// ملاحظة: تأكد من أن mapController قد تم تهيئته (initialized)
|
||||
@@ -1277,6 +1349,9 @@ class MapPassengerController extends GetxController {
|
||||
return;
|
||||
}
|
||||
|
||||
_rideAcceptedViaSource = source;
|
||||
_socketLocationUpdatesCount = 0;
|
||||
|
||||
_isAcceptanceProcessed = true; // قفل الباب
|
||||
Log.print("🚀 Winner: $source triggered acceptance! Processing...");
|
||||
|
||||
@@ -1321,7 +1396,7 @@ class MapPassengerController extends GetxController {
|
||||
// 5. 🔥 العمليات الجغرافية (المسار والوقت) 🔥
|
||||
|
||||
// أ) جلب موقع السائق الأولي
|
||||
// await getDriverCarsLocationToPassengerAfterApplied();// stop this to use socket update
|
||||
await getDriverCarsLocationToPassengerAfterApplied();
|
||||
_startSocketWatchdog();
|
||||
// ب) رسم المسار وحساب الوقت
|
||||
if (driverCarsLocationToPassengerAfterApplied.isNotEmpty) {
|
||||
@@ -1336,63 +1411,63 @@ class MapPassengerController extends GetxController {
|
||||
final int timeToPassengerSeconds =
|
||||
timeToPassengerFromDriverAfterApplied; // مثلاً من السيرفر
|
||||
final double distanceDriverToPassengerMeters =
|
||||
double.parse(distanceByPassenger);
|
||||
// [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,
|
||||
// );
|
||||
double.tryParse(distanceByPassenger.toString()) ?? 0.0;
|
||||
|
||||
// [PiP] تفعيل PiP عند بدء الرحلة (سيدخل وضع النافذة العائمة عند خروج المستخدم)
|
||||
PipService.enablePip();
|
||||
|
||||
// 6. بدء تتبع الموقع الدوري (Polling Backup + Smart Rerouting)
|
||||
// سيبدأ العمل بعد 6 ثواني
|
||||
_startDriverLocationPollingWithTimer();
|
||||
// 6. 🔥 القرار الذكي: هل نبدأ البولينج أم نعتمد على السوكيت؟ 🔥
|
||||
if (source == "Socket" && isSocketConnected) {
|
||||
// ✅ السوكيت هو من أخبرنا بالقبول — نثق به ولا نشغل البولينج
|
||||
Log.print(
|
||||
"🧠 Smart Mode: Socket accepted ride. Skipping polling, relying on WebSocket.");
|
||||
// الـ watchdog وحده يكفي كشبكة أمان
|
||||
} else {
|
||||
// ⚠️ القبول من FCM أو Polling — نشغل البولينج كالمعتاد
|
||||
Log.print("🔄 Fallback Mode: $source accepted ride. Starting polling.");
|
||||
_startDriverLocationPollingWithTimer();
|
||||
}
|
||||
}
|
||||
|
||||
Timer? _watchdogTimer;
|
||||
|
||||
void _startSocketWatchdog() {
|
||||
_watchdogTimer?.cancel();
|
||||
|
||||
Log.print("👀 Starting Socket Watchdog (Hybrid Mode)...");
|
||||
|
||||
// نفحص كل 5 ثواني
|
||||
_watchdogTimer = Timer.periodic(const Duration(seconds: 5), (timer) async {
|
||||
// شروط إيقاف الحارس (إذا انتهت الرحلة)
|
||||
if (currentRideState.value != RideState.driverApplied &&
|
||||
currentRideState.value != RideState.driverArrived &&
|
||||
currentRideState.value != RideState.inProgress) {
|
||||
timer.cancel();
|
||||
return;
|
||||
}
|
||||
|
||||
// 1. حساب الزمن المنقضي منذ آخر تحديث سوكيت
|
||||
final lastTime = _lastSocketLocationTime ??
|
||||
DateTime.now().subtract(const Duration(minutes: 1));
|
||||
final difference = DateTime.now().difference(lastTime).inSeconds;
|
||||
|
||||
// 2. القرار
|
||||
if (difference < 15 && isSocketConnected) {
|
||||
// ✅ الوضع ممتاز: وصلنا تحديث في آخر 10 ثواني والسوكيت متصل
|
||||
// لا تفعل شيئاً (وفر السيرفر والبطارية)
|
||||
// Log.print("✅ Socket is healthy. Skipping API poll.");
|
||||
} else {
|
||||
// ⚠️ الوضع حرج: السوكيت معلق أو مفصول لأكثر من 10 ثواني
|
||||
Log.print("⚠️ Socket silent for ${difference}s. Forcing API Poll...");
|
||||
|
||||
// استدعاء دالة البولينغ (لمرة واحدة)
|
||||
// ✅ السوكيت صحي — تأكد أن البولينج متوقف إذا كنا في وضع السوكيت
|
||||
if (_locationPollingTimer != null &&
|
||||
_rideAcceptedViaSource == "Socket") {
|
||||
Log.print("✅ Socket recovered. Stopping polling fallback.");
|
||||
_stopDriverLocationPolling();
|
||||
}
|
||||
} else if (difference >= 15 && difference < 30) {
|
||||
// ⚠️ تأخر متوسط — جلب واحد فقط
|
||||
Log.print("⚠️ Socket silent for ${difference}s. Single API Poll...");
|
||||
await getDriverCarsLocationToPassengerAfterApplied();
|
||||
} else if (difference >= 30) {
|
||||
// 🔴 السوكيت ميت — تفعيل البولينج الكامل كـ fallback
|
||||
if (_locationPollingTimer == null) {
|
||||
Log.print(
|
||||
"🔴 Socket dead for ${difference}s. Activating polling fallback!");
|
||||
_startDriverLocationPollingWithTimer();
|
||||
} else {
|
||||
// البولينج يعمل بالفعل، فقط أكمل
|
||||
await getDriverCarsLocationToPassengerAfterApplied();
|
||||
}
|
||||
}
|
||||
});
|
||||
}
|
||||
@@ -1441,9 +1516,7 @@ class MapPassengerController extends GetxController {
|
||||
_uiCountdownTimer?.cancel();
|
||||
|
||||
// 3. إيقاف مؤقتات الخريطة
|
||||
markerReloadingTimer?.cancel();
|
||||
markerReloadingTimer1?.cancel();
|
||||
markerReloadingTimer2?.cancel();
|
||||
|
||||
_animationTimers.forEach((key, timer) => timer.cancel());
|
||||
_animationTimers.clear();
|
||||
|
||||
@@ -1518,17 +1591,18 @@ class MapPassengerController extends GetxController {
|
||||
break;
|
||||
|
||||
case RideState.searching:
|
||||
// 1. التحقق من حالة الطلب (هل قبله أحد؟) عبر البولينج فقط إذا كان السوكيت غير متصل
|
||||
if (!isSocketConnected) {
|
||||
try {
|
||||
String statusFromServer = await getRideStatus(rideId);
|
||||
if (statusFromServer == 'Apply' || statusFromServer == 'Applied') {
|
||||
await processRideAcceptance(source: "Polling");
|
||||
break;
|
||||
}
|
||||
} catch (e) {
|
||||
Log.print('Error polling getRideStatus: $e');
|
||||
// Guard: Don't poll if ride is not registered yet
|
||||
if (rideId == 'yet' || rideId.isEmpty) break;
|
||||
|
||||
// 1. التحقق من حالة الطلب (هل قبله أحد؟) عبر البولينج كشبكة أمان
|
||||
try {
|
||||
String statusFromServer = await getRideStatus(rideId);
|
||||
if (statusFromServer == 'Apply' || statusFromServer == 'Applied') {
|
||||
await processRideAcceptance(source: "Polling");
|
||||
break;
|
||||
}
|
||||
} catch (e) {
|
||||
Log.print('Error polling getRideStatus: $e');
|
||||
}
|
||||
|
||||
final now = DateTime.now();
|
||||
@@ -1604,7 +1678,10 @@ class MapPassengerController extends GetxController {
|
||||
Log.print('Error polling for Arrived/Begin status: $e');
|
||||
}
|
||||
}
|
||||
getDriverCarsLocationToPassengerAfterApplied();
|
||||
// 🧠 جلب الموقع فقط إذا السوكيت غير صحي
|
||||
if (!_isSocketHealthy()) {
|
||||
getDriverCarsLocationToPassengerAfterApplied();
|
||||
}
|
||||
break;
|
||||
|
||||
case RideState.driverArrived:
|
||||
@@ -1653,7 +1730,10 @@ class MapPassengerController extends GetxController {
|
||||
_isRideBeginLogicExecuted = true;
|
||||
_executeBeginRideLogic();
|
||||
}
|
||||
getDriverCarsLocationToPassengerAfterApplied();
|
||||
// 🧠 جلب الموقع فقط إذا السوكيت غير صحي
|
||||
if (!_isSocketHealthy()) {
|
||||
getDriverCarsLocationToPassengerAfterApplied();
|
||||
}
|
||||
break;
|
||||
case RideState.finished:
|
||||
tripFinishedFromDriver();
|
||||
@@ -1975,40 +2055,6 @@ class MapPassengerController extends GetxController {
|
||||
_stopWaitPassengerTimer();
|
||||
|
||||
// 1) بيانات السائق والرحلة
|
||||
final driverName = this.driverName ?? 'السائق';
|
||||
final driverPhone = this.driverPhone ?? '';
|
||||
final carBrand = this.make ?? '';
|
||||
final carColor = this.carColor ?? '';
|
||||
final carPlate = this.licensePlate ?? '';
|
||||
final carDetails = '$carBrand • $carColor • $carPlate';
|
||||
final driverLat = driverCarsLocationToPassengerAfterApplied.last.latitude;
|
||||
final driverLng = driverCarsLocationToPassengerAfterApplied.last.longitude;
|
||||
final passengerLat = passengerLocation.latitude;
|
||||
final passengerLng = passengerLocation.longitude;
|
||||
final destLat = myDestination.latitude ?? 0.0;
|
||||
final destLng = myDestination.longitude ?? 0.0;
|
||||
|
||||
final int timeToDestinationSeconds =
|
||||
durationToRide; // موجود عندك من التايمر
|
||||
final double totalDistanceMeters = double.parse(distanceByPassenger);
|
||||
|
||||
// [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();
|
||||
update();
|
||||
}
|
||||
@@ -2095,28 +2141,6 @@ class MapPassengerController extends GetxController {
|
||||
update();
|
||||
}
|
||||
|
||||
void convertHintTextStartNewPlaces(int index) {
|
||||
if (placesStart.isEmpty) {
|
||||
hintTextStartPoint = 'Search for your Start point'.tr;
|
||||
update();
|
||||
} else {
|
||||
var res = placesStart[index];
|
||||
|
||||
hintTextStartPoint = res['displayName']?['text'] ??
|
||||
res['formattedAddress'] ??
|
||||
'Unknown Place';
|
||||
|
||||
double? lat = res['location']?['latitude'];
|
||||
double? lng = res['location']?['longitude'];
|
||||
|
||||
if (lat != null && lng != null) {
|
||||
newStartPointLocation = LatLng(lat, lng);
|
||||
}
|
||||
|
||||
update();
|
||||
}
|
||||
}
|
||||
|
||||
void convertHintTextPlaces(int index, var res) {
|
||||
if (placeListResponseAll[index].isEmpty) {
|
||||
placeListResponseAll[index] = res;
|
||||
@@ -2137,62 +2161,6 @@ class MapPassengerController extends GetxController {
|
||||
}
|
||||
}
|
||||
|
||||
void convertHintTextPlaces1(int index) {
|
||||
if (wayPoint1.isEmpty) {
|
||||
hintTextwayPoint1 = 'Search for your Start point'.tr;
|
||||
update();
|
||||
} else {
|
||||
hintTextwayPoint1 = wayPoint1[index]['name'];
|
||||
currentLocationString1 = wayPoint1[index]['name'];
|
||||
double lat = wayPoint1[index]['geometry']['location']['lat'];
|
||||
double lng = wayPoint1[index]['geometry']['location']['lng'];
|
||||
newPointLocation1 = LatLng(lat, lng);
|
||||
update();
|
||||
}
|
||||
}
|
||||
|
||||
void convertHintTextPlaces2(int index) {
|
||||
if (wayPoint1.isEmpty) {
|
||||
hintTextwayPoint2 = 'Search for your Start point'.tr;
|
||||
update();
|
||||
} else {
|
||||
hintTextwayPoint2 = wayPoint2[index]['name'];
|
||||
currentLocationString2 = wayPoint1[index]['name'];
|
||||
double lat = wayPoint2[index]['geometry']['location']['lat'];
|
||||
double lng = wayPoint2[index]['geometry']['location']['lng'];
|
||||
newPointLocation2 = LatLng(lat, lng);
|
||||
update();
|
||||
}
|
||||
}
|
||||
|
||||
void convertHintTextPlaces3(int index) {
|
||||
if (wayPoint1.isEmpty) {
|
||||
hintTextwayPoint3 = 'Search for your Start point'.tr;
|
||||
update();
|
||||
} else {
|
||||
hintTextwayPoint3 = wayPoint3[index]['name'];
|
||||
currentLocationString3 = wayPoint1[index]['name'];
|
||||
double lat = wayPoint3[index]['geometry']['location']['lat'];
|
||||
double lng = wayPoint3[index]['geometry']['location']['lng'];
|
||||
newPointLocation3 = LatLng(lat, lng);
|
||||
update();
|
||||
}
|
||||
}
|
||||
|
||||
void convertHintTextPlaces4(int index) {
|
||||
if (wayPoint1.isEmpty) {
|
||||
hintTextwayPoint4 = 'Search for your Start point'.tr;
|
||||
update();
|
||||
} else {
|
||||
hintTextwayPoint4 = wayPoint4[index]['name'];
|
||||
currentLocationString4 = wayPoint1[index]['name'];
|
||||
double lat = wayPoint4[index]['geometry']['location']['lat'];
|
||||
double lng = wayPoint4[index]['geometry']['location']['lng'];
|
||||
newPointLocation4 = LatLng(lat, lng);
|
||||
update();
|
||||
}
|
||||
}
|
||||
|
||||
void convertHintTextDestinationNewPlaces(int index) {
|
||||
if (placesDestination.isEmpty) {
|
||||
hintTextDestinationPoint = 'Search for your destination'.tr;
|
||||
@@ -3004,30 +2972,6 @@ class MapPassengerController extends GetxController {
|
||||
update();
|
||||
}
|
||||
|
||||
void clearPlaces1() {
|
||||
wayPoint1 = [];
|
||||
hintTextwayPoint1 = 'Search for waypoint'.tr;
|
||||
update();
|
||||
}
|
||||
|
||||
void clearPlaces2() {
|
||||
wayPoint2 = [];
|
||||
hintTextwayPoint2 = 'Search for waypoint'.tr;
|
||||
update();
|
||||
}
|
||||
|
||||
void clearPlaces3() {
|
||||
wayPoint3 = [];
|
||||
hintTextwayPoint3 = 'Search for waypoint'.tr;
|
||||
update();
|
||||
}
|
||||
|
||||
void clearPlaces4() {
|
||||
wayPoint4 = [];
|
||||
hintTextwayPoint4 = 'Search for waypoint'.tr;
|
||||
update();
|
||||
}
|
||||
|
||||
int selectedReason = -1;
|
||||
String? cancelNote;
|
||||
void selectReason0(int index, String note) {
|
||||
@@ -3675,118 +3619,6 @@ class MapPassengerController extends GetxController {
|
||||
late LatLng currentDriverLocation;
|
||||
late double headingList;
|
||||
|
||||
// Future getCarsLocationByPassengerAndReloadMarker() async {
|
||||
// if (statusRide == 'wait') {
|
||||
// carsLocationByPassenger = [];
|
||||
// LatLngBounds bounds = calculateBounds(
|
||||
// passengerLocation.latitude, passengerLocation.longitude, 7000);
|
||||
// var res;
|
||||
// if (box.read(BoxName.carType) == 'Lady') {
|
||||
// res = await CRUD()
|
||||
// .get(link: AppLink.getFemalDriverLocationByPassenger, payload: {
|
||||
// 'southwestLat': bounds.southwest.latitude.toString(),
|
||||
// 'southwestLon': bounds.southwest.longitude.toString(),
|
||||
// 'northeastLat': bounds.northeast.latitude.toString(),
|
||||
// 'northeastLon': bounds.northeast.longitude.toString(),
|
||||
// });
|
||||
// } else if (box.read(BoxName.carType) == 'Speed') {
|
||||
// res = await CRUD().get(
|
||||
// link: AppLink.getCarsLocationByPassengerSpeed,
|
||||
// payload: {
|
||||
// 'southwestLat': bounds.southwest.latitude.toString(),
|
||||
// 'southwestLon': bounds.southwest.longitude.toString(),
|
||||
// 'northeastLat': bounds.northeast.latitude.toString(),
|
||||
// 'northeastLon': bounds.northeast.longitude.toString(),
|
||||
// },
|
||||
// );
|
||||
// } else if (box.read(BoxName.carType) == 'Delivery') {
|
||||
// res = await CRUD().get(
|
||||
// link: AppLink.getCarsLocationByPassengerDelivery,
|
||||
// payload: {
|
||||
// 'southwestLat': bounds.southwest.latitude.toString(),
|
||||
// 'southwestLon': bounds.southwest.longitude.toString(),
|
||||
// 'northeastLat': bounds.northeast.latitude.toString(),
|
||||
// 'northeastLon': bounds.northeast.longitude.toString(),
|
||||
// },
|
||||
// );
|
||||
// } else {
|
||||
// res = await CRUD()
|
||||
// .get(link: AppLink.getCarsLocationByPassenger, payload: {
|
||||
// 'southwestLat': bounds.southwest.latitude.toString(),
|
||||
// 'southwestLon': bounds.southwest.longitude.toString(),
|
||||
// 'northeastLat': bounds.northeast.latitude.toString(),
|
||||
// 'northeastLon': bounds.northeast.longitude.toString(),
|
||||
// });
|
||||
// }
|
||||
// if (res == 'failure') {
|
||||
// noCarString = true;
|
||||
// dataCarsLocationByPassenger = res;
|
||||
// update();
|
||||
// } else {
|
||||
// // Get.snackbar('no car', 'message');
|
||||
// noCarString = false;
|
||||
// dataCarsLocationByPassenger = jsonDecode(res);
|
||||
// // if (dataCarsLocationByPassenger.length > carsOrder) {
|
||||
// driverId = dataCarsLocationByPassenger['message'][carsOrder]
|
||||
// ['driver_id']
|
||||
// .toString();
|
||||
// gender = dataCarsLocationByPassenger['message'][carsOrder]['gender']
|
||||
// .toString();
|
||||
// // }
|
||||
|
||||
// carsLocationByPassenger.clear(); // Clear existing markers
|
||||
|
||||
// // late LatLng lastDriverLocation; // Initialize a variable for last location
|
||||
|
||||
// for (var i = 0;
|
||||
// i < dataCarsLocationByPassenger['message'].length;
|
||||
// i++) {
|
||||
// var json = dataCarsLocationByPassenger['message'][i];
|
||||
// // CarLocationModel model = CarLocationModel.fromJson(json);
|
||||
// if (carLocationsModels.length < i + 1) {
|
||||
// // carLocationsModels.add(model);
|
||||
// markers.add(
|
||||
// Marker(
|
||||
// markerId: MarkerId(json['latitude']),
|
||||
// position: LatLng(
|
||||
// double.parse(json['latitude']),
|
||||
// double.parse(json['longitude']),
|
||||
// ),
|
||||
// rotation: double.parse(json['heading']),
|
||||
// icon: json['model'].toString().contains('دراجة')
|
||||
// ? motoIcon
|
||||
// : json['gender'] == 'Male'.tr
|
||||
// ? carIcon
|
||||
// : ladyIcon,
|
||||
// ),
|
||||
// );
|
||||
// driversToken.add(json['token']);
|
||||
// // driversToken = json['token'];
|
||||
// } else {
|
||||
// // carLocationsModels[i] = model;
|
||||
// markers[i] = Marker(
|
||||
// markerId: MarkerId(json['latitude']),
|
||||
// position: LatLng(
|
||||
// double.parse(json['latitude']),
|
||||
// double.parse(json['longitude']),
|
||||
// ),
|
||||
// rotation: double.parse(json['heading']),
|
||||
// icon: json['model'].contains('دراجة')
|
||||
// ? motoIcon
|
||||
// : json['gender'] == 'Male'.tr
|
||||
// ? carIcon
|
||||
// : ladyIcon,
|
||||
// );
|
||||
// // driversToken = json['token'];
|
||||
// driversToken.add(json['token']);
|
||||
// }
|
||||
// }
|
||||
// }
|
||||
|
||||
// update();
|
||||
// }
|
||||
// }
|
||||
|
||||
Map<String, Timer> _animationTimers = {};
|
||||
final int updateIntervalMs = 100; // Update every 100ms
|
||||
final double minMovementThreshold =
|
||||
@@ -3808,14 +3640,6 @@ class MapPassengerController extends GetxController {
|
||||
// After 4 attempts, stop the search
|
||||
t.cancel();
|
||||
|
||||
// No cars found after 4 attempts
|
||||
// MyDialog().getDialog(
|
||||
// "No Car or Driver Found in your area.".tr,
|
||||
// "No Car or Driver Found in your area.".tr,
|
||||
// () {
|
||||
// Get.back();
|
||||
// },
|
||||
// );
|
||||
if (!foundCars) {
|
||||
noCarString = true;
|
||||
dataCarsLocationByPassenger = 'failure';
|
||||
@@ -3876,43 +3700,6 @@ class MapPassengerController extends GetxController {
|
||||
});
|
||||
}
|
||||
|
||||
// String getLocationArea(double latitude, double longitude) {
|
||||
// final locations = box.read(BoxName.locationName) ?? [];
|
||||
// for (final location in locations) {
|
||||
// final locationData = location as Map<String, dynamic>;
|
||||
|
||||
// // Debugging: Print location data
|
||||
// // Log.print('Location Data: $locationData');
|
||||
|
||||
// // Convert string values to double
|
||||
// final minLatitude =
|
||||
// double.tryParse(locationData['min_latitude'].toString()) ?? 0.0;
|
||||
// final maxLatitude =
|
||||
// double.tryParse(locationData['max_latitude'].toString()) ?? 0.0;
|
||||
// final minLongitude =
|
||||
// double.tryParse(locationData['min_longitude'].toString()) ?? 0.0;
|
||||
// final maxLongitude =
|
||||
// double.tryParse(locationData['max_longitude'].toString()) ?? 0.0;
|
||||
|
||||
// // Debugging: Print converted values
|
||||
// Log.print(
|
||||
// 'Converted Values: minLatitude=$minLatitude, maxLatitude=$maxLatitude, minLongitude=$minLongitude, maxLongitude=$maxLongitude');
|
||||
|
||||
// if (latitude >= minLatitude &&
|
||||
// latitude <= maxLatitude &&
|
||||
// longitude >= minLongitude &&
|
||||
// longitude <= maxLongitude) {
|
||||
// box.write(BoxName.serverChosen, (locationData['server_link']));
|
||||
// // Log.print(
|
||||
// // 'locationData----server_link: ${(locationData['server_link'])}');
|
||||
// return locationData['name'];
|
||||
// }
|
||||
// }
|
||||
|
||||
// // Default case
|
||||
// box.write(BoxName.serverChosen, AppLink.IntaleqSyriaServer);
|
||||
// return 'Cairo';
|
||||
// }
|
||||
String getLocationArea(double latitude, double longitude) {
|
||||
LatLng passengerPoint = LatLng(latitude, longitude);
|
||||
|
||||
@@ -4762,9 +4549,7 @@ Intaleq Team''';
|
||||
"--- MapPassengerController: Closing and cleaning up all resources. ---");
|
||||
|
||||
// 1. إلغاء المؤقتات الفردية (باستخدام ?. الآمن)
|
||||
markerReloadingTimer?.cancel();
|
||||
markerReloadingTimer1?.cancel();
|
||||
markerReloadingTimer2?.cancel();
|
||||
|
||||
timerToPassengerFromDriverAfterApplied?.cancel();
|
||||
_timer?.cancel();
|
||||
_masterTimer?.cancel(); // (أضف المؤقت الرئيسي)
|
||||
@@ -6039,55 +5824,73 @@ Intaleq Team''';
|
||||
if (context == null) return;
|
||||
|
||||
WidgetsBinding.instance.addPostFrameCallback((_) {
|
||||
// إغلاق أي سناك بار مفتوح حالياً لتجنب التكرار
|
||||
ScaffoldMessenger.of(context).hideCurrentSnackBar();
|
||||
// Close any existing open dialogs first
|
||||
if (Get.isDialogOpen == true) {
|
||||
Get.back();
|
||||
}
|
||||
|
||||
ScaffoldMessenger.of(context).showSnackBar(
|
||||
SnackBar(
|
||||
content: Row(
|
||||
children: [
|
||||
const SizedBox(
|
||||
width: 24,
|
||||
height: 24,
|
||||
child: MyCircularProgressIndicator(),
|
||||
),
|
||||
const SizedBox(width: 16),
|
||||
Expanded(
|
||||
child: Column(
|
||||
mainAxisSize: MainAxisSize.min,
|
||||
crossAxisAlignment: CrossAxisAlignment.start,
|
||||
children: [
|
||||
Text(
|
||||
'Drawing route on map...'.tr,
|
||||
style: const TextStyle(
|
||||
color: Colors.white,
|
||||
fontWeight: FontWeight.bold,
|
||||
fontSize: 14,
|
||||
),
|
||||
),
|
||||
const SizedBox(height: 4),
|
||||
Text(
|
||||
'Please wait while we prepare your trip.'.tr,
|
||||
style: const TextStyle(
|
||||
color: Colors.white70,
|
||||
fontSize: 12,
|
||||
),
|
||||
),
|
||||
],
|
||||
Get.dialog(
|
||||
Dialog(
|
||||
backgroundColor: Colors.transparent,
|
||||
elevation: 0,
|
||||
child: Container(
|
||||
padding: const EdgeInsets.all(24),
|
||||
width: 180,
|
||||
decoration: BoxDecoration(
|
||||
color: Colors.white.withOpacity(0.95),
|
||||
borderRadius: BorderRadius.circular(24),
|
||||
boxShadow: [
|
||||
BoxShadow(
|
||||
color: Colors.black.withOpacity(0.15),
|
||||
blurRadius: 20,
|
||||
spreadRadius: 5,
|
||||
)
|
||||
],
|
||||
),
|
||||
child: Column(
|
||||
mainAxisSize: MainAxisSize.min,
|
||||
mainAxisAlignment: MainAxisAlignment.center,
|
||||
crossAxisAlignment: CrossAxisAlignment.center,
|
||||
children: [
|
||||
// App Logo
|
||||
Image.asset(
|
||||
'assets/images/logo.gif',
|
||||
height: 64,
|
||||
errorBuilder: (context, error, stackTrace) => const Icon(
|
||||
Icons.map,
|
||||
size: 64,
|
||||
color: AppColor.primaryColor,
|
||||
),
|
||||
),
|
||||
),
|
||||
],
|
||||
const SizedBox(height: 16),
|
||||
const SizedBox(
|
||||
width: 24,
|
||||
height: 24,
|
||||
child: MyCircularProgressIndicator(),
|
||||
),
|
||||
const SizedBox(height: 16),
|
||||
Text(
|
||||
'Drawing route on map...'.tr,
|
||||
style: const TextStyle(
|
||||
fontWeight: FontWeight.bold,
|
||||
fontSize: 14,
|
||||
color: AppColor.primaryColor,
|
||||
),
|
||||
textAlign: TextAlign.center,
|
||||
),
|
||||
],
|
||||
),
|
||||
),
|
||||
backgroundColor: AppColor.primaryColor.withOpacity(0.95),
|
||||
duration: const Duration(seconds: 3),
|
||||
behavior: SnackBarBehavior.floating,
|
||||
margin: const EdgeInsets.fromLTRB(16, 0, 16, 110),
|
||||
shape: RoundedRectangleBorder(
|
||||
borderRadius: BorderRadius.circular(16),
|
||||
),
|
||||
elevation: 8,
|
||||
),
|
||||
barrierDismissible: false,
|
||||
);
|
||||
|
||||
// Auto-dismiss after exactly 2 seconds
|
||||
Future.delayed(const Duration(seconds: 2), () {
|
||||
if (Get.isDialogOpen == true) {
|
||||
Get.back();
|
||||
}
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
@@ -7007,9 +6810,23 @@ Intaleq Team''';
|
||||
|
||||
// --- ⬇️ الإضافة الجديدة: التحقق من سياق حدود المطار ⬇️ ---
|
||||
// !! ⚠️ تأكد من أن هذه هي المتغيرات الصحيحة لإحداثيات نقطة النهاية !!
|
||||
double destLat = 0.0;
|
||||
double destLng = 0.0;
|
||||
try {
|
||||
destLat = myDestination.latitude;
|
||||
destLng = myDestination.longitude;
|
||||
} catch (_) {
|
||||
if (coordinatesWithoutEmpty.isNotEmpty) {
|
||||
destLat =
|
||||
double.tryParse(coordinatesWithoutEmpty.last.split(',')[0]) ?? 0.0;
|
||||
destLng =
|
||||
double.tryParse(coordinatesWithoutEmpty.last.split(',')[1]) ?? 0.0;
|
||||
}
|
||||
}
|
||||
|
||||
final bool damascusAirportBoundCtx = _isInsideDamascusAirportBounds(
|
||||
myDestination.latitude.toDouble(), // <-- ⚠️ غيّر هذا للمتغير الصحيح
|
||||
myDestination.longitude.toDouble(), // <-- ⚠️ غيّر هذا للمتغير الصحيح
|
||||
destLat,
|
||||
destLng,
|
||||
);
|
||||
final bool isInDamascusAirportBoundCtx = _isInsideDamascusAirportBounds(
|
||||
newMyLocation.latitude.toDouble(), // <-- ⚠️ غيّر هذا للمتغير الصحيح
|
||||
@@ -7494,10 +7311,6 @@ Intaleq Team''';
|
||||
}
|
||||
|
||||
late List recentPlaces = [];
|
||||
getFavioratePlaces0() async {
|
||||
recentPlaces = await sql.getCustomQuery(
|
||||
'SELECT DISTINCT latitude, longitude, name, rate FROM ${TableName.recentLocations}');
|
||||
}
|
||||
|
||||
getFavioratePlaces() async {
|
||||
recentPlaces = await sql.getCustomQuery(
|
||||
|
||||
3
lib/env/env.dart
vendored
3
lib/env/env.dart
vendored
@@ -32,6 +32,9 @@ abstract class Env {
|
||||
@EnviedField(varName: 'allowed', obfuscate: true)
|
||||
static final String allowed = _Env.allowed;
|
||||
|
||||
@EnviedField(varName: 'allowedWallet', obfuscate: true)
|
||||
static final String allowedWallet = _Env.allowedWallet;
|
||||
|
||||
@EnviedField(varName: 'apiKeyHere', obfuscate: true)
|
||||
static final String apiKeyHere = _Env.apiKeyHere;
|
||||
|
||||
|
||||
26332
lib/env/env.g.dart
vendored
26332
lib/env/env.g.dart
vendored
File diff suppressed because it is too large
Load Diff
Reference in New Issue
Block a user