Update: 2026-06-26 02:00:23
This commit is contained in:
@@ -20,10 +20,14 @@ try {
|
|||||||
|
|
||||||
// البحث بالبصمة للموظف
|
// البحث بالبصمة للموظف
|
||||||
$fpHash = hash('sha256', $fingerprint);
|
$fpHash = hash('sha256', $fingerprint);
|
||||||
|
$foundByFingerprint = false;
|
||||||
$sql = "SELECT * FROM `users` WHERE `fingerprint_hash` = :fp AND `user_type` = 'service' LIMIT 1";
|
$sql = "SELECT * FROM `users` WHERE `fingerprint_hash` = :fp AND `user_type` = 'service' LIMIT 1";
|
||||||
$stmt = $con->prepare($sql);
|
$stmt = $con->prepare($sql);
|
||||||
$stmt->execute([':fp' => $fpHash]);
|
$stmt->execute([':fp' => $fpHash]);
|
||||||
$user = $stmt->fetch(PDO::FETCH_ASSOC);
|
$user = $stmt->fetch(PDO::FETCH_ASSOC);
|
||||||
|
if ($user) {
|
||||||
|
$foundByFingerprint = true;
|
||||||
|
}
|
||||||
|
|
||||||
// إذا لم يتم العثور بالبصمة، وتم تمرير الإيميل (تسجيل دخول لأول مرة أو من جهاز جديد)
|
// إذا لم يتم العثور بالبصمة، وتم تمرير الإيميل (تسجيل دخول لأول مرة أو من جهاز جديد)
|
||||||
if (!$user && !empty($email)) {
|
if (!$user && !empty($email)) {
|
||||||
@@ -109,7 +113,9 @@ try {
|
|||||||
"message" => "Login successful",
|
"message" => "Login successful",
|
||||||
"data" => $user,
|
"data" => $user,
|
||||||
"jwt" => $jwt,
|
"jwt" => $jwt,
|
||||||
"expires_in" => $expires_in
|
"expires_in" => $expires_in,
|
||||||
|
"hmac" => hash_hmac('sha256', (string)$user['id'], getenv('SECRET_KEY_HMAC') ?: ''),
|
||||||
|
"otp_required" => !$foundByFingerprint
|
||||||
]);
|
]);
|
||||||
|
|
||||||
|
|
||||||
|
|||||||
@@ -49,7 +49,7 @@ class RegisterController extends GetxController {
|
|||||||
// Request OTP
|
// Request OTP
|
||||||
bool otpSent = await _sendOtp(phone.text);
|
bool otpSent = await _sendOtp(phone.text);
|
||||||
if (otpSent) {
|
if (otpSent) {
|
||||||
_showOtpDialog(phone.text, res['message']?['message'] ?? "تم تقديم طلبك بنجاح. يرجى انتظار موافقة الإدارة.");
|
_showOtpDialog(phone.text, res['message']?['message'] ?? 'Your request has been submitted successfully. Please wait for admin approval.'.tr);
|
||||||
} else {
|
} else {
|
||||||
mySnackbarError('Failed to send OTP'.tr);
|
mySnackbarError('Failed to send OTP'.tr);
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -25,6 +25,33 @@ class CRUD {
|
|||||||
static DateTime _lastErrorTimestamp = DateTime(2000);
|
static DateTime _lastErrorTimestamp = DateTime(2000);
|
||||||
static const Duration _errorLogDebounceDuration = Duration(minutes: 1);
|
static const Duration _errorLogDebounceDuration = Duration(minutes: 1);
|
||||||
|
|
||||||
|
// ── Country-aware phone normalizer ─────────────────────────────
|
||||||
|
static String normalizePhone(String input) {
|
||||||
|
final country = box.read(BoxName.countryCode)?.toString() ?? 'Jordan';
|
||||||
|
|
||||||
|
final clean = input.replaceAll(RegExp(r'\D+'), '');
|
||||||
|
switch (country) {
|
||||||
|
case 'Syria':
|
||||||
|
if (clean.length == 10 && clean.startsWith('09'))
|
||||||
|
return '963${clean.substring(1)}';
|
||||||
|
if (clean.length == 12 && clean.startsWith('963')) return clean;
|
||||||
|
if (clean.length == 9 && clean.startsWith('9')) return '963$clean';
|
||||||
|
return clean;
|
||||||
|
case 'Egypt':
|
||||||
|
if (clean.length == 11 && clean.startsWith('01'))
|
||||||
|
return '20${clean.substring(1)}';
|
||||||
|
if (clean.length == 13 && clean.startsWith('20')) return clean;
|
||||||
|
return clean;
|
||||||
|
case 'Jordan':
|
||||||
|
default:
|
||||||
|
if (clean.length == 10 && clean.startsWith('07'))
|
||||||
|
return '962${clean.substring(1)}';
|
||||||
|
if (clean.length == 12 && clean.startsWith('962')) return clean;
|
||||||
|
if (clean.length == 9 && clean.startsWith('7')) return '962$clean';
|
||||||
|
return clean;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
// ── JWT Validity Check (No external libs) ──────────────────────
|
// ── JWT Validity Check (No external libs) ──────────────────────
|
||||||
static bool isJwtValid(String? token) {
|
static bool isJwtValid(String? token) {
|
||||||
if (token == null || token.isEmpty) return false;
|
if (token == null || token.isEmpty) return false;
|
||||||
|
|||||||
@@ -93,7 +93,7 @@ final Map<String, String> ar_eg = {
|
|||||||
"Camera not initilaized yet": "Camera not initilaized yet",
|
"Camera not initilaized yet": "Camera not initilaized yet",
|
||||||
"Can I cancel my ride?": "Can I cancel my ride?",
|
"Can I cancel my ride?": "Can I cancel my ride?",
|
||||||
"Can we know why you want to cancel Ride ?": "Can we know why you want to cancel Ride ?",
|
"Can we know why you want to cancel Ride ?": "Can we know why you want to cancel Ride ?",
|
||||||
"Cancel": "Cancel",
|
"Cancel": "إلغاء",
|
||||||
"Cancel Ride": "Cancel Ride",
|
"Cancel Ride": "Cancel Ride",
|
||||||
"Cancel Trip": "Cancel Trip",
|
"Cancel Trip": "Cancel Trip",
|
||||||
"Canceled": "Canceled",
|
"Canceled": "Canceled",
|
||||||
@@ -142,7 +142,7 @@ final Map<String, String> ar_eg = {
|
|||||||
"Created At": "أنشئ في",
|
"Created At": "أنشئ في",
|
||||||
"created time": "created time",
|
"created time": "created time",
|
||||||
"Credit card is": "Credit card is",
|
"Credit card is": "Credit card is",
|
||||||
"Criminal Record": "Criminal Record",
|
"Criminal Record": "سجل جنائي",
|
||||||
"Cropper": "Cropper",
|
"Cropper": "Cropper",
|
||||||
"Current Location": "Current Location",
|
"Current Location": "Current Location",
|
||||||
"cyan": "سماوي",
|
"cyan": "سماوي",
|
||||||
@@ -888,4 +888,18 @@ final Map<String, String> ar_eg = {
|
|||||||
"Please enter a valid email.": "يرجى إدخال بريد إلكتروني صحيح",
|
"Please enter a valid email.": "يرجى إدخال بريد إلكتروني صحيح",
|
||||||
"Please enter a valid phone number.": "يرجى إدخال رقم هاتف صحيح",
|
"Please enter a valid phone number.": "يرجى إدخال رقم هاتف صحيح",
|
||||||
"Connection error": "خطأ في الاتصال",
|
"Connection error": "خطأ في الاتصال",
|
||||||
|
"Edit": "تعديل",
|
||||||
|
"No rides found for this number": "لا توجد رحلات لهذا الرقم",
|
||||||
|
"User not found": "لم يتم العثور على المستخدم",
|
||||||
|
"No ride found for this number": "لم يتم العثور على رحلة بهذا الرقم",
|
||||||
|
"Failed to fetch rides": "فشل في جلب الرحلات",
|
||||||
|
"Your request has been submitted successfully. Please wait for admin approval.": "تم تقديم طلبك بنجاح. يرجى انتظار موافقة الإدارة.",
|
||||||
|
"Passenger ID": "معرف الراكب",
|
||||||
|
"Review the \"لا حكم عليه\" document. Verify name matches driver.": "Review the \"لا حكم عليه\" document. Verify name matches driver.",
|
||||||
|
"Review the \"عدم محكومية\" document. Verify name matches driver.": "Review the \"عدم محكومية\" document. Verify name matches driver.",
|
||||||
|
"Review the \"فيش وتشبيه\" document. Verify name matches driver.": "Review the \"فيش وتشبيه\" document. Verify name matches driver.",
|
||||||
|
"Review the criminal record document. Verify name matches driver.": "يرجى مراجعة وثيقة السجل الجنائي. التحقق من تطابق الاسم مع السائق.",
|
||||||
|
"حفظ": "حفظ",
|
||||||
|
"Failed to load data": "فشل في تحميل البيانات",
|
||||||
|
"گازوئيل": "گازوئيل",
|
||||||
};
|
};
|
||||||
|
|||||||
@@ -93,7 +93,7 @@ final Map<String, String> ar_jo = {
|
|||||||
"Camera not initilaized yet": "Camera not initilaized yet",
|
"Camera not initilaized yet": "Camera not initilaized yet",
|
||||||
"Can I cancel my ride?": "Can I cancel my ride?",
|
"Can I cancel my ride?": "Can I cancel my ride?",
|
||||||
"Can we know why you want to cancel Ride ?": "Can we know why you want to cancel Ride ?",
|
"Can we know why you want to cancel Ride ?": "Can we know why you want to cancel Ride ?",
|
||||||
"Cancel": "Cancel",
|
"Cancel": "إلغاء",
|
||||||
"Cancel Ride": "Cancel Ride",
|
"Cancel Ride": "Cancel Ride",
|
||||||
"Cancel Trip": "Cancel Trip",
|
"Cancel Trip": "Cancel Trip",
|
||||||
"Canceled": "Canceled",
|
"Canceled": "Canceled",
|
||||||
@@ -142,7 +142,7 @@ final Map<String, String> ar_jo = {
|
|||||||
"Created At": "أنشئ في",
|
"Created At": "أنشئ في",
|
||||||
"created time": "created time",
|
"created time": "created time",
|
||||||
"Credit card is": "Credit card is",
|
"Credit card is": "Credit card is",
|
||||||
"Criminal Record": "Criminal Record",
|
"Criminal Record": "سجل جنائي",
|
||||||
"Cropper": "Cropper",
|
"Cropper": "Cropper",
|
||||||
"Current Location": "Current Location",
|
"Current Location": "Current Location",
|
||||||
"cyan": "سماوي",
|
"cyan": "سماوي",
|
||||||
@@ -888,4 +888,18 @@ final Map<String, String> ar_jo = {
|
|||||||
"Please enter a valid email.": "يرجى إدخال بريد إلكتروني صحيح",
|
"Please enter a valid email.": "يرجى إدخال بريد إلكتروني صحيح",
|
||||||
"Please enter a valid phone number.": "يرجى إدخال رقم هاتف صحيح",
|
"Please enter a valid phone number.": "يرجى إدخال رقم هاتف صحيح",
|
||||||
"Connection error": "خطأ في الاتصال",
|
"Connection error": "خطأ في الاتصال",
|
||||||
|
"Edit": "تعديل",
|
||||||
|
"No rides found for this number": "لا توجد رحلات لهذا الرقم",
|
||||||
|
"User not found": "لم يتم العثور على المستخدم",
|
||||||
|
"No ride found for this number": "لم يتم العثور على رحلة بهذا الرقم",
|
||||||
|
"Failed to fetch rides": "فشل في جلب الرحلات",
|
||||||
|
"Your request has been submitted successfully. Please wait for admin approval.": "تم تقديم طلبك بنجاح. يرجى انتظار موافقة الإدارة.",
|
||||||
|
"Passenger ID": "معرف الراكب",
|
||||||
|
"Review the \"لا حكم عليه\" document. Verify name matches driver.": "Review the \"لا حكم عليه\" document. Verify name matches driver.",
|
||||||
|
"Review the \"عدم محكومية\" document. Verify name matches driver.": "Review the \"عدم محكومية\" document. Verify name matches driver.",
|
||||||
|
"Review the \"فيش وتشبيه\" document. Verify name matches driver.": "Review the \"فيش وتشبيه\" document. Verify name matches driver.",
|
||||||
|
"Review the criminal record document. Verify name matches driver.": "يرجى مراجعة وثيقة السجل الجنائي. التحقق من تطابق الاسم مع السائق.",
|
||||||
|
"حفظ": "حفظ",
|
||||||
|
"Failed to load data": "فشل في تحميل البيانات",
|
||||||
|
"گازوئيل": "گازوئيل",
|
||||||
};
|
};
|
||||||
|
|||||||
@@ -93,7 +93,7 @@ final Map<String, String> ar_sy = {
|
|||||||
"Camera not initilaized yet": "Camera not initilaized yet",
|
"Camera not initilaized yet": "Camera not initilaized yet",
|
||||||
"Can I cancel my ride?": "Can I cancel my ride?",
|
"Can I cancel my ride?": "Can I cancel my ride?",
|
||||||
"Can we know why you want to cancel Ride ?": "Can we know why you want to cancel Ride ?",
|
"Can we know why you want to cancel Ride ?": "Can we know why you want to cancel Ride ?",
|
||||||
"Cancel": "Cancel",
|
"Cancel": "إلغاء",
|
||||||
"Cancel Ride": "Cancel Ride",
|
"Cancel Ride": "Cancel Ride",
|
||||||
"Cancel Trip": "Cancel Trip",
|
"Cancel Trip": "Cancel Trip",
|
||||||
"Canceled": "Canceled",
|
"Canceled": "Canceled",
|
||||||
@@ -142,7 +142,7 @@ final Map<String, String> ar_sy = {
|
|||||||
"Created At": "أنشئ في",
|
"Created At": "أنشئ في",
|
||||||
"created time": "created time",
|
"created time": "created time",
|
||||||
"Credit card is": "Credit card is",
|
"Credit card is": "Credit card is",
|
||||||
"Criminal Record": "Criminal Record",
|
"Criminal Record": "سجل جنائي",
|
||||||
"Cropper": "Cropper",
|
"Cropper": "Cropper",
|
||||||
"Current Location": "Current Location",
|
"Current Location": "Current Location",
|
||||||
"cyan": "سماوي",
|
"cyan": "سماوي",
|
||||||
@@ -888,4 +888,18 @@ final Map<String, String> ar_sy = {
|
|||||||
"Please enter a valid email.": "يرجى إدخال بريد إلكتروني صحيح",
|
"Please enter a valid email.": "يرجى إدخال بريد إلكتروني صحيح",
|
||||||
"Please enter a valid phone number.": "يرجى إدخال رقم هاتف صحيح",
|
"Please enter a valid phone number.": "يرجى إدخال رقم هاتف صحيح",
|
||||||
"Connection error": "خطأ في الاتصال",
|
"Connection error": "خطأ في الاتصال",
|
||||||
|
"Edit": "تعديل",
|
||||||
|
"No rides found for this number": "لا توجد رحلات لهذا الرقم",
|
||||||
|
"User not found": "لم يتم العثور على المستخدم",
|
||||||
|
"No ride found for this number": "لم يتم العثور على رحلة بهذا الرقم",
|
||||||
|
"Failed to fetch rides": "فشل في جلب الرحلات",
|
||||||
|
"Your request has been submitted successfully. Please wait for admin approval.": "تم تقديم طلبك بنجاح. يرجى انتظار موافقة الإدارة.",
|
||||||
|
"Passenger ID": "معرف الراكب",
|
||||||
|
"Review the \"لا حكم عليه\" document. Verify name matches driver.": "Review the \"لا حكم عليه\" document. Verify name matches driver.",
|
||||||
|
"Review the \"عدم محكومية\" document. Verify name matches driver.": "Review the \"عدم محكومية\" document. Verify name matches driver.",
|
||||||
|
"Review the \"فيش وتشبيه\" document. Verify name matches driver.": "Review the \"فيش وتشبيه\" document. Verify name matches driver.",
|
||||||
|
"Review the criminal record document. Verify name matches driver.": "يرجى مراجعة وثيقة السجل الجنائي. التحقق من تطابق الاسم مع السائق.",
|
||||||
|
"حفظ": "حفظ",
|
||||||
|
"Failed to load data": "فشل في تحميل البيانات",
|
||||||
|
"گازوئيل": "گازوئيل",
|
||||||
};
|
};
|
||||||
|
|||||||
@@ -62,13 +62,17 @@ class LoginController extends GetxController {
|
|||||||
await storage.write(key: 'email', value: emailStr);
|
await storage.write(key: 'email', value: emailStr);
|
||||||
await box.write(BoxName.email, emailStr);
|
await box.write(BoxName.email, emailStr);
|
||||||
|
|
||||||
// Request OTP from unified module
|
bool otpRequired = d['otp_required'] ?? true;
|
||||||
String phone = d['data']['phone'] ?? '';
|
if (otpRequired) {
|
||||||
bool otpSent = await _sendOtp(phone);
|
String phone = d['data']['phone'] ?? '';
|
||||||
if (otpSent) {
|
bool otpSent = await _sendOtp(phone);
|
||||||
_showOtpDialog(phone, pass, fingerprint, d);
|
if (otpSent) {
|
||||||
|
_showOtpDialog(phone, pass, fingerprint, d);
|
||||||
|
} else {
|
||||||
|
mySnackbarError('Failed to send OTP'.tr);
|
||||||
|
}
|
||||||
} else {
|
} else {
|
||||||
mySnackbarError('Failed to send OTP'.tr);
|
await _finalizeLogin(pass, d);
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
mySnackbarError(
|
mySnackbarError(
|
||||||
@@ -138,26 +142,7 @@ class LoginController extends GetxController {
|
|||||||
Get.back(); // close loading
|
Get.back(); // close loading
|
||||||
|
|
||||||
if (response != 'failure' && response['status'] == 'success') {
|
if (response != 'failure' && response['status'] == 'success') {
|
||||||
// Finalize login
|
await _finalizeLogin(pass, loginData);
|
||||||
final jwt = loginData['jwt'];
|
|
||||||
final hmac = loginData['hmac'];
|
|
||||||
await box.write(BoxName.jwt, c(jwt));
|
|
||||||
await storage.write(key: BoxName.jwt, value: c(jwt));
|
|
||||||
if (hmac != null) {
|
|
||||||
await box.write(BoxName.hmac, hmac);
|
|
||||||
}
|
|
||||||
|
|
||||||
var userData = loginData['data'];
|
|
||||||
await storage.write(key: 'name', value: userData['first_name']);
|
|
||||||
await storage.write(key: 'driverID', value: userData['id'].toString());
|
|
||||||
await storage.write(key: 'password', value: pass);
|
|
||||||
await box.write(BoxName.employeename, userData['first_name']);
|
|
||||||
await box.write(BoxName.password, pass);
|
|
||||||
if (userData['country'] != null) {
|
|
||||||
await box.write(BoxName.countryCode, userData['country']);
|
|
||||||
}
|
|
||||||
|
|
||||||
Get.offAll(() => Main());
|
|
||||||
} else {
|
} else {
|
||||||
mySnackbarError('Invalid OTP'.tr);
|
mySnackbarError('Invalid OTP'.tr);
|
||||||
}
|
}
|
||||||
@@ -168,6 +153,28 @@ class LoginController extends GetxController {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
Future<void> _finalizeLogin(String pass, dynamic loginData) async {
|
||||||
|
final jwt = loginData['jwt'];
|
||||||
|
final hmac = loginData['hmac'];
|
||||||
|
await box.write(BoxName.jwt, c(jwt));
|
||||||
|
await storage.write(key: BoxName.jwt, value: c(jwt));
|
||||||
|
if (hmac != null) {
|
||||||
|
await box.write(BoxName.hmac, hmac);
|
||||||
|
}
|
||||||
|
|
||||||
|
var userData = loginData['data'];
|
||||||
|
await storage.write(key: 'name', value: userData['first_name']);
|
||||||
|
await storage.write(key: 'driverID', value: userData['id'].toString());
|
||||||
|
await storage.write(key: 'password', value: pass);
|
||||||
|
await box.write(BoxName.employeename, userData['first_name']);
|
||||||
|
await box.write(BoxName.password, pass);
|
||||||
|
if (userData['country'] != null) {
|
||||||
|
await box.write(BoxName.countryCode, userData['country']);
|
||||||
|
}
|
||||||
|
|
||||||
|
Get.offAll(() => Main());
|
||||||
|
}
|
||||||
|
|
||||||
@override
|
@override
|
||||||
void onInit() async {
|
void onInit() async {
|
||||||
await EncryptionHelper.initialize();
|
await EncryptionHelper.initialize();
|
||||||
|
|||||||
@@ -28,9 +28,9 @@ class DriverTheBestAlexandria extends StatelessWidget {
|
|||||||
),
|
),
|
||||||
),
|
),
|
||||||
title: Text((driver['name_arabic']) ??
|
title: Text((driver['name_arabic']) ??
|
||||||
'Unknown Name'),
|
'Unknown Name'.tr),
|
||||||
subtitle: Text(
|
subtitle: Text(
|
||||||
'Phone: ${(driver['phone']) ?? 'N/A'}'),
|
'${'Phone'.tr}: ${(driver['phone']) ?? 'N/A'.tr}'),
|
||||||
trailing: IconButton(
|
trailing: IconButton(
|
||||||
onPressed: () async {
|
onPressed: () async {
|
||||||
Get.defaultDialog(
|
Get.defaultDialog(
|
||||||
@@ -45,8 +45,8 @@ class DriverTheBestAlexandria extends StatelessWidget {
|
|||||||
);
|
);
|
||||||
},
|
},
|
||||||
)
|
)
|
||||||
: const Center(
|
: Center(
|
||||||
child: Text('No drivers available.'),
|
child: Text('No drivers available.'.tr),
|
||||||
);
|
);
|
||||||
})
|
})
|
||||||
],
|
],
|
||||||
|
|||||||
@@ -55,9 +55,9 @@ class DriverTheBest extends StatelessWidget {
|
|||||||
),
|
),
|
||||||
),
|
),
|
||||||
title: Text((driver['name_arabic']) ??
|
title: Text((driver['name_arabic']) ??
|
||||||
'Unknown Name'),
|
'Unknown Name'.tr),
|
||||||
subtitle: Text(
|
subtitle: Text(
|
||||||
'Phone: ${(driver['phone']) ?? 'N/A'}'),
|
'${'Phone'.tr}: ${(driver['phone']) ?? 'N/A'.tr}'),
|
||||||
trailing: IconButton(
|
trailing: IconButton(
|
||||||
onPressed: () async {
|
onPressed: () async {
|
||||||
// Get.defaultDialog(
|
// Get.defaultDialog(
|
||||||
@@ -79,8 +79,8 @@ class DriverTheBest extends StatelessWidget {
|
|||||||
},
|
},
|
||||||
),
|
),
|
||||||
)
|
)
|
||||||
: const Center(
|
: Center(
|
||||||
child: Text('No drivers available.'),
|
child: Text('No drivers available.'.tr),
|
||||||
);
|
);
|
||||||
}),
|
}),
|
||||||
],
|
],
|
||||||
|
|||||||
@@ -60,11 +60,11 @@ class DriverPage extends StatelessWidget {
|
|||||||
Get.bottomSheet(
|
Get.bottomSheet(
|
||||||
CupertinoPageScaffold(
|
CupertinoPageScaffold(
|
||||||
navigationBar: CupertinoNavigationBar(
|
navigationBar: CupertinoNavigationBar(
|
||||||
middle: Text("Edit $label"),
|
middle: Text('${'Edit'.tr} $label'),
|
||||||
trailing: GestureDetector(
|
trailing: GestureDetector(
|
||||||
child: const Text(
|
child: Text(
|
||||||
"Save",
|
'Save'.tr,
|
||||||
style: TextStyle(color: CupertinoColors.activeBlue),
|
style: const TextStyle(color: CupertinoColors.activeBlue),
|
||||||
),
|
),
|
||||||
onTap: () {
|
onTap: () {
|
||||||
mainController.updateDriverField(key, controller.text);
|
mainController.updateDriverField(key, controller.text);
|
||||||
|
|||||||
@@ -28,9 +28,9 @@ class DriverTheBestGiza extends StatelessWidget {
|
|||||||
),
|
),
|
||||||
),
|
),
|
||||||
title: Text((driver['name_arabic']) ??
|
title: Text((driver['name_arabic']) ??
|
||||||
'Unknown Name'),
|
'Unknown Name'.tr),
|
||||||
subtitle: Text(
|
subtitle: Text(
|
||||||
'Phone: ${(driver['phone']) ?? 'N/A'}'),
|
'${'Phone'.tr}: ${(driver['phone']) ?? 'N/A'.tr}'),
|
||||||
trailing: IconButton(
|
trailing: IconButton(
|
||||||
onPressed: () async {
|
onPressed: () async {
|
||||||
Get.defaultDialog(
|
Get.defaultDialog(
|
||||||
@@ -45,8 +45,8 @@ class DriverTheBestGiza extends StatelessWidget {
|
|||||||
);
|
);
|
||||||
},
|
},
|
||||||
)
|
)
|
||||||
: const Center(
|
: Center(
|
||||||
child: Text('No drivers available.'),
|
child: Text('No drivers available.'.tr),
|
||||||
);
|
);
|
||||||
})
|
})
|
||||||
],
|
],
|
||||||
|
|||||||
@@ -25,7 +25,7 @@ class PassengersCantRegister extends StatelessWidget {
|
|||||||
return Padding(
|
return Padding(
|
||||||
padding: const EdgeInsets.all(8.0),
|
padding: const EdgeInsets.all(8.0),
|
||||||
child: CupertinoFormSection(
|
child: CupertinoFormSection(
|
||||||
header: Text('Passenger ID: ${passenger['id']}'),
|
header: Text('${'Passenger ID'.tr}: ${passenger['id']}'),
|
||||||
children: [
|
children: [
|
||||||
InkWell(
|
InkWell(
|
||||||
onTap: () =>
|
onTap: () =>
|
||||||
|
|||||||
@@ -28,12 +28,12 @@ class PassengersPage extends StatelessWidget {
|
|||||||
],
|
],
|
||||||
)
|
)
|
||||||
else
|
else
|
||||||
const Center(
|
Center(
|
||||||
child: Padding(
|
child: Padding(
|
||||||
padding: EdgeInsets.all(24.0),
|
padding: const EdgeInsets.all(24.0),
|
||||||
child: Text(
|
child: Text(
|
||||||
'No passenger data available.',
|
'No passenger data available.'.tr,
|
||||||
style: TextStyle(fontSize: 18, fontWeight: FontWeight.w500),
|
style: const TextStyle(fontSize: 18, fontWeight: FontWeight.w500),
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
|
|||||||
@@ -140,7 +140,7 @@ class ReviewDriverPage extends StatelessWidget {
|
|||||||
children: [
|
children: [
|
||||||
Icon(Icons.image_not_supported, size: 48, color: Colors.grey[400]),
|
Icon(Icons.image_not_supported, size: 48, color: Colors.grey[400]),
|
||||||
const SizedBox(height: 8),
|
const SizedBox(height: 8),
|
||||||
Text('No image available', style: TextStyle(color: Colors.grey[500])),
|
Text('No image available'.tr, style: TextStyle(color: Colors.grey[500])),
|
||||||
],
|
],
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
@@ -159,7 +159,7 @@ class ReviewDriverPage extends StatelessWidget {
|
|||||||
height: 180,
|
height: 180,
|
||||||
color: Colors.grey[200],
|
color: Colors.grey[200],
|
||||||
child: Center(
|
child: Center(
|
||||||
child: Text('Failed to load image',
|
child: Text('Failed to load image'.tr,
|
||||||
style: TextStyle(color: Colors.grey[500])),
|
style: TextStyle(color: Colors.grey[500])),
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
@@ -193,7 +193,7 @@ class ReviewDriverPage extends StatelessWidget {
|
|||||||
return Column(
|
return Column(
|
||||||
crossAxisAlignment: CrossAxisAlignment.start,
|
crossAxisAlignment: CrossAxisAlignment.start,
|
||||||
children: [
|
children: [
|
||||||
Text('${c.country} - Criminal Record',
|
Text('${c.country} - ${'Criminal Record'.tr}',
|
||||||
style: const TextStyle(fontSize: 18, fontWeight: FontWeight.bold)),
|
style: const TextStyle(fontSize: 18, fontWeight: FontWeight.bold)),
|
||||||
const Divider(),
|
const Divider(),
|
||||||
Card(
|
Card(
|
||||||
@@ -223,7 +223,7 @@ class ReviewDriverPage extends StatelessWidget {
|
|||||||
return Column(
|
return Column(
|
||||||
crossAxisAlignment: CrossAxisAlignment.start,
|
crossAxisAlignment: CrossAxisAlignment.start,
|
||||||
children: [
|
children: [
|
||||||
Text('Profile Photo',
|
Text('Profile Photo'.tr,
|
||||||
style: const TextStyle(fontSize: 18, fontWeight: FontWeight.bold)),
|
style: const TextStyle(fontSize: 18, fontWeight: FontWeight.bold)),
|
||||||
const Divider(),
|
const Divider(),
|
||||||
Card(
|
Card(
|
||||||
@@ -237,7 +237,7 @@ class ReviewDriverPage extends StatelessWidget {
|
|||||||
Expanded(
|
Expanded(
|
||||||
child: Text(
|
child: Text(
|
||||||
'Verify the profile photo matches the person in the ID '
|
'Verify the profile photo matches the person in the ID '
|
||||||
'and Driver License photos above.',
|
'and Driver License photos above.'.tr,
|
||||||
style: TextStyle(color: Colors.blue[800]),
|
style: TextStyle(color: Colors.blue[800]),
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
@@ -256,7 +256,7 @@ class ReviewDriverPage extends StatelessWidget {
|
|||||||
crossAxisAlignment: CrossAxisAlignment.start,
|
crossAxisAlignment: CrossAxisAlignment.start,
|
||||||
children: [
|
children: [
|
||||||
Text(
|
Text(
|
||||||
'${c.country} - ${c.tabLabels[tabKey] ?? tabKey}',
|
'${c.country} - ${(c.tabLabels[tabKey] ?? tabKey).tr}',
|
||||||
style: const TextStyle(fontSize: 18, fontWeight: FontWeight.bold),
|
style: const TextStyle(fontSize: 18, fontWeight: FontWeight.bold),
|
||||||
),
|
),
|
||||||
const Divider(),
|
const Divider(),
|
||||||
@@ -268,13 +268,13 @@ class ReviewDriverPage extends StatelessWidget {
|
|||||||
String _getCriminalRecordHint(String country) {
|
String _getCriminalRecordHint(String country) {
|
||||||
switch (country) {
|
switch (country) {
|
||||||
case 'Syria':
|
case 'Syria':
|
||||||
return 'Review the "لا حكم عليه" document. Verify name matches driver.';
|
return 'Review the "لا حكم عليه" document. Verify name matches driver.'.tr;
|
||||||
case 'Jordan':
|
case 'Jordan':
|
||||||
return 'Review the "عدم محكومية" document. Verify name matches driver.';
|
return 'Review the "عدم محكومية" document. Verify name matches driver.'.tr;
|
||||||
case 'Egypt':
|
case 'Egypt':
|
||||||
return 'Review the "فيش وتشبيه" document. Verify name matches driver.';
|
return 'Review the "فيش وتشبيه" document. Verify name matches driver.'.tr;
|
||||||
default:
|
default:
|
||||||
return 'Review the criminal record document. Verify name matches driver.';
|
return 'Review the criminal record document. Verify name matches driver.'.tr;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -481,7 +481,7 @@ class ReviewDriverPage extends StatelessWidget {
|
|||||||
const EdgeInsets.symmetric(horizontal: 12, vertical: 14),
|
const EdgeInsets.symmetric(horizontal: 12, vertical: 14),
|
||||||
),
|
),
|
||||||
items: ReviewDriverController.kFuelOptions.map((v) {
|
items: ReviewDriverController.kFuelOptions.map((v) {
|
||||||
return DropdownMenuItem(value: v, child: Text(v));
|
return DropdownMenuItem(value: v, child: Text(v.tr));
|
||||||
}).toList(),
|
}).toList(),
|
||||||
onChanged: (v) {
|
onChanged: (v) {
|
||||||
if (v != null) c.selectedFuel.value = v;
|
if (v != null) c.selectedFuel.value = v;
|
||||||
|
|||||||
@@ -55,28 +55,6 @@ class ActiveRideModel {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/// --------------------------------------------------------------------------
|
|
||||||
/// تطبيع رقم الهاتف تلقائياً حسب الدولة
|
|
||||||
/// --------------------------------------------------------------------------
|
|
||||||
String normalizePhone(String input) {
|
|
||||||
final clean = input.replaceAll(RegExp(r'\D+'), '');
|
|
||||||
// Syria
|
|
||||||
if (clean.length == 10 && clean.startsWith('09'))
|
|
||||||
return '963${clean.substring(1)}';
|
|
||||||
if (clean.length == 12 && clean.startsWith('963')) return clean;
|
|
||||||
if (clean.length == 9 && clean.startsWith('9')) return '963$clean';
|
|
||||||
// Jordan
|
|
||||||
if (clean.length == 10 && clean.startsWith('07'))
|
|
||||||
return '962${clean.substring(1)}';
|
|
||||||
if (clean.length == 12 && clean.startsWith('962')) return clean;
|
|
||||||
if (clean.length == 9 && clean.startsWith('7')) return '962$clean';
|
|
||||||
// Egypt
|
|
||||||
if (clean.length == 11 && clean.startsWith('01'))
|
|
||||||
return '20${clean.substring(1)}';
|
|
||||||
if (clean.length == 13 && clean.startsWith('20')) return clean;
|
|
||||||
return clean;
|
|
||||||
}
|
|
||||||
|
|
||||||
/// --------------------------------------------------------------------------
|
/// --------------------------------------------------------------------------
|
||||||
/// الـ Controller
|
/// الـ Controller
|
||||||
/// --------------------------------------------------------------------------
|
/// --------------------------------------------------------------------------
|
||||||
@@ -116,7 +94,7 @@ class RideMonitorServiceController extends GetxController {
|
|||||||
activeRides.clear();
|
activeRides.clear();
|
||||||
|
|
||||||
try {
|
try {
|
||||||
final normalizedPhone = normalizePhone(phone);
|
final normalizedPhone = CRUD.normalizePhone(phone);
|
||||||
|
|
||||||
// محاولة البحث أولاً عن رحلة نشطة
|
// محاولة البحث أولاً عن رحلة نشطة
|
||||||
var res = await CRUD().post(
|
var res = await CRUD().post(
|
||||||
@@ -158,15 +136,15 @@ class RideMonitorServiceController extends GetxController {
|
|||||||
hasResult.value = true;
|
hasResult.value = true;
|
||||||
} else {
|
} else {
|
||||||
hasError.value = true;
|
hasError.value = true;
|
||||||
errorMessage.value = 'لا توجد رحلات لهذا الرقم';
|
errorMessage.value = 'No rides found for this number'.tr;
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
hasError.value = true;
|
hasError.value = true;
|
||||||
errorMessage.value = 'لم يتم العثور على المستخدم';
|
errorMessage.value = 'User not found'.tr;
|
||||||
}
|
}
|
||||||
} catch (e) {
|
} catch (e) {
|
||||||
hasError.value = true;
|
hasError.value = true;
|
||||||
errorMessage.value = 'خطأ في الاتصال: $e';
|
errorMessage.value = '${'Connection error'.tr}: $e';
|
||||||
} finally {
|
} finally {
|
||||||
isLoading.value = false;
|
isLoading.value = false;
|
||||||
}
|
}
|
||||||
@@ -213,15 +191,15 @@ class RideMonitorServiceController extends GetxController {
|
|||||||
hasResult.value = true;
|
hasResult.value = true;
|
||||||
} else {
|
} else {
|
||||||
hasError.value = true;
|
hasError.value = true;
|
||||||
errorMessage.value = 'لم يتم العثور على رحلة بهذا الرقم';
|
errorMessage.value = 'No ride found for this number'.tr;
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
hasError.value = true;
|
hasError.value = true;
|
||||||
errorMessage.value = 'فشل في جلب الرحلات';
|
errorMessage.value = 'Failed to fetch rides'.tr;
|
||||||
}
|
}
|
||||||
} catch (e) {
|
} catch (e) {
|
||||||
hasError.value = true;
|
hasError.value = true;
|
||||||
errorMessage.value = 'خطأ في الاتصال: $e';
|
errorMessage.value = '${'Connection error'.tr}: $e';
|
||||||
} finally {
|
} finally {
|
||||||
isLoading.value = false;
|
isLoading.value = false;
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -24,12 +24,12 @@ class WelcomeCall extends StatelessWidget {
|
|||||||
final drivers = mainController.newDriverRegister;
|
final drivers = mainController.newDriverRegister;
|
||||||
|
|
||||||
if (drivers.isEmpty) {
|
if (drivers.isEmpty) {
|
||||||
return const Padding(
|
return Padding(
|
||||||
padding: EdgeInsets.all(32.0),
|
padding: const EdgeInsets.all(32.0),
|
||||||
child: Center(
|
child: Center(
|
||||||
child: Text(
|
child: Text(
|
||||||
'No new drivers found.',
|
'No new drivers found.'.tr,
|
||||||
style: TextStyle(fontSize: 18),
|
style: const TextStyle(fontSize: 18),
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
);
|
);
|
||||||
|
|||||||
@@ -172,7 +172,7 @@ class ReviewDriverController extends GetxController {
|
|||||||
_populateDocUrls(raw['documents']);
|
_populateDocUrls(raw['documents']);
|
||||||
}
|
}
|
||||||
} catch (e) {
|
} catch (e) {
|
||||||
mySnackbarError('Failed to load data: $e');
|
mySnackbarError('${'Failed to load data'.tr}: $e');
|
||||||
} finally {
|
} finally {
|
||||||
isLoading.value = false;
|
isLoading.value = false;
|
||||||
}
|
}
|
||||||
@@ -258,7 +258,7 @@ class ReviewDriverController extends GetxController {
|
|||||||
),
|
),
|
||||||
),
|
),
|
||||||
CupertinoButton(
|
CupertinoButton(
|
||||||
child: const Text('Done'),
|
child: Text('Done'.tr),
|
||||||
onPressed: () {
|
onPressed: () {
|
||||||
if (pickedDate != null) {
|
if (pickedDate != null) {
|
||||||
controller.text =
|
controller.text =
|
||||||
@@ -313,12 +313,12 @@ class ReviewDriverController extends GetxController {
|
|||||||
payload: payload,
|
payload: payload,
|
||||||
);
|
);
|
||||||
if (response != 'failure' && response['status'] == 'success') {
|
if (response != 'failure' && response['status'] == 'success') {
|
||||||
mySnackbarSuccess('Data saved successfully');
|
mySnackbarSuccess('Data saved successfully'.tr);
|
||||||
} else {
|
} else {
|
||||||
mySnackbarError('Failed to save changes');
|
mySnackbarError('Failed to save changes'.tr);
|
||||||
}
|
}
|
||||||
} catch (e) {
|
} catch (e) {
|
||||||
mySnackbarError('Error: $e');
|
mySnackbarError('${'Error'.tr}: $e');
|
||||||
} finally {
|
} finally {
|
||||||
isSaving.value = false;
|
isSaving.value = false;
|
||||||
}
|
}
|
||||||
@@ -333,14 +333,14 @@ class ReviewDriverController extends GetxController {
|
|||||||
payload: payload,
|
payload: payload,
|
||||||
);
|
);
|
||||||
if (response != 'failure' && response['status'] == 'success') {
|
if (response != 'failure' && response['status'] == 'success') {
|
||||||
mySnackbarSuccess('Driver activated successfully!');
|
mySnackbarSuccess('Driver activated successfully!'.tr);
|
||||||
await Future.delayed(const Duration(milliseconds: 500));
|
await Future.delayed(const Duration(milliseconds: 500));
|
||||||
Get.back();
|
Get.back();
|
||||||
} else {
|
} else {
|
||||||
mySnackbarError('Failed to activate driver');
|
mySnackbarError('Failed to activate driver'.tr);
|
||||||
}
|
}
|
||||||
} catch (e) {
|
} catch (e) {
|
||||||
mySnackbarError('Error: $e');
|
mySnackbarError('${'Error'.tr}: $e');
|
||||||
} finally {
|
} finally {
|
||||||
isSaving.value = false;
|
isSaving.value = false;
|
||||||
}
|
}
|
||||||
@@ -348,7 +348,7 @@ class ReviewDriverController extends GetxController {
|
|||||||
|
|
||||||
Future<void> rejectDriver(String reason) async {
|
Future<void> rejectDriver(String reason) async {
|
||||||
if (reason.trim().isEmpty) {
|
if (reason.trim().isEmpty) {
|
||||||
mySnackbarError('Please enter a rejection reason');
|
mySnackbarError('Please enter a rejection reason'.tr);
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
isSaving.value = true;
|
isSaving.value = true;
|
||||||
@@ -361,14 +361,14 @@ class ReviewDriverController extends GetxController {
|
|||||||
},
|
},
|
||||||
);
|
);
|
||||||
if (response != 'failure' && response['status'] == 'success') {
|
if (response != 'failure' && response['status'] == 'success') {
|
||||||
mySnackbarSuccess('Driver rejected');
|
mySnackbarSuccess('Driver rejected'.tr);
|
||||||
await Future.delayed(const Duration(milliseconds: 500));
|
await Future.delayed(const Duration(milliseconds: 500));
|
||||||
Get.back();
|
Get.back();
|
||||||
} else {
|
} else {
|
||||||
mySnackbarError('Failed to reject driver');
|
mySnackbarError('Failed to reject driver'.tr);
|
||||||
}
|
}
|
||||||
} catch (e) {
|
} catch (e) {
|
||||||
mySnackbarError('Error: $e');
|
mySnackbarError('${'Error'.tr}: $e');
|
||||||
} finally {
|
} finally {
|
||||||
isSaving.value = false;
|
isSaving.value = false;
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -160,7 +160,7 @@ class RegisterPage extends StatelessWidget {
|
|||||||
return Padding(
|
return Padding(
|
||||||
padding: const EdgeInsets.only(bottom: 8.0, right: 4.0),
|
padding: const EdgeInsets.only(bottom: 8.0, right: 4.0),
|
||||||
child: Text(
|
child: Text(
|
||||||
title,
|
title.tr,
|
||||||
style: const TextStyle(
|
style: const TextStyle(
|
||||||
fontSize: 14,
|
fontSize: 14,
|
||||||
fontWeight: FontWeight.w600,
|
fontWeight: FontWeight.w600,
|
||||||
|
|||||||
@@ -1,6 +1,7 @@
|
|||||||
import 'dart:io';
|
import 'dart:io';
|
||||||
import 'package:flutter/material.dart';
|
import 'package:flutter/material.dart';
|
||||||
import 'package:flutter/services.dart';
|
import 'package:flutter/services.dart';
|
||||||
|
import 'package:get/get.dart';
|
||||||
import 'package:vibration/vibration.dart';
|
import 'package:vibration/vibration.dart';
|
||||||
|
|
||||||
import '../../constant/box_name.dart';
|
import '../../constant/box_name.dart';
|
||||||
@@ -60,7 +61,7 @@ class MyElevatedButton extends StatelessWidget {
|
|||||||
)
|
)
|
||||||
: child ??
|
: child ??
|
||||||
Text(
|
Text(
|
||||||
title,
|
title.tr,
|
||||||
textAlign: TextAlign.center,
|
textAlign: TextAlign.center,
|
||||||
style: AppStyle.title.copyWith(color: AppColor.secondaryColor),
|
style: AppStyle.title.copyWith(color: AppColor.secondaryColor),
|
||||||
),
|
),
|
||||||
|
|||||||
@@ -31,7 +31,7 @@ class MyDialog extends GetxController {
|
|||||||
kolor: AppColor.greenColor,
|
kolor: AppColor.greenColor,
|
||||||
),
|
),
|
||||||
cancel: MyElevatedButton(
|
cancel: MyElevatedButton(
|
||||||
title: 'Cancel',
|
title: 'Cancel'.tr,
|
||||||
kolor: AppColor.redColor,
|
kolor: AppColor.redColor,
|
||||||
onPressed: () {
|
onPressed: () {
|
||||||
Get.back();
|
Get.back();
|
||||||
|
|||||||
Reference in New Issue
Block a user