Initial commit for Intaleq Driver

This commit is contained in:
Hamza-Ayed
2025-09-01 19:04:50 +03:00
parent 889c67a691
commit 8c7f3e3a75
46 changed files with 4300 additions and 6192 deletions

View File

@@ -5,9 +5,8 @@ import 'package:crypto/crypto.dart';
import 'dart:math';
import 'package:http/http.dart' as http;
import 'package:permission_handler/permission_handler.dart';
import 'package:secure_string_operations/secure_string_operations.dart';
import 'package:sefer_driver/controller/functions/location_background_controller.dart';
import 'package:sefer_driver/views/auth/captin/cards/sms_signup.dart';
import 'package:sefer_driver/views/auth/syria/registration_view.dart';
import 'package:sefer_driver/views/widgets/elevated_btn.dart';
import 'package:sefer_driver/views/widgets/error_snakbar.dart';
import 'package:flutter/material.dart';
@@ -21,13 +20,10 @@ import 'package:sefer_driver/views/home/Captin/home_captain/home_captin.dart';
import 'package:location/location.dart';
import '../../../constant/api_key.dart';
import '../../../constant/char_map.dart';
import '../../../constant/info.dart';
import '../../../constant/table_names.dart';
import '../../../print.dart';
import '../../../views/auth/captin/cards/syrian_card_a_i.dart';
import '../../../views/auth/captin/otp_page.dart';
import '../../../views/auth/captin/otp_token_page.dart';
import '../../../views/auth/syria/pending_driver_page.dart';
import '../../firebase/firbase_messge.dart';
import '../../functions/encrypt_decrypt.dart';
import '../../functions/package_info.dart';
@@ -79,11 +75,11 @@ class LoginDriverController extends GetxController {
var res = await CRUD().get(
link: AppLink.getTesterApp,
payload: {'appPlatform': AppInformation.appName});
Log.print('res: ${res}');
// Log.print('res: ${res}');
if (res != 'failure') {
var d = jsonDecode(res);
isTest = d['message'][0]['isTest'];
Log.print('isTest: ${isTest}');
// Log.print('isTest: ${isTest}');
box.write(BoxName.isTest, isTest);
// Log.print('isTest: ${box.read(BoxName.isTest)}');
@@ -108,7 +104,8 @@ class LoginDriverController extends GetxController {
)
});
if (res != 'failure') {
Get.offAll(() => SyrianCardAI());
// Get.offAll(() => SyrianCardAI());
Get.offAll(() => RegistrationView());
// isloading = false;
// update();
@@ -322,46 +319,53 @@ class LoginDriverController extends GetxController {
} else if (int.parse(d['year'].toString()) < 2002) {
box.write(BoxName.carTypeOfDriver, 'Awfar Car');
}
updateAppTester(AppInformation.appName);
// updateAppTester(AppInformation.appName);
if (d['status'].toString() != 'yet') {
var token = await CRUD().get(
link: AppLink.getDriverToken,
payload: {
'captain_id': (box.read(BoxName.driverID)).toString()
});
var token = await CRUD().get(
link: AppLink.getDriverToken,
payload: {'captain_id': (box.read(BoxName.driverID)).toString()});
String fingerPrint = await DeviceHelper.getDeviceFingerprint();
await storage.write(
key: BoxName.fingerPrint, value: fingerPrint.toString());
// print(jsonDecode(token)['data'][0]['token'].toString());
// print(box.read(BoxName.tokenDriver).toString());
if (email == '962798583052@intaleqapp.com') {
} else {
if (token != 'failure') {
if ((jsonDecode(token)['data'][0]['token'].toString()) !=
box.read(BoxName.tokenDriver).toString()) {
await Get.defaultDialog(
title: 'Device Change Detected'.tr,
middleText: 'Please verify your identity'.tr,
textConfirm: 'Verify'.tr,
confirmTextColor: Colors.white,
onConfirm: () {
// Get.back();
// انتقل لصفحة OTP الجديدة
Get.to(
() => OtpVerificationPage(
phone: d['phone'].toString(),
deviceToken: fingerPrint.toString(),
token: token.toString(),
ptoken:
jsonDecode(token)['data'][0]['token'].toString(),
),
);
},
);
String fingerPrint = await DeviceHelper.getDeviceFingerprint();
await storage.write(
key: BoxName.fingerPrint, value: fingerPrint.toString());
// print(jsonDecode(token)['data'][0]['token'].toString());
// print(box.read(BoxName.tokenDriver).toString());
if (email == '962798583052@intaleqapp.com') {
} else {
if (token != 'failure') {
if ((jsonDecode(token)['data'][0]['token'].toString()) !=
box.read(BoxName.tokenDriver).toString()) {
await Get.defaultDialog(
title: 'Device Change Detected'.tr,
middleText: 'Please verify your identity'.tr,
textConfirm: 'Verify'.tr,
confirmTextColor: Colors.white,
onConfirm: () {
// Get.back();
// انتقل لصفحة OTP الجديدة
Get.to(
() => OtpVerificationPage(
phone: d['phone'].toString(),
deviceToken: fingerPrint.toString(),
token: token.toString(),
ptoken:
jsonDecode(token)['data'][0]['token'].toString(),
),
);
},
);
}
}
}
Get.offAll(() => HomeCaptain());
} else {
Get.off(() => DriverVerificationScreen());
}
Get.off(() => HomeCaptain());
// Get.off(() => HomeCaptain());
} else {
Get.offAll(() => PhoneNumberScreen());
@@ -476,7 +480,8 @@ class LoginDriverController extends GetxController {
if (res == 'failure') {
//Failure
if (box.read(BoxName.phoneVerified).toString() == '1') {
Get.offAll(() => SyrianCardAI());
// Get.offAll(() => SyrianCardAI());
Get.offAll(() => RegistrationView());
} else {
Get.offAll(() => SmsSignupEgypt());
}
@@ -551,24 +556,7 @@ class LoginDriverController extends GetxController {
'captain_id': box.read(BoxName.driverID).toString(),
'fingerPrint': (fingerPrint).toString()
});
await CRUD().post(
link:
"${AppLink.seferAlexandriaServer}/ride/firebase/addDriver.php",
payload: {
'token': box.read(BoxName.tokenDriver),
'captain_id':
box.read(BoxName.driverID).toString(),
'fingerPrint': (fingerPrint).toString()
});
await CRUD().post(
link:
"${AppLink.seferGizaServer}/ride/firebase/addDriver.php",
payload: {
'token': box.read(BoxName.tokenDriver),
'captain_id':
box.read(BoxName.driverID).toString(),
'fingerPrint': (fingerPrint).toString()
});
Get.back();
}));
}

View File

@@ -1,15 +1,14 @@
import 'package:get/get.dart';
import 'package:sefer_driver/controller/auth/captin/login_captin_controller.dart';
import 'package:sefer_driver/controller/functions/crud.dart';
import 'package:sefer_driver/views/auth/captin/cards/syrian_card_a_i.dart';
import 'package:sefer_driver/print.dart';
import 'package:sefer_driver/views/home/on_boarding_page.dart';
import 'package:sefer_driver/views/widgets/error_snakbar.dart';
import '../../../constant/box_name.dart';
import '../../../constant/links.dart';
import '../../../main.dart';
import '../../../print.dart';
import '../../../views/auth/captin/otp_page.dart';
import '../../../views/auth/syria/registration_view.dart';
// --- Helper Class for Phone Authentication ---
@@ -27,9 +26,9 @@ class PhoneAuthHelper {
link: _sendOtpUrl,
payload: {'receiver': phoneNumber},
);
Log.print('response: ${response}');
if (response != 'failure') {
final data = (response);
Log.print('data: ${data}');
// if (data['status'] == 'success') {
mySnackbarSuccess('An OTP has been sent to your WhatsApp number.'.tr);
return true;
@@ -42,7 +41,6 @@ class PhoneAuthHelper {
return false;
}
} catch (e) {
Log.print('e: ${e}');
// mySnackeBarError('An error occurred: $e');
return false;
}
@@ -61,17 +59,14 @@ class PhoneAuthHelper {
if (data['status'] == 'success') {
final isRegistered = data['message']['isRegistered'] ?? false;
Log.print('isRegistered: ${isRegistered}');
box.write(BoxName.phoneVerified, true);
box.write(BoxName.phoneDriver, phoneNumber);
box.write(BoxName.driverID, data['message']['driverID']);
Log.print('BoxName.driverID: ${box.read(BoxName.driverID)}');
if (isRegistered) {
// ✅ السائق مسجل مسبقًا - سجل دخوله واذهب إلى الصفحة الرئيسية
final driver = data['message']['driver'];
// mySnackbarSuccess('Welcome back, ${driver['first_name']}!');
Log.print('Welcome: }');
// حفظ بيانات السائق إذا أردت:
box.write(BoxName.driverID, driver['id']);
@@ -82,7 +77,8 @@ class PhoneAuthHelper {
} else {
// ✅ رقم الهاتف تم التحقق منه لكن السائق غير مسجل
// mySnackbarSuccess('Phone verified. Please complete registration.');
Get.to(() => SyrianCardAI());
// Get.offAll(() => SyrianCardAI());
Get.offAll(() => RegistrationView());
}
} else {
mySnackeBarError(data['message'] ?? 'Verification failed.');
@@ -92,7 +88,6 @@ class PhoneAuthHelper {
}
} catch (e) {
mySnackeBarError('An error occurred: $e');
Log.print('e: ${e}');
}
}
@@ -121,7 +116,6 @@ class PhoneAuthHelper {
"User with this phone number or email already exists.".tr);
}
} catch (e) {
Log.print('e: ${e}');
mySnackeBarError('An error occurred: $e');
}
}

View File

@@ -2,8 +2,6 @@ import 'dart:convert';
import 'dart:math';
import 'package:sefer_driver/controller/auth/captin/login_captin_controller.dart';
import 'package:sefer_driver/views/auth/captin/cards/syrian_card_a_i.dart';
import 'package:sefer_driver/views/auth/captin/register_captin.dart';
import 'package:sefer_driver/views/widgets/error_snakbar.dart';
import 'package:flutter/material.dart';
import 'package:get/get.dart';
@@ -17,9 +15,8 @@ import 'package:sefer_driver/views/auth/captin/verify_email_captain.dart';
import '../../../constant/colors.dart';
import '../../../views/auth/captin/ai_page.dart';
import '../../../views/auth/captin/car_license_page.dart';
import '../../../views/auth/syria/registration_view.dart';
import '../../../views/home/Captin/home_captain/home_captin.dart';
import '../../functions/encrypt_decrypt.dart';
import '../../functions/sms_egypt_controller.dart';
class RegisterCaptainController extends GetxController {
@@ -282,7 +279,8 @@ class RegisterCaptainController extends GetxController {
// box.read(BoxName.driverID).toString(),
// box.read(BoxName.emailDriver).toString(),
// );
Get.to(SyrianCardAI());
// Get.offAll(() => SyrianCardAI());
Get.offAll(() => RegistrationView());
// } else {
// Get.snackbar('title', 'message');
// }

View File

@@ -1,5 +1,6 @@
import 'package:sefer_driver/constant/box_name.dart';
import 'package:sefer_driver/controller/auth/captin/login_captin_controller.dart';
import 'package:sefer_driver/controller/functions/crud.dart';
import 'package:sefer_driver/main.dart';
import 'package:sefer_driver/views/auth/captin/cards/sms_signup.dart';
import 'package:sefer_driver/views/home/on_boarding_page.dart';
@@ -68,9 +69,10 @@ class GoogleSignInHelper {
}
return googleUser;
} catch (error) {
} catch (error, stackTrace) {
mySnackeBarError('$error');
addError(error.toString(), 'GoogleSignInAccount?> signInFromLogin()');
CRUD.addError(error.toString(), stackTrace.toString(),
'GoogleSignInAccount?> signInFromLogin()');
return null;
}
}

View File

@@ -5,6 +5,7 @@ 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:sefer_driver/constant/links.dart';
import '../../../constant/box_name.dart';
import 'package:path_provider/path_provider.dart';
// --- Final Submission ---
@@ -19,8 +20,10 @@ import 'package:path/path.dart' as p;
import '../../../constant/colors.dart';
import '../../../constant/info.dart';
import '../../../main.dart';
import '../../../print.dart';
import '../../functions/crud.dart';
import '../../functions/encrypt_decrypt.dart';
import '../captin/login_captin_controller.dart';
// You can create a simple enum to manage image types
enum ImageType {
@@ -222,6 +225,97 @@ class RegistrationController extends GetxController {
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();
}
}
// دالة رفع إلى السيرفر السوري: ترجع 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 {
// 1) تحقق من الصور
if (driverLicenseFrontImage == null ||
@@ -229,30 +323,82 @@ class RegistrationController extends GetxController {
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);
'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',
);
// روابط الـ API
final registerUri =
Uri.parse(AppLink.register_driver_and_car); // التسجيل الرئيسي (PHP)
final syrianUploadUri =
// Uri.parse(AppLink.uploadSyrianDocs); // رفع الصور في سوريا
Uri.parse(
'https://syria.intaleq.xyz/intaleq/auth/syria/uploadSyrianDocs.php'); // رفع الصور في سوريا
final client = http.Client();
try {
final req = http.MultipartRequest('POST', uri);
// ترويسات مشتركة
final bearer =
'Bearer ${r(box.read(BoxName.jwt)).split(AppInformation.addd)[0]}';
final hmac = '${box.read(BoxName.hmac)}';
// 2) ارفع الصور أولاً على السيرفر السوري واحصل على روابطها (Signed URLs)
final driverId = (box.read(BoxName.driverID) ?? '').toString();
final driverFrontUrl = await uploadToSyria(
docType: 'driver_license_front',
file: driverLicenseFrontImage!,
syrianUploadUri: syrianUploadUri,
authHeader: bearer,
hmacHeader: hmac,
driverId: driverId,
clientOverride: client,
);
final driverBackUrl = await uploadToSyria(
docType: 'driver_license_back',
file: driverLicenseBackImage!,
syrianUploadUri: syrianUploadUri,
authHeader: bearer,
hmacHeader: hmac,
driverId: driverId,
clientOverride: client,
);
final carFrontUrl = await uploadToSyria(
docType: 'car_license_front',
file: carLicenseFrontImage!,
syrianUploadUri: syrianUploadUri,
authHeader: bearer,
hmacHeader: hmac,
driverId: driverId,
clientOverride: client,
);
final carBackUrl = await uploadToSyria(
docType: 'car_license_back',
file: carLicenseBackImage!,
syrianUploadUri: syrianUploadUri,
authHeader: bearer,
hmacHeader: hmac,
driverId: driverId,
clientOverride: client,
);
// 3) جهّز طلب التسجيل الرئيسي: نرسل الحقول + روابط الصور (لا نرفع الصور مرة ثانية)
final req = http.MultipartRequest('POST', registerUri);
req.headers.addAll({
'Authorization': bearer,
'X-HMAC-Auth': hmac,
});
// مهم: لا تضع 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 ---
@@ -266,48 +412,33 @@ class RegistrationController extends GetxController {
fields, 'password', 'generate_your_password_here'); // عدّل حسب منطقك
_addField(fields, 'status', 'yet');
_addField(fields, 'email',
'Not specified'); // سكربت السيرفر سيحوّلها null ويبني ايميل افتراضي
'Not specified'); // السيرفر سيحوّلها null ويبني ايميل افتراضي
_addField(fields, 'gender', 'Male');
// --- Car Data (مطابقة لما يتوقّعه السكربت) ---
_addField(fields, 'vin', 'carVinController.text);');
// --- 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', 'carRegistrationExpiryController');
_addField(fields, 'expiration_date',
driverLicenseExpiryController.text); // تم التصحيح
_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,
),
);
_addField(fields, 'fuel', 'Gasoline');
if (colorHex != null && colorHex!.isNotEmpty) {
_addField(fields, 'color_hex', colorHex!);
}
_addField(fields, 'owner',
'${firstNameController.text} ${lastNameController.text}');
await addFile('driver_license_front', driverLicenseFrontImage!);
await addFile('driver_license_back', driverLicenseBackImage!);
await addFile('car_license_front', carLicenseFrontImage!);
await addFile('car_license_back', carLicenseBackImage!);
// --- روابط الصور الموقّعة من سوريا ---
_addField(fields, 'driver_license_front', driverFrontUrl);
_addField(fields, 'driver_license_back', driverBackUrl);
_addField(fields, 'car_license_front', carFrontUrl);
_addField(fields, 'car_license_back', carBackUrl);
// (اختياري) هيدر للقبول بـ JSON
// أضف الحقول
req.fields.addAll(fields);
// 4) الإرسال
final streamed =
@@ -320,46 +451,187 @@ class RegistrationController extends GetxController {
json = jsonDecode(resp.body) as Map<String, dynamic>;
} catch (_) {}
if (resp.statusCode == 200 &&
json != null &&
json['status'] == 'success') {
// ممكن يرجّع driverID, carRegID, documents
if (resp.statusCode == 200 && json?['status'] == 'success') {
final driverID =
(json['data']?['driverID'] ?? json['driverID'])?.toString();
(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);
Get.snackbar(
'Success'.tr,
'Registration completed successfully!'.tr,
snackPosition: SnackPosition.BOTTOM,
backgroundColor: Colors.green,
colorText: Colors.white,
);
// TODO: انتقل للصفحة التالية أو حدّث الحالة…
// TODO: التنقّل أو تحديث الحالة…
final email = box.read<String?>(BoxName.emailDriver) ?? '';
final c = Get.isRegistered<LoginDriverController>()
? Get.find<LoginDriverController>()
: Get.put(LoginDriverController());
c.loginWithGoogleCredential(driverId, email);
} 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',
Log.print('msg: ${msg}');
Get.snackbar(
'Error'.tr,
msg,
snackPosition: SnackPosition.BOTTOM,
backgroundColor: Colors.red,
colorText: Colors.white);
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;
}
}
// Future<void> submitRegistration() async {
// // 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
void _addField(Map<String, String> fields, String key, String? value) {
if (value != null && value.toString().trim().isNotEmpty) {
fields[key] = value.toString().trim();
}
}
}