Update: 2026-06-26 02:00:23

This commit is contained in:
Hamza-Ayed
2026-06-26 02:00:23 +03:00
parent bd3ba7ecd7
commit 21f5105fa1
20 changed files with 175 additions and 114 deletions

View File

@@ -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
]); ]);

View File

@@ -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);
} }

View File

@@ -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;

View File

@@ -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": "فشل في تحميل البيانات",
"گازوئيل": "گازوئيل",
}; };

View File

@@ -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": "فشل في تحميل البيانات",
"گازوئيل": "گازوئيل",
}; };

View File

@@ -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": "فشل في تحميل البيانات",
"گازوئيل": "گازوئيل",
}; };

View File

@@ -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();

View File

@@ -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),
); );
}) })
], ],

View File

@@ -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),
); );
}), }),
], ],

View File

@@ -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);

View File

@@ -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),
); );
}) })
], ],

View File

@@ -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: () =>

View File

@@ -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),
), ),
), ),
), ),

View File

@@ -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;

View File

@@ -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;
} }

View File

@@ -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),
), ),
), ),
); );

View File

@@ -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;
} }

View File

@@ -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,

View File

@@ -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),
), ),

View File

@@ -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();