import 'dart:convert'; import 'dart:io'; import 'package:flutter/material.dart'; import 'package:get/get.dart'; import 'package:http/http.dart' as http; import 'package:image_cropper/image_cropper.dart'; import 'package:image_picker/image_picker.dart'; import 'package:path/path.dart'; import 'package:flutter_image_compress/flutter_image_compress.dart'; import 'package:sefer_driver/controller/firebase/notification_service.dart'; import 'package:sefer_driver/views/widgets/error_snakbar.dart'; import '../../../constant/box_name.dart'; import '../../../constant/colors.dart'; import '../../../constant/info.dart'; import '../../../constant/links.dart'; import '../../../controller/auth/captin/login_captin_controller.dart'; import '../../../controller/firebase/local_notification.dart'; import '../../../controller/functions/crud.dart'; import '../../../controller/functions/encrypt_decrypt.dart'; import '../../../main.dart'; import '../../../print.dart'; import '../../home/Captin/home_captain/home_captin.dart'; import '../../widgets/mydialoug.dart'; // تأكد من إضافة هذه المكتبة class AI extends GetxController { // --- متغيرات الحالة --- bool isLoading = false; bool isEgypt = true; // لتحديد جنسية السائق // --- متغيرات تخزين بيانات الوثائق --- Map responseIdEgyptDriverLicense = {}; Map responseIdCardDriverEgyptFront = {}; Map responseIdCardDriverEgyptBack = {}; Map responseIdEgyptFront = {}; Map responseIdEgyptBack = {}; Map responseCriminalRecordEgypt = {}; // للمقيمين الأجانب Map responseNonIdCardFront = {}; Map responseNonIdCardBack = {}; // --- دالة تغيير الجنسية --- void changeNationality() { isEgypt = !isEgypt; update(); } // =========================================================================== // 1. الدالة الأساسية للذكاء الاصطناعي (رفع، تحقق، استخراج) // =========================================================================== Future allMethodForAI(String type) async { isLoading = true; update(); try { // 1. التقاط الصورة final ImagePicker picker = ImagePicker(); final XFile? pickedImage = await picker.pickImage( source: ImageSource.camera, preferredCameraDevice: CameraDevice.rear, ); if (pickedImage == null) { isLoading = false; update(); return; } // 2. معالجة الصورة (قص) CroppedFile? croppedFile = await ImageCropper().cropImage( sourcePath: pickedImage.path, uiSettings: [ AndroidUiSettings( toolbarTitle: 'ضبط الصورة', toolbarColor: AppColor.primaryColor, toolbarWidgetColor: Colors.white, initAspectRatio: CropAspectRatioPreset.original, lockAspectRatio: false, ), IOSUiSettings( title: 'ضبط الصورة', doneButtonTitle: 'تم', cancelButtonTitle: 'إلغاء', ), ], ); if (croppedFile == null) { isLoading = false; update(); return; } // 3. ضغط الصورة File processedImage = File(croppedFile.path); File compressedImage = await testCompressAndGetFile( processedImage, processedImage.path, ); // 4. تجهيز الطلب var request = http.MultipartRequest("POST", Uri.parse('${AppLink.server}/auth/captin/ai_document_egypt.php')); request.fields['type'] = type; request.fields['driver_id'] = box.read(BoxName.driverID)?.toString() ?? 'unknown'; // تأمين الهيدر من القيم الفارغة String jwt = box.read(BoxName.jwt) ?? ''; String cleanJwt = r(jwt).split(AppInformation.addd)[0]; final headers = { 'Authorization': 'Bearer $cleanJwt', 'X-HMAC-Auth': '${box.read(BoxName.hmac) ?? ''}', }; request.headers.addAll(headers); var stream = http.ByteStream(compressedImage.openRead()); var length = await compressedImage.length(); var multipartFile = http.MultipartFile('image', stream, length, filename: basename(compressedImage.path)); request.files.add(multipartFile); // 5. إرسال الطلب Log.print("Uploading image... type: $type"); var response = await request.send(); var responseBody = await http.Response.fromStream(response); Log.print("Response Code: ${response.statusCode}"); Log.print("Raw Response Body: ${responseBody.body}"); if (response.statusCode == 200) { // تنظيف الرد من أي نصوص زائدة (PHP Logs) String cleanJsonString = responseBody.body; int firstBrace = cleanJsonString.indexOf('{'); int lastBrace = cleanJsonString.lastIndexOf('}'); if (firstBrace != -1 && lastBrace != -1) { cleanJsonString = cleanJsonString.substring(firstBrace, lastBrace + 1); } else { throw Exception("No valid JSON found in response"); } var jsonResponse = jsonDecode(cleanJsonString); if (jsonResponse['status'] == 'success') { // --- التعديل هنا: استخراج البيانات بشكل صحيح --- Map aiData = {}; // 1. محاولة قراءة البيانات من message.data (كما في اللوج الأخير) if (jsonResponse['message'] is Map && jsonResponse['message'].containsKey('data')) { aiData = jsonResponse['message']['data']; } // 2. محاولة قراءة البيانات من data مباشرة (احتياط) else if (jsonResponse.containsKey('data') && jsonResponse['data'] is Map) { aiData = jsonResponse['data']; } // 3. حالة أخيرة: إذا كانت الرسالة نفسها هي البيانات else if (jsonResponse['message'] is Map) { aiData = jsonResponse['message']; } if (aiData.containsKey('is_wrong_document') && aiData['is_wrong_document'] == true) { String detectedEnglish = aiData['detected_type'] ?? ''; String detectedArabic = _translateDetectedType(detectedEnglish); MyDialog().getDialog( 'وثيقة غير صحيحة'.tr, 'لقد قمت برفع: $detectedArabic\nالمطلوب هو: ${_getReadableName(type)}\n\nيرجى إعادة المحاولة.' .tr, () => Get.back(), ); } else { // النجاح: تخزين البيانات _assignDataToVariable(type, aiData); } } else { MyDialog().getDialog( 'فشل التحليل'.tr, jsonResponse['message'] is String ? jsonResponse['message'] : 'لم يتم استخراج بيانات', () => Get.back()); } } else { MyDialog().getDialog('خطأ في السيرفر'.tr, 'كود: ${response.statusCode}', () => Get.back()); } } catch (e, stacktrace) { Log.print("Error inside allMethodForAI: $e"); Log.print("Stacktrace: $stacktrace"); MyDialog().getDialog( 'خطأ'.tr, 'حدث خطأ غير متوقع.\nالسبب: $e', () => Get.back()); } finally { isLoading = false; update(); } } // 2. معالجة الصورة (قص) // =========================================================================== // 2. دالة التسجيل الموحدة (السائق + السيارة) - Transaction // =========================================================================== Future registerCompleteAccount() async { isLoading = true; update(); final loginDriverController = Get.put(LoginDriverController()); // 1. كلمة المرور String passwordRaw = loginDriverController.passwordController.text.trim(); if (passwordRaw.isEmpty) { passwordRaw = '${box.read(BoxName.phoneDriver)}${box.read(BoxName.driverID)}'; } // 2. التحقق من الهوية String driverId = box.read(BoxName.driverID)?.toString() ?? ''; String email = box.read(BoxName.emailDriver)?.toString() ?? ''; String phone = box.read(BoxName.phoneDriver)?.toString() ?? ''; if (driverId.isEmpty || phone.isEmpty) { isLoading = false; update(); mySnackeBarError("Missing Registration Info".tr); return; } // 3. استخراج الاسم الأول والأخير من (name_arabic) // حسب ملف الـ AI الخاص بك، المفتاح هو 'name_arabic' للرخصة String fullArabicName = (isEgypt ? responseIdEgyptDriverLicense['name_arabic'] : responseNonIdCardFront['full_name']) ?.toString() ?? ''; // تنظيف الاسم وتقسيمه fullArabicName = fullArabicName.trim().replaceAll(RegExp(r'\s+'), ' '); List nameParts = fullArabicName.split(' '); // إذا كان الاسم العربي فارغ، نحاول الإنجليزي أو أي اسم متاح if (nameParts.isEmpty || nameParts[0].isEmpty) { String altName = responseIdEgyptFront['full_name']?.toString() ?? ''; nameParts = altName.trim().split(' '); } String firstName = nameParts.isNotEmpty ? nameParts.first : ''; String lastName = nameParts.length > 1 ? nameParts.last : ''; // 4. تجهيز البيانات Map driverData = {}; if (isEgypt) { driverData = { 'is_egypt': '1', 'first_name': firstName, // تم التعديل 'last_name': lastName, // تم التعديل 'name_arabic': fullArabicName, 'national_number': responseIdEgyptDriverLicense['national_number'] ?? responseIdEgyptBack['nationalID'] ?? '', 'name_english': responseIdEgyptDriverLicense['name_english'] ?? '', 'gender': responseIdEgyptBack['gender'] ?? 'Male', 'license_type': responseIdEgyptDriverLicense['license_type'] ?? 'خاصة', 'license_categories': responseIdEgyptDriverLicense['license_type'] ?? '', // استخدام النوع كفئة إذا لم تتوفر الفئات 'birthdate': extractDOB( responseIdEgyptDriverLicense['national_number'].toString()), 'address': responseIdEgyptFront['address'] ?? '', 'occupation': responseIdEgyptBack['occupation'] ?? 'Driver', 'religion': responseIdEgyptBack['religion'] ?? '', 'maritalStatus': responseIdEgyptBack['maritalStatus'] ?? '', 'card_id': responseIdEgyptBack['nationalID'] ?? '', // رقم البطاقة 'site': responseIdEgyptFront['address'] ?? '', 'education': responseIdEgyptBack['education'] ?? '', 'employmentType': '', 'bankCode': 'yet', 'accountBank': 'yet', }; } else { driverData = { 'is_egypt': '0', 'first_name': firstName, 'last_name': lastName, 'name_arabic': fullArabicName, 'national_number': responseNonIdCardFront['passport_no'] ?? '', 'name_english': '', 'gender': responseNonIdCardFront['gender'] ?? 'Male', 'license_type': 'Foreign', 'license_categories': 'Foreign', 'birthdate': responseNonIdCardFront['birthdate'] ?? '', 'address': responseNonIdCardFront['address'] ?? '', 'occupation': responseNonIdCardBack['workStatus'] ?? '', 'religion': responseNonIdCardFront['country'] ?? '', 'maritalStatus': '', 'card_id': responseNonIdCardFront['card_id'] ?? '', 'site': '', 'education': '', 'employmentType': '', 'bankCode': '', 'accountBank': '', }; } // إضافة البيانات الأساسية driverData.addAll({ 'id': driverId, 'email': email, 'phone': phone, 'password': passwordRaw, }); // بيانات السيارة Map carData = { 'vin': responseIdCardDriverEgyptBack['chassis'].toString(), 'car_plate': responseIdCardDriverEgyptFront['car_plate'].toString(), 'make': responseIdCardDriverEgyptBack['make'].toString(), 'model': responseIdCardDriverEgyptBack['model'].toString(), 'year': responseIdCardDriverEgyptBack['year'].toString(), 'expiration_date': responseIdCardDriverEgyptFront['LicenseExpirationDate'].toString(), 'color': responseIdCardDriverEgyptBack['color'].toString(), 'color_hex': responseIdCardDriverEgyptBack['color_hex'].toString(), 'owner': responseIdCardDriverEgyptFront['owner'].toString(), 'displacement': responseIdCardDriverEgyptBack['displacement'].toString(), 'fuel': responseIdCardDriverEgyptBack['fuel'].toString(), 'registration_date': responseIdCardDriverEgyptBack['inspection_date'].toString(), }; // تحويل الكل إلى String Map finalPayload = {}; driverData .forEach((key, value) => finalPayload[key] = value?.toString() ?? ''); carData .forEach((key, value) => finalPayload[key] = value?.toString() ?? ''); try { Log.print("Sending Payload: $finalPayload"); var res = await CRUD().post( link: '${AppLink.authCaptin}/register_complete.php', payload: finalPayload); Log.print("Raw Response: $res"); if (res == 'failure' || res == null) { mySnackeBarError("Connection Failed".tr); isLoading = false; update(); return; } // 🟢 الإصلاح الرئيسي للكراش: التعامل مع الرد سواء كان Map أو String Map statusResponse = {}; if (res is Map) { statusResponse = Map.from(res); } else if (res is String) { try { statusResponse = jsonDecode(res); } catch (e) { Log.print("JSON Parse Error: $e"); // في حال فشل الفك، نعتبره فشل mySnackeBarError("Invalid Server Data".tr); isLoading = false; update(); return; } } isLoading = false; update(); if (statusResponse['status'] == 'success') { box.write(BoxName.phoneVerified, '1'); mySnackbarSuccess("Registration Successful".tr); await addToken(); NotificationService.sendNotification( target: 'service', title: 'New Driver', body: 'New driver registered: $fullArabicName', isTopic: true, category: 'new_service_request', ); Get.offAll(() => HomeCaptain()); } else { String msg = statusResponse['message']?.toString() ?? "Error"; mySnackeBarError(msg.tr); } } catch (e) { isLoading = false; update(); Log.print("Critical Error: $e"); mySnackeBarError("Unexpected Error".tr); } } // =========================================================================== // 3. دوال مساعدة (Helpers) // =========================================================================== addToken() async { String? fingerPrint = await storage.read(key: BoxName.fingerPrint); final payload = { 'token': (box.read(BoxName.tokenDriver)), 'captain_id': (box.read(BoxName.driverID)).toString(), 'fingerPrint': (fingerPrint).toString() }; Log.print('payload for addToken: ${payload}'); CRUD().post(link: AppLink.addTokensDriver, payload: payload); } // ضغط الصورة Future testCompressAndGetFile(File file, String targetPath) async { var newPath = targetPath .replaceAll(".jpg", "_compressed.webp") .replaceAll(".jpeg", "_compressed.webp"); var result = await FlutterImageCompress.compressAndGetFile( file.absolute.path, newPath, quality: 88, format: CompressFormat.webp, rotate: 0, ); return File(result!.path); } // استخراج تاريخ الميلاد من الرقم القومي المصري String extractDOB(String nationalId) { if (nationalId.length != 14) return ''; String century = nationalId[0] == '2' ? '19' : '20'; String year = nationalId.substring(1, 3); String month = nationalId.substring(3, 5); String day = nationalId.substring(5, 7); return "$century$year-$month-$day"; } // توزيع البيانات القادمة من Gemini void _assignDataToVariable(String type, Map data) { switch (type) { case 'driver_license': responseIdEgyptDriverLicense = data; break; case 'car_front': responseIdCardDriverEgyptFront = data; break; case 'car_back': responseIdCardDriverEgyptBack = data; break; case 'id_front': responseIdEgyptFront = data; break; case 'id_back': responseIdEgyptBack = data; break; case 'non_id_front': responseNonIdCardFront = data; break; case 'non_id_back': responseNonIdCardBack = data; break; case 'criminal_record': responseCriminalRecordEgypt = data; break; } update(); // تحديث الواجهة لعرض البيانات الجديدة } // ترجمة أنواع الوثائق للعربية لرسائل الخطأ String _getReadableName(String type) { switch (type) { case 'driver_license': return 'رخصة القيادة (الوجه)'; case 'car_front': return 'رخصة السيارة (الوجه - اللوحة)'; case 'car_back': return 'رخصة السيارة (الخلف - البيانات)'; case 'id_front': return 'البطاقة الشخصية (الوجه)'; case 'id_back': return 'البطاقة الشخصية (الخلف)'; case 'non_id_front': return 'إقامة أجانب (الوجه)'; case 'non_id_back': return 'إقامة أجانب (الخلف)'; case 'criminal_record': return 'صحيفة الحالة الجنائية (الفيش)'; default: return 'الوثيقة المطلوبة'; } } // ترجمة ما اكتشفه الذكاء الاصطناعي (لرسالة الخطأ) String _translateDetectedType(String englishText) { String lower = englishText.toLowerCase(); if (lower.contains('back') && lower.contains('vehicle')) { return 'ظهر رخصة السيارة'; } if (lower.contains('front') && lower.contains('vehicle')) { return 'وجه رخصة السيارة'; } if (lower.contains('back') && lower.contains('id')) { return 'ظهر البطاقة الشخصية'; } if (lower.contains('front') && lower.contains('id')) { return 'وجه البطاقة الشخصية'; } if (lower.contains('driver')) return 'رخصة قيادة'; if (lower.contains('car')) return 'رخصة سيارة'; return 'وثيقة مختلفة'; } // التحقق من صحة البيانات (هل الحقول فارغة؟) bool _isValidDocumentData(String type, Map data) { if (data.containsKey('is_wrong_document') && data['is_wrong_document'] == true) { return false; } switch (type) { case 'driver_license': return (data['national_number'] != null && data['national_number'].toString().length > 5) || (data['name_arabic'] != null); case 'car_front': return data['car_plate'] != null; case 'car_back': return data['chassis'] != null || data['motor'] != null || data['color'] != null; case 'id_front': return data['full_name'] != null || data['card_id'] != null; case 'id_back': return data['nationalID'] != null; case 'criminal_record': return data['NationalID'] != null || data['FullName'] != null; default: return true; } } bool isInviteDriverFound = false; Future updateInvitationCodeFromRegister() async { var res = await CRUD().post( link: AppLink.updateDriverInvitationDirectly, payload: { "inviterDriverPhone": '+2${box.read(BoxName.phoneDriver).toString()}', "driverId": box.read(BoxName.driverID).toString(), }, ); if (res != 'failure') { isInviteDriverFound = true; update(); // mySnackbarSuccess("Code approved".tr); // Localized success message box.write(BoxName.isInstall, '1'); NotificationController().showNotification( "Code approved".tr, "Code approved".tr, 'tone2', ''); // Notification text with dynamic token NotificationService.sendNotification( target: jsonDecode(res)['message'][0]['token'].toString(), title: 'You have received a gift token!'.tr, body: 'for '.tr + box.read(BoxName.phoneDriver).toString(), category: 'gift_token', tone: 'tone2', isTopic: false, ); } else { // mySnackeBarError( // "You dont have invitation code".tr); // Localized error message } } // =========================================================================== // حفظ الفيش الجنائي فقط (تحديث حالة السائق) // =========================================================================== Future saveCriminalRecordOnly() async { isLoading = true; update(); try { // تجهيز البيانات var payload = { 'driver_id': box.read(BoxName.driverID) ?? box.read(BoxName.driverID), 'status_result': responseCriminalRecordEgypt['InspectionResult'] ?? 'Unknown', 'issue_date': responseCriminalRecordEgypt['IssueDate'] ?? '', 'national_id': responseCriminalRecordEgypt['NationalID'] ?? '', // يفضل إرسال الاسم أيضاً للتأكيد 'full_name': responseCriminalRecordEgypt['FullName'] ?? '', }; // الاتصال بملف PHP الجديد // تأكد من إضافة هذا الرابط في ملف links.dart var res = await CRUD().post( link: '${AppLink.server}${'/auth/captin/update_criminal_record.php'}', payload: payload); var json = jsonDecode(res); if (json['status'] == 'success') { // نجاح Get.snackbar( "تم بنجاح".tr, "تم التحقق من صحيفة الحالة الجنائية وحفظها في ملفك".tr, backgroundColor: AppColor.greenColor, colorText: Colors.white, duration: const Duration(seconds: 4), ); // العودة للصفحة الرئيسية أو الإعدادات Get.offAll(() => HomeCaptain()); } else { // فشل Get.snackbar( "تنبيه".tr, "فشل الحفظ: ${json['message']}".tr, backgroundColor: AppColor.redColor, colorText: Colors.white, ); } } catch (e) { Log.print("Error saving criminal record: $e"); Get.snackbar("خطأ".tr, "حدث خطأ أثناء الاتصال بالسيرفر".tr, backgroundColor: AppColor.redColor, colorText: Colors.white); } finally { isLoading = false; update(); } } // Future updatePassengersInvitation() async { // if (formKey.currentState!.validate()) { // var res = await CRUD().post( // link: AppLink.updatePassengersInvitation, // payload: {"inviteCode": invitationCodeController.text}); // if (res != 'failure') { // isInviteDriverFound = true; // update(); // mySnackbarSuccess("Code approved".tr); // } else { // mySnackeBarError("Code not approved".tr); // } // } // } } // إضافة بسيطة لتسهيل قراءة أول وآخر عنصر في المصفوفة (Extension) extension ListExtension on List { E? get firstOrNull => isEmpty ? null : first; E? get lastOrNull => isEmpty ? null : last; }