add 8-tab review page, review controller, updateDriverToActive handles new fields, drivers_cant_register navigates to review

This commit is contained in:
Hamza-Ayed
2026-06-25 17:05:11 +03:00
parent d4db89f04e
commit 28b8558b6d
5 changed files with 1100 additions and 15 deletions

View File

@@ -0,0 +1,643 @@
import 'package:flutter/material.dart';
import 'package:get/get.dart';
import 'package:siro_service/constant/colors.dart';
import 'package:siro_service/controller/mainController/review_driver_controller.dart';
import 'package:siro_service/views/widgets/elevated_btn.dart';
import 'package:siro_service/views/widgets/my_scafold.dart';
class ReviewDriverPage extends StatelessWidget {
const ReviewDriverPage({super.key});
@override
Widget build(BuildContext context) {
final controller = Get.put(ReviewDriverController());
return MyScaffold(
title: 'Review Driver'.tr,
isleading: true,
body: [
Obx(() {
if (controller.isLoading.value) {
return const Center(child: CircularProgressIndicator());
}
return Column(
children: [
_buildTabBar(controller),
Expanded(child: _buildTabBarView(controller)),
_buildBottomActions(controller, context),
],
);
}),
],
);
}
Widget _buildTabBar(ReviewDriverController c) {
final keys = c.docUrls.keys.toList();
return SizedBox(
height: 64,
child: ListView.builder(
scrollDirection: Axis.horizontal,
padding: const EdgeInsets.symmetric(horizontal: 8, vertical: 6),
itemCount: keys.length,
itemBuilder: (context, index) {
final key = keys[index];
final isSelected = c.currentTabIndex.value == index;
return GestureDetector(
onTap: () => c.currentTabIndex.value = index,
child: Container(
margin: const EdgeInsets.symmetric(horizontal: 3),
padding: const EdgeInsets.symmetric(horizontal: 10, vertical: 6),
decoration: BoxDecoration(
color: isSelected
? AppColor.primaryColor
: AppColor.primaryLight,
borderRadius: BorderRadius.circular(20),
),
child: Row(
mainAxisSize: MainAxisSize.min,
children: [
Icon(
c.tabIcons[key] ?? Icons.image,
size: 16,
color: isSelected ? Colors.white : AppColor.primaryColor,
),
const SizedBox(width: 4),
Text(
c.tabLabels[key] ?? key,
style: TextStyle(
fontSize: 11,
fontWeight:
isSelected ? FontWeight.bold : FontWeight.normal,
color:
isSelected ? Colors.white : AppColor.primaryColor,
),
),
],
),
),
);
},
),
);
}
Widget _buildTabBarView(ReviewDriverController c) {
return Obx(() {
final index = c.currentTabIndex.value;
final keys = c.docUrls.keys.toList();
final key = keys[index];
return SingleChildScrollView(
padding: const EdgeInsets.all(12),
child: Column(
crossAxisAlignment: CrossAxisAlignment.stretch,
children: [
_buildDocumentImage(c.docUrls[key]!.value),
const SizedBox(height: 16),
_buildFormFieldsForTab(c, key),
],
),
);
});
}
Widget _buildDocumentImage(String url) {
if (url.isEmpty) {
return Container(
height: 180,
decoration: BoxDecoration(
color: Colors.grey[100],
borderRadius: BorderRadius.circular(12),
),
child: Center(
child: Column(
mainAxisSize: MainAxisSize.min,
children: [
Icon(Icons.image_not_supported, size: 48, color: Colors.grey[400]),
const SizedBox(height: 8),
Text('No image available', style: TextStyle(color: Colors.grey[500])),
],
),
),
);
}
return GestureDetector(
onTap: () => _showImageFullscreen(url),
child: ClipRRect(
borderRadius: BorderRadius.circular(12),
child: Image.network(
url,
height: 200,
fit: BoxFit.contain,
width: double.infinity,
errorBuilder: (_, __, ___) => Container(
height: 180,
color: Colors.grey[200],
child: Center(
child: Text('Failed to load image',
style: TextStyle(color: Colors.grey[500])),
),
),
),
),
);
}
void _showImageFullscreen(String url) {
Get.dialog(
Scaffold(
backgroundColor: Colors.black,
appBar: AppBar(
backgroundColor: Colors.black,
iconTheme: const IconThemeData(color: Colors.white),
),
body: Center(
child: InteractiveViewer(
child: Image.network(url, fit: BoxFit.contain),
),
),
),
);
}
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);
default:
return const SizedBox();
}
}
Widget _buildTextField({
required String label,
required TextEditingController controller,
IconData? icon,
TextInputType keyboardType = TextInputType.text,
VoidCallback? onTap,
String? hint,
int maxLines = 1,
}) {
return Padding(
padding: const EdgeInsets.symmetric(vertical: 6),
child: TextFormField(
controller: controller,
keyboardType: keyboardType,
maxLines: maxLines,
readOnly: onTap != null,
onTap: onTap,
decoration: InputDecoration(
labelText: label.tr,
hintText: hint,
prefixIcon: icon != null
? Icon(icon, color: AppColor.secondaryColor)
: null,
border: OutlineInputBorder(
borderRadius: BorderRadius.circular(10),
),
contentPadding:
const EdgeInsets.symmetric(horizontal: 12, vertical: 14),
),
),
);
}
Widget _buildGenderDropdown(ReviewDriverController c) {
return Padding(
padding: const EdgeInsets.symmetric(vertical: 6),
child: Obx(
() => DropdownButtonFormField<String>(
value: c.selectedGender.value.isEmpty
? null
: c.selectedGender.value,
isExpanded: true,
decoration: InputDecoration(
labelText: 'Gender'.tr,
border: OutlineInputBorder(
borderRadius: BorderRadius.circular(10),
),
contentPadding:
const EdgeInsets.symmetric(horizontal: 12, vertical: 14),
),
items: ['Male', 'Female'].map((v) {
return DropdownMenuItem(value: v, child: Text(v.tr));
}).toList(),
onChanged: (v) {
if (v != null) c.selectedGender.value = v;
},
),
),
);
}
Widget _buildColorDropdown(ReviewDriverController c) {
return Padding(
padding: const EdgeInsets.symmetric(vertical: 6),
child: Obx(
() => DropdownButtonFormField<String>(
value: c.colorHex.value.isEmpty ? null : c.colorHex.value,
isExpanded: true,
decoration: InputDecoration(
labelText: 'Car Color'.tr,
border: OutlineInputBorder(
borderRadius: BorderRadius.circular(10),
),
contentPadding:
const EdgeInsets.symmetric(horizontal: 12, vertical: 14),
),
items: ReviewDriverController.kCarColorOptions.map((opt) {
return DropdownMenuItem<String>(
value: opt['hex'],
child: Row(
children: [
Container(
width: 20,
height: 20,
decoration: BoxDecoration(
color: c.hexToColor(opt['hex']!),
shape: BoxShape.circle,
border: Border.all(color: Colors.black12),
),
),
const SizedBox(width: 10),
Text(opt['key']!.tr),
],
),
);
}).toList(),
onChanged: (hex) {
if (hex != null) c.updateColorSelection(hex);
},
),
),
);
}
Widget _buildFuelDropdown(ReviewDriverController c) {
return Padding(
padding: const EdgeInsets.symmetric(vertical: 6),
child: Obx(
() => DropdownButtonFormField<String>(
value: ReviewDriverController.kFuelOptions
.contains(c.selectedFuel.value)
? c.selectedFuel.value
: null,
isExpanded: true,
decoration: InputDecoration(
labelText: 'Fuel Type'.tr,
border: OutlineInputBorder(
borderRadius: BorderRadius.circular(10),
),
contentPadding:
const EdgeInsets.symmetric(horizontal: 12, vertical: 14),
),
items: ReviewDriverController.kFuelOptions.map((v) {
return DropdownMenuItem(value: v, child: Text(v));
}).toList(),
onChanged: (v) {
if (v != null) c.selectedFuel.value = v;
},
),
),
);
}
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;
return Container(
padding: const EdgeInsets.all(12),
decoration: BoxDecoration(
color: Colors.white,
boxShadow: [
BoxShadow(
color: Colors.black.withValues(alpha: 0.05),
blurRadius: 10,
offset: const Offset(0, -4),
),
],
),
child: Row(
children: [
Expanded(
child: MyElevatedButton(
title: 'Save'.tr,
onPressed: () { c.saveChanges(); },
kolor: AppColor.blueColor,
loading: saving,
),
),
const SizedBox(width: 8),
Expanded(
child: MyElevatedButton(
title: 'Activate'.tr,
onPressed: () { c.activateDriver(); },
kolor: AppColor.greenColor,
loading: saving,
),
),
const SizedBox(width: 8),
Expanded(
child: MyElevatedButton(
title: 'Reject'.tr,
onPressed: () { c.showRejectDialog(); },
kolor: AppColor.redColor,
loading: saving,
),
),
],
),
);
});
}
}