add country-specific field config to review page, add warning snackbar, write AI extraction prompt
This commit is contained in:
@@ -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;
|
||||
|
||||
Reference in New Issue
Block a user