Update codebase

This commit is contained in:
Hamza-Ayed
2026-08-09 16:56:13 +03:00
parent 95e2e4f35d
commit b64debaa88
1058 changed files with 164327 additions and 113928 deletions
@@ -1,4 +1,4 @@
import 'package:tripz_rider/print.dart';
import 'package:siro_rider/print.dart';
import 'dart:convert';
import 'dart:io';
import 'dart:typed_data';
@@ -1,26 +1,25 @@
import 'dart:async';
import 'dart:convert';
import 'dart:io';
import 'package:tripz_rider/services/geofencing_service.dart';
import 'package:siro_rider/services/geofencing_service.dart';
import 'package:geolocator/geolocator.dart';
import 'package:tripz_rider/constant/api_key.dart';
import 'package:tripz_rider/controller/firebase/firbase_messge.dart';
import 'package:tripz_rider/views/auth/otp_page.dart';
import 'package:tripz_rider/views/widgets/error_snakbar.dart';
import 'package:siro_rider/constant/api_key.dart';
import 'package:siro_rider/controller/firebase/firbase_messge.dart';
import 'package:siro_rider/views/auth/otp_page.dart';
import 'package:siro_rider/views/widgets/error_snakbar.dart';
import 'package:http/http.dart' as http;
import 'package:tripz_rider/constant/info.dart';
import 'package:tripz_rider/controller/functions/add_error.dart';
import 'package:tripz_rider/views/auth/login_page.dart';
import 'package:siro_rider/constant/info.dart';
import 'package:siro_rider/controller/functions/add_error.dart';
import 'package:flutter/material.dart';
import 'package:get/get.dart';
import 'package:tripz_rider/constant/box_name.dart';
import 'package:tripz_rider/constant/links.dart';
import 'package:tripz_rider/controller/functions/crud.dart';
import 'package:tripz_rider/main.dart';
import 'package:tripz_rider/views/home/map_page_passenger.dart';
import 'package:siro_rider/constant/box_name.dart';
import 'package:siro_rider/constant/links.dart';
import 'package:siro_rider/controller/functions/crud.dart';
import 'package:siro_rider/main.dart';
import 'package:siro_rider/views/home/map_page_passenger.dart';
import 'package:location/location.dart' as loc;
@@ -82,26 +81,55 @@ class LoginController extends GetxController {
// • firstTimeLoadKey != false ← أول مرة يفتح التطبيق → loginFirstTime
// • firstTimeLoadKey == false ← مستخدم موجود → loginJwtRider
// ─────────────────────────────────────────────────────────────
Future<void> getJWT({bool force = false}) async {
// إذا كان التوكن الحالي لا يزال صالحاً، لا داعي لطلب واحد جديد
static Future<bool>? _jwtFuture;
static DateTime _jwtCooldownUntil = DateTime(2000);
static int _jwtFailures = 0;
static Duration get jwtCooldownRemaining {
final d = _jwtCooldownUntil.difference(DateTime.now());
return d.isNegative ? Duration.zero : d;
}
Future<bool> getJWT({bool force = false}) async {
if (_jwtFuture != null) {
Log.print('⏳ getJWT: تجديد قيد التنفيذ — إعادة استخدام نفس الـ future.');
return _jwtFuture!;
}
_jwtFuture = _getJwtInternal(force: force).catchError((e) {
return _onJwtFailure('exception: $e');
});
try {
return await _jwtFuture!;
} finally {
_jwtFuture = null;
}
}
Future<bool> _getJwtInternal({bool force = false}) async {
if (!force && isTokenValid()) {
Log.print("JWT is still valid. Skipping request.");
return;
_jwtFailures = 0;
_jwtCooldownUntil = DateTime(2000);
return true;
}
if (DateTime.now().isBefore(_jwtCooldownUntil)) {
Log.print(
'🛑 getJWT: بـ cooldown لمدة ${jwtCooldownRemaining.inSeconds}ث — تخطّي التجديد.');
return false;
}
try {
dev = Platform.isAndroid ? 'android' : 'ios';
// تأكد إن البصمة محدّثة قبل أي طلب
await DeviceHelper.getDeviceFingerprint();
final String fp = box.read(BoxName.deviceFpEncrypted) ?? '';
final passengerId = box.read(BoxName.passengerID);
final isRegistering = passengerId == null || passengerId.toString().isEmpty;
if (box.read(BoxName.firstTimeLoadKey).toString() != 'false') {
// ── أول تسجيل ─────────────────────────────────────────
// نرسل البصمة المشفرة مع باقي البيانات
// السيرفر سيعمل hash لها ويخزنها في JWT payload
if (isRegistering && box.read(BoxName.firstTimeLoadKey).toString() != 'false') {
var payload = {
'id': box.read(BoxName.passengerID) ?? AK.newId,
'id': passengerId ?? AK.newId,
'password': AK.passnpassenger,
'aud': '${AK.allowed}$dev',
'fingerPrint': fp,
@@ -110,12 +138,14 @@ class LoginController extends GetxController {
var response = await http.post(
Uri.parse(AppLink.loginFirstTime),
body: payload,
);
Log.print('AppLink.loginFirstTime: ${AppLink.loginFirstTime}');
Log.print('payload: $payload');
Log.print('response code: ${response.statusCode}');
Log.print('response body: ${response.body}');
).timeout(const Duration(seconds: 30));
if (response.statusCode == 429) {
final retryAfter = int.tryParse(response.headers['retry-after'] ?? '') ?? 60;
_jwtCooldownUntil = DateTime.now().add(Duration(seconds: retryAfter));
Log.print('🛑 getJWT(firstTime): 429 — cooldown ${retryAfter}s');
return false;
}
if (response.statusCode == 200) {
final decoded = jsonDecode(response.body);
@@ -126,18 +156,20 @@ class LoginController extends GetxController {
: decoded['jwt']);
if (jwt != null) {
// نشفر الـ JWT بالتشفير الثلاثي قبل التخزين في GetStorage
box.write(BoxName.jwt, c(jwt));
storage.write(key: BoxName.jwt, value: c(jwt));
await storage.write(key: BoxName.jwt, value: jwt);
await EncryptionHelper.initialize();
return _onJwtSuccess();
}
await EncryptionHelper.initialize();
return _onJwtFailure('firstTime: لا يوجد jwt بالرد');
}
return _onJwtFailure('firstTime: HTTP ${response.statusCode}');
} else {
// ── مستخدم موجود: تجديد التوكن
if (isRegistering) {
return _onJwtFailure('renew: لا يوجد passengerID');
}
var payload = {
'id': box.read(BoxName.passengerID),
'id': passengerId,
'fingerPrint': fp,
'aud': '${AK.allowed}$dev',
};
@@ -145,11 +177,15 @@ class LoginController extends GetxController {
var response = await http.post(
Uri.parse(AppLink.loginJwtRider),
body: payload,
);
Log.print('AppLink.loginJwtRider: ${AppLink.loginJwtRider}');
).timeout(const Duration(seconds: 30));
if (response.statusCode == 429) {
final retryAfter = int.tryParse(response.headers['retry-after'] ?? '') ?? 60;
_jwtCooldownUntil = DateTime.now().add(Duration(seconds: retryAfter));
Log.print('🛑 getJWT: 429 — cooldown ${retryAfter}ث');
return false;
}
Log.print('payload: $payload');
Log.print('response: ${response.body}');
if (response.statusCode == 200) {
final decoded = jsonDecode(response.body);
final String? jwt = decoded['data'] != null
@@ -159,16 +195,33 @@ class LoginController extends GetxController {
: decoded['jwt']);
if (jwt != null) {
box.write(BoxName.jwt, c(jwt));
storage.write(key: BoxName.jwt, value: c(jwt));
await storage.write(key: BoxName.jwt, value: jwt);
return _onJwtSuccess();
}
return _onJwtFailure('renew: لا يوجد jwt بالرد');
}
return _onJwtFailure('renew: HTTP ${response.statusCode}');
}
} catch (e) {
Log.print('Error in getJWT: $e');
return _onJwtFailure('Error: $e');
}
}
bool _onJwtSuccess() {
_jwtFailures = 0;
_jwtCooldownUntil = DateTime(2000);
Log.print('✅ getJWT: تم توليد توكن جديد بنجاح.');
return true;
}
bool _onJwtFailure(String reason) {
_jwtFailures++;
final seconds = _jwtFailures >= 6 ? 60 : (1 << _jwtFailures);
_jwtCooldownUntil = DateTime.now().add(Duration(seconds: seconds));
Log.print('❌ getJWT فشل ($reason) — محاولة #$_jwtFailures، cooldown ${seconds}ث');
return false;
}
// ─────────────────────────────────────────────────────────────
// التحقق من صلاحية التوكن يدوياً (بدون مكاتب خارجية)
// ─────────────────────────────────────────────────────────────
@@ -231,9 +284,67 @@ class LoginController extends GetxController {
// • يرجع hmac مع الـ jwt ويخزنه في GetStorage
// • الـ JWT لا يُشفَّر ثلاثياً (يُستخدم مباشرة في الـ header)
// ─────────────────────────────────────────────────────────────
// • يُخزَّن التوكن ويُعاد استخدامه ما دام أمامه أكثر من 10 ثوانٍ
// (عمره 300ث)، فلا نُسجّل دخولاً مع كل طلب محفظة.
static Future<String?>? _walletJwtFuture;
Future<String?> getJwtWallet() async {
// طلب قيد التنفيذ؟ شارك نتيجته بدل إطلاق طلب ثانٍ.
if (_walletJwtFuture != null) {
Log.print('⏳ getJwtWallet: طلب قيد التنفيذ — إعادة استخدام نفس الـ future.');
return _walletJwtFuture!;
}
_walletJwtFuture = _getJwtWalletInternal();
try {
return await _walletJwtFuture!;
} finally {
_walletJwtFuture = null;
}
}
/// هل التوكن المخزَّن صالح ويخصّ الراكب الحالي؟
/// نتحقق من `user_id` أيضاً كي لا يُعاد استخدام توكن حساب سابق
/// على نفس الجهاز بعد تبديل المستخدم.
bool _isWalletJwtUsable(String jwt, String passengerId) {
try {
final parts = jwt.split('.');
if (parts.length != 3) return false;
String payload = parts[1];
while (payload.length % 4 != 0) {
payload += '=';
}
final decoded = jsonDecode(utf8.decode(base64Url.decode(payload)));
if ((decoded['user_id'] ?? '').toString() != passengerId) return false;
final exp = decoded['exp'];
if (exp == null) return false;
// هامش 10 ثوانٍ حتى لا ينتهي التوكن أثناء الطلب نفسه
return DateTime.now().millisecondsSinceEpoch < (exp * 1000 - 10000);
} catch (_) {
return false;
}
}
Future<String?> _getJwtWalletInternal() async {
dev = Platform.isAndroid ? 'android' : 'ios';
final String passengerId = box.read(BoxName.passengerID)?.toString() ?? '';
// ── إعادة استخدام التوكن المخزَّن إن كان صالحاً ────────────────
final String cachedJwt = box.read(BoxName.walletJwt)?.toString() ?? '';
final String cachedHmac = box.read(BoxName.hmac)?.toString() ?? '';
if (cachedJwt.isNotEmpty &&
cachedHmac.isNotEmpty &&
passengerId.isNotEmpty &&
_isWalletJwtUsable(cachedJwt, passengerId)) {
Log.print('🔑 توكن محفظة صالح في التخزين — تخطّي طلب السيرفر.');
return cachedJwt;
}
// نعيد حساب البصمة أولاً كي لا نرسل قيمة GCM قديمة عالقة في التخزين
// من نسخة سابقة من التطبيق (مثل getJWT تماماً).
await DeviceHelper.getDeviceFingerprint();
final String fp = box.read(BoxName.deviceFpEncrypted) ?? '';
var payload = {
@@ -267,6 +378,11 @@ class LoginController extends GetxController {
box.write(BoxName.hmac, hmac);
}
// نخزّن التوكن ليُعاد استخدامه حتى قرب انتهائه بدل طلب جديد كل مرة
if (jwt != null) {
box.write(BoxName.walletJwt, jwt);
}
return jwt;
}
@@ -362,39 +478,39 @@ class LoginController extends GetxController {
force: true); // Fetch access token after clearing firstTimeLoadKey
// ── 5. المقارنة: FCM token + fingerprint ──────────────────────
if (email != '962798583052@tripzapp.com') {
if (serverFCM == null &&
tokenResp != null &&
tokenResp != 'failure' &&
tokenResp != 'error' &&
tokenResp != 'token_expired') {
final tokenJson =
tokenResp is String ? jsonDecode(tokenResp) : tokenResp;
final serverData = tokenJson['data'] ?? tokenJson['message'];
if (serverData is Map) {
serverFCM = serverData['token']?.toString() ?? '';
serverFP = serverData['fingerPrint']?.toString() ?? '';
}
// ملاحظة: كان هنا استثناء لحساب اختبار بعينه يتخطى الفحص بالكامل.
// حُذف — لا استثناءات على التحقق من تغيّر الجهاز.
if (serverFCM == null &&
tokenResp != null &&
tokenResp != 'failure' &&
tokenResp != 'error' &&
tokenResp != 'token_expired') {
final tokenJson =
tokenResp is String ? jsonDecode(tokenResp) : 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();
if (serverFCM != null && serverFCM.isNotEmpty) {
final localFCM = (box.read(BoxName.tokenFCM) ?? '').toString();
// ── اختلاف أي منهما = جهاز مختلف أو تثبيت جديد ─────────
final fcmChanged = serverFCM != localFCM;
final fpChanged =
serverFP != null && serverFP.isNotEmpty && serverFP != localFP;
// ── اختلاف أي منهما = جهاز مختلف أو تثبيت جديد ─────────
final fcmChanged = serverFCM != localFCM;
final fpChanged =
serverFP != null && serverFP.isNotEmpty && serverFP != localFP;
if (fcmChanged || fpChanged) {
mySnackbarInfo('Device Change Detected'.tr);
await Get.to(() => OtpVerificationPage(
phone: data['phone'].toString(),
deviceToken: localFP,
token: tokenResp ?? '',
ptoken: serverFCM ?? '', // نمرر FCM القديم للـ OTP controller
));
return;
}
if (fcmChanged || fpChanged) {
mySnackbarInfo('Device Change Detected'.tr);
await Get.to(() => OtpVerificationPage(
phone: data['phone'].toString(),
deviceToken: localFP,
token: tokenResp ?? '',
ptoken: serverFCM ?? '', // نمرر FCM القديم للـ OTP controller
));
return;
}
}
@@ -482,7 +598,7 @@ class LoginController extends GetxController {
'aud': '${AK.allowed}${Platform.isAndroid ? 'android' : 'ios'}',
};
Log.print(
"Tester Login Payload: email=${payload['email']}, password=${payload['password']}, aud=${payload['aud']}");
"Tester Login Payload: email=${payload['email']}, aud=${payload['aud']}");
var response = await http.post(
Uri.parse(AppLink.loginUsingCredentialsWithoutGooglePassenger),
@@ -525,19 +641,27 @@ class LoginController extends GetxController {
Get.offAll(() => const MapPagePassenger());
} else {
Log.print(
"Tester Login Failed due to condition mismatch: status=${jsonDecoeded['status']}, verified=${jsonDecoeded['data']?[0]?['verified']}");
Get.offAll(() => LoginPage());
"Tester Login Condition mismatch: status=${jsonDecoeded['status']}, verified=${jsonDecoeded['data']?[0]?['verified']}");
mySnackbarError(
(jsonDecoeded['message'] ?? 'Login failed').toString().tr);
isloading = false;
update();
}
} else if (response.statusCode == 429) {
Log.print("Tester Login rate limited: ${response.body}");
mySnackbarError('Too many attempts. Please wait a minute.'.tr);
isloading = false;
update();
} else {
Log.print(
"Tester Login Failed with status code: ${response.statusCode}, body: ${response.body}");
"Tester Login HTTP error: ${response.statusCode}, body: ${response.body}");
mySnackbarError('Server error'.tr);
isloading = false;
update();
}
} catch (e) {
Log.print("Tester Login Error: $e");
Log.print("Tester Login Exception: $e");
mySnackbarError('Network error'.tr);
isloading = false;
update();
}
@@ -594,7 +718,7 @@ class LoginController extends GetxController {
// Initialize geofencing now that we have permission
if (Platform.isAndroid || Platform.isIOS) {
await TripzGeofencingService.initAndStart();
await SiroGeofencingService.initAndStart();
}
if (permissionGranted.toString() == 'PermissionStatus.granted' ||
@@ -1,8 +1,8 @@
import 'package:flutter/material.dart';
import 'package:get/get.dart';
import 'package:tripz_rider/constant/box_name.dart';
import 'package:tripz_rider/main.dart';
import 'package:tripz_rider/views/auth/login_page.dart';
import 'package:siro_rider/constant/box_name.dart';
import 'package:siro_rider/main.dart';
import 'package:siro_rider/views/auth/login_page.dart';
import '../../models/model/onboarding_model.dart';
@@ -1,4 +1,4 @@
import 'package:tripz_rider/constant/links.dart';
import 'package:siro_rider/constant/links.dart';
import 'package:get/get.dart';
import '../../../constant/box_name.dart';
import '../../../main.dart';
@@ -97,6 +97,15 @@ class PhoneAuthHelper {
// ✅ المستخدم موجود مسبقاً -> تسجيل دخول مباشر
await _handleSuccessfulLogin(data['message']['passenger']);
} else {
// ✅ Save the new JWT token for registration before navigating
final jwtToken = data['message']?['jwt'] ??
data['message']?['token'] ??
data['message']?['access_token'];
if (jwtToken != null && jwtToken.toString().isNotEmpty) {
box.write(BoxName.jwt, c(jwtToken.toString()));
await storage.write(
key: BoxName.jwt, value: c(jwtToken.toString()));
}
// ✅ مستخدم جديد -> الذهاب لصفحة التسجيل
mySnackbarSuccess(
'Phone verified. Please complete registration.'.tr);
@@ -144,13 +153,16 @@ class PhoneAuthHelper {
},
);
final data = (response);
if (data != 'failure') {
// Registration successful, log user in
await _handleSuccessfulLogin(data['message']['data']);
if (data != 'failure' && data is Map) {
if (data['status'] == 'success') {
// Registration successful, log user in
await _handleSuccessfulLogin(data['message']['data']);
} else {
mySnackbarError(
data['message']?.toString() ?? "Registration failed.".tr);
}
} else {
mySnackbarError(
"User with this phone number or email already exists.".tr);
mySnackbarError("Server error or user already exists.".tr);
}
} catch (e) {
Log.print('e: ${e}');
@@ -1,11 +1,11 @@
import 'dart:async';
import 'package:tripz_rider/constant/box_name.dart';
import 'package:tripz_rider/constant/links.dart';
import 'package:tripz_rider/controller/functions/crud.dart';
import 'package:tripz_rider/main.dart';
import 'package:siro_rider/constant/box_name.dart';
import 'package:siro_rider/constant/links.dart';
import 'package:siro_rider/controller/functions/crud.dart';
import 'package:siro_rider/main.dart';
import 'package:get/get.dart';
import 'package:tripz_rider/views/widgets/error_snakbar.dart';
import 'package:siro_rider/views/widgets/error_snakbar.dart';
import '../../print.dart';
import '../../views/home/map_page_passenger.dart';
@@ -2,7 +2,7 @@ import 'dart:convert';
import 'package:get/get.dart';
import 'package:http/http.dart' as http;
import 'package:tripz_rider/views/widgets/error_snakbar.dart';
import 'package:siro_rider/views/widgets/error_snakbar.dart';
import '../../constant/box_name.dart';
import '../../constant/links.dart';