add country-specific field config to review page, add warning snackbar, write AI extraction prompt
This commit is contained in:
288
docs/ai_document_extraction_prompt.md
Normal file
288
docs/ai_document_extraction_prompt.md
Normal file
@@ -0,0 +1,288 @@
|
||||
# AI Document Extraction System Prompt
|
||||
|
||||
## Overview
|
||||
You are a secure AI Assistant specialized in analyzing driver identification and vehicle documents for **Syria**, **Jordan**, and **Egypt**. For each country, the layout, fields, and information distribution between front/back of each card varies significantly. Scan ALL provided images and extract fields wherever they appear.
|
||||
|
||||
---
|
||||
|
||||
## Country-Specific Document Fields
|
||||
|
||||
### 🇸🇾 Syria - Syrian Arab Republic
|
||||
|
||||
#### 1. National ID (بطاقة شخصية سورية)
|
||||
**ID Front (`id_front`):**
|
||||
- `full_name_ar` — الاسم الكامل (الاسم + اسم الأب + اسم الجد + اللقب)
|
||||
- `father_name` — اسم الأب
|
||||
- `mother_name` — اسم الأم (قد يكون على الظهر)
|
||||
- `national_number` — الرقم الوطني (11-15 رقم، أحرف لاتينية)
|
||||
- `dob` — تاريخ الولادة (YYYY-MM-DD)
|
||||
- `birth_place` — محل الولادة (المدينة/المحافظة)
|
||||
- `gender` — الجنس (ذكر/أنثى → Male/Female)
|
||||
- `civil_registry` — قيد النفوس (رقم السجل المدني)
|
||||
- `blood_type` — فصيلة الدم (إن وجدت)
|
||||
|
||||
**ID Back (`id_back`):**
|
||||
- `address` — العنوان الكامل
|
||||
- `governorate` — المحافظة
|
||||
- `id_issue_date` — تاريخ إصدار الهوية
|
||||
- `id_expiry_date` — تاريخ انتهاء الهوية
|
||||
- `issue_authority` — جهة الإصدار
|
||||
- `marital_status` — الحالة الاجتماعية (أعزب/متزوج/مطلق/أرمل)
|
||||
- `spouse_name` — اسم الزوج/الزوجة
|
||||
- `religion` — الدين (إن وجد)
|
||||
|
||||
#### 2. Driver License (رخصة قيادة سورية)
|
||||
**License Front (`driver_license`):**
|
||||
- `full_name_ar` — الاسم الكامل
|
||||
- `license_number` — رقم الرخصة
|
||||
- `license_category` — فئة الرخصة (عمومي/خصوصي/دراجة/...)
|
||||
- `license_issue_date` — تاريخ إصدار الرخصة
|
||||
- `license_expiry_date` — تاريخ انتهاء الرخصة
|
||||
- `issue_authority` — جهة الإصدار
|
||||
- `blood_type` — فصيلة الدم
|
||||
|
||||
**License Back (`driver_license_back`):**
|
||||
- `allowed_categories` — الفئات المسموح بها
|
||||
- `restrictions` — القيود (نظارة طبية/...)
|
||||
- `renewals` — تجديدات سابقة
|
||||
|
||||
#### 3. Car Registration (تسجيل مركبة سورية)
|
||||
**Registration Front (`car_license_front`):**
|
||||
- `car_plate` — رقم اللوحة كاملاً (مثال: "155186 درعا")
|
||||
- `owner` — اسم المالك
|
||||
- `make` — الماركة/الصانع
|
||||
- `model` — الموديل
|
||||
- `year` — سنة الصنع
|
||||
- `color` — اللون (بالعربية)
|
||||
- `color_hex` — كود اللون (#FFFFFF)
|
||||
- `registration_date` — تاريخ التسجيل
|
||||
|
||||
**Registration Back (`car_license_back`):**
|
||||
- `vin` — رقم الشاصي (أحرف لاتينية/أرقام فقط)
|
||||
- `fuel` — نوع الوقود (بنزين/ديزل/غاز/كهرباء)
|
||||
|
||||
#### 4. Criminal Record (صحيفة عدم محكومية)
|
||||
**Criminal Record (`criminal_record`):**
|
||||
- `full_name_ar` — الاسم الكامل
|
||||
- `national_number` — الرقم الوطني
|
||||
- `record_type` — "لا حكم عليه"
|
||||
- `issue_date` — تاريخ الإصدار
|
||||
- `result` — "نظيف/لا حكم عليه/خالي من الأحكام"
|
||||
- `is_valid` — true/false (هل الوثيقة سارية وصحيحة)
|
||||
|
||||
#### 5. Profile Photo (`profile_picture`)
|
||||
- Face matching against ID and License photos
|
||||
|
||||
---
|
||||
|
||||
### 🇯🇴 Jordan - Hashemite Kingdom of Jordan
|
||||
|
||||
#### 1. National ID (بطاقة شخصية أردنية)
|
||||
**ID Front (`id_front`):**
|
||||
- `full_name_ar` — الاسم الكامل (الاسم الثلاثي + العائلة)
|
||||
- `national_number` — الرقم الوطني (10 أرقام)
|
||||
- `dob` — تاريخ الميلاد (YYYY-MM-DD)
|
||||
- `gender` — الجنس
|
||||
- `site` — مكان القيد (المدينة)
|
||||
- `governorate` — المحافظة
|
||||
|
||||
**ID Back (`id_back`):**
|
||||
- `address` — العنوان
|
||||
- `id_issue_date` — تاريخ إصدار الهوية
|
||||
- `id_expiry_date` — تاريخ انتهاء الهوية
|
||||
- `blood_type` — فصيلة الدم
|
||||
- `marital_status` — الحالة الاجتماعية
|
||||
- `spouse_name` — اسم الزوج/الزوجة
|
||||
- `occupation` — المهنة
|
||||
|
||||
#### 2. Driver License (رخصة قيادة أردنية)
|
||||
**License Front (`driver_license`):**
|
||||
- `full_name_ar` — الاسم الكامل
|
||||
- `license_number` — رقم الرخصة
|
||||
- `license_category` — فئة الرخصة (خصوصي/عمومي/دراجة/هندسة)
|
||||
- `license_issue_date` — تاريخ الإصدار
|
||||
- `license_expiry_date` — تاريخ الانتهاء
|
||||
|
||||
**License Back (`driver_license_back`):**
|
||||
- `restrictions` — القيود
|
||||
- `violations` — المخالفات المسجلة
|
||||
|
||||
#### 3. Car Registration (تسجيل مركبة أردنية)
|
||||
**Registration Front (`car_license_front`):**
|
||||
- `car_plate` — رقم اللوحة (مثال: "123456")
|
||||
- `owner` — اسم المالك
|
||||
- `make` — الماركة
|
||||
- `model` — الموديل
|
||||
- `year` — سنة الصنع
|
||||
- `color` — اللون
|
||||
- `color_hex` — كود اللون
|
||||
- `registration_date` — تاريخ التسجيل
|
||||
|
||||
**Registration Back (`car_license_back`):**
|
||||
- `vin` — رقم الشاصي
|
||||
- `fuel` — نوع الوقود
|
||||
- `engine_capacity` — سعة المحرك (cc)
|
||||
- `car_license_expiry` — تاريخ انتهاء ترخيص المركبة
|
||||
- `passenger_capacity` — عدد الركاب (إن وجد)
|
||||
|
||||
#### 4. Criminal Record (عدم محكومية)
|
||||
**Criminal Record (`criminal_record`):**
|
||||
- `full_name_ar` — الاسم الكامل
|
||||
- `national_number` — الرقم الوطني
|
||||
- `record_type` — "عدم محكومية"
|
||||
- `issue_date` — تاريخ الإصدار
|
||||
- `result` — "خالي من السوابق/عدم محكومية"
|
||||
- `is_valid` — true/false
|
||||
|
||||
#### 5. Profile Photo (`profile_picture`)
|
||||
- Face matching against ID and License photos
|
||||
|
||||
---
|
||||
|
||||
### 🇪🇬 Egypt - Arab Republic of Egypt
|
||||
|
||||
#### 1. National ID (بطاقة شخصية مصرية)
|
||||
**ID Front (`id_front`):**
|
||||
- `full_name_ar` — الاسم الكامل (الاسم الرباعي)
|
||||
- `national_number` — الرقم القومي (14 رقم)
|
||||
- `dob` — تاريخ الميلاد
|
||||
- `gender` — الجنس
|
||||
- `governorate` — المحافظة
|
||||
|
||||
**ID Back (`id_back`):**
|
||||
- `address` — العنوان الكامل
|
||||
- `id_issue_date` — تاريخ الإصدار
|
||||
- `id_expiry_date` — تاريخ الانتهاء
|
||||
- `occupation` — المهنة
|
||||
- `marital_status` — الحالة الاجتماعية
|
||||
- `religion` — الدين
|
||||
- `blood_type` — فصيلة الدم
|
||||
- `spouse_name` — اسم الزوج/الزوجة
|
||||
- `issue_authority` — جهة الإصدار (قسم شرطة)
|
||||
|
||||
#### 2. Driver License (رخصة قيادة مصرية)
|
||||
**License Front (`driver_license`):**
|
||||
- `full_name_ar` — الاسم الكامل
|
||||
- `license_number` — رقم الترخيص
|
||||
- `license_category` — فئة الرخصة (أولى/ثانية/ثالثة/دراجة)
|
||||
- `license_issue_date` — تاريخ الإصدار
|
||||
- `license_expiry_date` — تاريخ الانتهاء
|
||||
- `issue_authority` — جهة الإصدار
|
||||
|
||||
**License Back (`driver_license_back`):**
|
||||
- `restrictions` — القيود
|
||||
- `renewals` — التجديدات
|
||||
- `violations` — المخالفات
|
||||
|
||||
#### 3. Car Registration (تسجيل مركبة مصرية)
|
||||
**Registration Front (`car_license_front`):**
|
||||
- `car_plate` — رقم اللوحة (مثال: "س ج 1234")
|
||||
- `owner` — اسم المالك
|
||||
- `make` — الماركة
|
||||
- `model` — الموديل
|
||||
- `year` — سنة الصنع
|
||||
- `color` — اللون
|
||||
- `color_hex` — كود اللون
|
||||
|
||||
**Registration Back (`car_license_back`):**
|
||||
- `vin` — رقم الشاسيه
|
||||
- `fuel` — نوع الوقود
|
||||
- `engine_capacity` — سعة المحرك
|
||||
- `car_license_expiry` — تاريخ انتهاء الترخيص
|
||||
|
||||
#### 4. Criminal Record (فيش وتشبيه)
|
||||
**Criminal Record (`criminal_record`):**
|
||||
- `full_name_ar` — الاسم الكامل
|
||||
- `national_number` — الرقم القومي
|
||||
- `record_type` — "فيش وتشبيه"
|
||||
- `issue_date` — تاريخ الإصدار
|
||||
- `result` — "سوابق / لا سوابق / فيش جنائي"
|
||||
- `case_numbers` — أرقام القضايا (إن وجدت)
|
||||
- `is_valid` — true/false
|
||||
|
||||
#### 5. Profile Photo (`profile_picture`)
|
||||
- Face matching against ID and License photos
|
||||
|
||||
---
|
||||
|
||||
## Required JSON Output Format
|
||||
|
||||
```json
|
||||
{
|
||||
"status": "success|failure",
|
||||
"reason": "If failure, state the reason",
|
||||
"face_match_confidence": "high|medium|low",
|
||||
"country": "Syria|Jordan|Egypt",
|
||||
"driver": {
|
||||
"full_name_ar": "",
|
||||
"first_name": "",
|
||||
"last_name": "",
|
||||
"father_name": "",
|
||||
"mother_name": "",
|
||||
"national_number": "",
|
||||
"dob": "YYYY-MM-DD",
|
||||
"birth_place": "",
|
||||
"address": "",
|
||||
"governorate": "",
|
||||
"site": "",
|
||||
"gender": "Male|Female",
|
||||
"civil_registry": "",
|
||||
"blood_type": "",
|
||||
"marital_status": "",
|
||||
"spouse_name": "",
|
||||
"religion": "",
|
||||
"occupation": "",
|
||||
"id_issue_date": "YYYY-MM-DD",
|
||||
"id_expiry_date": "YYYY-MM-DD",
|
||||
"license_number": "",
|
||||
"license_category": "",
|
||||
"license_issue_date": "YYYY-MM-DD",
|
||||
"license_expiry_date": "YYYY-MM-DD",
|
||||
"restrictions": "",
|
||||
"phone": "",
|
||||
"email": ""
|
||||
},
|
||||
"car": {
|
||||
"car_plate": "",
|
||||
"owner": "",
|
||||
"vin": "",
|
||||
"color": "",
|
||||
"color_hex": "",
|
||||
"make": "",
|
||||
"model": "",
|
||||
"year": "",
|
||||
"fuel": "",
|
||||
"engine_capacity": "",
|
||||
"passenger_capacity": "",
|
||||
"car_issue_date": "YYYY-MM-DD",
|
||||
"car_license_expiry": "YYYY-MM-DD",
|
||||
"registration_date": ""
|
||||
},
|
||||
"criminal_record": {
|
||||
"full_name_ar": "",
|
||||
"national_number": "",
|
||||
"record_type": "",
|
||||
"issue_date": "YYYY-MM-DD",
|
||||
"result": "",
|
||||
"case_numbers": "",
|
||||
"is_valid": true
|
||||
},
|
||||
"face_matching": {
|
||||
"profile_vs_id": "match|mismatch|unclear",
|
||||
"profile_vs_license": "match|mismatch|unclear",
|
||||
"notes": ""
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
## Rules
|
||||
1. Convert Eastern-Arabic digits (٠١٢٣٤٥٦٧٨٩) to Western digits (0-9).
|
||||
2. Dates in ISO format: `YYYY-MM-DD`.
|
||||
3. If a field is unreadable/missing → set to `null`, do NOT fail.
|
||||
4. Fail only on: face mismatch, forged/fake documents, or missing primary identity.
|
||||
5. `national_number` and `vin` must contain Latin digits/characters only.
|
||||
6. Normalize color names: "أبيض" → "White", with hex code.
|
||||
7. Return ONLY raw JSON → no markdown formatting.
|
||||
8. For Syria: expect "لا حكم عليه" for criminal record.
|
||||
9. For Jordan: expect "عدم محكومية" for criminal record.
|
||||
10. For Egypt: expect "فيش وتشبيه" for criminal record.
|
||||
@@ -91,6 +91,32 @@ class ReviewDriverPage extends StatelessWidget {
|
||||
child: Column(
|
||||
crossAxisAlignment: CrossAxisAlignment.stretch,
|
||||
children: [
|
||||
// Country indicator badge
|
||||
Container(
|
||||
margin: const EdgeInsets.only(bottom: 8),
|
||||
padding: const EdgeInsets.symmetric(horizontal: 12, vertical: 6),
|
||||
decoration: BoxDecoration(
|
||||
color: AppColor.primaryLight,
|
||||
borderRadius: BorderRadius.circular(20),
|
||||
),
|
||||
child: Row(
|
||||
mainAxisSize: MainAxisSize.min,
|
||||
children: [
|
||||
Icon(Icons.public, size: 16, color: AppColor.primaryColor),
|
||||
const SizedBox(width: 6),
|
||||
Text(
|
||||
c.country.value.isNotEmpty
|
||||
? 'Country: ${c.country.value}'
|
||||
: 'Detecting country...',
|
||||
style: TextStyle(
|
||||
fontSize: 13,
|
||||
fontWeight: FontWeight.w600,
|
||||
color: AppColor.primaryColor,
|
||||
),
|
||||
),
|
||||
],
|
||||
),
|
||||
),
|
||||
_buildDocumentImage(c.docUrls[key]!.value),
|
||||
const SizedBox(height: 16),
|
||||
_buildFormFieldsForTab(c, key),
|
||||
@@ -160,25 +186,195 @@ class ReviewDriverPage extends StatelessWidget {
|
||||
}
|
||||
|
||||
Widget _buildFormFieldsForTab(ReviewDriverController c, String tabKey) {
|
||||
switch (tabKey) {
|
||||
case 'id_front':
|
||||
return _buildIdFrontFields(c);
|
||||
case 'id_back':
|
||||
return _buildIdBackFields(c);
|
||||
case 'driver_license':
|
||||
return _buildDriverLicenseFields(c);
|
||||
case 'driver_license_back':
|
||||
return _buildLicenseBackFields(c);
|
||||
case 'car_license_front':
|
||||
return _buildCarFrontFields(c);
|
||||
case 'car_license_back':
|
||||
return _buildCarBackFields(c);
|
||||
case 'criminal_record':
|
||||
return _buildCriminalRecordFields();
|
||||
case 'profile_picture':
|
||||
return _buildProfilePhotoFields(c);
|
||||
final fields = c.getFieldsForTab(tabKey);
|
||||
|
||||
// Special layouts for specific tabs
|
||||
if (tabKey == 'criminal_record') {
|
||||
return Column(
|
||||
crossAxisAlignment: CrossAxisAlignment.start,
|
||||
children: [
|
||||
Text('${c.country.value} - Criminal Record',
|
||||
style: const TextStyle(fontSize: 18, fontWeight: FontWeight.bold)),
|
||||
const Divider(),
|
||||
Card(
|
||||
color: Colors.green[50],
|
||||
child: Padding(
|
||||
padding: const EdgeInsets.all(16),
|
||||
child: Row(
|
||||
children: [
|
||||
Icon(Icons.check_circle, color: Colors.green[700], size: 32),
|
||||
const SizedBox(width: 12),
|
||||
Expanded(
|
||||
child: Text(
|
||||
_getCriminalRecordHint(c.country.value),
|
||||
style: TextStyle(color: Colors.green[800]),
|
||||
),
|
||||
),
|
||||
],
|
||||
),
|
||||
),
|
||||
),
|
||||
...fields.map((f) => _buildFieldFromConfig(c, f)),
|
||||
],
|
||||
);
|
||||
}
|
||||
|
||||
if (tabKey == 'profile_picture') {
|
||||
return Column(
|
||||
crossAxisAlignment: CrossAxisAlignment.start,
|
||||
children: [
|
||||
Text('Profile Photo',
|
||||
style: const TextStyle(fontSize: 18, fontWeight: FontWeight.bold)),
|
||||
const Divider(),
|
||||
Card(
|
||||
color: Colors.blue[50],
|
||||
child: Padding(
|
||||
padding: const EdgeInsets.all(16),
|
||||
child: Row(
|
||||
children: [
|
||||
Icon(Icons.face, color: Colors.blue[700], size: 32),
|
||||
const SizedBox(width: 12),
|
||||
Expanded(
|
||||
child: Text(
|
||||
'Verify the profile photo matches the person in the ID '
|
||||
'and Driver License photos above.',
|
||||
style: TextStyle(color: Colors.blue[800]),
|
||||
),
|
||||
),
|
||||
],
|
||||
),
|
||||
),
|
||||
),
|
||||
const SizedBox(height: 12),
|
||||
...fields.map((f) => _buildFieldFromConfig(c, f)),
|
||||
],
|
||||
);
|
||||
}
|
||||
|
||||
// Dynamic fields for all other tabs
|
||||
return Column(
|
||||
crossAxisAlignment: CrossAxisAlignment.start,
|
||||
children: [
|
||||
Text(
|
||||
'${c.country.value} - ${c.tabLabels[tabKey] ?? tabKey}',
|
||||
style: const TextStyle(fontSize: 18, fontWeight: FontWeight.bold),
|
||||
),
|
||||
const Divider(),
|
||||
...fields.map((f) => _buildFieldFromConfig(c, f)),
|
||||
],
|
||||
);
|
||||
}
|
||||
|
||||
String _getCriminalRecordHint(String country) {
|
||||
switch (country) {
|
||||
case 'Syria':
|
||||
return 'Review the "لا حكم عليه" document. Verify name matches driver.';
|
||||
case 'Jordan':
|
||||
return 'Review the "عدم محكومية" document. Verify name matches driver.';
|
||||
case 'Egypt':
|
||||
return 'Review the "فيش وتشبيه" document. Verify name matches driver.';
|
||||
default:
|
||||
return const SizedBox();
|
||||
return 'Review the criminal record document. Verify name matches driver.';
|
||||
}
|
||||
}
|
||||
|
||||
Widget _buildFieldFromConfig(ReviewDriverController c, List<dynamic> fieldDef) {
|
||||
if (fieldDef.length < 6) return const SizedBox();
|
||||
final key = fieldDef[0] as String;
|
||||
final label = fieldDef[1] as String;
|
||||
final isDate = fieldDef[2] as bool;
|
||||
final isGender = fieldDef[3] as bool;
|
||||
final isColor = fieldDef[4] as bool;
|
||||
final isFuel = fieldDef[5] as bool;
|
||||
|
||||
if (isGender) return _buildGenderDropdown(c, label);
|
||||
if (isColor) return _buildColorDropdown(c, label);
|
||||
if (isFuel) return _buildFuelDropdown(c, label);
|
||||
|
||||
final controller = _getController(c, key);
|
||||
if (controller == null) return const SizedBox();
|
||||
|
||||
if (isDate) {
|
||||
return _buildTextField(
|
||||
label: label,
|
||||
controller: controller,
|
||||
icon: Icons.event,
|
||||
onTap: () => c.selectDate(Get.context!, controller),
|
||||
);
|
||||
}
|
||||
|
||||
// Determine icon based on common field names
|
||||
final icon = _getFieldIcon(key, label);
|
||||
|
||||
return _buildTextField(
|
||||
label: label,
|
||||
controller: controller,
|
||||
icon: icon,
|
||||
);
|
||||
}
|
||||
|
||||
IconData? _getFieldIcon(String key, String label) {
|
||||
final l = label.toLowerCase();
|
||||
if (l.contains('national')) return Icons.fingerprint;
|
||||
if (l.contains('phone')) return Icons.phone;
|
||||
if (l.contains('email')) return Icons.email;
|
||||
if (l.contains('address')) return Icons.location_on;
|
||||
if (l.contains('birth')) return Icons.cake;
|
||||
if (l.contains('father') || l.contains('mother')) return Icons.people;
|
||||
if (l.contains('license') || l.contains('category')) return Icons.card_membership;
|
||||
if (l.contains('plate')) return Icons.confirmation_number;
|
||||
if (l.contains('owner')) return Icons.person_search;
|
||||
if (l.contains('make') || l.contains('model')) return Icons.directions_car;
|
||||
if (l.contains('year')) return Icons.calendar_today;
|
||||
if (l.contains('vin')) return Icons.confirmation_number;
|
||||
if (l.contains('governorate') || l.contains('place')) return Icons.location_city;
|
||||
if (l.contains('blood')) return Icons.water_drop;
|
||||
if (l.contains('spouse') || l.contains('marital')) return Icons.people;
|
||||
if (l.contains('occupation')) return Icons.work;
|
||||
if (l.contains('restriction')) return Icons.block;
|
||||
if (l.contains('engine') || l.contains('capacity')) return Icons.settings;
|
||||
if (l.contains('first name') || l.contains('last name')) return Icons.person;
|
||||
return null;
|
||||
}
|
||||
|
||||
TextEditingController? _getController(ReviewDriverController c, String key) {
|
||||
switch (key) {
|
||||
case 'firstNameController': return c.firstNameController;
|
||||
case 'lastNameController': return c.lastNameController;
|
||||
case 'phoneController': return c.phoneController;
|
||||
case 'emailController': return c.emailController;
|
||||
case 'siteController': return c.siteController;
|
||||
case 'nationalNumberController': return c.nationalNumberController;
|
||||
case 'birthdateController': return c.birthdateController;
|
||||
case 'addressController': return c.addressController;
|
||||
case 'licenseCategoriesController': return c.licenseCategoriesController;
|
||||
case 'licenseTypeController': return c.licenseTypeController;
|
||||
case 'expiryDateController': return c.expiryDateController;
|
||||
case 'licenseIssueDateController': return c.licenseIssueDateController;
|
||||
case 'ownerController': return c.ownerController;
|
||||
case 'carPlateController': return c.carPlateController;
|
||||
case 'vinController': return c.vinController;
|
||||
case 'carLicenseExpiryDateController': return c.carLicenseExpiryDateController;
|
||||
case 'makeController': return c.makeController;
|
||||
case 'modelController': return c.modelController;
|
||||
case 'yearController': return c.yearController;
|
||||
case 'fatherNameController': return c.fatherNameController;
|
||||
case 'motherNameController': return c.motherNameController;
|
||||
case 'birthPlaceController': return c.birthPlaceController;
|
||||
case 'bloodTypeController': return c.bloodTypeController;
|
||||
case 'maritalStatusController': return c.maritalStatusController;
|
||||
case 'spouseNameController': return c.spouseNameController;
|
||||
case 'idIssueDateController': return c.idIssueDateController;
|
||||
case 'idExpiryDateController': return c.idExpiryDateController;
|
||||
case 'licenseNumberController': return c.licenseNumberController;
|
||||
case 'licenseCategoryController': return c.licenseCategoryController;
|
||||
case 'restrictionsController': return c.restrictionsController;
|
||||
case 'governorateController': return c.governorateController;
|
||||
case 'occupationController': return c.occupationController;
|
||||
case 'religionController': return c.religionController;
|
||||
case 'engineCapacityController': return c.engineCapacityController;
|
||||
case 'passengerCapacityController': return c.passengerCapacityController;
|
||||
default: return null;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -215,7 +411,7 @@ class ReviewDriverPage extends StatelessWidget {
|
||||
);
|
||||
}
|
||||
|
||||
Widget _buildGenderDropdown(ReviewDriverController c) {
|
||||
Widget _buildGenderDropdown(ReviewDriverController c, String label) {
|
||||
return Padding(
|
||||
padding: const EdgeInsets.symmetric(vertical: 6),
|
||||
child: Obx(
|
||||
@@ -225,7 +421,7 @@ class ReviewDriverPage extends StatelessWidget {
|
||||
: c.selectedGender.value,
|
||||
isExpanded: true,
|
||||
decoration: InputDecoration(
|
||||
labelText: 'Gender'.tr,
|
||||
labelText: label.tr,
|
||||
border: OutlineInputBorder(
|
||||
borderRadius: BorderRadius.circular(10),
|
||||
),
|
||||
@@ -243,7 +439,7 @@ class ReviewDriverPage extends StatelessWidget {
|
||||
);
|
||||
}
|
||||
|
||||
Widget _buildColorDropdown(ReviewDriverController c) {
|
||||
Widget _buildColorDropdown(ReviewDriverController c, String label) {
|
||||
return Padding(
|
||||
padding: const EdgeInsets.symmetric(vertical: 6),
|
||||
child: Obx(
|
||||
@@ -251,7 +447,7 @@ class ReviewDriverPage extends StatelessWidget {
|
||||
value: c.colorHex.value.isEmpty ? null : c.colorHex.value,
|
||||
isExpanded: true,
|
||||
decoration: InputDecoration(
|
||||
labelText: 'Car Color'.tr,
|
||||
labelText: label.tr,
|
||||
border: OutlineInputBorder(
|
||||
borderRadius: BorderRadius.circular(10),
|
||||
),
|
||||
@@ -286,7 +482,7 @@ class ReviewDriverPage extends StatelessWidget {
|
||||
);
|
||||
}
|
||||
|
||||
Widget _buildFuelDropdown(ReviewDriverController c) {
|
||||
Widget _buildFuelDropdown(ReviewDriverController c, String label) {
|
||||
return Padding(
|
||||
padding: const EdgeInsets.symmetric(vertical: 6),
|
||||
child: Obx(
|
||||
@@ -297,7 +493,7 @@ class ReviewDriverPage extends StatelessWidget {
|
||||
: null,
|
||||
isExpanded: true,
|
||||
decoration: InputDecoration(
|
||||
labelText: 'Fuel Type'.tr,
|
||||
labelText: label.tr,
|
||||
border: OutlineInputBorder(
|
||||
borderRadius: BorderRadius.circular(10),
|
||||
),
|
||||
@@ -315,283 +511,6 @@ class ReviewDriverPage extends StatelessWidget {
|
||||
);
|
||||
}
|
||||
|
||||
Widget _buildIdFrontFields(ReviewDriverController c) {
|
||||
return Column(
|
||||
crossAxisAlignment: CrossAxisAlignment.start,
|
||||
children: [
|
||||
Text('ID Card Information (Front)',
|
||||
style: Get.textTheme.titleMedium),
|
||||
const Divider(),
|
||||
Row(
|
||||
children: [
|
||||
Expanded(
|
||||
child: _buildTextField(
|
||||
label: 'First Name',
|
||||
controller: c.firstNameController,
|
||||
icon: Icons.person,
|
||||
),
|
||||
),
|
||||
const SizedBox(width: 8),
|
||||
Expanded(
|
||||
child: _buildTextField(
|
||||
label: 'Last Name',
|
||||
controller: c.lastNameController,
|
||||
icon: Icons.person,
|
||||
),
|
||||
),
|
||||
],
|
||||
),
|
||||
Row(
|
||||
children: [
|
||||
Expanded(
|
||||
child: _buildTextField(
|
||||
label: 'National Number',
|
||||
controller: c.nationalNumberController,
|
||||
icon: Icons.fingerprint,
|
||||
),
|
||||
),
|
||||
const SizedBox(width: 8),
|
||||
Expanded(child: _buildGenderDropdown(c)),
|
||||
],
|
||||
),
|
||||
_buildTextField(
|
||||
label: 'Birthdate',
|
||||
controller: c.birthdateController,
|
||||
icon: Icons.cake,
|
||||
onTap: () => c.selectDate(Get.context!, c.birthdateController),
|
||||
),
|
||||
],
|
||||
);
|
||||
}
|
||||
|
||||
Widget _buildIdBackFields(ReviewDriverController c) {
|
||||
return Column(
|
||||
crossAxisAlignment: CrossAxisAlignment.start,
|
||||
children: [
|
||||
Text('ID Card Information (Back)',
|
||||
style: Get.textTheme.titleMedium),
|
||||
const Divider(),
|
||||
_buildTextField(
|
||||
label: 'Address',
|
||||
controller: c.addressController,
|
||||
icon: Icons.location_on,
|
||||
maxLines: 2,
|
||||
),
|
||||
_buildTextField(
|
||||
label: 'Place of Registration',
|
||||
controller: c.siteController,
|
||||
icon: Icons.location_city,
|
||||
),
|
||||
],
|
||||
);
|
||||
}
|
||||
|
||||
Widget _buildDriverLicenseFields(ReviewDriverController c) {
|
||||
return Column(
|
||||
crossAxisAlignment: CrossAxisAlignment.start,
|
||||
children: [
|
||||
Text('Driver License Information',
|
||||
style: Get.textTheme.titleMedium),
|
||||
const Divider(),
|
||||
_buildTextField(
|
||||
label: 'License Type',
|
||||
controller: c.licenseTypeController,
|
||||
icon: Icons.card_membership,
|
||||
),
|
||||
_buildTextField(
|
||||
label: 'License Categories',
|
||||
controller: c.licenseCategoriesController,
|
||||
icon: Icons.category,
|
||||
),
|
||||
_buildTextField(
|
||||
label: 'License Issue Date',
|
||||
controller: c.licenseIssueDateController,
|
||||
icon: Icons.event,
|
||||
onTap: () =>
|
||||
c.selectDate(Get.context!, c.licenseIssueDateController),
|
||||
),
|
||||
],
|
||||
);
|
||||
}
|
||||
|
||||
Widget _buildLicenseBackFields(ReviewDriverController c) {
|
||||
return Column(
|
||||
crossAxisAlignment: CrossAxisAlignment.start,
|
||||
children: [
|
||||
Text('Driver License (Back)', style: Get.textTheme.titleMedium),
|
||||
const Divider(),
|
||||
_buildTextField(
|
||||
label: 'Expiry Date',
|
||||
controller: c.expiryDateController,
|
||||
icon: Icons.event_busy,
|
||||
onTap: () => c.selectDate(Get.context!, c.expiryDateController),
|
||||
),
|
||||
],
|
||||
);
|
||||
}
|
||||
|
||||
Widget _buildCarFrontFields(ReviewDriverController c) {
|
||||
return Column(
|
||||
crossAxisAlignment: CrossAxisAlignment.start,
|
||||
children: [
|
||||
Text('Car Registration (Front)',
|
||||
style: Get.textTheme.titleMedium),
|
||||
const Divider(),
|
||||
_buildTextField(
|
||||
label: 'Owner Name',
|
||||
controller: c.ownerController,
|
||||
icon: Icons.person_search,
|
||||
),
|
||||
Row(
|
||||
children: [
|
||||
Expanded(child: _buildColorDropdown(c)),
|
||||
const SizedBox(width: 8),
|
||||
Expanded(
|
||||
child: _buildTextField(
|
||||
label: 'Car Plate',
|
||||
controller: c.carPlateController,
|
||||
icon: Icons.confirmation_number,
|
||||
),
|
||||
),
|
||||
],
|
||||
),
|
||||
_buildTextField(
|
||||
label: 'License Issue Date',
|
||||
controller: c.licenseIssueDateController,
|
||||
icon: Icons.event,
|
||||
onTap: () =>
|
||||
c.selectDate(Get.context!, c.licenseIssueDateController),
|
||||
),
|
||||
],
|
||||
);
|
||||
}
|
||||
|
||||
Widget _buildCarBackFields(ReviewDriverController c) {
|
||||
return Column(
|
||||
crossAxisAlignment: CrossAxisAlignment.start,
|
||||
children: [
|
||||
Text('Car Registration (Back)',
|
||||
style: Get.textTheme.titleMedium),
|
||||
const Divider(),
|
||||
Row(
|
||||
children: [
|
||||
Expanded(
|
||||
child: _buildTextField(
|
||||
label: 'Make',
|
||||
controller: c.makeController,
|
||||
icon: Icons.directions_car,
|
||||
),
|
||||
),
|
||||
const SizedBox(width: 8),
|
||||
Expanded(
|
||||
child: _buildTextField(
|
||||
label: 'Model',
|
||||
controller: c.modelController,
|
||||
icon: Icons.model_training,
|
||||
),
|
||||
),
|
||||
],
|
||||
),
|
||||
Row(
|
||||
children: [
|
||||
Expanded(
|
||||
child: _buildTextField(
|
||||
label: 'Year',
|
||||
controller: c.yearController,
|
||||
keyboardType: TextInputType.number,
|
||||
),
|
||||
),
|
||||
const SizedBox(width: 8),
|
||||
Expanded(child: _buildFuelDropdown(c)),
|
||||
],
|
||||
),
|
||||
_buildTextField(
|
||||
label: 'VIN (Chassis Number)',
|
||||
controller: c.vinController,
|
||||
icon: Icons.confirmation_number,
|
||||
),
|
||||
_buildTextField(
|
||||
label: 'Expiration Date',
|
||||
controller: c.carLicenseExpiryDateController,
|
||||
icon: Icons.event_busy,
|
||||
onTap: () => c.selectDate(
|
||||
Get.context!, c.carLicenseExpiryDateController),
|
||||
),
|
||||
],
|
||||
);
|
||||
}
|
||||
|
||||
Widget _buildCriminalRecordFields() {
|
||||
return Column(
|
||||
crossAxisAlignment: CrossAxisAlignment.start,
|
||||
children: [
|
||||
Text('Criminal Record', style: Get.textTheme.titleMedium),
|
||||
const Divider(),
|
||||
Card(
|
||||
color: Colors.green[50],
|
||||
child: Padding(
|
||||
padding: const EdgeInsets.all(16),
|
||||
child: Row(
|
||||
children: [
|
||||
Icon(Icons.check_circle, color: Colors.green[700], size: 32),
|
||||
const SizedBox(width: 12),
|
||||
Expanded(
|
||||
child: Text(
|
||||
'Review the criminal record document above. '
|
||||
'Verify the name matches the driver and the record is valid.',
|
||||
style: TextStyle(color: Colors.green[800]),
|
||||
),
|
||||
),
|
||||
],
|
||||
),
|
||||
),
|
||||
),
|
||||
],
|
||||
);
|
||||
}
|
||||
|
||||
Widget _buildProfilePhotoFields(ReviewDriverController c) {
|
||||
return Column(
|
||||
crossAxisAlignment: CrossAxisAlignment.start,
|
||||
children: [
|
||||
Text('Profile Photo', style: Get.textTheme.titleMedium),
|
||||
const Divider(),
|
||||
Card(
|
||||
color: Colors.blue[50],
|
||||
child: Padding(
|
||||
padding: const EdgeInsets.all(16),
|
||||
child: Row(
|
||||
children: [
|
||||
Icon(Icons.face, color: Colors.blue[700], size: 32),
|
||||
const SizedBox(width: 12),
|
||||
Expanded(
|
||||
child: Text(
|
||||
'Verify the profile photo matches the person in the ID '
|
||||
'and Driver License photos above.',
|
||||
style: TextStyle(color: Colors.blue[800]),
|
||||
),
|
||||
),
|
||||
],
|
||||
),
|
||||
),
|
||||
),
|
||||
const SizedBox(height: 12),
|
||||
_buildTextField(
|
||||
label: 'Phone Number',
|
||||
controller: c.phoneController,
|
||||
icon: Icons.phone,
|
||||
keyboardType: TextInputType.phone,
|
||||
),
|
||||
_buildTextField(
|
||||
label: 'Email',
|
||||
controller: c.emailController,
|
||||
icon: Icons.email,
|
||||
keyboardType: TextInputType.emailAddress,
|
||||
),
|
||||
],
|
||||
);
|
||||
}
|
||||
|
||||
Widget _buildBottomActions(ReviewDriverController c, BuildContext context) {
|
||||
return Obx(() {
|
||||
final saving = c.isSaving.value;
|
||||
|
||||
@@ -6,6 +6,7 @@ import 'package:siro_service/constant/box_name.dart';
|
||||
import 'package:siro_service/constant/links.dart';
|
||||
import 'package:siro_service/controller/functions/crud.dart';
|
||||
import 'package:siro_service/main.dart';
|
||||
import 'package:siro_service/views/widgets/mycircular.dart';
|
||||
|
||||
class ReviewDriverController extends GetxController {
|
||||
var isLoading = true.obs;
|
||||
@@ -48,6 +49,189 @@ class ReviewDriverController extends GetxController {
|
||||
'profile_picture': Icons.person,
|
||||
};
|
||||
|
||||
var country = ''.obs;
|
||||
var countryDetected = false;
|
||||
|
||||
/// Get current country code from box or server data
|
||||
String get currentCountry {
|
||||
if (country.value.isNotEmpty) return country.value;
|
||||
final boxCountry = box.read('countryCode')?.toString() ?? '';
|
||||
if (boxCountry.isNotEmpty) {
|
||||
country.value = boxCountry;
|
||||
return boxCountry;
|
||||
}
|
||||
final site = serverData['site'] ?? '';
|
||||
if (site.contains('سوريا') || site.contains('دمشق') || site.contains('حلب')) {
|
||||
country.value = 'Syria';
|
||||
return 'Syria';
|
||||
}
|
||||
if (site.contains('أردن') || site.contains('عمان') || site.contains('اربد')) {
|
||||
country.value = 'Jordan';
|
||||
return 'Jordan';
|
||||
}
|
||||
country.value = 'Syria';
|
||||
return 'Syria';
|
||||
}
|
||||
|
||||
/// Field configuration: which fields appear on which tab, per country
|
||||
/// Each entry: [key, label, isDate, isGender, isColor, isFuel]
|
||||
static final Map<String, Map<String, List<List<dynamic>>>> countryFieldConfig = {
|
||||
'Syria': {
|
||||
'id_front': [
|
||||
['firstNameController', 'First Name', false, false, false, false],
|
||||
['lastNameController', 'Last Name', false, false, false, false],
|
||||
['fatherNameController', 'Father Name', false, false, false, false],
|
||||
['motherNameController', 'Mother Name', false, false, false, false],
|
||||
['nationalNumberController', 'National Number', false, false, false, false],
|
||||
['gender', 'Gender', false, true, false, false],
|
||||
['birthdateController', 'Birthdate', true, false, false, false],
|
||||
['birthPlaceController', 'Birth Place', false, false, false, false],
|
||||
['bloodTypeController', 'Blood Type', false, false, false, false],
|
||||
],
|
||||
'id_back': [
|
||||
['addressController', 'Address', false, false, false, false],
|
||||
['governorateController', 'Governorate', false, false, false, false],
|
||||
['siteController', 'Place of Registration', false, false, false, false],
|
||||
['maritalStatusController', 'Marital Status', false, false, false, false],
|
||||
['spouseNameController', 'Spouse Name', false, false, false, false],
|
||||
['idIssueDateController', 'ID Issue Date', true, false, false, false],
|
||||
['idExpiryDateController', 'ID Expiry Date', true, false, false, false],
|
||||
],
|
||||
'driver_license': [
|
||||
['licenseNumberController', 'License Number', false, false, false, false],
|
||||
['licenseCategoryController', 'License Category', false, false, false, false],
|
||||
['licenseTypeController', 'License Type', false, false, false, false],
|
||||
['licenseIssueDateController', 'Issue Date', true, false, false, false],
|
||||
['expiryDateController', 'Expiry Date', true, false, false, false],
|
||||
],
|
||||
'driver_license_back': [
|
||||
['restrictionsController', 'Restrictions', false, false, false, false],
|
||||
],
|
||||
'car_license_front': [
|
||||
['ownerController', 'Owner Name', false, false, false, false],
|
||||
['carPlateController', 'Car Plate', false, false, false, false],
|
||||
['makeController', 'Make', false, false, false, false],
|
||||
['modelController', 'Model', false, false, false, false],
|
||||
['yearController', 'Year', false, false, false, false],
|
||||
['color', 'Car Color', false, false, true, false],
|
||||
],
|
||||
'car_license_back': [
|
||||
['vinController', 'VIN / Chassis', false, false, false, false],
|
||||
['fuel', 'Fuel Type', false, false, false, true],
|
||||
['engineCapacityController', 'Engine Capacity', false, false, false, false],
|
||||
['carLicenseExpiryDateController', 'License Expiry', true, false, false, false],
|
||||
],
|
||||
'criminal_record': [],
|
||||
'profile_picture': [
|
||||
['phoneController', 'Phone Number', false, false, false, false],
|
||||
['emailController', 'Email', false, false, false, false],
|
||||
],
|
||||
},
|
||||
'Jordan': {
|
||||
'id_front': [
|
||||
['firstNameController', 'First Name', false, false, false, false],
|
||||
['lastNameController', 'Last Name', false, false, false, false],
|
||||
['nationalNumberController', 'National Number', false, false, false, false],
|
||||
['gender', 'Gender', false, true, false, false],
|
||||
['birthdateController', 'Birthdate', true, false, false, false],
|
||||
['siteController', 'Place of Registration', false, false, false, false],
|
||||
['governorateController', 'Governorate', false, false, false, false],
|
||||
],
|
||||
'id_back': [
|
||||
['addressController', 'Address', false, false, false, false],
|
||||
['idIssueDateController', 'ID Issue Date', true, false, false, false],
|
||||
['idExpiryDateController', 'ID Expiry Date', true, false, false, false],
|
||||
['bloodTypeController', 'Blood Type', false, false, false, false],
|
||||
['maritalStatusController', 'Marital Status', false, false, false, false],
|
||||
['spouseNameController', 'Spouse Name', false, false, false, false],
|
||||
['occupationController', 'Occupation', false, false, false, false],
|
||||
],
|
||||
'driver_license': [
|
||||
['licenseNumberController', 'License Number', false, false, false, false],
|
||||
['licenseCategoryController', 'License Category', false, false, false, false],
|
||||
['licenseIssueDateController', 'Issue Date', true, false, false, false],
|
||||
['expiryDateController', 'Expiry Date', true, false, false, false],
|
||||
],
|
||||
'driver_license_back': [
|
||||
['restrictionsController', 'Restrictions', false, false, false, false],
|
||||
],
|
||||
'car_license_front': [
|
||||
['ownerController', 'Owner Name', false, false, false, false],
|
||||
['carPlateController', 'Car Plate', false, false, false, false],
|
||||
['makeController', 'Make', false, false, false, false],
|
||||
['modelController', 'Model', false, false, false, false],
|
||||
['yearController', 'Year', false, false, false, false],
|
||||
['color', 'Car Color', false, false, true, false],
|
||||
],
|
||||
'car_license_back': [
|
||||
['vinController', 'VIN / Chassis', false, false, false, false],
|
||||
['fuel', 'Fuel Type', false, false, false, true],
|
||||
['engineCapacityController', 'Engine Capacity', false, false, false, false],
|
||||
['passengerCapacityController', 'Passenger Capacity', false, false, false, false],
|
||||
['carLicenseExpiryDateController', 'License Expiry', true, false, false, false],
|
||||
],
|
||||
'criminal_record': [],
|
||||
'profile_picture': [
|
||||
['phoneController', 'Phone Number', false, false, false, false],
|
||||
['emailController', 'Email', false, false, false, false],
|
||||
],
|
||||
},
|
||||
'Egypt': {
|
||||
'id_front': [
|
||||
['firstNameController', 'First Name', false, false, false, false],
|
||||
['lastNameController', 'Last Name', false, false, false, false],
|
||||
['nationalNumberController', 'National Number', false, false, false, false],
|
||||
['gender', 'Gender', false, true, false, false],
|
||||
['birthdateController', 'Birthdate', true, false, false, false],
|
||||
['governorateController', 'Governorate', false, false, false, false],
|
||||
],
|
||||
'id_back': [
|
||||
['addressController', 'Address', false, false, false, false],
|
||||
['idIssueDateController', 'ID Issue Date', true, false, false, false],
|
||||
['idExpiryDateController', 'ID Expiry Date', true, false, false, false],
|
||||
['occupationController', 'Occupation', false, false, false, false],
|
||||
['maritalStatusController', 'Marital Status', false, false, false, false],
|
||||
['religionController', 'Religion', false, false, false, false],
|
||||
['bloodTypeController', 'Blood Type', false, false, false, false],
|
||||
['spouseNameController', 'Spouse Name', false, false, false, false],
|
||||
],
|
||||
'driver_license': [
|
||||
['licenseNumberController', 'License Number', false, false, false, false],
|
||||
['licenseCategoryController', 'License Category', false, false, false, false],
|
||||
['licenseIssueDateController', 'Issue Date', true, false, false, false],
|
||||
['expiryDateController', 'Expiry Date', true, false, false, false],
|
||||
],
|
||||
'driver_license_back': [
|
||||
['restrictionsController', 'Restrictions', false, false, false, false],
|
||||
],
|
||||
'car_license_front': [
|
||||
['ownerController', 'Owner Name', false, false, false, false],
|
||||
['carPlateController', 'Car Plate', false, false, false, false],
|
||||
['makeController', 'Make', false, false, false, false],
|
||||
['modelController', 'Model', false, false, false, false],
|
||||
['yearController', 'Year', false, false, false, false],
|
||||
['color', 'Car Color', false, false, true, false],
|
||||
],
|
||||
'car_license_back': [
|
||||
['vinController', 'VIN / Chassis', false, false, false, false],
|
||||
['fuel', 'Fuel Type', false, false, false, true],
|
||||
['engineCapacityController', 'Engine Capacity', false, false, false, false],
|
||||
['carLicenseExpiryDateController', 'License Expiry', true, false, false, false],
|
||||
],
|
||||
'criminal_record': [],
|
||||
'profile_picture': [
|
||||
['phoneController', 'Phone Number', false, false, false, false],
|
||||
['emailController', 'Email', false, false, false, false],
|
||||
],
|
||||
},
|
||||
};
|
||||
|
||||
List<List<dynamic>> getFieldsForTab(String tabKey) {
|
||||
final c = currentCountry;
|
||||
final config = countryFieldConfig[c] ?? countryFieldConfig['Syria']!;
|
||||
return config[tabKey] ?? [];
|
||||
}
|
||||
|
||||
late TextEditingController firstNameController;
|
||||
late TextEditingController lastNameController;
|
||||
late TextEditingController phoneController;
|
||||
@@ -67,6 +251,22 @@ class ReviewDriverController extends GetxController {
|
||||
late TextEditingController makeController;
|
||||
late TextEditingController modelController;
|
||||
late TextEditingController yearController;
|
||||
late TextEditingController fatherNameController;
|
||||
late TextEditingController motherNameController;
|
||||
late TextEditingController birthPlaceController;
|
||||
late TextEditingController bloodTypeController;
|
||||
late TextEditingController maritalStatusController;
|
||||
late TextEditingController spouseNameController;
|
||||
late TextEditingController idIssueDateController;
|
||||
late TextEditingController idExpiryDateController;
|
||||
late TextEditingController licenseNumberController;
|
||||
late TextEditingController licenseCategoryController;
|
||||
late TextEditingController restrictionsController;
|
||||
late TextEditingController governorateController;
|
||||
late TextEditingController occupationController;
|
||||
late TextEditingController religionController;
|
||||
late TextEditingController engineCapacityController;
|
||||
late TextEditingController passengerCapacityController;
|
||||
|
||||
var selectedGender = ''.obs;
|
||||
var colorHex = ''.obs;
|
||||
@@ -105,6 +305,22 @@ class ReviewDriverController extends GetxController {
|
||||
makeController = TextEditingController();
|
||||
modelController = TextEditingController();
|
||||
yearController = TextEditingController();
|
||||
fatherNameController = TextEditingController();
|
||||
motherNameController = TextEditingController();
|
||||
birthPlaceController = TextEditingController();
|
||||
bloodTypeController = TextEditingController();
|
||||
maritalStatusController = TextEditingController();
|
||||
spouseNameController = TextEditingController();
|
||||
idIssueDateController = TextEditingController();
|
||||
idExpiryDateController = TextEditingController();
|
||||
licenseNumberController = TextEditingController();
|
||||
licenseCategoryController = TextEditingController();
|
||||
restrictionsController = TextEditingController();
|
||||
governorateController = TextEditingController();
|
||||
occupationController = TextEditingController();
|
||||
religionController = TextEditingController();
|
||||
engineCapacityController = TextEditingController();
|
||||
passengerCapacityController = TextEditingController();
|
||||
}
|
||||
|
||||
Future<void> fetchData() async {
|
||||
@@ -124,7 +340,7 @@ class ReviewDriverController extends GetxController {
|
||||
_populateDocUrls(raw['documents']);
|
||||
}
|
||||
} catch (e) {
|
||||
Get.snackbar('Error', 'Failed to load data: $e');
|
||||
mySnackeBarError('Failed to load data: $e');
|
||||
} finally {
|
||||
isLoading.value = false;
|
||||
}
|
||||
@@ -168,6 +384,39 @@ class ReviewDriverController extends GetxController {
|
||||
modelController.text = serverData['model'] ?? '';
|
||||
yearController.text = serverData['year'] ?? '';
|
||||
selectedFuel.value = serverData['fuel'] ?? '';
|
||||
fatherNameController.text = serverData['father_name'] ?? '';
|
||||
motherNameController.text = serverData['mother_name'] ?? '';
|
||||
birthPlaceController.text = serverData['birth_place'] ?? '';
|
||||
bloodTypeController.text = serverData['blood_type'] ?? '';
|
||||
maritalStatusController.text = serverData['marital_status'] ?? '';
|
||||
spouseNameController.text = serverData['spouse_name'] ?? '';
|
||||
idIssueDateController.text = serverData['id_issue_date'] ?? '';
|
||||
idExpiryDateController.text = serverData['id_expiry_date'] ?? '';
|
||||
licenseNumberController.text = serverData['license_number'] ?? '';
|
||||
licenseCategoryController.text = serverData['license_category'] ?? '';
|
||||
restrictionsController.text = serverData['restrictions'] ?? '';
|
||||
governorateController.text = serverData['governorate'] ?? '';
|
||||
occupationController.text = serverData['occupation'] ?? '';
|
||||
religionController.text = serverData['religion'] ?? '';
|
||||
engineCapacityController.text = serverData['engine_capacity'] ?? '';
|
||||
passengerCapacityController.text = serverData['passenger_capacity'] ?? '';
|
||||
|
||||
// Detect country from server data if available
|
||||
if (!countryDetected) {
|
||||
countryDetected = true;
|
||||
if (serverData['country']?.isNotEmpty == true) {
|
||||
country.value = serverData['country']!;
|
||||
} else if (serverData['site']?.isNotEmpty == true) {
|
||||
final s = serverData['site']!;
|
||||
if (s.contains('سوريا') || s.contains('دمشق')) {
|
||||
country.value = 'Syria';
|
||||
} else if (s.contains('أردن') || s.contains('عمان')) {
|
||||
country.value = 'Jordan';
|
||||
} else if (s.contains('مصر') || s.contains('القاهرة')) {
|
||||
country.value = 'Egypt';
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
final serverColorName = serverData['color'] ?? '';
|
||||
final colorOption = kCarColorOptions.firstWhere(
|
||||
@@ -264,12 +513,12 @@ class ReviewDriverController extends GetxController {
|
||||
payload: payload,
|
||||
);
|
||||
if (response != 'failure' && response['status'] == 'success') {
|
||||
Get.snackbar('Success', 'Data saved successfully');
|
||||
mySnackbarSuccess('Data saved successfully');
|
||||
} else {
|
||||
Get.snackbar('Error', 'Failed to save changes');
|
||||
mySnackeBarError('Failed to save changes');
|
||||
}
|
||||
} catch (e) {
|
||||
Get.snackbar('Error', 'Error: $e');
|
||||
mySnackeBarError('Error: $e');
|
||||
} finally {
|
||||
isSaving.value = false;
|
||||
}
|
||||
@@ -284,14 +533,14 @@ class ReviewDriverController extends GetxController {
|
||||
payload: payload,
|
||||
);
|
||||
if (response != 'failure' && response['status'] == 'success') {
|
||||
Get.snackbar('Success', 'Driver activated successfully!');
|
||||
mySnackbarSuccess('Driver activated successfully!');
|
||||
await Future.delayed(const Duration(milliseconds: 500));
|
||||
Get.back();
|
||||
} else {
|
||||
Get.snackbar('Error', 'Failed to activate driver');
|
||||
mySnackeBarError('Failed to activate driver');
|
||||
}
|
||||
} catch (e) {
|
||||
Get.snackbar('Error', 'Error: $e');
|
||||
mySnackeBarError('Error: $e');
|
||||
} finally {
|
||||
isSaving.value = false;
|
||||
}
|
||||
@@ -299,7 +548,7 @@ class ReviewDriverController extends GetxController {
|
||||
|
||||
Future<void> rejectDriver(String reason) async {
|
||||
if (reason.trim().isEmpty) {
|
||||
Get.snackbar('Error', 'Please enter a rejection reason');
|
||||
mySnackeBarError('Please enter a rejection reason');
|
||||
return;
|
||||
}
|
||||
isSaving.value = true;
|
||||
@@ -312,14 +561,14 @@ class ReviewDriverController extends GetxController {
|
||||
},
|
||||
);
|
||||
if (response != 'failure' && response['status'] == 'success') {
|
||||
Get.snackbar('Success', 'Driver rejected');
|
||||
mySnackbarSuccess('Driver rejected');
|
||||
await Future.delayed(const Duration(milliseconds: 500));
|
||||
Get.back();
|
||||
} else {
|
||||
Get.snackbar('Error', 'Failed to reject driver');
|
||||
mySnackeBarError('Failed to reject driver');
|
||||
}
|
||||
} catch (e) {
|
||||
Get.snackbar('Error', 'Error: $e');
|
||||
mySnackeBarError('Error: $e');
|
||||
} finally {
|
||||
isSaving.value = false;
|
||||
}
|
||||
@@ -414,6 +663,22 @@ class ReviewDriverController extends GetxController {
|
||||
makeController.dispose();
|
||||
modelController.dispose();
|
||||
yearController.dispose();
|
||||
fatherNameController.dispose();
|
||||
motherNameController.dispose();
|
||||
birthPlaceController.dispose();
|
||||
bloodTypeController.dispose();
|
||||
maritalStatusController.dispose();
|
||||
spouseNameController.dispose();
|
||||
idIssueDateController.dispose();
|
||||
idExpiryDateController.dispose();
|
||||
licenseNumberController.dispose();
|
||||
licenseCategoryController.dispose();
|
||||
restrictionsController.dispose();
|
||||
governorateController.dispose();
|
||||
occupationController.dispose();
|
||||
religionController.dispose();
|
||||
engineCapacityController.dispose();
|
||||
passengerCapacityController.dispose();
|
||||
super.onClose();
|
||||
}
|
||||
}
|
||||
|
||||
@@ -107,6 +107,57 @@ SnackbarController mySnackeBarError(String message) {
|
||||
);
|
||||
}
|
||||
|
||||
SnackbarController mySnackbarWarning(String message) {
|
||||
HapticFeedback.mediumImpact();
|
||||
|
||||
return Get.snackbar(
|
||||
'Warning'.tr,
|
||||
message,
|
||||
backgroundColor: AppColor.yellowColor.withOpacity(0.95),
|
||||
colorText: AppColor.writeColor,
|
||||
icon: const Icon(
|
||||
Icons.warning_amber_rounded,
|
||||
color: AppColor.writeColor,
|
||||
size: 28,
|
||||
),
|
||||
shouldIconPulse: true,
|
||||
snackPosition: SnackPosition.TOP,
|
||||
margin: SnackbarConfig.margin,
|
||||
borderRadius: SnackbarConfig.borderRadius,
|
||||
duration: SnackbarConfig.duration,
|
||||
animationDuration: SnackbarConfig.animationDuration,
|
||||
forwardAnimationCurve: Curves.easeOutCirc,
|
||||
reverseAnimationCurve: Curves.easeInCirc,
|
||||
boxShadows: [SnackbarConfig.shadow],
|
||||
padding: const EdgeInsets.symmetric(horizontal: 20, vertical: 12),
|
||||
titleText: Text(
|
||||
'Warning'.tr,
|
||||
style: const TextStyle(
|
||||
fontWeight: FontWeight.w700,
|
||||
color: Colors.black87,
|
||||
fontSize: 16,
|
||||
letterSpacing: 0.2,
|
||||
),
|
||||
),
|
||||
messageText: Text(
|
||||
message,
|
||||
style: const TextStyle(
|
||||
color: Colors.black87,
|
||||
fontSize: 14,
|
||||
height: 1.3,
|
||||
),
|
||||
),
|
||||
onTap: (_) {
|
||||
HapticFeedback.lightImpact();
|
||||
Get.closeCurrentSnackbar();
|
||||
},
|
||||
isDismissible: true,
|
||||
dismissDirection: DismissDirection.horizontal,
|
||||
overlayBlur: 0.8,
|
||||
overlayColor: Colors.black12,
|
||||
);
|
||||
}
|
||||
|
||||
SnackbarController mySnackbarSuccess(String message) {
|
||||
// Trigger success haptic feedback
|
||||
HapticFeedback.lightImpact();
|
||||
|
||||
Reference in New Issue
Block a user