import 'dart:convert'; import 'dart:io'; import 'package:crypto/crypto.dart'; import 'dart:math'; import 'package:http/http.dart' as http; import 'package:permission_handler/permission_handler.dart'; import 'package:siro_driver/views/auth/syria/registration_view.dart'; import 'package:siro_driver/views/widgets/elevated_btn.dart'; import 'package:siro_driver/views/widgets/error_snakbar.dart'; import 'package:flutter/material.dart'; import 'package:flutter_secure_storage/flutter_secure_storage.dart'; import 'package:get/get.dart'; import 'package:siro_driver/constant/box_name.dart'; import 'package:siro_driver/constant/links.dart'; import 'package:siro_driver/controller/functions/crud.dart'; import 'package:siro_driver/main.dart'; import 'package:siro_driver/views/home/Captin/home_captain/home_captin.dart'; import 'package:location/location.dart'; import '../../../constant/api_key.dart'; import '../../../constant/info.dart'; import '../../../print.dart'; import '../../../views/auth/captin/otp_page.dart'; import '../../../views/auth/captin/otp_token_page.dart'; import '../../../views/auth/syria/pending_driver_page.dart'; import '../../firebase/firbase_messge.dart'; import '../../firebase/local_notification.dart'; import '../../firebase/notification_service.dart'; import '../../functions/country_logic.dart'; import '../../functions/encrypt_decrypt.dart'; import '../../functions/package_info.dart'; import '../../functions/secure_storage.dart'; import '../../functions/security_checks.dart'; class LoginDriverController extends GetxController { final formKey = GlobalKey(); TextEditingController emailController = TextEditingController(); TextEditingController phoneController = TextEditingController(); TextEditingController passwordController = TextEditingController(); TextEditingController passwordController2 = TextEditingController(); bool isAgreeTerms = false; bool isGoogleDashOpen = false; bool isGoogleLogin = false; bool isloading = false; late int isTest = 1; final location = Location(); void changeAgreeTerm() { isAgreeTerms = !isAgreeTerms; update(); } bool isPasswordHidden = true; void togglePasswordVisibility() { isPasswordHidden = !isPasswordHidden; update([ 'passwordVisibility', ]); // Use a unique ID to only update the password field } void changeGoogleDashOpen() { isGoogleDashOpen = !isGoogleDashOpen; update(); } @override void onInit() async { // كشف الدولة تلقائياً عبر الموقع الجغرافي await CountryLogic.initializeCountry(); // box.write(BoxName.driverID, '34feffd3fa72d6bee56b'); // await getAppTester(); await getJWT(); super.onInit(); } // getAppTester() async { // var res = await CRUD().get( // link: AppLink.getTesterApp, // payload: {'appPlatform': AppInformation.appName}); // // Log.print('res: ${res}'); // if (res != 'failure') { // var d = jsonDecode(res); // isTest = d['message'][0]['isTest']; // // Log.print('isTest: ${isTest}'); // box.write(BoxName.isTest, isTest); // // Log.print('isTest: ${box.read(BoxName.isTest)}'); // update(); // } else { // isTest = 0; // box.write(BoxName.isTest, isTest); // update(); // return false; // } // } // updateAppTester(String appPlatform) async { // await CRUD().post( // link: AppLink.updateTesterApp, payload: {'appPlatform': appPlatform}); // } isPhoneVerified() async { // If the phone is already verified locally and we have a driver ID, skip the API check // This prevents asking for OTP again if the user restarts the app during registration. if (box.read(BoxName.phoneVerified) == '1' && box.read(BoxName.driverID) != null) { Get.offAll(() => RegistrationView()); return; } var res = await CRUD().post( link: AppLink.isPhoneVerified, payload: {'phone_number': box.read(BoxName.phoneDriver)}, ); if (res != 'failure') { // Get.offAll(() => SyrianCardAI()); Get.offAll(() => RegistrationView()); // isloading = false; // update(); } else { Get.offAll(() => PhoneNumberScreen()); } } void saveAgreementTerms() { box.write(BoxName.agreeTerms, 'agreed'); update(); } void saveCountryCode(String countryCode) { box.write(BoxName.countryCode, countryCode); update(); } String shortHash(String password) { var bytes = utf8.encode(password); var digest = sha256.convert(bytes); return base64UrlEncode(digest.bytes); } var dev = ''; Future? _walletJwtFuture; getJwtWallet() async { if (_walletJwtFuture != null) { Log.print('⏳ getJwtWallet: Request already in progress, reusing future.'); return _walletJwtFuture!; } _walletJwtFuture = _getJwtWalletInternal(); try { return await _walletJwtFuture!; } finally { _walletJwtFuture = null; } } Future _getJwtWalletInternal() async { if (box.read(BoxName.security_check).toString() != 'passed') { Log.print('Security check failed'); return ''; } // 1. Check GetStorage first to avoid redundant API calls String? cachedJwt = box.read(BoxName.walletJwt)?.toString(); if (cachedJwt != null && cachedJwt.isNotEmpty) { bool isTokenValid = false; try { final parts = cachedJwt.split('.'); if (parts.length == 3) { var payload = parts[1]; switch (payload.length % 4) { case 2: payload += '=='; break; case 3: payload += '='; break; } final decoded = jsonDecode(utf8.decode(base64Url.decode(payload))); final exp = decoded['exp']; if (exp != null) { // Check if token has at least 10 seconds remaining isTokenValid = DateTime.now().millisecondsSinceEpoch < (exp * 1000 - 10000); } } } catch (_) {} if (isTokenValid) { Log.print('🔑 Valid Wallet JWT found in storage. Skipping generation.'); return cachedJwt; } } Log.print('Security check passed'); dev = Platform.isAndroid ? 'android' : 'ios'; var fingerPrint = box.read(BoxName.deviceFingerprint)?.toString() ?? await DeviceHelper.getDeviceFingerprint(); var payload = { 'id': box.read(BoxName.driverID), 'password': AK.passnpassenger, 'aud': '${AK.allowedWallet}$dev', 'fingerPrint': fingerPrint, }; var response1 = await http.post( Uri.parse(AppLink.loginJwtWalletDriver), body: payload, ); Log.print('response.request: ${response1.request}'); Log.print('response.body: ${response1.body}'); var decoded = jsonDecode(response1.body); var jwt = decoded['message'] is Map && decoded['message']['jwt'] != null ? decoded['message']['jwt'] : decoded['jwt']; var hmac = decoded['message'] is Map && decoded['message']['hmac'] != null ? decoded['message']['hmac'] : decoded['hmac']; Log.print('payment["jwt"]: $jwt'); if (jwt != null) { await box.write(BoxName.walletJwt, jwt.toString()); await box.write(BoxName.hmac, hmac); return jwt.toString(); } return ''; } static Future? _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; } /// [force] يتجاوز الـ backoff cooldown — يُستخدم فقط في المسارات التي /// يبدأها المستخدم (فتح التطبيق / تسجيل الدخول)، لا في الطلبات الخلفية، /// حتى لا يعلق الكابتن خارج التطبيق بانتظار انتهاء الـ cooldown. Future getJWT({bool force = false}) async { if (force) { _jwtCooldownUntil = DateTime(2000); _jwtFailures = 0; } if (_jwtFuture != null) { Log.print('⏳ getJWT: تجديد قيد التنفيذ — إعادة استخدام نفس الـ future.'); return _jwtFuture!; } _jwtFuture = _getJwtInternal().catchError((e) { return _onJwtFailure('exception: $e'); }); try { return await _jwtFuture!; } finally { _jwtFuture = null; } } /// يفكّ payload الـ JWT محلياً (بدون التحقق من التوقيع) لفحص نوعه فقط. static Map? decodeJwtPayload(String? token) { if (token == null || token.isEmpty) return null; try { final parts = token.split('.'); if (parts.length != 3) return null; String payload = parts[1]; switch (payload.length % 4) { case 2: payload += '=='; break; case 3: payload += '='; break; } final decoded = jsonDecode(utf8.decode(base64Url.decode(payload))); return decoded is Map ? Map.from(decoded) : null; } catch (_) { return null; } } /// توكن تسجيل (`registration` / `new`): يصدره loginFirstTimeDriver قبل أن /// يصير للسائق id. الباك إند يسمح له بمسارات التسجيل فقط، وأي endpoint /// آخر يردّ 403 "Token not authorized for this action". static bool isRegistrationToken(Map payload) { final type = payload['token_type']?.toString(); return type == 'registration' || type == 'new' || payload['user_id']?.toString() == 'new'; } Future _getJwtInternal() async { await EncryptionHelper.initialize(); dev = Platform.isAndroid ? 'android' : 'ios'; final driverId = box.read(BoxName.driverID)?.toString() ?? ''; final isRegistering = driverId.isEmpty; String? secureJwt = await storage.read(key: BoxName.jwt); final cached = decodeJwtPayload(secureJwt); if (cached != null) { final exp = cached['exp']; final notExpired = exp is int && DateTime.now().millisecondsSinceEpoch < (exp * 1000 - 30000); if (notExpired) { // ═══════════════════════════════════════════════════════════ // الصلاحية الزمنية وحدها لا تكفي: توكن التسجيل يبقى صالحاً // ساعة كاملة لكنه بلا صلاحيات. لو اكتفينا بفحص exp يبقى // مخزّناً بعد اكتمال الدخول فيردّ كل endpoint محمي 403. // ═══════════════════════════════════════════════════════════ if (isRegistrationToken(cached) && !isRegistering) { Log.print( '♻️ getJWT: توكن registration مع driverID موجود — ترقية إلى توكن driver.', ); } else if (!isRegistering && cached['user_id'] != null && cached['user_id'].toString() != driverId) { // توكن يخص سائقاً/جهازاً سابقاً على نفس التثبيت. Log.print( '♻️ getJWT: التوكن مُصدر لـ ${cached['user_id']} لا $driverId — إعادة توليد.', ); } else { Log.print('🔑 Valid JWT found in secure storage. Skipping generation.'); _jwtFailures = 0; _jwtCooldownUntil = DateTime(2000); return true; } } } if (DateTime.now().isBefore(_jwtCooldownUntil)) { Log.print( '🛑 getJWT: بـ cooldown لمدة ${jwtCooldownRemaining.inSeconds}ث — تخطّي التجديد.', ); return false; } if (isRegistering && box.read(BoxName.firstTimeLoadKey).toString() != 'false') { return _getRegistrationJwt(); } else { await EncryptionHelper.initialize(); if (isRegistering) { return _onJwtFailure('renew: لا يوجد driverID'); } var payload = { 'id': box.read(BoxName.driverID), 'aud': '${AK.allowed}$dev', 'fingerPrint': box.read(BoxName.deviceFingerprint) ?? await DeviceHelper.getDeviceFingerprint(), }; var response1 = await http .post(Uri.parse(AppLink.loginJwtDriver), body: payload) .timeout(const Duration(seconds: 30)); if (response1.statusCode == 429) { final retryAfter = int.tryParse(response1.headers['retry-after'] ?? '') ?? 60; _jwtCooldownUntil = DateTime.now().add(Duration(seconds: retryAfter)); Log.print('🛑 getJWT: 429 — cooldown ${retryAfter}ث'); return false; } if (response1.statusCode == 200) { final decodedResponse1 = jsonDecode(response1.body); String? jwt; if (decodedResponse1['message'] is Map && decodedResponse1['message']['jwt'] != null) { jwt = decodedResponse1['message']['jwt']; } else { jwt = decodedResponse1['jwt']; } if (jwt != null) { await storage.write(key: BoxName.jwt, value: jwt); needsDeviceVerification = false; return _onJwtSuccess(); } return _onJwtFailure('renew: لا يوجد jwt بالرد'); } if (response1.statusCode == 401) { // سائق لم يُكمل التسجيل بعد: عنده driverID من تحقق الهاتف لكن لا // صفّ له في جدول driver، فـ loginJwtDriver يردّ 401 دائماً. هنا // توكن التسجيل هو الصحيح — وبدون هذا الرجوع يعلق في منتصف التسجيل. if (box.read(BoxName.firstTimeLoadKey).toString() != 'false') { Log.print('↩️ getJWT: التسجيل لم يكتمل — الرجوع لتوكن التسجيل.'); return _getRegistrationJwt(); } // سائق مكتمل: البصمة لا تطابق المخزّنة (جهاز جديد). الحل الوحيد // OTP يعيد ربط البصمة — لا فائدة من إعادة المحاولة، فنرفع العلم // ليحوّله loginDriver لصفحة التحقق. needsDeviceVerification = true; Log.print('🔐 getJWT: 401 من loginJwtDriver — الجهاز يحتاج تحقق OTP.'); } return _onJwtFailure('renew: HTTP ${response1.statusCode}'); } } /// يُرفع عندما يرفض loginJwtDriver بصمة هذا الجهاز (401): السائق مسجّل /// لكن الجهاز غير مربوط، ولا يمكن إصدار توكن driver قبل تحقق OTP. static bool needsDeviceVerification = false; /// توكن التسجيل — صالح لمسارات التسجيل وحدها (loginFromGoogle، register، /// رفع المستندات…). يُستعمل قبل وجود صفّ للسائق، ويُستبدل بتوكن driver /// فور اكتمال الدخول. Future _getRegistrationJwt() async { final payload = { 'id': box.read(BoxName.driverID) ?? AK.newId, 'password': AK.passnpassenger, 'aud': '${AK.allowed}$dev', 'fingerPrint': box.read(BoxName.deviceFingerprint) ?? await DeviceHelper.getDeviceFingerprint(), }; final response0 = await http .post(Uri.parse(AppLink.loginFirstTimeDriver), body: payload) .timeout(const Duration(seconds: 30)); if (response0.statusCode == 429) { final retryAfter = int.tryParse(response0.headers['retry-after'] ?? '') ?? 60; _jwtCooldownUntil = DateTime.now().add(Duration(seconds: retryAfter)); Log.print('🛑 getJWT(firstTime): 429 — cooldown ${retryAfter}s'); return false; } if (response0.statusCode == 200) { final decodedResponse1 = jsonDecode(response0.body); String? jwt; if (decodedResponse1['message'] is Map && decodedResponse1['message']['jwt'] != null) { jwt = decodedResponse1['message']['jwt']; } else { jwt = decodedResponse1['jwt']; } if (jwt != null) { await storage.write(key: BoxName.jwt, value: jwt); await EncryptionHelper.initialize(); return _onJwtSuccess(); } return _onJwtFailure('firstTime: لا يوجد jwt بالرد'); } return _onJwtFailure('firstTime: HTTP ${response0.statusCode}'); } 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; } Future getLocationPermission() async { var status = await Permission.locationAlways.status; if (!status.isGranted) { await Permission.locationAlways.request(); } update(); } bool isInviteDriverFound = false; Future updateInvitationCodeFromRegister() async { var res = await CRUD().post( link: AppLink.updateDriverInvitationDirectly, payload: {"inviterDriverPhone": box.read(BoxName.phoneDriver).toString()}, ); Log.print('invite: ${res}'); // حماية من النوع — res قد يكون String ('failure'/'token_expired') بدل Map if (res is! Map) return; if (res['status'] != 'failure') { isInviteDriverFound = true; update(); box.write(BoxName.isInstall, '1'); NotificationController().showNotification( "Code approved".tr, "Code approved".tr, 'tone2', '', ); try { NotificationService.sendNotification( target: (res)['message'][0]['token'].toString(), title: 'You have received a gift token!'.tr, body: 'for '.tr + box.read(BoxName.phoneDriver).toString(), isTopic: false, tone: 'tone2', driverList: [], category: 'You have received a gift token!', ); } catch (e) { Log.print('invite notification error: $e'); } } } /// جلسة محلية صالحة: عندنا driverID + رقم متحقق منه مسبقاً. /// في هذه الحالة أي فشل شبكة/توكن هو عارض ولا يجوز إرجاع الكابتن لـ OTP. bool get hasLocalVerifiedSession { final id = box.read(BoxName.driverID)?.toString() ?? ''; return id.isNotEmpty && box.read(BoxName.phoneVerified).toString() == '1' && box.read(BoxName.firstTimeLoadKey).toString() == 'false'; } /// نتائج عارضة من CRUD (ليست JSON) — شبكة/توكن/ضغط سيرفر. static const _transientResults = { 'failure', 'no_internet', 'token_expired', 'rate_limited', }; loginDriver(String driverID, email) async { isloading = true; update(); // await SecurityHelper.performSecurityChecks(); // Log.print('(BoxName.emailDriver): ${box.read(BoxName.emailDriver)}'); // await getJWT(); var res = await CRUD().get( link: AppLink.loginFromGoogleCaptin, payload: {'driver_id': driverID}, ); Log.print('loginDriver: ${res}'); // ═══════════════════════════════════════════════════════════════ // فشل عارض (شبكة / توكن منتهي / rate limit): // إذا الجهاز محفوظ والرقم متحقق ⇒ لا نرجع لـ OTP إطلاقاً. // نجدّد التوكن مرة (متجاوزين الـ cooldown) ونعيد المحاولة مرة واحدة. // ═══════════════════════════════════════════════════════════════ if (res is String && _transientResults.contains(res)) { if (hasLocalVerifiedSession) { Log.print( '⚠️ loginDriver: نتيجة عارضة "$res" مع جلسة محلية صالحة — ' 'إعادة محاولة بعد تجديد التوكن قسراً.', ); final refreshed = await getJWT(force: true); if (refreshed) { res = await CRUD().get( link: AppLink.loginFromGoogleCaptin, payload: {'driver_id': driverID}, ); Log.print('loginDriver(retry): $res'); } } // ما زالت النتيجة عارضة if (res is String && _transientResults.contains(res)) { isloading = false; update(); if (hasLocalVerifiedSession) { // ندخله للتطبيق بجلسته المحفوظة بدل رميه على OTP. mySnackbarError('Connection problem, please try again'.tr); Get.offAll(() => HomeCaptain()); return true; } await isPhoneVerified(); return false; } } if (res == 'failure') { await isPhoneVerified(); isloading = false; // <--- أضفت هذا أيضاً update(); return false; // Get.snackbar('Failure', '', backgroundColor: Colors.red); } else { dynamic jsonDecoeded; try { jsonDecoeded = jsonDecode(res); } catch (e) { // رد غير JSON — لا نُسقط الجلسة بسببه. Log.print('❌ loginDriver: رد غير صالح "$res" ($e)'); isloading = false; update(); if (hasLocalVerifiedSession) { Get.offAll(() => HomeCaptain()); return true; } await isPhoneVerified(); return false; } var d = jsonDecoeded['data'][0]; if (jsonDecoeded.isNotEmpty) { final driverStatus = d['status']?.toString() ?? ''; final isAccountBlocked = driverStatus == 'blocked' || driverStatus == 'banned' || driverStatus == 'disabled'; final isVerifiedUser = !isAccountBlocked && (d['is_verified'].toString() == '1' || d['is_test'].toString() == '1' || driverStatus == 'actives' || driverStatus == 'active'); if (jsonDecoeded['status'] == 'success' && isVerifiedUser) { box.write(BoxName.emailDriver, d['email']); box.write(BoxName.firstTimeLoadKey, 'false'); box.write(BoxName.driverID, (d['id'])); box.write( BoxName.isTest, d['is_test']?.toString() == '1' ? '1' : '0', ); box.write(BoxName.gender, (d['gender'])); box.write(BoxName.phoneVerified, '1'); box.write(BoxName.phoneDriver, (d['phone'])); box.write(BoxName.is_claimed, d['is_claimed']); box.write(BoxName.isInstall, d['isInstall']); // box.write( // BoxName.isGiftToken, d['isGiftToken']); box.write(BoxName.nameArabic, (d['name_arabic'])); box.write(BoxName.carYear, d['year']); box.write(BoxName.bankCodeDriver, (d['bankCode'])); box.write(BoxName.accountBankNumberDriver, (d['accountBank'])); box.write( BoxName.nameDriver, '${(d['first_name'])}' ' ${(d['last_name'])}', ); if (((d['model']).toString().contains('دراجه') || d['make'].toString().contains('دراجه '))) { if ((d['gender']).toString() == 'Male') { box.write(BoxName.carTypeOfDriver, 'Scooter'); } else { box.write(BoxName.carTypeOfDriver, 'Pink Bike'); } } else if (int.parse(d['year'].toString()) > 2016) { if (d['gender'].toString() != 'Male') { box.write(BoxName.carTypeOfDriver, 'Lady'); } else { box.write(BoxName.carTypeOfDriver, 'Comfort'); } } else if (int.parse(d['year'].toString()) > 2002 && int.parse(d['year'].toString()) < 2016) { box.write(BoxName.carTypeOfDriver, 'Speed'); } else if (int.parse(d['year'].toString()) < 2002) { box.write(BoxName.carTypeOfDriver, 'Awfar Car'); } // ═══════════════════════════════════════════════════════════ // ترقية التوكن: حتى هنا كنا نستعمل توكن registration (المسموح // له بـ loginFromGoogle فقط). الآن صار عندنا driverID، فنستبدله // بتوكن driver قبل أي طلب لاحق — بدون هذه الخطوة يردّ كل // endpoint محمي 403 "Token not authorized for this action". // ═══════════════════════════════════════════════════════════ Log.print('🔑 Getting access token after login...'); String fingerPrint = await DeviceHelper.getDeviceFingerprint(); await storage.write(key: BoxName.fingerPrint, value: fingerPrint); final upgraded = await getJWT(force: true); Log.print( upgraded ? '🔑 Access token obtained.' : '⚠️ تعذّر الحصول على توكن driver بعد الدخول.', ); // جهاز جديد: البصمة غير مربوطة، فلا يمكن إصدار توكن driver إلا // بعد OTP يعيد الربط. نحوّله مباشرة بدل إدخاله بتوكن بلا صلاحيات. bool isTester = d['email']?.toString() == 'driver_tester@siromove.com' || d['is_test']?.toString() == '1'; if (!upgraded && needsDeviceVerification && !isTester) { isloading = false; update(); Get.offAll( () => OtpVerificationPage( phone: d['phone'].toString(), deviceToken: fingerPrint, token: '', ptoken: '', ), ); return true; } // add invitations if (box.read(BoxName.isInstall) == null || box.read(BoxName.isInstall).toString() == '0') { updateInvitationCodeFromRegister(); } // updateAppTester(AppInformation.appName); final driverStatus = d['status'].toString(); if (driverStatus != 'yet' && driverStatus != 'pending_review') { var token = await CRUD().get( link: AppLink.getDriverToken, payload: {'captain_id': (box.read(BoxName.driverID)).toString()}, ); // print(jsonDecode(token)['data'][0]['token'].toString()); // print(box.read(BoxName.tokenDriver).toString()); // if (box.read(BoxName.emailDriver).toString() != // '963992952235@intaleqapp.com') { if (token != 'failure') { var serverData = jsonDecode(token); final serverToken = serverData['data'][0]['token'].toString(); final storedToken = box.read(BoxName.tokenDriver)?.toString() ?? ''; final serverFp = serverData['data'][0]['fingerPrint'].toString(); final tokenMismatch = storedToken.isNotEmpty && serverToken != storedToken; // البصمة تُخزَّن مُهشّمة (sha256 + FP_PEPPER) بعد أول تجديد // توكن. التطبيق لا يملك الـ pepper فلا يستطيع مقارنتها — // ومقارنتها خاماً تعطي "اختلاف" دائماً وتفرض OTP بلا سبب. // في هذه الحالة نتّكل على تحقّق السيرفر عند إصدار التوكن // (loginJwtDriver يردّ 401 لجهاز غير مربوط). final isHashedFp = RegExp(r'^[a-f0-9]{64}$').hasMatch(serverFp); final fingerprintMismatch = serverFp.isNotEmpty && !isHashedFp && serverFp != fingerPrint.toString(); Log.print( '🔍 [FP_COMPARE] serverFp: $serverFp | localFp: ${fingerPrint.toString()} | fingerprintMismatch: $fingerprintMismatch | tokenMismatch: $tokenMismatch | serverToken: $serverToken | storedToken: $storedToken', ); bool isTester = d['email']?.toString() == 'driver_tester@siromove.com' || d['is_test']?.toString() == '1'; if (!isTester && (fingerprintMismatch || tokenMismatch)) { Get.defaultDialog( barrierDismissible: false, title: 'Device Change Detected'.tr, middleText: 'Please verify your identity'.tr, textConfirm: 'Verify'.tr, confirmTextColor: Colors.white, onConfirm: () { // نغلق الـ Dialog أولاً بشكل صريح if (Get.isDialogOpen ?? false) { Get.back(); } // ثم ننتقل لصفحة OTP Get.offAll( () => OtpVerificationPage( phone: d['phone'].toString(), deviceToken: fingerPrint.toString(), token: token.toString(), ptoken: jsonDecode( token, )['data'][0]['token'].toString(), ), ); }, ); isloading = false; update(); return true; // نخرج من الدالة هنا لنسمح لـ OTP بالتعامل مع الأمر } // } } Get.offAll(() => HomeCaptain()); // افترض أن هذا الكلاس موجود isloading = false; // <--- أضفت هذا update(); // <--- أضفت هذا return true; } else { Get.offAll( () => DriverVerificationScreen(), ); // افترض أن هذا الكلاس موجود isloading = false; // <--- أضفت هذا update(); // <--- أضفت هذا return false; } // Get.off(() => HomeCaptain()); } else { Get.offAll(() => PhoneNumberScreen()); isloading = false; update(); return false; // <--- ✅ وهذا السطر موجود للحالات الأخرى } } else { mySnackbarSuccess(''); isloading = false; update(); } } } logintest(String driverID, email) async { isloading = true; update(); // await SecurityHelper.performSecurityChecks(); // Log.print('(BoxName.emailDriver): ${box.read(BoxName.emailDriver)}'); var res = await CRUD().get( link: AppLink.loginFromGoogleCaptin, payload: {'driver_id': driverID}, ); // print('res is $res'); // if (res == 'failure') { // await isPhoneVerified(); // // Get.snackbar('Failure', '', backgroundColor: Colors.red); // } else // { var jsonDecoeded = jsonDecode(res); var d = jsonDecoeded['data'][0]; if (jsonDecoeded.isNotEmpty) { if (jsonDecoeded['status'] == 'success') // && // d['is_verified'].toString() == '1') { box.write(BoxName.emailDriver, d['email']); box.write(BoxName.firstTimeLoadKey, 'false'); box.write(BoxName.driverID, (d['id'])); box.write(BoxName.isTest, '1'); box.write(BoxName.gender, (d['gender'])); box.write(BoxName.phoneVerified, d['is_verified'].toString()); box.write(BoxName.phoneDriver, (d['phone'])); box.write(BoxName.is_claimed, d['is_claimed']); box.write(BoxName.isInstall, d['isInstall']); // box.write( // BoxName.isGiftToken, d['isGiftToken']); box.write(BoxName.nameArabic, (d['name_arabic'])); box.write(BoxName.carYear, d['year']); box.write(BoxName.bankCodeDriver, (d['bankCode'])); box.write(BoxName.accountBankNumberDriver, (d['accountBank'])); box.write( BoxName.nameDriver, '${(d['first_name'])}' ' ${(d['last_name'])}', ); if (((d['model']).toString().contains('دراجه') || d['make'].toString().contains('دراجه '))) { if ((d['gender']).toString() == 'Male') { box.write(BoxName.carTypeOfDriver, 'Scooter'); } else { box.write(BoxName.carTypeOfDriver, 'Pink Bike'); } } else if (int.parse(d['year'].toString()) > 2016) { if (d['gender'].toString() != 'Male') { box.write(BoxName.carTypeOfDriver, 'Lady'); } else { box.write(BoxName.carTypeOfDriver, 'Comfort'); } } else if (int.parse(d['year'].toString()) > 2002 && int.parse(d['year'].toString()) < 2016) { box.write(BoxName.carTypeOfDriver, 'Speed'); } else if (int.parse(d['year'].toString()) < 2002) { box.write(BoxName.carTypeOfDriver, 'Awfar Car'); } // updateAppTester(AppInformation.appName); // var token = await CRUD().get( // link: AppLink.getDriverToken, // payload: {'captain_id': (box.read(BoxName.driverID)).toString()}); // String fingerPrint = await DeviceHelper.getDeviceFingerprint(); // await storage.write( // key: BoxName.fingerPrint, value: fingerPrint.toString()); Get.off(() => HomeCaptain()); // } else { // Get.offAll(() => PhoneNumberScreen()); // isloading = false; // update(); // } // } // else { // mySnackbarSuccess(''); // isloading = false; // update(); } } } loginUsingCredentialsWithoutGoogle(String password, email) async { isloading = true; update(); try { var fingerPrint = await DeviceHelper.getDeviceFingerprint(); var payload = { 'email': email, 'password': password, 'fingerPrint': fingerPrint, 'aud': AppInformation.appName, }; Log.print( '🚀 loginUsingCredentialsWithoutGoogle Request URL: ${AppLink.loginUsingCredentialsWithoutGoogle}', ); Log.print('📦 loginUsingCredentialsWithoutGoogle Payload: $payload'); var response = await http.post( Uri.parse(AppLink.loginUsingCredentialsWithoutGoogle), body: payload, ); Log.print( '✅ loginUsingCredentialsWithoutGoogle Status Code: ${response.statusCode}', ); Log.print( '📄 loginUsingCredentialsWithoutGoogle Response Body: ${response.body}', ); if (response.statusCode == 200 || response.statusCode == 201) { var jsonDecoeded = jsonDecode(response.body); if (jsonDecoeded['status'] == 'success') { var d = jsonDecoeded['data'][0]; var jwt = jsonDecoeded['jwt']; // حفظ التوكن في FlutterSecureStorage فقط if (jwt != null) { await storage.write(key: BoxName.jwt, value: jwt); } box.write(BoxName.emailDriver, (d['email'])); box.write(BoxName.driverID, (d['id'])); box.write(BoxName.isTest, '1'); box.write(BoxName.gender, (d['gender'])); final isVerifiedFlag = (d['is_verified'].toString() == '1' || d['is_test'].toString() == '1' || d['status'].toString() == 'actives' || d['status'].toString() == 'active') ? '1' : '0'; box.write(BoxName.phoneVerified, isVerifiedFlag); box.write(BoxName.phoneDriver, (d['phone'])); box.write(BoxName.nameArabic, (d['name_arabic'])); box.write(BoxName.bankCodeDriver, (d['bankCode'])); box.write(BoxName.accountBankNumberDriver, d['accountBank']); box.write( BoxName.nameDriver, '${(d['first_name'])}' ' ${(d['last_name'])}', ); await storage.write(key: BoxName.fingerPrint, value: fingerPrint); Get.off(() => HomeCaptain()); } else { Log.print('❌ Login failed message: ${jsonDecoeded['message']}'); mySnackbarError( (jsonDecoeded['message'] ?? 'Login failed').toString().tr, ); isloading = false; update(); } } else if (response.statusCode == 429) { Log.print('❌ Too many attempts (429)'); mySnackbarError('Too many attempts. Please wait a minute.'.tr); isloading = false; update(); } else { Log.print('❌ Server error response (Not 200/201/429)'); mySnackbarError('Server error'.tr); isloading = false; update(); } } catch (e) { Log.print('❌ Network/Exception error: $e'); mySnackbarError('Network error'.tr); isloading = false; update(); } } void loginByBoxData() async { Get.to(() => HomeCaptain()); await CRUD().post( link: AppLink.addTokensDriver, payload: { 'token': box.read(BoxName.tokenDriver).toString(), 'captain_id': box.read(BoxName.driverID).toString(), }, ); CRUD().post( link: "${AppLink.seferAlexandriaServer}/ride/firebase/addDriver.php", payload: { 'token': box.read(BoxName.tokenDriver), 'captain_id': box.read(BoxName.driverID).toString(), }, ); CRUD().post( link: "${AppLink.seferGizaServer}/ride/firebase/addDriver.php", payload: { 'token': box.read(BoxName.tokenDriver), 'captain_id': box.read(BoxName.driverID).toString(), }, ); } }