refactor: إعادة تسمية التطبيقات الأربعة siro_* → intaleq_*

المجلدات وأسماء حزم dart واستيراداتها:
  siro_rider → intaleq_rider    siro_driver  → intaleq_driver
  siro_admin → intaleq_admin    siro_service → intaleq_service

شمل ذلك `name:` في كل pubspec وكل `package:siro_*` في الاستيرادات
(115 ملفاً في rider وحده)، وإشارات أسماء المجلدات في docs/ وتعليق في
backend/Admin/notifications/broadcast.php. لم تبقَ إشارة واحدة (تحقّقت).

+ ضُمّت حزم get و get_storage داخل intaleq_driver/packages/ وحُوّلت
مساراتها الثلاثة من `../../Intaleq/packages/*` إلى `./packages/*`. كانت
تُحل عرضاً إلى مجلد App/Intaleq القديم المجاور — تبعية خارج المستودع
تنكسر بأول نقل. لم يبقَ في أي تطبيق تبعية مسار خارج الشجرة.

⚠️ لم تُمسّ بعد: هويات المتاجر (applicationId · PRODUCT_BUNDLE_IDENTIFIER ·
shorebird app_id) ولا الألوان ولا النصوص المرئية ولا النطاقات — كل منها
كوميت منفصل، والهويات تحتاج قرار المالك.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
This commit is contained in:
Hamza-Ayed
2026-07-27 16:10:58 +03:00
co-authored by Claude Opus 5
parent 92dc6b3641
commit 0aab85c732
3137 changed files with 65294 additions and 1950 deletions
@@ -0,0 +1,883 @@
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:intaleq_driver/constant/links.dart';
import 'package:intaleq_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;
final siteController = TextEditingController(); // المدينة/الموقع
// 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 driver’s 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) {
mySnackbarError('${'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 = {
'id_front': '',
'id_back': '',
'driver_license': '',
'driver_license_back': '',
'profile_picture': '',
'criminal_record': '',
'car_license_front': '',
'car_license_back': '',
};
String getCriminalRecordTitle() {
String currentCountry = box.read(BoxName.countryCode) ?? 'Jordan';
if (currentCountry == 'Syria') return 'لا حكم عليه'.tr;
if (currentCountry == 'Egypt') return 'فيش وتشبيه'.tr;
return 'عدم محكومية'.tr;
}
/// التصرّف العام لاختيار/قص/ضغط/رفع الصورة حسب type
Future<void> choosImage(String link, String imageType, {String? backupLink}) async {
if (isloading) return;
try {
isloading = true;
update();
final pickedImage = await picker.pickImage(
source: ImageSource.camera,
preferredCameraDevice: CameraDevice.rear,
);
if (pickedImage == null) {
isloading = false;
update();
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) {
isloading = false;
update();
return;
}
// صورة للمعاينة داخل التطبيق
myImage = File(croppedFile.path);
// ضغط (وأيضاً يمكنك إضافة rotateImageIfNeeded قبل/بعد الضغط إن رغبت)
final File compressedImage = await compressImage(File(croppedFile.path));
// تجهيز الحقول
final driverId = box.read(BoxName.driverID);
final payload = <String, String>{
'driverID': driverId?.toString() ?? '',
'imageType': imageType,
'country': box.read(BoxName.countryCode) ?? '',
};
// الرفع وإرجاع الرابط (يحاول الأساسي ثم الاحتياطي)
final String imageUrl =
await uploadImage(compressedImage, payload, link, backupLink: backupLink);
// حفظ الرابط محلياً حسب النوع
docUrls[imageType] = imageUrl;
Log.print('✅ Uploaded $imageType => $imageUrl');
} catch (e, st) {
Log.print('❌ Error in choosImage: $e\n$st');
mySnackbarError('Image Upload Failed'.tr);
} finally {
isloading = false;
update();
}
}
/// ترفع الملف وترجع رابط الصورة النهائي كـ String مع إعادة المحاولة.
/// يحاول الرابط الأساسي first، فإن فشل جرب الاحتياطي (إن وُجد).
Future<String> uploadImage(
File file,
Map<String, String> data,
String link, {
String? backupLink,
}) async {
final List<String> urlsToTry = [link, if (backupLink != null && backupLink.trim().isNotEmpty) backupLink];
for (int i = 0; i < urlsToTry.length; i++) {
final currentUrl = urlsToTry[i];
final maxRetries = (i == 0) ? 3 : 1; // 3 attempts for primary, 1 for backup
for (int attempt = 1; attempt <= maxRetries; attempt++) {
try {
final uri = Uri.parse(currentUrl);
final request = http.MultipartRequest('POST', uri);
final _jwt = box.read(BoxName.jwt);
final String _token = _jwt != null ? r(_jwt).split(AppInformation.addd)[0] : '';
String timestamp = DateTime.now().millisecondsSinceEpoch.toString();
String nonce = timestamp;
final headers = <String, String>{
'Authorization': 'Bearer $_token',
'X-Device-FP': box.read(BoxName.deviceFingerprint)?.toString() ?? '',
'X-Timestamp': timestamp,
'X-Nonce': nonce,
};
request.headers.addAll(headers);
final forcedName = '${box.read(BoxName.driverID) ?? 'image'}.jpg';
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().timeout(const Duration(seconds: 120));
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);
String? url;
// بعض السيرفرات تعيد الرابط داخل message
if (body['message'] is Map) {
url = body['message']['url'] ??
body['message']['file_link'] ??
body['message']['image_url'];
}
// وإلا حاول من الجذر
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;
} catch (e) {
Log.print("⚠️ [Upload Attempt $attempt/$maxRetries @ $currentUrl] Error: $e");
if (attempt >= maxRetries) {
if (urlsToTry.last == currentUrl) {
rethrow;
}
Log.print("➡️ Switching to backup URL...");
} else {
await Future.delayed(Duration(seconds: attempt * 2));
}
}
}
}
throw Exception('Upload failed after all attempts');
}
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);
}
// دالة رفع إلى السيرفر السوري مع مهلة 120 ثانية وإعادة محاولة تلقائية في حال الفشل
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: 120),
http.Client? clientOverride,
}) async {
int maxRetries = 3;
int attempt = 0;
while (attempt < maxRetries) {
attempt++;
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-Device-FP': box.read(BoxName.deviceFingerprint)?.toString() ?? '',
});
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),
),
);
final streamed = await client.send(req).timeout(timeout);
final resp = await http.Response.fromStream(streamed);
Log.print('--- Syrian Upload Response (Attempt $attempt) ---');
Log.print('Status: ${resp.statusCode}');
Map<String, dynamic> j = {};
try {
j = jsonDecode(resp.body) as Map<String, dynamic>;
} catch (e) {
Log.print('⚠️ Failed to parse JSON: $e');
}
final statusOk = j['status'] == 'success';
final fileUrl =
(j['file_url'] ?? j['message']?['file_url'])?.toString();
if (resp.statusCode == 200 &&
statusOk &&
(fileUrl?.isNotEmpty ?? false)) {
return fileUrl!;
}
throw Exception(
'❌ Syrian upload failed ($docType): ${j['message'] ?? resp.body}');
} catch (e) {
Log.print("⚠️ [Syria Upload Attempt $attempt Failed] Error: $e");
if (attempt >= maxRetries) {
rethrow;
}
await Future.delayed(Duration(seconds: attempt * 2));
} finally {
if (clientOverride == null) client.close();
}
}
throw Exception('Syrian upload failed after $maxRetries attempts');
}
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 idFrontUrl = docUrls['id_front'];
final idBackUrl = docUrls['id_back'];
final driverLicenseUrl = docUrls['driver_license'];
final driverLicenseBackUrl = docUrls['driver_license_back'];
final profilePicUrl = docUrls['profile_picture'];
final criminalRecordUrl = docUrls['criminal_record'];
final carFrontUrl = docUrls['car_license_front'];
final carBackUrl = docUrls['car_license_back'];
final isSyria = box.read(BoxName.countryCode) == 'Syria';
if (idFrontUrl == null ||
idFrontUrl.isEmpty ||
idBackUrl == null ||
idBackUrl.isEmpty ||
driverLicenseUrl == null ||
driverLicenseUrl.isEmpty ||
(isSyria &&
(driverLicenseBackUrl == null || driverLicenseBackUrl.isEmpty)) ||
profilePicUrl == null ||
profilePicUrl.isEmpty ||
carFrontUrl == null ||
carFrontUrl.isEmpty ||
carBackUrl == null ||
carBackUrl.isEmpty) {
mySnackbarWarning(
'Please wait for all documents to finish uploading before registering.'
.tr);
return;
}
isLoading.value = true;
update();
final registerUri = Uri.parse(AppLink.register_driver_and_car);
final client = http.Client();
try {
// ترويسات مشتركة
final _jwt = box.read(BoxName.jwt);
final bearer = 'Bearer ${_jwt != null ? r(_jwt).split(AppInformation.addd)[0] : ''}';
final hmac = '${box.read(BoxName.hmac)}';
String fingerPrint =
box.read(BoxName.deviceFingerprint)?.toString() ?? '';
String timestamp = DateTime.now().millisecondsSinceEpoch.toString();
String nonce = timestamp; // Simple nonce for now
final req = http.MultipartRequest('POST', registerUri);
req.headers.addAll({
'Authorization': bearer,
// 'X-HMAC-Auth': hmac, // Removed to bypass "Invalid HMAC signature" check
'X-Device-FP': fingerPrint,
'X-Timestamp': timestamp,
'X-Nonce': nonce,
});
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');
String generatedEmail = 'driver_${box.read(BoxName.driverID) ?? DateTime.now().millisecondsSinceEpoch}@siromove.com';
_addField(fields, 'email', generatedEmail);
_addField(fields, 'gender', 'Male'); // يفضل ربطها بـ Dropdown أيضاً
_addField(fields, 'country', box.read(BoxName.countryCode)?.toString() ?? 'Jordan');
_addField(fields, 'site', siteController.text.isNotEmpty ? siteController.text : null);
// --- Car Data ---
_addField(fields, 'vin', carVinController.text.isNotEmpty ? carVinController.text : '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.text.isNotEmpty
? carRegistrationExpiryController.text
: driverLicenseExpiryController.text);
_addField(
fields,
'color',
carColorController.text.isNotEmpty
? carColorController.text
: 'White');
_addField(fields, 'color_hex',
(colorHex != null && colorHex!.isNotEmpty) ? colorHex! : '#FFFFFF');
_addField(
fields,
'owner',
'${firstNameController.text} ${lastNameController.text}'
.trim()
.isNotEmpty
? '${firstNameController.text} ${lastNameController.text}'
: 'Driver Owner');
// ============================================================
// 🔥 التعديل الجديد: إرسال الأرقام (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, 'id_front', idFrontUrl);
_addField(fields, 'id_back', idBackUrl);
_addField(fields, 'driver_license', driverLicenseUrl);
// Always add the key to avoid PHP undefined index error
fields['driver_license_back'] = driverLicenseBackUrl ?? '';
_addField(fields, 'profile_picture', profilePicUrl);
_addField(fields, 'criminal_record', criminalRecordUrl);
_addField(fields, 'car_license_front', carFrontUrl);
_addField(fields, 'car_license_back', carBackUrl);
req.fields.addAll(fields);
Log.print('--- 🚀 Registration Request Payload ---');
req.fields.forEach((key, value) {
Log.print('[$key]: $value');
});
Log.print('---------------------------------------');
// 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 {
Log.print('--- Registration Response: ${resp.body} ---');
json = jsonDecode(resp.body) as Map<String, dynamic>;
} catch (_) {}
if (resp.statusCode == 200 && json?['status'] == 'success') {
mySnackbarSuccess('Registration completed successfully!'.tr);
// منطق التوكن والإشعارات وتسجيل الدخول...
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());
box.write(BoxName.firstTimeLoadKey, 'false');
await c.getJWT();
c.loginDriver(driverID, email);
} else {
final msg = (json?['message'] ?? 'Registration failed.').toString();
mySnackbarError(msg);
}
} catch (e) {
mySnackbarError('Error: $e');
} 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/siro/auth/driver/register.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
}