Files
intaleq_driver/lib/controller/auth/syria/registration_controller.dart
2025-09-01 19:04:50 +03:00

638 lines
23 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: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 ---
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 '../../../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 {
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<FormState>();
final carInfoFormKey = GlobalKey<FormState>();
// 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 drivers 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<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();
}
}
// دالة رفع إلى السيرفر السوري: ترجع 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 ||
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;
// روابط الـ 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 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,
});
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', '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);
_addField(fields, 'fuel', 'Gasoline');
if (colorHex != null && colorHex!.isNotEmpty) {
_addField(fields, 'color_hex', colorHex!);
}
_addField(fields, 'owner',
'${firstNameController.text} ${lastNameController.text}');
// --- روابط الصور الموقّعة من سوريا ---
_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);
// 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?['status'] == 'success') {
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: التنقّل أو تحديث الحالة…
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();
Log.print('msg: ${msg}');
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;
}
}
// 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
}