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

@@ -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;
}
}
}