import 'dart:convert'; import 'dart:io'; import 'package:flutter/material.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 '../../../constant/box_name.dart'; import 'package:path_provider/path_provider.dart'; // --- Final Submission --- import 'dart:convert'; import 'dart:io'; import 'package:get/get.dart'; 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 '../../functions/crud.dart'; import '../../functions/encrypt_decrypt.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; String? colorHex; // سيُملى من الدروب داون // Form Keys for validation final driverInfoFormKey = GlobalKey(); final carInfoFormKey = GlobalKey(); // STEP 1: Driver Information Controllers final firstNameController = TextEditingController(); final lastNameController = TextEditingController(); final nationalIdController = 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; // 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.', 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 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> 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)); } Future 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 = {}; // --- 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 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!); // (اختياري) هيدر للقبول بـ JSON // 4) الإرسال final streamed = await client.send(req).timeout(const Duration(seconds: 60)); final resp = await http.Response.fromStream(streamed); // 5) فحص النتيجة Map? json; try { json = jsonDecode(resp.body) as Map; } 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 fields, String key, String? value) { if (value != null && value.toString().trim().isNotEmpty) { fields[key] = value.toString().trim(); } } }