Files
intaleq_driver/lib/controller/auth/syria/registration_controller.dart
Hamza-Ayed 889c67a691 25-8-9-1
2025-08-09 14:35:09 +03:00

366 lines
13 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 '../../../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<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));
}
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!);
// (اختياري) هيدر للقبول بـ JSON
// 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();
}
}
}