Files
intaleq_driver/lib/controller/auth/syria/registration_controller.dart
Hamza-Ayed 3c0ae4cf2f 26-1-20/1
2026-01-20 10:11:10 +03:00

771 lines
28 KiB
Dart
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
import 'dart:convert';
import 'dart:io';
import 'package:flutter/material.dart';
import 'package:flutter_image_compress/flutter_image_compress.dart';
import 'package:get/get.dart';
import 'package:image_picker/image_picker.dart';
import 'package:image_cropper/image_cropper.dart';
import 'package:image/image.dart' as img;
import 'package:path/path.dart';
import 'package:sefer_driver/constant/links.dart';
import 'package:sefer_driver/controller/firebase/notification_service.dart';
import '../../../constant/box_name.dart';
import 'package:path_provider/path_provider.dart';
// --- Final Submission ---
import 'package:path_provider/path_provider.dart' as path_provider;
import 'package:http/http.dart' as http;
import 'package:http_parser/http_parser.dart';
import 'package:mime/mime.dart';
import 'package:path/path.dart' as p;
import '../../../constant/colors.dart';
import '../../../constant/info.dart';
import '../../../main.dart';
import '../../../print.dart';
import '../../../views/widgets/error_snakbar.dart';
import '../../functions/crud.dart';
import '../../functions/encrypt_decrypt.dart';
import '../../functions/package_info.dart';
import '../captin/login_captin_controller.dart';
// You can create a simple enum to manage image types
enum ImageType {
driverLicenseFront,
driverLicenseBack,
carLicenseFront,
carLicenseBack,
}
class RegistrationController extends GetxController {
// Page Controller for managing steps
late PageController pageController;
var currentPage =
0.obs; // Use .obs for reactive updates on the step indicator
// Loading state
var isLoading = false.obs;
var isloading = false;
CroppedFile? croppedFile;
final picker = ImagePicker();
var image;
File? myImage;
String? colorHex; // سيُملى من الدروب داون
// Form Keys for validation
final driverInfoFormKey = GlobalKey<FormState>();
final carInfoFormKey = GlobalKey<FormState>();
// STEP 1: Driver Information Controllers
final firstNameController = TextEditingController();
final lastNameController = TextEditingController();
final nationalIdController = TextEditingController();
final bithdateController = TextEditingController();
final phoneController = TextEditingController(); // You can pre-fill this
final driverLicenseExpiryController = TextEditingController();
DateTime? driverLicenseExpiryDate;
// STEP 2: Car Information Controllers
final carPlateController = TextEditingController();
final carMakeController = TextEditingController();
final carModelController = TextEditingController();
final carYearController = TextEditingController();
final carColorController = TextEditingController();
final carVinController = TextEditingController(); // Chassis number
final carRegistrationExpiryController = TextEditingController();
DateTime? carRegistrationExpiryDate;
// داخل RegistrationController
// المتغيرات لتخزين القيم المختارة (لإرسالها للـ API لاحقاً)
int? selectedVehicleCategoryId; // سيخزن 1 أو 2 أو 3
int? selectedFuelTypeId; // سيخزن 1 أو 2 أو 3 أو 4
// قائمة أنواع المركبات (مطابقة لقاعدة البيانات)
final List<Map<String, dynamic>> vehicleCategoryOptions = [
{'id': 1, 'name': 'Car'.tr}, // ترجمة: سيارة
{'id': 2, 'name': 'Motorcycle'.tr}, // ترجمة: دراجة نارية
{'id': 3, 'name': 'Van / Bus'.tr}, // ترجمة: فان / باص
];
// قائمة أنواع الوقود
final List<Map<String, dynamic>> fuelTypeOptions = [
{'id': 1, 'name': 'Petrol'.tr}, // ترجمة: بنزين
{'id': 2, 'name': 'Diesel'.tr}, // ترجمة: ديزل
{'id': 3, 'name': 'Electric'.tr}, // ترجمة: كهربائي
{'id': 4, 'name': 'Hybrid'.tr}, // ترجمة: هايبرد
];
// STEP 3: Document Uploads
File? driverLicenseFrontImage;
File? driverLicenseBackImage;
File? carLicenseFrontImage;
File? carLicenseBackImage;
@override
void onInit() {
super.onInit();
pageController = PageController();
// Pre-fill phone number if it exists in storage
// phoneController.text = box.read(BoxName.phoneDriver) ?? '';
}
@override
void onClose() {
pageController.dispose();
// Dispose all other text controllers
super.onClose();
}
// --- Page Navigation ---
void goToNextStep() {
bool isValid = false;
if (currentPage.value == 0) {
// Validate Step 1
isValid = driverInfoFormKey.currentState!.validate();
if (isValid) {
// Optional: Check if license is expired
// if (driverLicenseExpiryDate != null &&
// driverLicenseExpiryDate!.isBefore(DateTime.now())) {
// Get.snackbar('Expired License', 'Your drivers license has expired.'.tr
// ,
// snackPosition: SnackPosition.BOTTOM,
// backgroundColor: Colors.red,
// colorText: Colors.white);
// return; // Stop progression
// }
}
} else if (currentPage.value == 1) {
// Validate Step 2
isValid = carInfoFormKey.currentState!.validate();
}
if (isValid) {
pageController.nextPage(
duration: const Duration(milliseconds: 300),
curve: Curves.easeIn,
);
}
}
void goToPreviousStep() {
pageController.previousPage(
duration: const Duration(milliseconds: 300),
curve: Curves.easeIn,
);
}
// --- Image Picking ---
Future<void> pickImage(ImageType type) async {
try {
final picker = ImagePicker();
final picked = await picker.pickImage(
source: ImageSource.camera,
imageQuality: 95, // جودة أولية من الكاميرا
maxWidth: 3000, // نسمح بصورة كبيرة ثم نصغّر نحن
);
if (picked == null) return;
// قصّ الصورة
final cropped = await ImageCropper().cropImage(
sourcePath: picked.path,
uiSettings: [
AndroidUiSettings(
toolbarTitle: 'Cropper'.tr,
toolbarColor: AppColor.accentColor,
toolbarWidgetColor: AppColor.redColor,
initAspectRatio: CropAspectRatioPreset.original,
lockAspectRatio: false,
),
IOSUiSettings(title: 'Cropper'.tr),
],
);
if (cropped == null) return; // المستخدم ألغى
// قراءة bytes + التصحيح حسب EXIF ثم التصغير
final rawBytes = await File(cropped.path).readAsBytes();
final decoded = img.decodeImage(rawBytes);
if (decoded == null) throw Exception('Decode image failed');
// تصحيح اتجاه الصورة (EXIF)
final fixed = img.bakeOrientation(decoded);
// تصغير لعرض 800px (عدّل عند الحاجة)
final resized = img.copyResize(fixed, width: 800);
// حفظ مؤقت بصيغة JPG
final tmpDir = await getTemporaryDirectory();
final outPath =
'${tmpDir.path}/doc_${DateTime.now().millisecondsSinceEpoch}.jpg';
final outFile = File(outPath);
await outFile.writeAsBytes(img.encodeJpg(resized, quality: 85));
// عيّن الملف في المتغير الصحيح حسب النوع
if (outFile != null) {
switch (type) {
case ImageType.driverLicenseFront:
driverLicenseFrontImage = File(outFile.path);
break;
case ImageType.driverLicenseBack:
driverLicenseBackImage = File(outFile.path);
break;
case ImageType.carLicenseFront:
carLicenseFrontImage = File(outFile.path);
break;
case ImageType.carLicenseBack:
carLicenseBackImage = File(outFile.path);
break;
}
update(); // Use update() to refresh the GetBuilder UI
}
update(); // لتحديث الـ UI
// // الإرسال للذكاء الاصطناعي
// await sendToAI(type, imageFile: outFile);
} catch (e) {
Get.snackbar('Error'.tr, '${'An unexpected error occurred:'.tr} $e');
}
}
// ثابت: 20 لون سيارة شائع
static const List<Map<String, String>> kCarColorOptions = [
{'key': 'color.white', 'hex': '#FFFFFF'},
{'key': 'color.black', 'hex': '#000000'},
{'key': 'color.silver', 'hex': '#C0C0C0'},
{'key': 'color.gray', 'hex': '#808080'},
{'key': 'color.gunmetal', 'hex': '#2A3439'},
{'key': 'color.red', 'hex': '#C62828'},
{'key': 'color.blue', 'hex': '#1565C0'},
{'key': 'color.navy', 'hex': '#0D47A1'},
{'key': 'color.green', 'hex': '#2E7D32'},
{'key': 'color.darkGreen', 'hex': '#1B5E20'},
{'key': 'color.beige', 'hex': '#D7CCC8'},
{'key': 'color.brown', 'hex': '#5D4037'},
{'key': 'color.maroon', 'hex': '#800000'},
{'key': 'color.burgundy', 'hex': '#800020'},
{'key': 'color.yellow', 'hex': '#F9A825'},
{'key': 'color.orange', 'hex': '#EF6C00'},
{'key': 'color.gold', 'hex': '#D4AF37'},
{'key': 'color.bronze', 'hex': '#CD7F32'},
{'key': 'color.champagne', 'hex': '#EFE1C6'},
{'key': 'color.purple', 'hex': '#6A1B9A'},
];
Color hexToColor(String hex) {
var v = hex.replaceAll('#', '');
if (v.length == 6) v = 'FF$v';
return Color(int.parse(v, radix: 16));
}
//uploadSyrianDocs
// دالة مساعدة: تضيف الحقل إذا كان له قيمة
void _addField(Map<String, String> fields, String key, String? value) {
if (value != null && value.toString().isNotEmpty) {
fields[key] = value.toString();
}
}
/// خريطة لتخزين روابط المستندات بعد الرفع
final Map<String, String> docUrls = {
'driver_license_front': '',
'driver_license_back': '',
'car_license_front': '',
'car_license_back': '',
};
/// التصرّف العام لاختيار/قص/ضغط/رفع الصورة حسب type
Future<void> choosImage(String link, String imageType) async {
try {
final pickedImage = await picker.pickImage(
source: ImageSource.camera,
preferredCameraDevice: CameraDevice.rear,
);
if (pickedImage == null) return;
image = File(pickedImage.path);
final croppedFile = await ImageCropper().cropImage(
sourcePath: image!.path,
uiSettings: [
AndroidUiSettings(
toolbarTitle: 'Cropper'.tr,
toolbarColor: AppColor.blueColor,
toolbarWidgetColor: AppColor.yellowColor,
initAspectRatio: CropAspectRatioPreset.original,
lockAspectRatio: false,
),
IOSUiSettings(title: 'Cropper'.tr),
],
);
if (croppedFile == null) return;
// صورة للمعاينة داخل التطبيق
myImage = File(croppedFile.path);
isloading = true;
update();
// ضغط (وأيضاً يمكنك إضافة rotateImageIfNeeded قبل/بعد الضغط إن رغبت)
final File compressedImage = await compressImage(File(croppedFile.path));
// تجهيز الحقول
final driverId = box.read(BoxName.driverID);
final payload = <String, String>{
'driverID': driverId,
'imageType': imageType, // مثال: driver_license_front
};
// الرفع وإرجاع الرابط
final String imageUrl = await uploadImage(compressedImage, payload, link);
// حفظ الرابط محلياً حسب النوع
docUrls[imageType] = imageUrl;
Log.print('✅ Uploaded $imageType => $imageUrl');
} catch (e, st) {
Log.print('❌ Error in choosImage: $e\n$st');
mySnackeBarError('Image Upload Failed'.tr);
} finally {
isloading = false;
update();
}
}
/// ترفع الملف وترجع رابط الصورة النهائي كـ String
Future<String> uploadImage(
File file, Map<String, String> data, String link) async {
final uri = Uri.parse(link);
final request = http.MultipartRequest('POST', uri);
// الهيدرز (كما عندك)
final headers = <String, String>{
'Authorization':
'Bearer ${r(box.read(BoxName.jwt)).split(AppInformation.addd)[0]}',
'X-HMAC-Auth': '${box.read(BoxName.hmac)}',
};
request.headers.addAll(headers);
// اسم الملف: driverID.jpg (اختياري)
final forcedName = '${box.read(BoxName.driverID) ?? 'image'}.jpg';
// إضافة الملف (من المسار مباشرة أسلم من الـ stream)
request.files.add(
await http.MultipartFile.fromPath(
'image', // تأكد أنه نفس اسم الحقل على السيرفر
file.path,
filename: forcedName,
),
);
// الحقول الإضافية
data.forEach((k, v) => request.fields[k] = v);
// الإرسال
final streamed = await request.send();
final res = await http.Response.fromStream(streamed);
if (res.statusCode != 200) {
throw Exception(
'Failed to upload image: ${res.statusCode} - ${res.body}');
}
// نحاول استخراج رابط الصورة من أكثر من مفتاح محتمل
final body = jsonDecode(res.body);
final String? url = body['url'] ??
body['file_link'] ??
body['image_url'] ??
(body['data'] is Map ? body['data']['url'] : null);
if (url == null || url.isEmpty) {
// لو السيرفر يرجع هيكل مختلف، عدّل هنا المفتاح حسب استجابتك الفعلية
throw Exception(
'Upload succeeded but no image URL found in response: ${res.body}');
}
return url;
}
Future<File> compressImage(File file) async {
final dir = await path_provider.getTemporaryDirectory();
final targetPath = "${dir.absolute.path}/temp.jpg";
var result = await FlutterImageCompress.compressAndGetFile(
file.absolute.path,
targetPath,
quality: 70,
minWidth: 1024,
minHeight: 1024,
);
return File(result!.path);
}
// دالة رفع إلى السيرفر السوري: ترجع file_url (Signed URL)
Future<String> uploadToSyria({
required String docType,
required File file,
required Uri syrianUploadUri,
required String authHeader,
required String hmacHeader,
required String driverId,
Duration timeout = const Duration(seconds: 60),
http.Client? clientOverride,
}) async {
final client = clientOverride ?? http.Client();
try {
final mime = lookupMimeType(file.path) ?? 'image/jpeg';
final parts = mime.split('/');
final req = http.MultipartRequest('POST', syrianUploadUri);
req.headers.addAll({
'Authorization': authHeader,
'X-HMAC-Auth': hmacHeader,
});
req.fields['driver_id'] = driverId;
req.fields['doc_type'] = docType;
req.files.add(
await http.MultipartFile.fromPath(
'file',
file.path,
filename: p.basename(file.path),
contentType: MediaType(parts.first, parts.last),
),
);
// ====== الطباعة قبل الإرسال ======
// Log.print('--- Syrian Upload Request ---');
// Log.print('URL: $syrianUploadUri');
// // Log.print('Method: POST');
// // Log.print('Headers: ${req.headers}');
// Log.print('Fields: ${req.fields}');
// // Log.print(
// // 'File: ${file.path} (${await file.length()} bytes, mime: $mime)');
// Log.print('-----------------------------');
// الإرسال
final streamed = await client.send(req).timeout(timeout);
final resp = await http.Response.fromStream(streamed);
// ====== الطباعة بعد الاستجابة ======
// Log.print('--- Syrian Upload Response ---');
Log.print('Status: ${resp.statusCode}');
// Log.print('Headers: ${resp.headers}');
// Log.print('Body: ${resp.body}');
// Log.print('-------------------------------');
Map<String, dynamic> j = {};
try {
j = jsonDecode(resp.body) as Map<String, dynamic>;
} catch (e) {
Log.print('⚠️ Failed to parse JSON: $e');
}
// التحمّل لشكلين من الـ JSON:
final statusOk = j['status'] == 'success';
final fileUrl = (j['file_url'] ?? j['message']?['file_url'])?.toString();
final fileName =
(j['file_name'] ?? j['message']?['file_name'])?.toString();
if (resp.statusCode == 200 &&
statusOk &&
(fileUrl?.isNotEmpty ?? false)) {
// Log.print(
// '✅ Syrian upload success: $fileUrl (file: ${fileName ?? "-"})');
return fileUrl!;
}
throw Exception(
'❌ Syrian upload failed ($docType): ${j['message'] ?? resp.body}');
} finally {
if (clientOverride == null) client.close();
}
}
Future<void> submitRegistration() async {
// 0) دوال/مساعدات محلية
void _addField(Map<String, String> fields, String key, String? value) {
if (value != null && value.isNotEmpty) {
fields[key] = value;
}
}
// 1) تحقق من وجود الروابط
final driverFrontUrl = docUrls['driver_license_front'];
final driverBackUrl = docUrls['driver_license_back'];
final carFrontUrl = docUrls['car_license_front'];
final carBackUrl = docUrls['car_license_back'];
isLoading.value = true;
update();
final registerUri = Uri.parse(AppLink.register_driver_and_car);
final client = http.Client();
try {
// ترويسات مشتركة
final bearer =
'Bearer ${r(box.read(BoxName.jwt)).split(AppInformation.addd)[0]}';
final hmac = '${box.read(BoxName.hmac)}';
final req = http.MultipartRequest('POST', registerUri);
req.headers.addAll({
'Authorization': bearer,
'X-HMAC-Auth': hmac,
});
final fields = <String, String>{};
// --- Driver Data ---
_addField(fields, 'id', box.read(BoxName.driverID)?.toString());
_addField(fields, 'first_name', firstNameController.text);
_addField(fields, 'last_name', lastNameController.text);
_addField(fields, 'phone', box.read(BoxName.phoneDriver) ?? '');
_addField(fields, 'national_number', nationalIdController.text);
_addField(fields, 'birthdate', bithdateController.text);
_addField(fields, 'expiry_date', driverLicenseExpiryController.text);
_addField(fields, 'password', 'generated_password_or_token');
_addField(fields, 'status', 'yet');
_addField(fields, 'email', 'Not specified');
_addField(fields, 'gender', 'Male'); // يفضل ربطها بـ Dropdown أيضاً
// --- Car Data ---
_addField(fields, 'vin', 'yet');
_addField(fields, 'car_plate', carPlateController.text);
_addField(fields, 'make', carMakeController.text);
_addField(fields, 'model', carModelController.text);
_addField(fields, 'year', carYearController.text);
_addField(
fields,
'expiration_date',
driverLicenseExpiryController
.text); // تأكد من أن هذا تاريخ انتهاء السيارة وليس الرخصة
_addField(fields, 'color', carColorController.text);
if (colorHex != null && colorHex!.isNotEmpty) {
_addField(fields, 'color_hex', colorHex!);
}
_addField(fields, 'owner',
'${firstNameController.text} ${lastNameController.text}');
// ============================================================
// 🔥 التعديل الجديد: إرسال الأرقام (IDs) لتصنيف المركبة والوقود
// ============================================================
// 1. إرسال رقم تصنيف المركبة (1=سيارة, 2=دراجة...)
if (selectedVehicleCategoryId != null) {
_addField(fields, 'vehicle_category_id',
selectedVehicleCategoryId.toString());
} else {
_addField(fields, 'vehicle_category_id', '1'); // قيمة افتراضية (سيارة)
}
// 2. إرسال رقم ونوع الوقود
if (selectedFuelTypeId != null) {
// إرسال الرقم (للبحث السريع)
_addField(fields, 'fuel_type_id', selectedFuelTypeId.toString());
// إرسال الاسم نصاً (للتوافق مع العمود القديم 'fuel' إذا لزم الأمر)
// نبحث عن الاسم داخل القائمة بناءً على الرقم المختار
final fuelObj = fuelTypeOptions.firstWhere(
(e) => e['id'] == selectedFuelTypeId,
orElse: () => {'name': 'Petrol'});
_addField(fields, 'fuel', fuelObj['name'].toString());
} else {
_addField(fields, 'fuel_type_id', '1');
_addField(fields, 'fuel', 'Petrol');
}
// --- روابط الصور ---
_addField(fields, 'driver_license_front', driverFrontUrl!);
_addField(fields, 'driver_license_back', driverBackUrl!);
_addField(fields, 'car_license_front', carFrontUrl!);
_addField(fields, 'car_license_back', carBackUrl!);
req.fields.addAll(fields);
// 3) الإرسال
final streamed =
await client.send(req).timeout(const Duration(seconds: 60));
final resp = await http.Response.fromStream(streamed);
// 4) معالجة الاستجابة
Map<String, dynamic>? json;
try {
json = jsonDecode(resp.body) as Map<String, dynamic>;
} catch (_) {}
if (resp.statusCode == 200 && json?['status'] == 'success') {
Get.snackbar('Success'.tr, 'Registration completed successfully!'.tr,
backgroundColor: Colors.green, colorText: Colors.white);
// منطق التوكن والإشعارات وتسجيل الدخول...
final email = box.read(BoxName.emailDriver);
final driverID = box.read(BoxName.driverID);
String fingerPrint = await DeviceHelper.getDeviceFingerprint();
await CRUD().post(link: AppLink.addTokensDriver, payload: {
'captain_id': (box.read(BoxName.driverID)).toString(),
'token': (box.read(BoxName.tokenDriver)).toString(),
'fingerPrint': fingerPrint.toString(),
});
NotificationService.sendNotification(
target: 'service',
title: 'New Driver Registration',
body: 'Driver $driverID has submitted registration.',
isTopic: true,
category: 'new_service_request',
);
final c = Get.isRegistered<LoginDriverController>()
? Get.find<LoginDriverController>()
: Get.put(LoginDriverController());
c.loginWithGoogleCredential(driverID, email);
} else {
final msg = (json?['message'] ?? 'Registration failed.').toString();
Get.snackbar('Error'.tr, msg,
backgroundColor: Colors.red, colorText: Colors.white);
}
} catch (e) {
Get.snackbar('Error'.tr, 'Error: $e',
backgroundColor: Colors.red, colorText: Colors.white);
} finally {
client.close();
isLoading.value = false;
update();
}
}
// // 1) تحقق من الصور
// if (driverLicenseFrontImage == null ||
// driverLicenseBackImage == null ||
// carLicenseFrontImage == null ||
// carLicenseBackImage == null) {
// Get.snackbar(
// 'Missing Documents'.tr, 'Please upload all 4 required documents.'.tr,
// snackPosition: SnackPosition.BOTTOM,
// backgroundColor: Colors.orange,
// colorText: Colors.white);
// return;
// }
// isLoading.value = true;
// final uri = Uri.parse(
// 'https://intaleq.xyz/intaleq/auth/syria/driver/register_driver_and_car.php',
// );
// final client = http.Client();
// try {
// final req = http.MultipartRequest('POST', uri);
// // مهم: لا تضع Content-Type يدويًا، الـ MultipartRequest يتكفّل فيه ببناء boundary.
// final headers = {
// 'Authorization':
// 'Bearer ${r(box.read(BoxName.jwt)).split(AppInformation.addd)[0]}',
// 'X-HMAC-Auth': '${box.read(BoxName.hmac)}',
// };
// // 2) الحقول النصية
// final fields = <String, String>{};
// // --- Driver Data ---
// _addField(fields, 'id', box.read(BoxName.driverID)?.toString());
// _addField(fields, 'first_name', firstNameController.text);
// _addField(fields, 'last_name', lastNameController.text);
// _addField(fields, 'phone', box.read(BoxName.phoneDriver) ?? '');
// _addField(fields, 'national_number', nationalIdController.text);
// _addField(fields, 'expiry_date', driverLicenseExpiryController.text);
// _addField(
// fields, 'password', 'generate_your_password_here'); // عدّل حسب منطقك
// _addField(fields, 'status', 'yet');
// _addField(fields, 'email',
// 'Not specified'); // سكربت السيرفر سيحوّلها null ويبني ايميل افتراضي
// _addField(fields, 'gender', 'Male');
// // --- Car Data (مطابقة لما يتوقّعه السكربت) ---
// _addField(fields, 'vin', 'carVinController.text);');
// _addField(fields, 'car_plate', carPlateController.text);
// _addField(fields, 'make', carMakeController.text);
// _addField(fields, 'model', carModelController.text);
// _addField(fields, 'year', carYearController.text);
// _addField(fields, 'expiration_date', 'carRegistrationExpiryController');
// _addField(fields, 'color', carColorController.text);
// _addField(fields, 'fuel', 'Gasoline'); // أو حسب اختيارك
// _addField(fields, 'color_hex', colorHex); // مهم
// // لو عندك حقول إضافية مطلوبة بالسكربت (مالك المركبة / الكود اللوني / الوقود) مرّرها:
// _addField(fields, 'owner',
// firstNameController.text + ' ' + lastNameController.text);
// // if (colorHex != null) _addField(fields, 'color_hex', colorHex);
// // if (fuelType != null) _addField(fields, 'fuel', fuelType);
// req.headers.addAll(headers);
// req.fields.addAll(fields);
// // 3) الملفات (4 صور) — مفاتيحها مطابقة للسكربت
// Future<void> addFile(String field, File file) async {
// final mime = lookupMimeType(file.path) ?? 'image/jpeg';
// final parts = mime.split('/');
// final mediaType = MediaType(parts.first, parts.last);
// req.files.add(
// await http.MultipartFile.fromPath(
// field,
// file.path,
// filename: p.basename(file.path),
// contentType: mediaType,
// ),
// );
// }
// await addFile('driver_license_front', driverLicenseFrontImage!);
// await addFile('driver_license_back', driverLicenseBackImage!);
// await addFile('car_license_front', carLicenseFrontImage!);
// await addFile('car_license_back', carLicenseBackImage!);
// // 4) الإرسال
// final streamed =
// await client.send(req).timeout(const Duration(seconds: 60));
// final resp = await http.Response.fromStream(streamed);
// // 5) فحص النتيجة
// Map<String, dynamic>? json;
// try {
// json = jsonDecode(resp.body) as Map<String, dynamic>;
// } catch (_) {}
// if (resp.statusCode == 200 &&
// json != null &&
// json['status'] == 'success') {
// // ممكن يرجّع driverID, carRegID, documents
// final driverID =
// (json['data']?['driverID'] ?? json['driverID'])?.toString();
// if (driverID != null && driverID.isNotEmpty) {
// box.write(BoxName.driverID, driverID);
// }
// Get.snackbar('Success'.tr, 'Registration completed successfully!'.tr,
// snackPosition: SnackPosition.BOTTOM,
// backgroundColor: Colors.green,
// colorText: Colors.white);
// // TODO: انتقل للصفحة التالية أو حدّث الحالة…
// } else {
// final msg =
// (json?['message'] ?? 'Registration failed. Please try again.')
// .toString();
// Get.snackbar('Error'.tr, msg,
// snackPosition: SnackPosition.BOTTOM,
// backgroundColor: Colors.red,
// colorText: Colors.white);
// }
// } catch (e) {
// Get.snackbar('Error'.tr, '${'An unexpected error occurred:'.tr} $e',
// snackPosition: SnackPosition.BOTTOM,
// backgroundColor: Colors.red,
// colorText: Colors.white);
// } finally {
// client.close();
// isLoading.value = false;
// }
// }
// Helpers
}