Initial push to my private server

This commit is contained in:
Hamza-Ayed
2025-09-21 15:02:12 +03:00
parent 7e904ae460
commit f08ee61a7e
32 changed files with 1622 additions and 373 deletions

View File

@@ -1,18 +1,18 @@
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: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:path_provider/path_provider.dart' as path_provider;
import 'package:get/get.dart';
import 'package:http/http.dart' as http;
import 'package:http_parser/http_parser.dart';
import 'package:mime/mime.dart';
@@ -21,8 +21,10 @@ 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
@@ -41,6 +43,11 @@ class RegistrationController extends GetxController {
// 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>();
@@ -233,6 +240,142 @@ class RegistrationController extends GetxController {
}
}
/// خريطة لتخزين روابط المستندات بعد الرفع
final Map<String, String> docUrls = {
'driver_license_front': '',
'driver_license_back': '',
'car_license_front': '',
'car_license_back': '',
};
/// التصرّف العام لاختيار/قص/ضغط/رفع الصورة حسب type
Future<void> choosImage(String link, String imageType) async {
try {
final pickedImage = await picker.pickImage(
source: ImageSource.camera,
preferredCameraDevice: CameraDevice.rear,
);
if (pickedImage == null) 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) return;
// صورة للمعاينة داخل التطبيق
myImage = File(croppedFile.path);
isloading = true;
update();
// ضغط (وأيضاً يمكنك إضافة rotateImageIfNeeded قبل/بعد الضغط إن رغبت)
final File compressedImage = await compressImage(File(croppedFile.path));
// تجهيز الحقول
final driverId = box.read(BoxName.driverID);
final payload = <String, String>{
'driverID': driverId,
'imageType': imageType, // مثال: driver_license_front
};
// الرفع وإرجاع الرابط
final String imageUrl = await uploadImage(compressedImage, payload, link);
// حفظ الرابط محلياً حسب النوع
docUrls[imageType] = imageUrl;
Log.print('✅ Uploaded $imageType => $imageUrl');
} catch (e, st) {
Log.print('❌ Error in choosImage: $e\n$st');
mySnackeBarError('Image Upload Failed'.tr);
} finally {
isloading = false;
update();
}
}
/// ترفع الملف وترجع رابط الصورة النهائي كـ String
Future<String> uploadImage(
File file, Map<String, String> data, String link) async {
final uri = Uri.parse(link);
final request = http.MultipartRequest('POST', uri);
// الهيدرز (كما عندك)
final headers = <String, String>{
'Authorization':
'Bearer ${r(box.read(BoxName.jwt)).split(AppInformation.addd)[0]}',
'X-HMAC-Auth': '${box.read(BoxName.hmac)}',
};
request.headers.addAll(headers);
// اسم الملف: driverID.jpg (اختياري)
final forcedName = '${box.read(BoxName.driverID) ?? 'image'}.jpg';
// إضافة الملف (من المسار مباشرة أسلم من الـ stream)
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();
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);
final String? 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;
}
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);
}
// دالة رفع إلى السيرفر السوري: ترجع file_url (Signed URL)
Future<String> uploadToSyria({
required String docType,
@@ -317,30 +460,17 @@ class RegistrationController extends GetxController {
}
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;
}
// 0) دوال/مساعدات محلية
// 1) تحقق من وجود الروابط بدل الملفات
final driverFrontUrl = docUrls['driver_license_front'];
final driverBackUrl = docUrls['driver_license_back'];
final carFrontUrl = docUrls['car_license_front'];
final carBackUrl = docUrls['car_license_back'];
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 registerUri = Uri.parse(AppLink.register_driver_and_car);
final client = http.Client();
try {
@@ -349,50 +479,7 @@ class RegistrationController extends GetxController {
'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) جهّز طلب التسجيل الرئيسي: نرسل الحقول + روابط الصور (لا نرفع الصور مرة ثانية)
// 2) جهّز طلب التسجيل الرئيسي: حقول فقط + روابط الصور (لا نرفع صور إطلاقًا)
final req = http.MultipartRequest('POST', registerUri);
req.headers.addAll({
'Authorization': bearer,
@@ -411,18 +498,16 @@ class RegistrationController extends GetxController {
_addField(
fields, 'password', 'generate_your_password_here'); // عدّل حسب منطقك
_addField(fields, 'status', 'yet');
_addField(fields, 'email',
'Not specified'); // السيرفر سيحوّلها null ويبني ايميل افتراضي
_addField(fields, 'email', 'Not specified');
_addField(fields, 'gender', 'Male');
// --- Car Data ---
_addField(fields, 'vin', 'yet'); // تم تصحيح الاقتباس
_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, 'expiration_date', driverLicenseExpiryController.text);
_addField(fields, 'color', carColorController.text);
_addField(fields, 'fuel', 'Gasoline');
if (colorHex != null && colorHex!.isNotEmpty) {
@@ -431,32 +516,32 @@ class RegistrationController extends GetxController {
_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);
// --- روابط الصور المخزنة مسبقًا ---
_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) الإرسال
// 3) الإرسال
final streamed =
await client.send(req).timeout(const Duration(seconds: 60));
final resp = await http.Response.fromStream(streamed);
// 5) فحص النتيجة
// 4) فحص النتيجة
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);
}
// final driverID =
// (json!['data']?['driverID'] ?? json['driverID'])?.toString();
// if (driverID != null && driverID.isNotEmpty) {
// box.write(BoxName.driverID, driverID);
// }
Get.snackbar(
'Success'.tr,
@@ -466,20 +551,31 @@ class RegistrationController extends GetxController {
colorText: Colors.white,
);
// TODO: التنقّل أو تحديث الحالة…
final email = box.read<String?>(BoxName.emailDriver) ?? '';
// متابعة تسجيل الدخول إن لزم
final email = box.read(BoxName.emailDriver);
final driverID = box.read(BoxName.driverID);
final c = Get.isRegistered<LoginDriverController>()
? Get.find<LoginDriverController>()
: Get.put(LoginDriverController());
//token to server
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(),
});
await CRUD().post(link: AppLink.addTokensDriverWallet, payload: {
'token': box.read(BoxName.tokenDriver).toString(),
'fingerPrint': fingerPrint.toString(),
'captain_id': box.read(BoxName.driverID).toString(),
});
c.loginWithGoogleCredential(driverId, email);
c.loginWithGoogleCredential(driverID, email);
} else {
final msg =
(json?['message'] ?? 'Registration failed. Please try again.')
.toString();
Log.print('msg: ${msg}');
Log.print('msg: $msg');
Get.snackbar(
'Error'.tr,
msg,
@@ -500,8 +596,7 @@ class RegistrationController extends GetxController {
client.close();
isLoading.value = false;
}
}
// Future<void> submitRegistration() async {
} // Future<void> submitRegistration() async {
// // 1) تحقق من الصور
// if (driverLicenseFrontImage == null ||
// driverLicenseBackImage == null ||