From 6bfc15abb20d8314c62395bc4e0ffdb0f05eb3e4 Mon Sep 17 00:00:00 2001 From: Hamza-Ayed Date: Tue, 28 Apr 2026 01:13:15 +0300 Subject: [PATCH] refactor: upgrade API endpoints to v3 and refactor navigation and response handling across controllers --- lib/constant/links.dart | 6 +- lib/controller/auth/login_controller.dart | 286 +++++++++++------- lib/controller/functions/crud.dart | 31 +- .../home/map_passenger_controller.dart | 197 +++++++----- .../home/profile/invit_controller.dart | 12 +- .../profile/order_history_controller.dart | 4 +- .../home/splash_screen_controlle.dart | 4 + .../passenger_notification_controller.dart | 17 +- .../car_details_widget_to_go.dart | 27 +- lib/views/notification/notification_page.dart | 2 +- 10 files changed, 366 insertions(+), 220 deletions(-) diff --git a/lib/constant/links.dart b/lib/constant/links.dart index c3dc2cb..bf9601c 100644 --- a/lib/constant/links.dart +++ b/lib/constant/links.dart @@ -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) للتعامل مع عمليات إلغاء الرحلات وتحديث حالة الرحلات وغيرها من العمليات المتعلقة بالرحلات. diff --git a/lib/controller/auth/login_controller.dart b/lib/controller/auth/login_controller.dart index c8bd572..55467c3 100644 --- a/lib/controller/auth/login_controller.dart +++ b/lib/controller/auth/login_controller.dart @@ -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(); final formKeyAdmin = GlobalKey(); @@ -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 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 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 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; } } } diff --git a/lib/controller/functions/crud.dart b/lib/controller/functions/crud.dart index d423d4a..5864dfa 100644 --- a/lib/controller/functions/crud.dart +++ b/lib/controller/functions/crud.dart @@ -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}'}); } } diff --git a/lib/controller/home/map_passenger_controller.dart b/lib/controller/home/map_passenger_controller.dart index aa74ca6..483211b 100644 --- a/lib/controller/home/map_passenger_controller.dart +++ b/lib/controller/home/map_passenger_controller.dart @@ -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(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 { diff --git a/lib/controller/home/profile/invit_controller.dart b/lib/controller/home/profile/invit_controller.dart index 7234977..eecd52b 100644 --- a/lib/controller/home/profile/invit_controller.dart +++ b/lib/controller/home/profile/invit_controller.dart @@ -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 = diff --git a/lib/controller/home/profile/order_history_controller.dart b/lib/controller/home/profile/order_history_controller.dart index d35fb6e..98b6081 100644 --- a/lib/controller/home/profile/order_history_controller.dart +++ b/lib/controller/home/profile/order_history_controller.dart @@ -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(); } diff --git a/lib/controller/home/splash_screen_controlle.dart b/lib/controller/home/splash_screen_controlle.dart index 6d1ba17..b48085f 100644 --- a/lib/controller/home/splash_screen_controlle.dart +++ b/lib/controller/home/splash_screen_controlle.dart @@ -147,6 +147,10 @@ class SplashScreenController extends GetxController await _getPackageInfo(); SecureStorage().saveData('iss', 'mobile-app:'); + // ── [OPTIMIZATION] جلب التوكن عند بداية تشغيل التطبيق ──────────────── + Log.print("SplashScreen: Initializing JWT..."); + await Get.find().getJWT(); + if (box.read(BoxName.onBoarding) == null) { Get.off(() => OnBoardingPage()); } else if (box.read(BoxName.email) != null && diff --git a/lib/controller/notification/passenger_notification_controller.dart b/lib/controller/notification/passenger_notification_controller.dart index 04c8cb4..d5fd25b 100644 --- a/lib/controller/notification/passenger_notification_controller.dart +++ b/lib/controller/notification/passenger_notification_controller.dart @@ -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(); } diff --git a/lib/views/home/map_widget.dart/car_details_widget_to_go.dart b/lib/views/home/map_widget.dart/car_details_widget_to_go.dart index 587c2fd..29bb104 100644 --- a/lib/views/home/map_widget.dart/car_details_widget_to_go.dart +++ b/lib/views/home/map_widget.dart/car_details_widget_to_go.dart @@ -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(); diff --git a/lib/views/notification/notification_page.dart b/lib/views/notification/notification_page.dart index 076649e..54f0388 100644 --- a/lib/views/notification/notification_page.dart +++ b/lib/views/notification/notification_page.dart @@ -16,7 +16,7 @@ class NotificationPage extends StatelessWidget { Get.put(PassengerNotificationController()); return MyScafolld( isleading: true, - title: 'Notifications', + title: 'Notifications'.tr, body: [ GetBuilder( builder: (notificationCaptainController) => notificationCaptainController