refactor: upgrade API endpoints to v3 and refactor navigation and response handling across controllers

This commit is contained in:
Hamza-Ayed
2026-04-28 01:13:15 +03:00
parent 13ef5f21b7
commit 6bfc15abb2
10 changed files with 366 additions and 220 deletions

View File

@@ -6,7 +6,7 @@ class AppLink {
static String paymentServer = 'https://walletintaleq.intaleq.xyz/v2/main';
///https://api.intaleq.xyz/intaleq/ride/location
static String location = 'https://api.intaleq.xyz/intaleq_v1/ride/location';
static String location = 'https://api.intaleq.xyz/intaleq_v3/ride/location';
/// هذا الرابط خاص برحلات الركاب، ويستخدمه السيرفر الجانبي للرحلات (Ride Server Side) للتعامل مع عمليات إلغاء الرحلات وتحديث حالة الرحلات وغيرها من العمليات المتعلقة بالرحلات.
/// https://routesy.intaleq.xyz for syria
@@ -26,7 +26,7 @@ class AppLink {
'https://location.intaleq.xyz/intaleq/ride/location';
///https://api.intaleq.xyz/intaleq
static final String endPoint = 'https://api.intaleq.xyz/intaleq_v1';
static final String endPoint = 'https://api.intaleq.xyz/intaleq_v3';
/// هذا الرابط خاص برحلات الركاب، ويستخدمه السيرفر الجانبي للرحلات (Ride Server Side) للتعامل مع عمليات إلغاء الرحلات وتحديث حالة الرحلات وغيرها من العمليات المتعلقة بالرحلات.
/// https://rides.intaleq.xyz/intaleq
@@ -34,7 +34,7 @@ class AppLink {
///https://api.intaleq.xyz/intaleq
/// main api link for all api calls except rides and location
static final String server = 'https://api.intaleq.xyz/intaleq_v1';
static final String server = 'https://api.intaleq.xyz/intaleq_v3';
///https://rides.intaleq.xyz
/// هذا الرابط خاص برحلات الركاب، ويستخدمه السيرفر الجانبي للرحلات (Ride Server Side) للتعامل مع عمليات إلغاء الرحلات وتحديث حالة الرحلات وغيرها من العمليات المتعلقة بالرحلات.

View File

@@ -1,3 +1,4 @@
import 'dart:async';
import 'dart:convert';
import 'dart:io';
@@ -21,14 +22,12 @@ import 'package:Intaleq/views/home/map_page_passenger.dart';
import 'package:location/location.dart';
import '../../env/env.dart';
import '../../print.dart';
import '../../views/auth/otp_token_page.dart';
import '../functions/encrypt_decrypt.dart';
import '../functions/package_info.dart';
class LoginController extends GetxController {
final formKey = GlobalKey<FormState>();
final formKeyAdmin = GlobalKey<FormState>();
@@ -48,35 +47,12 @@ class LoginController extends GetxController {
var dev = '';
@override
void onInit() async {
await getJWT();
// Log.print('box.read(BoxName.isTest): ${box.read(BoxName.isTest)}');
box.write(BoxName.countryCode, 'Syria');
FirebaseMessagesController().getToken();
super.onInit();
}
// getAppTester() async {
// var res = await CRUD().get(
// link: AppLink.getTesterApp,
// payload: {'appPlatform': AppInformation.appName});
// if (res != 'failure') {
// var d = jsonDecode(res);
// isTest = int.parse(d['message'][0]['isTest'].toString());
// box.write(BoxName.isTest, isTest);
// update();
// } else {
// box.write(BoxName.isTest, '1');
// update();
// return false;
// }
// }
// updateAppTester(String appPlatform) async {
// await CRUD().post(
// link: AppLink.updateTesterApp, payload: {'appPlatform': appPlatform});
// }
void saveAgreementTerms() {
box.write(BoxName.agreeTerms, 'agreed');
update();
@@ -87,7 +63,6 @@ class LoginController extends GetxController {
update();
}
// ═══════════════════════════════════════════════════════════════
// LoginController — دوال إدارة الـ JWT
// ───────────────────────────────────────────────────────────────
@@ -104,70 +79,134 @@ class LoginController extends GetxController {
// • firstTimeLoadKey == false ← مستخدم موجود → loginJwtRider
// ─────────────────────────────────────────────────────────────
Future<void> getJWT() async {
dev = Platform.isAndroid ? 'android' : 'ios';
// إذا كان التوكن الحالي لا يزال صالحاً، لا داعي لطلب واحد جديد
if (isTokenValid()) {
Log.print("JWT is still valid. Skipping request.");
return;
}
// تأكد إن البصمة محدّثة قبل أي طلب
await DeviceHelper.getDeviceFingerprint();
final String fp = box.read(BoxName.deviceFpEncrypted) ?? '';
try {
dev = Platform.isAndroid ? 'android' : 'ios';
if (box.read(BoxName.firstTimeLoadKey).toString() != 'false') {
// ── أول تسجيل ─────────────────────────────────────────
// نرسل البصمة المشفرة مع باقي البيانات
// السيرفر سيعمل hash لها ويخزنها في JWT payload
var payload = {
'id': box.read(BoxName.passengerID) ?? AK.newId,
'password': AK.passnpassenger,
'aud': '${AK.allowed}$dev',
'fingerPrint': fp,
};
// تأكد إن البصمة محدّثة قبل أي طلب
await DeviceHelper.getDeviceFingerprint();
final String fp = box.read(BoxName.deviceFpEncrypted) ?? '';
var response = await http.post(
Uri.parse(AppLink.loginFirstTime),
body: payload,
);
Log.print('AppLink.loginFirstTime: ${AppLink.loginFirstTime}');
if (box.read(BoxName.firstTimeLoadKey).toString() != 'false') {
// ── أول تسجيل ─────────────────────────────────────────
// نرسل البصمة المشفرة مع باقي البيانات
// السيرفر سيعمل hash لها ويخزنها في JWT payload
var payload = {
'id': box.read(BoxName.passengerID) ?? AK.newId,
'password': AK.passnpassenger,
'aud': '${AK.allowed}$dev',
'fingerPrint': fp,
};
Log.print('payload: $payload');
Log.print('response: $response');
var response = await http.post(
Uri.parse(AppLink.loginFirstTime),
body: payload,
);
Log.print('AppLink.loginFirstTime: ${AppLink.loginFirstTime}');
if (response.statusCode == 200) {
final decoded = jsonDecode(response.body);
final String jwt = decoded['jwt'];
Log.print('payload: $payload');
Log.print('response: $response');
// نشفر الـ JWT بالتشفير الثلاثي قبل التخزين في GetStorage
box.write(BoxName.jwt, c(jwt));
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']);
if (jwt != null) {
// نشفر الـ JWT بالتشفير الثلاثي قبل التخزين في GetStorage
box.write(BoxName.jwt, c(jwt));
}
await EncryptionHelper.initialize();
}
} else {
// ── مستخدم موجود: تجديد التوكن ────────────────────────
await EncryptionHelper.initialize();
var payload = {
'id': box.read(BoxName.passengerID),
'fingerPrint': fp,
'aud': '${AK.allowed}$dev',
};
var response = await http.post(
Uri.parse(AppLink.loginJwtRider),
body: payload,
);
Log.print('AppLink.loginJwtRider: ${AppLink.loginJwtRider}');
Log.print('payload: $payload');
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']);
if (jwt != null) {
box.write(BoxName.jwt, c(jwt));
}
}
}
} else {
// ── مستخدم موجود: تجديد التوكن ────────────────────────
await EncryptionHelper.initialize();
var payload = {
'id': box.read(BoxName.passengerID),
'fingerPrint': fp,
'aud': '${AK.allowed}$dev',
};
var response = await http.post(
Uri.parse(AppLink.loginJwtRider),
body: payload,
);
Log.print('AppLink.loginJwtRider: ${AppLink.loginJwtRider}');
Log.print('payload: $payload');
Log.print('response: ${response.body}');
if (response.statusCode == 200) {
final decoded = jsonDecode(response.body);
final String jwt = decoded['jwt'];
box.write(BoxName.jwt, c(jwt));
}
} catch (e) {
Log.print('Error in getJWT: $e');
}
}
// ─────────────────────────────────────────────────────────────
// getJwtWallet: الحصول على توكن لسيرفر المدفوعات
// التحقق من صلاحية التوكن يدوياً (بدون مكاتب خارجية)
// ─────────────────────────────────────────────────────────────
bool isTokenValid() {
try {
final String? encryptedJwt = box.read(BoxName.jwt);
if (encryptedJwt == null || encryptedJwt.isEmpty) {
Log.print("isTokenValid: No token found in storage.");
return false;
}
// 1. فك تشفير التوكن المخزن
final String jwtFull = r(encryptedJwt).toString();
final String jwt = jwtFull.split(Env.addd)[0];
final parts = jwt.split('.');
if (parts.length != 3) {
Log.print("isTokenValid: Invalid JWT format (parts: ${parts.length})");
return false;
}
// 2. فك Base64 للجزء الأوسط (Payload)
String payloadPart = parts[1];
while (payloadPart.length % 4 != 0) {
payloadPart += '=';
}
final String decodedPayload = utf8.decode(base64Url.decode(payloadPart));
final Map<String, dynamic> payload = jsonDecode(decodedPayload);
if (!payload.containsKey('exp')) {
Log.print("isTokenValid: No 'exp' claim in token.");
return false;
}
final int exp = payload['exp'];
final int now = DateTime.now().millisecondsSinceEpoch ~/ 1000;
Log.print("isTokenValid: Now=$now, Exp=$exp, Diff=${exp - now}s");
// اعتبار التوكن منتهي قبل دقيقة للأمان
final bool valid = exp > (now + 60);
Log.print("isTokenValid: Result=$valid");
return valid;
} catch (e) {
Log.print("isTokenValid Error: $e");
return false;
}
}
// ─────────────────────────────────────────────────────────────
// الفرق عن getJWT:
// • يستخدم endpoint مختلف (loginWallet)
@@ -197,11 +236,15 @@ class LoginController extends GetxController {
Log.print('response wallet: ${response.body}');
if (response.statusCode == 200) {
final decoded = jsonDecode(response.body);
final String jwt = decoded['jwt'];
final String hmac = decoded['hmac'];
final String? jwt =
decoded['data'] != null ? decoded['data']['jwt'] : decoded['jwt'];
final String? hmac =
decoded['data'] != null ? decoded['data']['hmac'] : decoded['hmac'];
// نخزن الـ hmac للاستخدام في X-HMAC-Auth header
box.write(BoxName.hmac, hmac);
if (hmac != null) {
// نخزن الـ hmac للاستخدام في X-HMAC-Auth header
box.write(BoxName.hmac, hmac);
}
// wallet JWT يُرجَع مباشرة دون تشفير ثلاثي
return jwt;
@@ -213,8 +256,8 @@ class LoginController extends GetxController {
Future<void> loginUsingCredentials(String passengerID, String email) async {
isloading = true;
update();
await getJWT();
// await getJWT();
Log.print("LoginController.loginUsingCredentials: ");
try {
// 1) استعلام تسجيل الدخول
final res = await CRUD().get(
@@ -227,16 +270,16 @@ class LoginController extends GetxController {
},
);
if (res == 'Failure') {
// 2) فك JSON مرة واحدة والتحقق من النجاح
final decoded = jsonDecode(res);
if (decoded is! Map || decoded.isEmpty) return;
if (decoded['status'] == 'failure' || decoded['status'] == 'Failure') {
Get.snackbar("User does not exist.".tr, '',
backgroundColor: Colors.red);
return;
}
// 2) فك JSON مرة واحدة
final decoded = jsonDecode(res);
if (decoded is! Map || decoded.isEmpty) return;
final status = (decoded['status'] ?? '').toString();
final data = (decoded['data'] as List?)?.firstOrNull as Map? ?? {};
if (status != 'success' || data['verified'].toString() != '1') {
@@ -263,47 +306,62 @@ class LoginController extends GetxController {
// مهم: تأكد من passengerID في الـ box
box.write(BoxName.passengerID, passengerID);
// 4) تنفيذ العمليات بالتوازي: getTokens + fingerprint محلي
final results = await Future.wait([
CRUD().get(
link: AppLink.getTokens, payload: {'passengerID': passengerID}),
DeviceHelper.getDeviceFingerprint(),
]);
// 4) فحص ما إذا كان التوكن موجوداً في رد الـ Login المدمج (V3 Optimization)
String? serverFCM;
String? serverFP;
final tokenResp = results[0];
final localFP = (results[1] ?? '').toString();
var userData = decoded['data'] is List
? (decoded['data'] as List).firstOrNull
: decoded['data'];
if (userData is Map && userData['fcm_token'] != null) {
serverFCM = userData['fcm_token'].toString();
serverFP = userData['fcm_fingerprint']?.toString() ?? '';
Log.print(
"✅ FCM Token found in Login response. Skipping separate getTokens call.");
}
// إذا لم يكن موجوداً (توافقية قديمة)، نقوم بطلبه
String? tokenResp;
if (serverFCM == null) {
tokenResp = await CRUD().get(
link: AppLink.getTokens, payload: {'passengerID': passengerID});
}
final localFP = (await DeviceHelper.getDeviceFingerprint()).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 serverData = tokenJson['message'] as Map?; // null = أول تسجيل
if (serverData != null) {
final serverFCM = serverData['token']?.toString() ?? '';
final serverFP = serverData['fingerPrint']?.toString() ?? '';
// ── 5. المقارنة: FCM token + fingerprint ──────────────────────
if (email != '962798583052@intaleqapp.com') {
if (serverFCM == null &&
tokenResp != null &&
tokenResp != 'failure' &&
tokenResp != 'error') {
final tokenJson = jsonDecode(tokenResp);
final serverData = tokenJson['data'] ?? tokenJson['message'];
if (serverData is Map) {
serverFCM = serverData['token']?.toString() ?? '';
serverFP = serverData['fingerPrint']?.toString() ?? '';
}
}
if (serverFCM != null && serverFCM.isNotEmpty) {
final localFCM = (box.read(BoxName.tokenFCM) ?? '').toString();
// ── اختلاف أي منهما = جهاز مختلف أو تثبيت جديد ─────────
final fcmChanged = serverFCM.isNotEmpty && serverFCM != localFCM;
final fpChanged = serverFP.isNotEmpty && serverFP != localFP;
final fcmChanged = serverFCM != localFCM;
final fpChanged =
serverFP != null && 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: localFP,
token: tokenResp,
ptoken: serverFCM, // نمرر FCM القديم للـ OTP controller
token: tokenResp ?? '',
ptoken: serverFCM ?? '', // نمرر FCM القديم للـ OTP controller
));
return; // لا تكمل — الـ OTP controller يتولى الانتقال
// }
return;
}
}
}

View File

@@ -53,6 +53,9 @@ class CRUD {
box.read(BoxName.driverID) != null ? 'Driver' : 'Passenger';
final phone = box.read(BoxName.phone) ?? box.read(BoxName.phoneDriver);
// طباعة الخطأ في الكونسول للمطور للمتابعة الفورية
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(
link: AppLink.addError,
@@ -203,23 +206,35 @@ class CRUD {
Log.print('payload: $payload');
if (response.statusCode == 200) {
var jsonData = jsonDecode(response.body);
if (jsonData['status'] == 'success') {
return response.body;
}
return jsonData['status'];
return response.body;
} else if (response.statusCode == 401) {
var jsonData = jsonDecode(response.body);
if (jsonData['error'] == 'Token expired') {
print("CRUD.get: Token expired, refreshing and retrying once...");
await Get.put(LoginController()).getJWT();
return 'token_expired';
// إعادة المحاولة مرة واحدة فقط بتوكن جديد
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]}',
'X-Device-FP': _getFpHeader(),
},
);
if (retryResponse.statusCode == 200) {
return retryResponse.body;
}
return jsonEncode({'status': 'failure', 'message': 'token_expired_retry_failed'});
} else {
return 'failure';
return jsonEncode({'status': 'failure', 'message': '401_unauthorized'});
}
} else {
addError('Non-200 response code: ${response.statusCode}',
'crud().get - Other', url.toString());
return 'failure';
return jsonEncode({'status': 'failure', 'message': 'server_error_${response.statusCode}'});
}
}

View File

@@ -1,6 +1,7 @@
import 'dart:async';
import 'package:Intaleq/services/offline_map_service.dart';
import 'package:Intaleq/services/emergency_signal_service.dart';
import 'package:Intaleq/views/widgets/mycircular.dart';
import 'dart:convert';
import 'dart:io';
import 'dart:math' show Random, atan2, cos, max, min, pi, pow, sin, sqrt;
@@ -1517,17 +1518,17 @@ class MapPassengerController extends GetxController {
break;
case RideState.searching:
// effectivePollingInterval = 5;
// 1. التحقق من حالة الطلب (هل قبله أحد؟)
try {
String statusFromServer = await getRideStatus(rideId);
if (statusFromServer == 'Apply' || statusFromServer == 'Applied') {
await processRideAcceptance(source: "Polling");
break;
// 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');
}
} catch (e) {
Log.print('Error polling getRideStatus: $e');
}
final now = DateTime.now();
@@ -1587,18 +1588,21 @@ class MapPassengerController extends GetxController {
rideAppliedFromDriver(true);
_isDriverAppliedLogicExecuted = true;
}
try {
String statusFromServer = await getRideStatus(rideId);
if (statusFromServer == 'Arrived') {
currentRideState.value = RideState.driverArrived;
break;
} else if (statusFromServer == 'Begin' ||
statusFromServer == 'inProgress') {
processRideBegin();
break;
if (!isSocketConnected) {
try {
String statusFromServer = await getRideStatus(rideId);
if (statusFromServer == 'Arrived') {
currentRideState.value = RideState.driverArrived;
break;
} else if (statusFromServer == 'Begin' ||
statusFromServer == 'inProgress') {
processRideBegin();
break;
}
} catch (e) {
Log.print('Error polling for Arrived/Begin status: $e');
}
} catch (e) {
Log.print('Error polling for Arrived/Begin status: $e');
}
getDriverCarsLocationToPassengerAfterApplied();
break;
@@ -1615,31 +1619,33 @@ class MapPassengerController extends GetxController {
case RideState.inProgress:
// effectivePollingInterval = 6;
try {
String statusFromServer = await getRideStatus(rideId);
if (!isSocketConnected) {
try {
String statusFromServer = await getRideStatus(rideId);
// !!! هنا التغيير الجذري !!!
if (statusFromServer == 'Finished' ||
statusFromServer == 'finished') {
Log.print(
'🏁 DETECTED FINISHED: Killing processes and forcing Review.');
// !!! هنا التغيير الجذري !!!
if (statusFromServer == 'Finished' ||
statusFromServer == 'finished') {
Log.print(
'🏁 DETECTED FINISHED: Killing processes and forcing Review.');
// 1. قتل العمليات فوراً
stopAllTimers();
// 1. قتل العمليات فوراً
stopAllTimers();
// 2. تغيير الحالة الداخلية لمنع أي كود آخر من العمل
currentRideState.value = RideState.preCheckReview;
// 2. تغيير الحالة الداخلية لمنع أي كود آخر من العمل
currentRideState.value = RideState.preCheckReview;
// 3. تنظيف الواجهة
tripFinishedFromDriver();
// 3. تنظيف الواجهة
tripFinishedFromDriver();
// 4. استدعاء شاشة التقييم فوراً
_checkLastRideForReview();
// 4. استدعاء شاشة التقييم فوراً
_checkLastRideForReview();
return; // خروج نهائي من الدالة لمنع أي كود بالأسفل من التنفيذ
return; // خروج نهائي من الدالة لمنع أي كود بالأسفل من التنفيذ
}
} catch (e) {
Log.print('Error polling status: $e');
}
} catch (e) {
Log.print('Error polling status: $e');
}
// بقية كود التتبع العادي (لن يتم الوصول إليه إذا انتهت الرحلة)
@@ -2906,8 +2912,17 @@ class MapPassengerController extends GetxController {
isStartAppHasRide = false;
Log.print(
"No rides found for the given passenger ID within the last hour.");
} else {
var decoded = jsonDecode(res);
if (decoded['status'] == 'failure') {
rideStatusFromStartApp = {
'data': {'status': 'NoRide', 'needsReview': false}
};
isStartAppHasRide = false;
} else {
rideStatusFromStartApp = decoded;
}
}
rideStatusFromStartApp = jsonDecode(res);
if (rideStatusFromStartApp['data']['status'] == 'Begin' ||
rideStatusFromStartApp['data']['status'] == 'Apply' ||
rideStatusFromStartApp['data']['status'] == 'Applied') {
@@ -6020,35 +6035,58 @@ Intaleq Team''';
Log.print(
'🔔 showDrawingBottomSheet called. isDrawingRoute: $isDrawingRoute');
// استخدام addPostFrameCallback لضمان ظهور الإشعار بعد انتهاء بناء الإطار
final context = Get.context;
if (context == null) return;
WidgetsBinding.instance.addPostFrameCallback((_) {
Get.rawSnackbar(
titleText: Text(
'Drawing route on map...'.tr,
style: const TextStyle(
color: Colors.white,
fontWeight: FontWeight.bold,
fontSize: 14,
// إغلاق أي سناك بار مفتوح حالياً لتجنب التكرار
ScaffoldMessenger.of(context).hideCurrentSnackBar();
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,
),
),
],
),
),
],
),
),
messageText: Text(
'Please wait while we prepare your trip.'.tr,
style: const TextStyle(
color: Colors.white70,
fontSize: 12,
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,
),
showProgressIndicator: true,
progressIndicatorBackgroundColor: Colors.white24,
progressIndicatorValueColor:
const AlwaysStoppedAnimation<Color>(Colors.white),
snackPosition: SnackPosition.BOTTOM,
backgroundColor: AppColor.primaryColor.withOpacity(0.9),
duration: const Duration(seconds: 3),
margin: const EdgeInsets.fromLTRB(16, 0, 16, 110),
borderRadius: 16,
icon: const Icon(Icons.map_outlined, color: Colors.white),
isDismissible: true,
);
});
}
@@ -7484,16 +7522,22 @@ Intaleq Team''';
);
if (res != 'failure') {
var json = jsonDecode(res);
kazan = double.parse(json['message'][0]['kazan']);
naturePrice = double.parse(json['message'][0]['naturePrice']);
heavyPrice = double.parse(json['message'][0]['heavyPrice']);
latePrice = double.parse(json['message'][0]['latePrice']);
comfortPrice = double.parse(json['message'][0]['comfortPrice']);
speedPrice = double.parse(json['message'][0]['speedPrice']);
deliveryPrice = double.parse(json['message'][0]['deliveryPrice']);
mashwariPrice = double.parse(json['message'][0]['freePrice']);
familyPrice = double.parse(json['message'][0]['familyPrice']);
fuelPrice = double.parse(json['message'][0]['fuelPrice']);
// التحقق الديناميكي من 'data' أو 'message'
var dataList = json['data'] ?? json['message'];
if (dataList != null && dataList is List && dataList.isNotEmpty) {
var firstRow = dataList[0];
kazan = double.parse(firstRow['kazan'].toString());
naturePrice = double.parse(firstRow['naturePrice'].toString());
heavyPrice = double.parse(firstRow['heavyPrice'].toString());
latePrice = double.parse(firstRow['latePrice'].toString());
comfortPrice = double.parse(firstRow['comfortPrice'].toString());
speedPrice = double.parse(firstRow['speedPrice'].toString());
deliveryPrice = double.parse(firstRow['deliveryPrice'].toString());
mashwariPrice = double.parse(firstRow['freePrice'].toString());
familyPrice = double.parse(firstRow['familyPrice'].toString());
fuelPrice = double.parse(firstRow['fuelPrice'].toString());
}
}
}
@@ -7502,7 +7546,8 @@ Intaleq Team''';
link: AppLink.getPassengerRate,
payload: {'passenger_id': box.read(BoxName.passengerID)});
if (res != 'failure') {
var message = jsonDecode(res)['message'];
var json = jsonDecode(res);
var message = json['data'] ?? json['message'];
if (message['rating'] == null) {
passengerRate = 5.0; // Default rating
} else {

View File

@@ -230,8 +230,16 @@ ${'Download the Intaleq app now and enjoy your ride!'.tr}
Get.snackbar('Success'.tr, 'Invite sent successfully'.tr,
backgroundColor: Colors.green, colorText: Colors.white);
String expirationTime = d['message']['expirationTime'].toString();
String inviteCode = d['message']['inviteCode'].toString();
// التحقق الديناميكي من مكان البيانات (V1 vs V3)
var payload = d['data'] ?? d['message'];
// إذا كان الـ message نصاً وليس خريطة (Map)، نأخذ البيانات من المستوى الأعلى
if (payload is String) {
payload = d;
}
String expirationTime = (payload['expirationTime'] ?? '').toString();
String inviteCode = (payload['inviteCode'] ?? '').toString();
// New and improved WhatsApp message for better user engagement.
String message =

View File

@@ -26,8 +26,8 @@ class OrderHistoryController extends GetxController {
update();
} else {
var jsonDecoded = jsonDecode(res);
orderHistoryListPassenger = jsonDecoded['data'];
var rawData = jsonDecoded['data'] ?? jsonDecoded['message'];
orderHistoryListPassenger = rawData is List ? rawData : [];
isloading = false;
update();
}

View File

@@ -147,6 +147,10 @@ class SplashScreenController extends GetxController
await _getPackageInfo();
SecureStorage().saveData('iss', 'mobile-app:');
// ── [OPTIMIZATION] جلب التوكن عند بداية تشغيل التطبيق ────────────────
Log.print("SplashScreen: Initializing JWT...");
await Get.find<LoginController>().getJWT();
if (box.read(BoxName.onBoarding) == null) {
Get.off(() => OnBoardingPage());
} else if (box.read(BoxName.email) != null &&

View File

@@ -20,13 +20,26 @@ class PassengerNotificationController extends GetxController {
var res = await CRUD().get(
link: AppLink.getNotificationPassenger,
payload: {'passenger_id': box.read(BoxName.passengerID)});
if (res == "failure") {
if (res == "failure" || res == "error") {
MyDialog().getDialog('There is no notification yet'.tr, '', () {
Get.back();
Get.back();
});
} else {
notificationData = jsonDecode(res);
final decoded = jsonDecode(res);
// التحقق من وجود البيانات في 'data' أو 'message'
var rawData = decoded['data'] ?? decoded['message'];
if (decoded['status'] == 'error' || decoded['status'] == 'failure' || rawData == "No notification data found") {
notificationData = {'status': 'success', 'message': []}; // قائمة فارغة لمنع الخطأ في UI
} else {
// التأكد أننا نخزن قائمة
notificationData = {
'status': 'success',
'message': rawData is List ? rawData : [rawData]
};
}
isloading = false;
update();
}

View File

@@ -638,7 +638,7 @@ class CarDetailsTypeToChoose extends StatelessWidget {
children: [
Expanded(
child: TextButton(
onPressed: () => Get.back(),
onPressed: () => Navigator.of(context).pop(),
style: TextButton.styleFrom(
foregroundColor: Colors.grey,
),
@@ -653,7 +653,7 @@ class CarDetailsTypeToChoose extends StatelessWidget {
if (controller.promoFormKey.currentState!
.validate()) {
controller.applyPromoCodeToPassenger(context);
Get.back();
Navigator.of(context).pop();
}
},
),
@@ -779,7 +779,7 @@ class CarDetailsTypeToChoose extends StatelessWidget {
children: [
Expanded(
child: TextButton(
onPressed: () => Get.back(),
onPressed: () => Navigator.of(context).pop(),
style: TextButton.styleFrom(
padding: const EdgeInsets.symmetric(vertical: 12),
shape: RoundedRectangleBorder(
@@ -799,7 +799,7 @@ class CarDetailsTypeToChoose extends StatelessWidget {
kolor: AppColor.greenColor,
title: 'Select This Ride'.tr,
onPressed: () {
Get.back();
Navigator.of(context).pop();
_handleCarSelection(
context, mapPassengerController, carType);
},
@@ -886,7 +886,7 @@ class CarDetailsTypeToChoose extends StatelessWidget {
mapPassengerController.totalPassenger =
_getOriginalPrice(carType, mapPassengerController);
if (carType.carType == 'Mishwar Vip') {
Get.back();
Navigator.of(context).pop();
mapPassengerController.mishwariOption();
} else if (carType.carType == 'Lady') {
if (box.read(BoxName.gender).toString() != '') {
@@ -905,17 +905,17 @@ class CarDetailsTypeToChoose extends StatelessWidget {
title: "Select betweeen types".tr,
content:
Row(mainAxisAlignment: MainAxisAlignment.spaceBetween, children: [
_buildRayehGaiOption(mapPassengerController, 'Awfar Car',
_buildRayehGaiOption(context, mapPassengerController, 'Awfar Car',
mapPassengerController.totalPassengerRayehGaiBalash),
_buildRayehGaiOption(mapPassengerController, 'Fixed Price',
_buildRayehGaiOption(context, mapPassengerController, 'Fixed Price',
mapPassengerController.totalPassengerRayehGai),
_buildRayehGaiOption(mapPassengerController, 'Comfort',
_buildRayehGaiOption(context, mapPassengerController, 'Comfort',
mapPassengerController.totalPassengerRayehGaiComfort)
]),
cancel: MyElevatedButton(
kolor: AppColor.redColor,
title: 'Cancel'.tr,
onPressed: () => Get.back()),
onPressed: () => Navigator.of(context).pop()),
confirm: MyElevatedButton(
kolor: AppColor.greenColor,
title: 'Next'.tr,
@@ -951,11 +951,14 @@ class CarDetailsTypeToChoose extends StatelessWidget {
}
}
Widget _buildRayehGaiOption(MapPassengerController mapPassengerController,
String carTypeName, double price) {
Widget _buildRayehGaiOption(
BuildContext context,
MapPassengerController mapPassengerController,
String carTypeName,
double price) {
return GestureDetector(
onTap: () {
Get.back();
Navigator.of(context).pop();
mapPassengerController.totalPassenger = price;
mapPassengerController.isBottomSheetShown = false;
mapPassengerController.update();

View File

@@ -16,7 +16,7 @@ class NotificationPage extends StatelessWidget {
Get.put(PassengerNotificationController());
return MyScafolld(
isleading: true,
title: 'Notifications',
title: 'Notifications'.tr,
body: [
GetBuilder<PassengerNotificationController>(
builder: (notificationCaptainController) => notificationCaptainController