import 'dart:async'; import 'dart:convert'; import 'dart:io'; import 'package:Intaleq/constant/api_key.dart'; import 'package:Intaleq/controller/firebase/firbase_messge.dart'; import 'package:Intaleq/views/auth/otp_page.dart'; import 'package:Intaleq/views/widgets/error_snakbar.dart'; import 'package:http/http.dart' as http; import 'package:Intaleq/constant/info.dart'; import 'package:Intaleq/controller/functions/add_error.dart'; import 'package:Intaleq/views/auth/login_page.dart'; import 'package:flutter/material.dart'; import 'package:get/get.dart'; import 'package:Intaleq/constant/box_name.dart'; import 'package:Intaleq/constant/links.dart'; import 'package:Intaleq/controller/functions/crud.dart'; import 'package:Intaleq/main.dart'; 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(); final formKeyAdmin = GlobalKey(); TextEditingController emailController = TextEditingController(); TextEditingController phoneController = TextEditingController(); TextEditingController passwordController = TextEditingController(); TextEditingController adminPasswordController = TextEditingController(); TextEditingController adminNameController = TextEditingController(); bool isAgreeTerms = false; bool isloading = false; late int isTest = 1; void changeAgreeTerm() { isAgreeTerms = !isAgreeTerms; update(); } var dev = ''; @override void onInit() async { // Log.print('box.read(BoxName.isTest): ${box.read(BoxName.isTest)}'); box.write(BoxName.countryCode, 'Syria'); FirebaseMessagesController().getToken(); super.onInit(); } void saveAgreementTerms() { box.write(BoxName.agreeTerms, 'agreed'); update(); } void saveCountryCode(String countryCode) { box.write(BoxName.countryCode, countryCode); update(); } // ═══════════════════════════════════════════════════════════════ // LoginController — دوال إدارة الـ JWT // ─────────────────────────────────────────────────────────────── // لا تغيير على اسم الكلاس أو أسماء الدوال // ═══════════════════════════════════════════════════════════════ // داخل class LoginController // ───────────────────────────────────────────────────────────── // getJWT: الحصول على توكن للراكب // ───────────────────────────────────────────────────────────── // المنطق: // • firstTimeLoadKey != false ← أول مرة يفتح التطبيق → loginFirstTime // • firstTimeLoadKey == false ← مستخدم موجود → loginJwtRider // ───────────────────────────────────────────────────────────── Future getJWT() async { // إذا كان التوكن الحالي لا يزال صالحاً، لا داعي لطلب واحد جديد if (isTokenValid()) { Log.print("JWT is still valid. Skipping request."); return; } try { dev = Platform.isAndroid ? 'android' : 'ios'; // تأكد إن البصمة محدّثة قبل أي طلب await DeviceHelper.getDeviceFingerprint(); final String fp = box.read(BoxName.deviceFpEncrypted) ?? ''; 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, }; var response = await http.post( Uri.parse(AppLink.loginFirstTime), body: payload, ); Log.print('AppLink.loginFirstTime: ${AppLink.loginFirstTime}'); Log.print('payload: $payload'); Log.print('response: $response'); 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)); } } } } catch (e) { Log.print('Error in getJWT: $e'); } } // ───────────────────────────────────────────────────────────── // التحقق من صلاحية التوكن يدوياً (بدون مكاتب خارجية) // ───────────────────────────────────────────────────────────── 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 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) // • يرجع hmac مع الـ jwt ويخزنه في GetStorage // • الـ JWT لا يُشفَّر ثلاثياً (يُستخدم مباشرة في الـ header) // ───────────────────────────────────────────────────────────── Future getJwtWallet() async { dev = Platform.isAndroid ? 'android' : 'ios'; final String fp = box.read(BoxName.deviceFpEncrypted) ?? ''; var payload = { 'id': box.read(BoxName.passengerID), 'password': AK.passnpassenger, 'aud': '${AK.allowedWallet}$dev', 'fingerPrint': fp, }; var response = await http.post( Uri.parse(AppLink.loginJwtWalletRider), body: payload, ); Log.print('AppLink.loginJwtWalletRider: ${AppLink.loginJwtWalletRider}'); Log.print('response wallet: ${response.body}'); if (response.statusCode == 200) { final decoded = jsonDecode(response.body); // ← الإصلاح: نقرأ من 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) { box.write(BoxName.hmac, hmac); } return jwt; } return null; } Future loginUsingCredentials(String passengerID, String email) async { isloading = true; update(); // await getJWT(); Log.print("LoginController.loginUsingCredentials: "); try { // 1) استعلام تسجيل الدخول final res = await CRUD().get( link: AppLink.loginFromGooglePassenger, payload: { 'email': email, 'id': passengerID, // استخدم المعامل مباشرة 'platform': Platform.isAndroid ? 'android' : 'ios', 'appName': AppInformation.appName, }, ); // 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; } final status = (decoded['status'] ?? '').toString(); final data = (decoded['data'] as List?)?.firstOrNull as Map? ?? {}; if (status != 'success' || data['verified'].toString() != '1') { // غير مُفعل -> أذهب للتسجيل بالـ SMS Get.offAll(() => PhoneNumberScreen()); return; } // 3) كتابة القيم (خفيفة) box.write(BoxName.isVerified, '1'); box.write(BoxName.email, data['email']); box.write(BoxName.phone, data['phone']); box.write(BoxName.name, data['first_name']); box.write(BoxName.isTest, '1'); box.write(BoxName.package, data['package']); box.write(BoxName.promo, data['promo']); box.write(BoxName.discount, data['discount']); box.write(BoxName.validity, data['validity']); box.write(BoxName.isInstall, data['isInstall'] ?? 'none'); box.write(BoxName.isGiftToken, data['isGiftToken'] ?? 'none'); if (data['inviteCode'] != null) { box.write(BoxName.inviteCode, data['inviteCode'].toString()); } // مهم: تأكد من passengerID في الـ box box.write(BoxName.passengerID, passengerID); // 4) فحص ما إذا كان التوكن موجوداً في رد الـ Login المدمج (V3 Optimization) String? serverFCM; String? serverFP; 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') { 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 != 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; } } } // 6) منطق الدعوة (إن وُجد) ثم الانتقال final invite = box.read(BoxName.inviteCode)?.toString() ?? 'none'; final isInstall = box.read(BoxName.isInstall)?.toString() ?? '0'; if (invite != 'none' && isInstall != '1') { try { await CRUD().post(link: AppLink.updatePassengersInvitation, payload: { "inviteCode": invite, "passengerID": passengerID, }); await Get.defaultDialog( title: 'Invitation Used'.tr, middleText: "Your invite code was successfully applied!".tr, textConfirm: "OK".tr, confirmTextColor: Colors.white, onConfirm: () async { try { await CRUD().post(link: AppLink.addPassengersPromo, payload: { "promoCode": 'I-${(box.read(BoxName.name)).toString().split(' ').first}', "amount": '25', "passengerID": passengerID, "description": 'promo first' }); } catch (e) { addError( e.toString(), 'promo on invitation in login_controller'); } finally { Get.offAll(() => const MapPagePassenger()); } }, ); return; } catch (_) { // حتى لو فشل، كمل للصفحة الرئيسية } } Get.offAll(() => const MapPagePassenger()); } catch (e) { addError('$e', 'loginUsingCredentials'); Get.snackbar('Error', e.toString(), backgroundColor: Colors.redAccent); } finally { isloading = false; update(); } } // Future _confirmDeviceChangeDialog() { // return Get.defaultDialog( // barrierDismissible: false, // title: 'Device Change Detected'.tr, // middleText: 'Please verify your identity'.tr, // textConfirm: 'Verify'.tr, // confirmTextColor: Colors.white, // onConfirm: () => Get.back(result: true), // textCancel: 'Cancel'.tr, // onCancel: () => Get.back(result: false), // ); // } void login() async { isloading = true; update(); var res = await CRUD().get(link: AppLink.loginFromGooglePassenger, payload: { 'email': (emailController.text), 'id': passwordController.text, "platform": Platform.isAndroid ? 'android' : 'ios', "appName": AppInformation.appName, }); isloading = false; update(); if (res == 'Failure') { //Failure Get.offAll(() => LoginPage()); isloading = false; update(); // Get.snackbar("User does not exist.".tr, '', backgroundColor: Colors.red); } else { var jsonDecoeded = jsonDecode(res); if (jsonDecoeded.isNotEmpty) { if (jsonDecoeded['status'] == 'success' && jsonDecoeded['data'][0]['verified'].toString() == '1') { // box.write(BoxName.isVerified, '1'); box.write(BoxName.email, jsonDecoeded['data'][0]['email']); box.write(BoxName.name, jsonDecoeded['data'][0]['first_name']); box.write(BoxName.phone, jsonDecoeded['data'][0]['phone']); box.write(BoxName.passengerID, passwordController.text); // var token = await CRUD().get(link: AppLink.getTokens, payload: { // 'passengerID': box.read(BoxName.passengerID).toString() // }); // await updateAppTester(AppInformation.appName); Get.offAll(() => const MapPagePassenger()); } else { // Get.offAll(() => SmsSignupEgypt()); // Get.snackbar(jsonDecoeded['status'], jsonDecoeded['data'], // backgroundColor: Colors.redAccent); isloading = false; update(); } } else { isloading = false; update(); } } } void goToMapPage() { if (box.read(BoxName.email) != null) { Get.offAll(() => const MapPagePassenger()); } } final location = Location(); // late PermissionStatus permissionGranted = PermissionStatus.denied; Future getLocationPermission() async { bool serviceEnabled; PermissionStatus permissionGranted; // Check if location services are enabled serviceEnabled = await location.serviceEnabled(); if (!serviceEnabled) { serviceEnabled = await location.requestService(); if (!serviceEnabled) { // Location services are still not enabled, handle the error return; } } // Check if the app has permission to access location permissionGranted = await location.hasPermission(); if (permissionGranted == PermissionStatus.denied) { permissionGranted = await location.requestPermission(); if (permissionGranted != PermissionStatus.granted) { // Location permission is still not granted, handle the error permissionGranted = await location.requestPermission(); return; } } if (permissionGranted.toString() == 'PermissionStatus.granted') { box.write(BoxName.locationPermission, 'true'); } update(); } }