refactor: upgrade API endpoints to v3 and refactor navigation and response handling across controllers
This commit is contained in:
@@ -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) للتعامل مع عمليات إلغاء الرحلات وتحديث حالة الرحلات وغيرها من العمليات المتعلقة بالرحلات.
|
||||
|
||||
@@ -1,3 +1,4 @@
|
||||
import 'dart:async';
|
||||
import 'dart:convert';
|
||||
import 'dart:io';
|
||||
|
||||
@@ -21,14 +22,12 @@ import 'package:Intaleq/views/home/map_page_passenger.dart';
|
||||
|
||||
import 'package:location/location.dart';
|
||||
|
||||
|
||||
|
||||
import '../../env/env.dart';
|
||||
import '../../print.dart';
|
||||
import '../../views/auth/otp_token_page.dart';
|
||||
import '../functions/encrypt_decrypt.dart';
|
||||
import '../functions/package_info.dart';
|
||||
|
||||
|
||||
class LoginController extends GetxController {
|
||||
final formKey = GlobalKey<FormState>();
|
||||
final formKeyAdmin = GlobalKey<FormState>();
|
||||
@@ -48,35 +47,12 @@ class LoginController extends GetxController {
|
||||
var dev = '';
|
||||
@override
|
||||
void onInit() async {
|
||||
await getJWT();
|
||||
// Log.print('box.read(BoxName.isTest): ${box.read(BoxName.isTest)}');
|
||||
box.write(BoxName.countryCode, 'Syria');
|
||||
FirebaseMessagesController().getToken();
|
||||
super.onInit();
|
||||
}
|
||||
|
||||
// getAppTester() async {
|
||||
// var res = await CRUD().get(
|
||||
// link: AppLink.getTesterApp,
|
||||
// payload: {'appPlatform': AppInformation.appName});
|
||||
// if (res != 'failure') {
|
||||
// var d = jsonDecode(res);
|
||||
|
||||
// isTest = int.parse(d['message'][0]['isTest'].toString());
|
||||
// box.write(BoxName.isTest, isTest);
|
||||
// update();
|
||||
// } else {
|
||||
// box.write(BoxName.isTest, '1');
|
||||
// update();
|
||||
// return false;
|
||||
// }
|
||||
// }
|
||||
|
||||
// updateAppTester(String appPlatform) async {
|
||||
// await CRUD().post(
|
||||
// link: AppLink.updateTesterApp, payload: {'appPlatform': appPlatform});
|
||||
// }
|
||||
|
||||
void saveAgreementTerms() {
|
||||
box.write(BoxName.agreeTerms, 'agreed');
|
||||
update();
|
||||
@@ -87,7 +63,6 @@ class LoginController extends GetxController {
|
||||
update();
|
||||
}
|
||||
|
||||
|
||||
// ═══════════════════════════════════════════════════════════════
|
||||
// LoginController — دوال إدارة الـ JWT
|
||||
// ───────────────────────────────────────────────────────────────
|
||||
@@ -104,6 +79,13 @@ class LoginController extends GetxController {
|
||||
// • firstTimeLoadKey == false ← مستخدم موجود → loginJwtRider
|
||||
// ─────────────────────────────────────────────────────────────
|
||||
Future<void> getJWT() async {
|
||||
// إذا كان التوكن الحالي لا يزال صالحاً، لا داعي لطلب واحد جديد
|
||||
if (isTokenValid()) {
|
||||
Log.print("JWT is still valid. Skipping request.");
|
||||
return;
|
||||
}
|
||||
|
||||
try {
|
||||
dev = Platform.isAndroid ? 'android' : 'ios';
|
||||
|
||||
// تأكد إن البصمة محدّثة قبل أي طلب
|
||||
@@ -132,10 +114,13 @@ class LoginController extends GetxController {
|
||||
|
||||
if (response.statusCode == 200) {
|
||||
final decoded = jsonDecode(response.body);
|
||||
final String jwt = decoded['jwt'];
|
||||
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();
|
||||
}
|
||||
@@ -159,15 +144,69 @@ class LoginController extends GetxController {
|
||||
Log.print('response: ${response.body}');
|
||||
if (response.statusCode == 200) {
|
||||
final decoded = jsonDecode(response.body);
|
||||
final String jwt = decoded['jwt'];
|
||||
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');
|
||||
}
|
||||
}
|
||||
|
||||
// ─────────────────────────────────────────────────────────────
|
||||
// getJwtWallet: الحصول على توكن لسيرفر المدفوعات
|
||||
// التحقق من صلاحية التوكن يدوياً (بدون مكاتب خارجية)
|
||||
// ─────────────────────────────────────────────────────────────
|
||||
bool isTokenValid() {
|
||||
try {
|
||||
final String? encryptedJwt = box.read(BoxName.jwt);
|
||||
if (encryptedJwt == null || encryptedJwt.isEmpty) {
|
||||
Log.print("isTokenValid: No token found in storage.");
|
||||
return false;
|
||||
}
|
||||
|
||||
// 1. فك تشفير التوكن المخزن
|
||||
final String jwtFull = r(encryptedJwt).toString();
|
||||
final String jwt = jwtFull.split(Env.addd)[0];
|
||||
|
||||
final parts = jwt.split('.');
|
||||
if (parts.length != 3) {
|
||||
Log.print("isTokenValid: Invalid JWT format (parts: ${parts.length})");
|
||||
return false;
|
||||
}
|
||||
|
||||
// 2. فك Base64 للجزء الأوسط (Payload)
|
||||
String payloadPart = parts[1];
|
||||
while (payloadPart.length % 4 != 0) {
|
||||
payloadPart += '=';
|
||||
}
|
||||
|
||||
final String decodedPayload = utf8.decode(base64Url.decode(payloadPart));
|
||||
final Map<String, dynamic> payload = jsonDecode(decodedPayload);
|
||||
|
||||
if (!payload.containsKey('exp')) {
|
||||
Log.print("isTokenValid: No 'exp' claim in token.");
|
||||
return false;
|
||||
}
|
||||
|
||||
final int exp = payload['exp'];
|
||||
final int now = DateTime.now().millisecondsSinceEpoch ~/ 1000;
|
||||
|
||||
Log.print("isTokenValid: Now=$now, Exp=$exp, Diff=${exp - now}s");
|
||||
|
||||
// اعتبار التوكن منتهي قبل دقيقة للأمان
|
||||
final bool valid = exp > (now + 60);
|
||||
Log.print("isTokenValid: Result=$valid");
|
||||
return valid;
|
||||
} catch (e) {
|
||||
Log.print("isTokenValid Error: $e");
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
// ─────────────────────────────────────────────────────────────
|
||||
// الفرق عن getJWT:
|
||||
// • يستخدم endpoint مختلف (loginWallet)
|
||||
@@ -197,11 +236,15 @@ class LoginController extends GetxController {
|
||||
Log.print('response wallet: ${response.body}');
|
||||
if (response.statusCode == 200) {
|
||||
final decoded = jsonDecode(response.body);
|
||||
final String jwt = decoded['jwt'];
|
||||
final String hmac = decoded['hmac'];
|
||||
final String? jwt =
|
||||
decoded['data'] != null ? decoded['data']['jwt'] : decoded['jwt'];
|
||||
final String? hmac =
|
||||
decoded['data'] != null ? decoded['data']['hmac'] : decoded['hmac'];
|
||||
|
||||
if (hmac != null) {
|
||||
// نخزن الـ hmac للاستخدام في X-HMAC-Auth header
|
||||
box.write(BoxName.hmac, hmac);
|
||||
}
|
||||
|
||||
// wallet JWT يُرجَع مباشرة دون تشفير ثلاثي
|
||||
return jwt;
|
||||
@@ -213,8 +256,8 @@ class LoginController extends GetxController {
|
||||
Future<void> loginUsingCredentials(String passengerID, String email) async {
|
||||
isloading = true;
|
||||
update();
|
||||
await getJWT();
|
||||
|
||||
// await getJWT();
|
||||
Log.print("LoginController.loginUsingCredentials: ");
|
||||
try {
|
||||
// 1) استعلام تسجيل الدخول
|
||||
final res = await CRUD().get(
|
||||
@@ -227,16 +270,16 @@ class LoginController extends GetxController {
|
||||
},
|
||||
);
|
||||
|
||||
if (res == 'Failure') {
|
||||
// 2) فك JSON مرة واحدة والتحقق من النجاح
|
||||
final decoded = jsonDecode(res);
|
||||
if (decoded is! Map || decoded.isEmpty) return;
|
||||
|
||||
if (decoded['status'] == 'failure' || decoded['status'] == 'Failure') {
|
||||
Get.snackbar("User does not exist.".tr, '',
|
||||
backgroundColor: Colors.red);
|
||||
return;
|
||||
}
|
||||
|
||||
// 2) فك JSON مرة واحدة
|
||||
final decoded = jsonDecode(res);
|
||||
if (decoded is! Map || decoded.isEmpty) return;
|
||||
|
||||
final status = (decoded['status'] ?? '').toString();
|
||||
final data = (decoded['data'] as List?)?.firstOrNull as Map? ?? {};
|
||||
if (status != 'success' || data['verified'].toString() != '1') {
|
||||
@@ -263,47 +306,62 @@ class LoginController extends GetxController {
|
||||
// مهم: تأكد من passengerID في الـ box
|
||||
box.write(BoxName.passengerID, passengerID);
|
||||
|
||||
// 4) تنفيذ العمليات بالتوازي: getTokens + fingerprint محلي
|
||||
final results = await Future.wait([
|
||||
CRUD().get(
|
||||
link: AppLink.getTokens, payload: {'passengerID': passengerID}),
|
||||
DeviceHelper.getDeviceFingerprint(),
|
||||
]);
|
||||
// 4) فحص ما إذا كان التوكن موجوداً في رد الـ Login المدمج (V3 Optimization)
|
||||
String? serverFCM;
|
||||
String? serverFP;
|
||||
|
||||
final tokenResp = results[0];
|
||||
final localFP = (results[1] ?? '').toString();
|
||||
var userData = decoded['data'] is List
|
||||
? (decoded['data'] as List).firstOrNull
|
||||
: decoded['data'];
|
||||
if (userData is Map && userData['fcm_token'] != null) {
|
||||
serverFCM = userData['fcm_token'].toString();
|
||||
serverFP = userData['fcm_fingerprint']?.toString() ?? '';
|
||||
Log.print(
|
||||
"✅ FCM Token found in Login response. Skipping separate getTokens call.");
|
||||
}
|
||||
|
||||
// إذا لم يكن موجوداً (توافقية قديمة)، نقوم بطلبه
|
||||
String? tokenResp;
|
||||
if (serverFCM == null) {
|
||||
tokenResp = await CRUD().get(
|
||||
link: AppLink.getTokens, payload: {'passengerID': passengerID});
|
||||
}
|
||||
|
||||
final localFP = (await DeviceHelper.getDeviceFingerprint()).toString();
|
||||
await storage.write(key: BoxName.fingerPrint, value: localFP);
|
||||
await box.write(BoxName.firstTimeLoadKey, 'false');
|
||||
|
||||
// ── 5. المقارنة: FCM token + fingerprint ──────────────────────
|
||||
if (email != '962798583052@intaleqapp.com' && tokenResp != 'failure') {
|
||||
if (email != '962798583052@intaleqapp.com') {
|
||||
if (serverFCM == null &&
|
||||
tokenResp != null &&
|
||||
tokenResp != 'failure' &&
|
||||
tokenResp != 'error') {
|
||||
final tokenJson = jsonDecode(tokenResp);
|
||||
final serverData = tokenJson['message'] as Map?; // null = أول تسجيل
|
||||
|
||||
if (serverData != null) {
|
||||
final serverFCM = serverData['token']?.toString() ?? '';
|
||||
final serverFP = serverData['fingerPrint']?.toString() ?? '';
|
||||
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;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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'];
|
||||
} 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}'});
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -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,9 +1518,8 @@ class MapPassengerController extends GetxController {
|
||||
break;
|
||||
|
||||
case RideState.searching:
|
||||
// effectivePollingInterval = 5;
|
||||
|
||||
// 1. التحقق من حالة الطلب (هل قبله أحد؟)
|
||||
// 1. التحقق من حالة الطلب (هل قبله أحد؟) عبر البولينج فقط إذا كان السوكيت غير متصل
|
||||
if (!isSocketConnected) {
|
||||
try {
|
||||
String statusFromServer = await getRideStatus(rideId);
|
||||
if (statusFromServer == 'Apply' || statusFromServer == 'Applied') {
|
||||
@@ -1529,6 +1529,7 @@ class MapPassengerController extends GetxController {
|
||||
} catch (e) {
|
||||
Log.print('Error polling getRideStatus: $e');
|
||||
}
|
||||
}
|
||||
|
||||
final now = DateTime.now();
|
||||
final int elapsedSeconds = now.difference(_searchStartTime!).inSeconds;
|
||||
@@ -1587,6 +1588,8 @@ class MapPassengerController extends GetxController {
|
||||
rideAppliedFromDriver(true);
|
||||
_isDriverAppliedLogicExecuted = true;
|
||||
}
|
||||
|
||||
if (!isSocketConnected) {
|
||||
try {
|
||||
String statusFromServer = await getRideStatus(rideId);
|
||||
if (statusFromServer == 'Arrived') {
|
||||
@@ -1600,6 +1603,7 @@ class MapPassengerController extends GetxController {
|
||||
} catch (e) {
|
||||
Log.print('Error polling for Arrived/Begin status: $e');
|
||||
}
|
||||
}
|
||||
getDriverCarsLocationToPassengerAfterApplied();
|
||||
break;
|
||||
|
||||
@@ -1615,6 +1619,7 @@ class MapPassengerController extends GetxController {
|
||||
case RideState.inProgress:
|
||||
// effectivePollingInterval = 6;
|
||||
|
||||
if (!isSocketConnected) {
|
||||
try {
|
||||
String statusFromServer = await getRideStatus(rideId);
|
||||
|
||||
@@ -1641,6 +1646,7 @@ class MapPassengerController extends GetxController {
|
||||
} catch (e) {
|
||||
Log.print('Error polling status: $e');
|
||||
}
|
||||
}
|
||||
|
||||
// بقية كود التتبع العادي (لن يتم الوصول إليه إذا انتهت الرحلة)
|
||||
if (!_isRideBeginLogicExecuted) {
|
||||
@@ -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,10 +6035,29 @@ Intaleq Team''';
|
||||
Log.print(
|
||||
'🔔 showDrawingBottomSheet called. isDrawingRoute: $isDrawingRoute');
|
||||
|
||||
// استخدام addPostFrameCallback لضمان ظهور الإشعار بعد انتهاء بناء الإطار
|
||||
final context = Get.context;
|
||||
if (context == null) return;
|
||||
|
||||
WidgetsBinding.instance.addPostFrameCallback((_) {
|
||||
Get.rawSnackbar(
|
||||
titleText: Text(
|
||||
// إغلاق أي سناك بار مفتوح حالياً لتجنب التكرار
|
||||
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,
|
||||
@@ -6031,24 +6065,28 @@ Intaleq Team''';
|
||||
fontSize: 14,
|
||||
),
|
||||
),
|
||||
messageText: Text(
|
||||
const SizedBox(height: 4),
|
||||
Text(
|
||||
'Please wait while we prepare your trip.'.tr,
|
||||
style: const TextStyle(
|
||||
color: Colors.white70,
|
||||
fontSize: 12,
|
||||
),
|
||||
),
|
||||
showProgressIndicator: true,
|
||||
progressIndicatorBackgroundColor: Colors.white24,
|
||||
progressIndicatorValueColor:
|
||||
const AlwaysStoppedAnimation<Color>(Colors.white),
|
||||
snackPosition: SnackPosition.BOTTOM,
|
||||
backgroundColor: AppColor.primaryColor.withOpacity(0.9),
|
||||
],
|
||||
),
|
||||
),
|
||||
],
|
||||
),
|
||||
backgroundColor: AppColor.primaryColor.withOpacity(0.95),
|
||||
duration: const Duration(seconds: 3),
|
||||
behavior: SnackBarBehavior.floating,
|
||||
margin: const EdgeInsets.fromLTRB(16, 0, 16, 110),
|
||||
borderRadius: 16,
|
||||
icon: const Icon(Icons.map_outlined, color: Colors.white),
|
||||
isDismissible: true,
|
||||
shape: RoundedRectangleBorder(
|
||||
borderRadius: BorderRadius.circular(16),
|
||||
),
|
||||
elevation: 8,
|
||||
),
|
||||
);
|
||||
});
|
||||
}
|
||||
@@ -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 {
|
||||
|
||||
@@ -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 =
|
||||
|
||||
@@ -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();
|
||||
}
|
||||
|
||||
@@ -147,6 +147,10 @@ class SplashScreenController extends GetxController
|
||||
await _getPackageInfo();
|
||||
SecureStorage().saveData('iss', 'mobile-app:');
|
||||
|
||||
// ── [OPTIMIZATION] جلب التوكن عند بداية تشغيل التطبيق ────────────────
|
||||
Log.print("SplashScreen: Initializing JWT...");
|
||||
await Get.find<LoginController>().getJWT();
|
||||
|
||||
if (box.read(BoxName.onBoarding) == null) {
|
||||
Get.off(() => OnBoardingPage());
|
||||
} else if (box.read(BoxName.email) != null &&
|
||||
|
||||
@@ -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();
|
||||
}
|
||||
|
||||
@@ -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();
|
||||
|
||||
@@ -16,7 +16,7 @@ class NotificationPage extends StatelessWidget {
|
||||
Get.put(PassengerNotificationController());
|
||||
return MyScafolld(
|
||||
isleading: true,
|
||||
title: 'Notifications',
|
||||
title: 'Notifications'.tr,
|
||||
body: [
|
||||
GetBuilder<PassengerNotificationController>(
|
||||
builder: (notificationCaptainController) => notificationCaptainController
|
||||
|
||||
Reference in New Issue
Block a user