This commit is contained in:
Hamza-Ayed
2025-08-09 14:35:09 +03:00
parent ba02d41e6d
commit 889c67a691
35 changed files with 1689 additions and 417 deletions

View File

@@ -79,12 +79,14 @@ class LoginDriverController extends GetxController {
var res = await CRUD().get(
link: AppLink.getTesterApp,
payload: {'appPlatform': AppInformation.appName});
Log.print('res: ${res}');
if (res != 'failure') {
var d = jsonDecode(res);
isTest = d['message'][0]['isTest'];
Log.print('isTest: ${isTest}');
box.write(BoxName.isTest, isTest);
Log.print('isTest: ${box.read(BoxName.isTest)}');
// Log.print('isTest: ${box.read(BoxName.isTest)}');
update();
} else {
isTest = 0;
@@ -164,22 +166,22 @@ class LoginDriverController extends GetxController {
getJWT() async {
dev = Platform.isAndroid ? 'android' : 'ios';
Log.print(
'box.read(BoxName.firstTimeLoadKey): ${box.read(BoxName.firstTimeLoadKey)}');
// Log.print(
// 'box.read(BoxName.firstTimeLoadKey): ${box.read(BoxName.firstTimeLoadKey)}');
if (box.read(BoxName.firstTimeLoadKey).toString() != 'false') {
var payload = {
'id': box.read(BoxName.driverID) ?? AK.newId,
'password': AK.passnpassenger,
'aud': '${AK.allowed}$dev',
};
Log.print('payload: ${payload}');
// Log.print('payload: ${payload}');
var response0 = await http.post(
Uri.parse(AppLink.loginFirstTimeDriver),
body: payload,
);
Log.print('response0: ${response0.body}');
Log.print('request: ${response0.request}');
// Log.print('response0: ${response0.body}');
// Log.print('request: ${response0.request}');
if (response0.statusCode == 200) {
final decodedResponse1 = jsonDecode(response0.body);
// Log.print('decodedResponse1: ${decodedResponse1}');
@@ -207,13 +209,13 @@ class LoginDriverController extends GetxController {
'password': box.read(BoxName.emailDriver),
'aud': '${AK.allowed}$dev',
};
print(payload);
// print(payload);
var response1 = await http.post(
Uri.parse(AppLink.loginJwtDriver),
body: payload,
);
print(response1.request);
print(response1.body);
// print(response1.request);
// print(response1.body);
if (response1.statusCode == 200) {
final decodedResponse1 = jsonDecode(response1.body);
@@ -265,7 +267,7 @@ class LoginDriverController extends GetxController {
isloading = true;
update();
await SecurityHelper.performSecurityChecks();
Log.print('(BoxName.emailDriver): ${box.read(BoxName.emailDriver)}');
// Log.print('(BoxName.emailDriver): ${box.read(BoxName.emailDriver)}');
var res = await CRUD().get(link: AppLink.loginFromGoogleCaptin, payload: {
'email': email ?? 'yet',
@@ -329,8 +331,8 @@ class LoginDriverController extends GetxController {
String fingerPrint = await DeviceHelper.getDeviceFingerprint();
await storage.write(
key: BoxName.fingerPrint, value: fingerPrint.toString());
print(jsonDecode(token)['data'][0]['token'].toString());
print(box.read(BoxName.tokenDriver).toString());
// print(jsonDecode(token)['data'][0]['token'].toString());
// print(box.read(BoxName.tokenDriver).toString());
if (email == '962798583052@intaleqapp.com') {
} else {
if (token != 'failure') {
@@ -379,7 +381,7 @@ class LoginDriverController extends GetxController {
isloading = true;
update();
await SecurityHelper.performSecurityChecks();
Log.print('(BoxName.emailDriver): ${box.read(BoxName.emailDriver)}');
// Log.print('(BoxName.emailDriver): ${box.read(BoxName.emailDriver)}');
var res = await CRUD().get(link: AppLink.loginFromGoogleCaptin, payload: {
'email': email ?? 'yet',

View File

@@ -0,0 +1,365 @@
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();
}
}
}

View File

@@ -0,0 +1,39 @@
import 'package:battery_plus/battery_plus.dart';
import 'package:flutter/material.dart';
import 'package:get/get.dart';
class BatteryNotifier {
static final Battery _battery = Battery();
static int? _lastNotifiedLevel;
static Future<void> checkBatteryAndNotify() async {
try {
final int batteryLevel = await _battery.batteryLevel;
// ✅ لا تكرر الإشعار إذا الفرق قليل
if (_lastNotifiedLevel != null &&
(batteryLevel >= _lastNotifiedLevel! - 2)) return;
if (batteryLevel <= 30) {
Color backgroundColor = Colors.yellow;
if (batteryLevel <= 20) {
backgroundColor = Colors.red;
}
Get.snackbar(
"⚠️ تنبيه البطارية", // العنوان
"مستوى البطارية: $batteryLevel٪", // النص
snackPosition: SnackPosition.TOP,
backgroundColor: backgroundColor,
colorText: Colors.white,
duration: const Duration(seconds: 10), // مدة الظهور
margin: const EdgeInsets.all(10),
);
_lastNotifiedLevel = batteryLevel;
}
} catch (e) {
print('Battery check error: $e');
}
}
}

View File

@@ -43,9 +43,9 @@ class CRUD {
'Bearer ${X.r(X.r(X.r(box.read(BoxName.jwt), cn), cC), cs).toString().split(AppInformation.addd)[0]}'
},
);
print(response.request);
Log.print('response.body: ${response.body}');
print(payload);
// print(response.request);
// Log.print('response.body: ${response.body}');
// print(payload);
if (response.statusCode == 200) {
var jsonData = jsonDecode(response.body);
if (jsonData['status'] == 'success') {
@@ -94,9 +94,9 @@ class CRUD {
'X-HMAC-Auth': hmac.toString(),
},
);
Log.print('response.request: ${response.request}');
Log.print('response.body: ${response.body}');
print(payload);
// Log.print('response.request: ${response.request}');
// Log.print('response.body: ${response.body}');
// print(payload);
if (response.statusCode == 200) {
var jsonData = jsonDecode(response.body);
if (jsonData['status'] == 'success') {
@@ -145,9 +145,9 @@ class CRUD {
'X-HMAC-Auth': hmac.toString(),
},
);
Log.print('response.request:${response.request}');
Log.print('response.body: ${response.body}');
Log.print('payload:$payload');
// Log.print('response.request:${response.request}');
// Log.print('response.body: ${response.body}');
// Log.print('payload:$payload');
if (response.statusCode == 200) {
try {
var jsonData = jsonDecode(response.body);
@@ -203,9 +203,9 @@ class CRUD {
// 'Authorization': 'Bearer ${box.read(BoxName.jwt)}'
},
);
print(response.request);
Log.print('response.body: ${response.body}');
print(payload);
// print(response.request);
// Log.print('response.body: ${response.body}');
// print(payload);
if (response.statusCode == 200) {
try {
var jsonData = jsonDecode(response.body);

View File

@@ -0,0 +1,205 @@
import 'dart:async';
import 'dart:io';
import 'package:device_info_plus/device_info_plus.dart';
import 'performance_test.dart'; // Make sure this path is correct
/// Analyzes various device hardware and software aspects to generate a compatibility score.
/// This class provides a standardized output for the UI to consume easily.
class DeviceAnalyzer {
final DeviceInfoPlugin _deviceInfo = DeviceInfoPlugin();
/// Reads the total RAM from the system's meminfo file.
/// Returns the value in Megabytes (MB).
Future<double> _readTotalRamMB() async {
try {
final file = File('/proc/meminfo');
if (!await file.exists()) return 0.0;
final lines = await file.readAsLines();
for (var line in lines) {
if (line.startsWith('MemTotal')) {
// Extracts the numeric value from the line.
final kb = int.tryParse(RegExp(r'\d+').stringMatch(line) ?? '0') ?? 0;
return kb / 1024.0; // Convert from Kilobytes to Megabytes
}
}
} catch (e) {
print('❌ Error reading total RAM: $e');
}
return 0.0;
}
/// Reads the current RAM usage percentage from the system's meminfo file.
Future<double> _readUsedRamPercent() async {
try {
final file = File('/proc/meminfo');
if (!await file.exists()) return 0.0;
final lines = await file.readAsLines();
int? total, available;
for (var line in lines) {
if (line.startsWith('MemTotal')) {
total = int.tryParse(RegExp(r'\d+').stringMatch(line) ?? '');
} else if (line.startsWith('MemAvailable')) {
available = int.tryParse(RegExp(r'\d+').stringMatch(line) ?? '');
}
}
if (total != null && available != null && total > 0) {
final used = total - available;
return (used / total) * 100.0;
}
} catch (e) {
print('❌ Error reading used RAM: $e');
}
return 0.0;
}
/// The main analysis function that runs all checks.
Future<Map<String, dynamic>> analyzeDevice() async {
List<Map<String, dynamic>> details = [];
if (!Platform.isAndroid) {
return {
'score': 0,
'details': [
{
'label': 'النظام غير مدعوم',
'status': false,
'achieved_score': 0,
'max_score': 100
}
]
};
}
final info = await _deviceInfo.androidInfo;
final data = info.data;
final features = List<String>.from(data['systemFeatures'] ?? []);
// 1. Android Version (Max: 10 points)
final version =
int.tryParse(info.version.release?.split('.').first ?? '0') ?? 0;
final int androidScore = version >= 9 ? 10 : 0;
details.add({
'label': 'إصدار أندرويد ${info.version.release}',
'status': androidScore > 0,
'achieved_score': androidScore,
'max_score': 10,
});
// 2. Total RAM (Max: 10 points)
final totalRam = await _readTotalRamMB();
int ramScore;
if (totalRam >= 8000) {
ramScore = 10;
} else if (totalRam >= 4000) {
ramScore = 5;
} else if (totalRam >= 3000) {
ramScore = 3;
} else {
ramScore = 0;
}
details.add({
'label': 'إجمالي الرام ${totalRam.toStringAsFixed(0)} ميجابايت',
'status': ramScore >= 5,
'achieved_score': ramScore,
'max_score': 10,
});
// 3. CPU Cores (Max: 10 points)
final cores = Platform.numberOfProcessors;
int coreScore = cores >= 6 ? 10 : (cores >= 4 ? 5 : 0);
details.add({
'label': 'أنوية المعالج ($cores)',
'status': coreScore >= 5,
'achieved_score': coreScore,
'max_score': 10,
});
// 4. Free Storage (Max: 5 points)
final freeBytes = data['freeDiskSize'] ?? 0;
final freeGB = freeBytes / (1024 * 1024 * 1024);
int storeScore = freeGB >= 5 ? 5 : (freeGB >= 2 ? 3 : 0);
details.add({
'label': 'المساحة الحرة ${freeGB.toStringAsFixed(1)} جيجابايت',
'status': storeScore >= 3,
'achieved_score': storeScore,
'max_score': 5,
});
// 5. GPS + Gyroscope Sensors (Max: 10 points)
bool okSensors = features.contains('android.hardware.location.gps') &&
features.contains('android.hardware.sensor.gyroscope');
final int sensorScore = okSensors ? 10 : 0;
details.add({
'label': 'حساسات GPS و Gyroscope',
'status': okSensors,
'achieved_score': sensorScore,
'max_score': 10,
});
// 6. Storage Write Speed (Max: 20 points)
final writeSpeed = await PerformanceTester.testStorageWriteSpeed();
int writeScore;
if (writeSpeed >= 30) {
writeScore = 20;
} else if (writeSpeed >= 15) {
writeScore = 15;
} else if (writeSpeed >= 5) {
writeScore = 10;
} else {
writeScore = 5;
}
details.add({
'label': 'سرعة الكتابة (${writeSpeed.toStringAsFixed(1)} MB/s)',
'status': writeScore >= 10,
'achieved_score': writeScore,
'max_score': 20,
});
// 7. CPU Compute Speed (Max: 20 points)
final cpuTime = await PerformanceTester.testCPUSpeed();
int cpuScore;
if (cpuTime <= 1.0) {
cpuScore = 20;
} else if (cpuTime <= 2.5) {
cpuScore = 15;
} else if (cpuTime <= 4.0) {
cpuScore = 10;
} else {
cpuScore = 5;
}
details.add({
'label': 'سرعة المعالجة (${cpuTime.toStringAsFixed(2)} ثانية)',
'status': cpuScore >= 10,
'achieved_score': cpuScore,
'max_score': 20,
});
// 8. Memory Pressure (Max: 15 points)
final usedPercent = await _readUsedRamPercent();
int memScore;
if (usedPercent <= 60) {
memScore = 15;
} else if (usedPercent <= 80) {
memScore = 10;
} else if (usedPercent <= 90) {
memScore = 5;
} else {
memScore = 0;
}
details.add({
'label': 'استخدام الرام الحالي (${usedPercent.toStringAsFixed(0)}%)',
'status': memScore >= 10,
'achieved_score': memScore,
'max_score': 15,
});
// Calculate the final total score by summing up the achieved scores.
final totalScore = details.fold<int>(
0, (sum, item) => sum + (item['achieved_score'] as int));
return {
'score': totalScore.clamp(0, 100),
'details': details,
};
}
}

View File

@@ -3,7 +3,6 @@ import 'dart:convert';
import 'dart:io';
import 'dart:convert';
import 'package:crypto/crypto.dart';
import 'package:flutter_contacts/flutter_contacts.dart';
import 'package:sefer_driver/constant/box_name.dart';
import 'package:sefer_driver/constant/info.dart';
@@ -15,7 +14,6 @@ import 'package:sefer_driver/controller/firebase/local_notification.dart';
import 'package:sefer_driver/controller/functions/crud.dart';
import 'package:sefer_driver/env/env.dart';
import 'package:sefer_driver/main.dart';
import 'package:sefer_driver/views/home/Captin/home_captain/home_captin.dart';
import 'package:sefer_driver/views/widgets/error_snakbar.dart';
import 'package:sefer_driver/views/widgets/mydialoug.dart';
import 'package:flutter/material.dart';
@@ -565,7 +563,7 @@ class AI extends GetxController {
Future<void> pickAndSendImage(String type) async {
final picker = ImagePicker();
final pickedImage = await picker.pickImage(source: ImageSource.gallery);
final pickedImage = await picker.pickImage(source: ImageSource.camera);
if (pickedImage != null) {
image = File(pickedImage.path);
// Crop the image

View File

@@ -13,6 +13,7 @@ import '../../main.dart';
import '../../print.dart';
import '../home/captin/home_captain_controller.dart';
import '../home/payment/captain_wallet_controller.dart';
import 'battery_status.dart';
import 'crud.dart';
import 'encrypt_decrypt.dart';
@@ -84,6 +85,7 @@ class LocationController extends GetxController {
_locationTimer =
Timer.periodic(const Duration(seconds: 5), (timer) async {
try {
await BatteryNotifier.checkBatteryAndNotify();
totalPoints =
Get.find<CaptainWalletController>().totalPoints.toString();
isActive = Get.find<HomeCaptainController>().isActive;

View File

@@ -0,0 +1,43 @@
import 'dart:io';
class PerformanceTester {
/// ✅ فحص سرعة الكتابة إلى التخزين (Storage Write Speed) بوحدة MB/s
static Future<double> testStorageWriteSpeed() async {
try {
final tempDir = Directory.systemTemp;
final testFile = File('${tempDir.path}/speed_test.txt');
final data = List<int>.filled(1024 * 1024 * 5, 0); // 5MB
final stopwatch = Stopwatch()..start();
await testFile.writeAsBytes(data, flush: true);
stopwatch.stop();
await testFile.delete();
double seconds = stopwatch.elapsedMilliseconds / 1000;
if (seconds == 0) seconds = 0.001;
final speed = 5 / seconds;
return double.parse(speed.toStringAsFixed(2));
} catch (e) {
print("❌ Storage write error: $e");
return 0.0;
}
}
/// ✅ فحص سرعة المعالج (CPU Compute Speed) بوحدة الثواني
static Future<double> testCPUSpeed() async {
try {
final stopwatch = Stopwatch()..start();
double x = 0;
for (int i = 0; i < 100000000; i++) {
x += i * 0.000001;
}
stopwatch.stop();
return stopwatch.elapsedMilliseconds / 1000.0;
} catch (e) {
print("❌ CPU compute error: $e");
return 999.0;
}
}
}

View File

@@ -2,6 +2,7 @@ import 'dart:async';
import 'dart:convert';
import 'dart:io';
import 'dart:math';
import 'dart:math' as math;
import 'package:sefer_driver/controller/home/captin/behavior_controller.dart';
import 'package:sefer_driver/controller/home/captin/home_captain_controller.dart';
import 'package:sefer_driver/views/widgets/mydialoug.dart';
@@ -399,6 +400,9 @@ class MapDriverController extends GetxController {
remainingTimeInPassengerLocatioWait = 0;
timeWaitingPassenger = 0;
box.write(BoxName.statusDriverLocation, 'on');
// // ابدأ التتبع الحيّ لخطوات الملاحة
await startListeningStepNavigation();
// box.write(BoxName.rideStatus, 'Begin'); //
// todo ride details
// Get.find<HomeCaptainController>().changeToAppliedRide('Begin');
@@ -615,7 +619,7 @@ class MapDriverController extends GetxController {
// originalDistanceM - distanceToDestination;
// 3. عتبة ثلث المسافة
final oneThirdDistanceM = originalDistanceM / 3;
final oneThirdDistanceM = originalDistanceM / 4;
// Logging للتتبع
Log.print('originalDistanceM: $originalDistanceM');
@@ -624,7 +628,7 @@ class MapDriverController extends GetxController {
Log.print('oneThirdDistanceM: $oneThirdDistanceM');
// 4. إذا لم يقطع السائق ثلث المسافة، نعرض التأكيد
if (movedDistanceM > oneThirdDistanceM * 2) {
if (movedDistanceM > oneThirdDistanceM) {
MyDialog().getDialog(
'Are you sure to exit ride?'.tr,
'',
@@ -1321,6 +1325,12 @@ class MapDriverController extends GetxController {
}
}
List<LatLngBounds> stepBounds = [];
List<LatLng> stepEndPoints = [];
List<String> stepInstructions = [];
StreamSubscription<Position>? _posSub;
DateTime _lastCameraUpdateTs = DateTime.fromMillisecondsSinceEpoch(0);
getMapDestination(String origin, destination) async {
var url =
('${AppLink.googleMapsLink}directions/json?&language=${box.read(BoxName.lang)}&avoid=tolls|ferries&destination=$destination&origin=$origin&key=${AK.mapAPIKEY}');
@@ -1329,56 +1339,251 @@ class MapDriverController extends GetxController {
dataDestination = response['routes'][0]['legs'];
final points =
decodePolyline(response["routes"][0]["overview_polyline"]["points"]);
polylineCoordinatesDestination.clear();
for (int i = 0; i < points.length; i++) {
double lat = points[i][0].toDouble();
double lng = points[i][1].toDouble();
polylineCoordinatesDestination.add(LatLng(lat, lng));
}
// استخراج الخطوات
// الخطوات من أول leg
routeSteps = List<Map<String, dynamic>>.from(dataDestination[0]['steps']);
Log.print('routeSteps: ${routeSteps}');
// حضّر بيانات كل step
_prepareStepData(routeSteps);
currentStepIndex = 0;
if (routeSteps.isNotEmpty) {
currentInstruction =
_parseInstruction(routeSteps[0]['html_instructions']);
Log.print('currentInstruction: ${currentInstruction}');
if (stepInstructions.isNotEmpty) {
currentInstruction = _parseInstruction(stepInstructions[0]);
Log.print('currentInstruction: $currentInstruction');
Get.isRegistered<TextToSpeechController>()
? Get.find<TextToSpeechController>().speakText(currentInstruction)
: Get.put(TextToSpeechController()).speakText(currentInstruction);
}
// ارسم الـ polyline الرئيسي (مثل ما لديك)
final summary = response["routes"][0]["summary"];
final polyline = Polyline(
polylineId: PolylineId(summary.isEmpty ? 'route' : summary),
points: polylineCoordinatesDestination,
width: 10,
color: AppColor.redColor,
);
polyLinesDestination.add(polyline);
// fit للخريطة على أول step
if (stepBounds.isNotEmpty) {
_fitToBounds(stepBounds[0]);
}
update();
// دالة مساعدة لتنظيف التعليمات
// // ابدأ التتبع الحيّ لخطوات الملاحة
// await startListeningStepNavigation();
}
if (polyLinesDestination.isNotEmpty) {
// clearPolyline();
var polyline = Polyline(
polylineId: PolylineId(response["routes"][0]["summary"]),
points: polylineCoordinatesDestination,
width: 10,
color: AppColor.redColor,
);
polyLinesDestination.add(polyline);
// rideConfirm = false;
update();
} else {
var polyline = Polyline(
polylineId: PolylineId(response["routes"][0]["summary"]),
points: polylineCoordinatesDestination,
width: 10,
color: AppColor.redColor,
);
// final dataBounds = response["routes"][0]["bounds"];
// ————————————————————————————————————————————————————————————————
// يُنادى عند كل تحديث للموقع
void _onLocationTick(LatLng pos) async {
if (routeSteps.isEmpty || currentStepIndex >= stepBounds.length) return;
// updateCameraFromBoundsAfterGetMap(dataBounds);
// polyLinesDestination.add(polyline);
// rideConfirm = false;
// Define the northeast and southwest coordinates
// إذا تجاوزت نهاية الـ step الحالية بمسافة صغيرة -> انتقل
final double dToEnd = _distanceMeters(pos, stepEndPoints[currentStepIndex]);
final bool nearEnd = dToEnd <= 25; // عتبة 25م (عدّلها حسب حاجتك)
update();
// إذا دخلت نطاق الـ step التالية -> انتقل
bool insideNext = false;
if (currentStepIndex < stepBounds.length - 1) {
insideNext = _contains(stepBounds[currentStepIndex + 1], pos);
}
if (nearEnd || insideNext) {
_advanceStep();
return;
}
// تحديث الكاميرا بشكل خفيف أثناء الحركة (كل 2 ثانية مثلاً)
final now = DateTime.now();
if (now.difference(_lastCameraUpdateTs).inMilliseconds > 2000) {
_lastCameraUpdateTs = now;
// ركّز على bounds الحالية مع الحفاظ على تتبّع عام
_fitToBounds(stepBounds[currentStepIndex], padding: 60);
}
}
LatLngBounds _boundsFromPoints(List<LatLng> pts) {
double? minLat, maxLat, minLng, maxLng;
for (final p in pts) {
minLat = (minLat == null) ? p.latitude : math.min(minLat, p.latitude);
maxLat = (maxLat == null) ? p.latitude : math.max(maxLat, p.latitude);
minLng = (minLng == null) ? p.longitude : math.min(minLng, p.longitude);
maxLng = (maxLng == null) ? p.longitude : math.max(maxLng, p.longitude);
}
return LatLngBounds(
southwest: LatLng(minLat ?? 0, minLng ?? 0),
northeast: LatLng(maxLat ?? 0, maxLng ?? 0),
);
}
bool _contains(LatLngBounds b, LatLng p) {
final south = math.min(b.southwest.latitude, b.northeast.latitude);
final north = math.max(b.southwest.latitude, b.northeast.latitude);
final west = math.min(b.southwest.longitude, b.northeast.longitude);
final east = math.max(b.southwest.longitude, b.northeast.longitude);
return (p.latitude >= south &&
p.latitude <= north &&
p.longitude >= west &&
p.longitude <= east);
}
double _distanceMeters(LatLng a, LatLng b) {
// هافرساين مبسطة
const R = 6371000.0; // m
final dLat = _deg2rad(b.latitude - a.latitude);
final dLng = _deg2rad(b.longitude - a.longitude);
final s1 = math.sin(dLat / 2);
final s2 = math.sin(dLng / 2);
final aa = s1 * s1 +
math.cos(_deg2rad(a.latitude)) *
math.cos(_deg2rad(b.latitude)) *
s2 *
s2;
final c = 2 * math.atan2(math.sqrt(aa), math.sqrt(1 - aa));
return R * c;
}
double _deg2rad(double d) => d * math.pi / 180.0;
// تحريك الكاميرا لباوند معيّن
Future<void> _fitToBounds(LatLngBounds b, {double padding = 40}) async {
if (mapController == null) return;
try {
// أحياناً يلزم انتظار فريم حتى تكون الخريطة مرسومة
await Future.delayed(const Duration(milliseconds: 50));
await mapController!.animateCamera(
CameraUpdate.newLatLngBounds(b, padding),
);
} catch (_) {
// fallback لو حصلت مشكلة الحجم
final center = LatLng(
(b.northeast.latitude + b.southwest.latitude) / 2,
(b.northeast.longitude + b.southwest.longitude) / 2,
);
await mapController!.animateCamera(CameraUpdate.newLatLng(center));
}
}
// الانتقال للخطوة التالية وتحديث التعليمات والكاميرا
void _advanceStep() {
if (currentStepIndex >= stepBounds.length - 1) return;
currentStepIndex++;
currentInstruction = _parseInstruction(stepInstructions[currentStepIndex]);
// نطق التعليمات
if (Get.isRegistered<TextToSpeechController>()) {
Get.find<TextToSpeechController>().speakText(currentInstruction);
} else {
Get.put(TextToSpeechController()).speakText(currentInstruction);
}
// تركيز الكاميرا على باوند الخطوة الجديدة
_fitToBounds(stepBounds[currentStepIndex]);
update();
}
void _prepareStepData(List<Map<String, dynamic>> steps) {
stepBounds.clear();
stepEndPoints.clear();
stepInstructions.clear();
for (final s in steps) {
// 1) instruction
final html = (s['html_instructions'] ?? '').toString();
stepInstructions.add(html);
// 2) end point
final end = s['end_location'];
final endLatLng = LatLng(
(end['lat'] as num).toDouble(), (end['lng'] as num).toDouble());
stepEndPoints.add(endLatLng);
// 3) bounds من الـ polyline (إن لم يوجد bounds جاهز من الـ API)
List<LatLng> pts = [];
if (s['polyline'] != null && s['polyline']['points'] != null) {
final decoded = decodePolyline(s['polyline']['points']);
for (var p in decoded) {
pts.add(LatLng((p[0] as num).toDouble(), (p[1] as num).toDouble()));
}
} else {
// fallback: استخدم start/end فقط
final start = s['start_location'];
pts.add(LatLng((start['lat'] as num).toDouble(),
(start['lng'] as num).toDouble()));
pts.add(endLatLng);
}
stepBounds.add(_boundsFromPoints(pts));
}
}
// getMapDestination(String origin, destination) async {
// var url =
// ('${AppLink.googleMapsLink}directions/json?&language=${box.read(BoxName.lang)}&avoid=tolls|ferries&destination=$destination&origin=$origin&key=${AK.mapAPIKEY}');
// var response = await CRUD().getGoogleApi(link: url, payload: {});
// dataDestination = response['routes'][0]['legs'];
// final points =
// decodePolyline(response["routes"][0]["overview_polyline"]["points"]);
// for (int i = 0; i < points.length; i++) {
// double lat = points[i][0].toDouble();
// double lng = points[i][1].toDouble();
// polylineCoordinatesDestination.add(LatLng(lat, lng));
// }
// // استخراج الخطوات
// routeSteps = List<Map<String, dynamic>>.from(dataDestination[0]['steps']);
// Log.print('routeSteps: ${routeSteps}');
// currentStepIndex = 0;
// if (routeSteps.isNotEmpty) {
// currentInstruction =
// _parseInstruction(routeSteps[0]['html_instructions']);
// Log.print('currentInstruction: ${currentInstruction}');
// Get.isRegistered<TextToSpeechController>()
// ? Get.find<TextToSpeechController>().speakText(currentInstruction)
// : Get.put(TextToSpeechController()).speakText(currentInstruction);
// }
// update();
// // دالة مساعدة لتنظيف التعليمات
// if (polyLinesDestination.isNotEmpty) {
// // clearPolyline();
// var polyline = Polyline(
// polylineId: PolylineId(response["routes"][0]["summary"]),
// points: polylineCoordinatesDestination,
// width: 10,
// color: AppColor.redColor,
// );
// polyLinesDestination.add(polyline);
// // rideConfirm = false;
// update();
// } else {
// var polyline = Polyline(
// polylineId: PolylineId(response["routes"][0]["summary"]),
// points: polylineCoordinatesDestination,
// width: 10,
// color: AppColor.redColor,
// );
// // final dataBounds = response["routes"][0]["bounds"];
// // updateCameraFromBoundsAfterGetMap(dataBounds);
// // polyLinesDestination.add(polyline);
// // rideConfirm = false;
// // Define the northeast and southwest coordinates
// update();
// }
// }
void updateCameraFromBoundsAfterGetMap(dynamic response) {
final bounds = response["routes"][0]["bounds"];
LatLng northeast =
@@ -1516,4 +1721,19 @@ class MapDriverController extends GetxController {
// checkIsDriverNearPassenger();
super.onInit();
}
Future<void> startListeningStepNavigation() async {
_posSub?.cancel();
_posSub = Geolocator.getPositionStream(
locationSettings: const LocationSettings(
accuracy: LocationAccuracy.best,
distanceFilter: 5, // حدّث كل ~5 متر لتقليل الاهتزاز
),
).listen((pos) => _onLocationTick(LatLng(pos.latitude, pos.longitude)));
}
void stopListeningStepNavigation() {
_posSub?.cancel();
_posSub = null;
}
}

View File

@@ -369,11 +369,71 @@ Raih Gai: For same-day return trips longer than 50km.
"Add a comment (optional)": "أضف تعليقاً (اختياري)",
"Type something...": "اكتب شيئاً...",
"Submit rating": "إرسال التقييم",
'Trip Summary with': "ملخص الرحلة مع",
'Original Fare': "الأجرة الأصلية",
'Your Earnings': "أرباحك",
'Is device compatible': "هل الجهاز متوافق",
'Exclusive offers and discounts always with the Intaleq app':
"عروض وخصومات حصرية دائماً مع تطبيق انطلق",
"Driver Registration": "تسجيل السائق",
"Driver": "السائق",
"Vehicle": "المركبة",
"Docs": "الوثائق",
"Driver's Personal Information": "المعلومات الشخصية للسائق",
"First Name": "الاسم الأول",
"Last Name": "اسم العائلة",
"National ID Number": "الرقم الوطني",
"License Expiry Date": "تاريخ انتهاء الرخصة",
"YYYY-MM-DD": "YYYY-MM-DD",
"Please select a date": "يرجى اختيار تاريخ",
"Vehicle Information": "معلومات المركبة",
"Car Plate Number": "رقم اللوحة",
"Car Make (e.g., Toyota)": "نوع السيارة (مثال تويوتا)",
"Car Model (e.g., Corolla)": "طراز السيارة (مثال كورولا)",
"Year of Manufacture": "سنة الصنع",
"Car Color": "لون السيارة",
"Upload Documents": "رفع المستندات",
"Driver License (Front)": "رخصة السائق (أمام)",
"Driver License (Back)": "رخصة السائق (خلف)",
"Car Registration (Front)": "ترخيص السيارة (أمام)",
"Car Registration (Back)": "ترخيص السيارة (خلف)",
"Tap to upload": "اضغط للرفع",
"<< BACK": "« السابق",
"NEXT >>": "التالي »",
"SUBMIT": "إرسال",
"Required field": "حقل مطلوب",
"Expired License": "الرخصة منتهية",
"Your drivers license has expired.": "انتهت صلاحية رخصة قيادتك.",
"Missing Documents": "مستندات ناقصة",
"Please upload all 4 required documents.":
"يرجى رفع المستندات الأربعة جميعها.",
"Success": "تم بنجاح",
"Registration completed successfully!": "تم التسجيل بنجاح!",
"Error": "خطأ",
"Car Color (Name)": "لون السيارة (الاسم)",
"Car Color (Hex)": "لون السيارة (كود HEX)",
"Required field": "حقل مطلوب",
"color.white": "أبيض",
"color.black": "أسود",
"color.silver": "فضي",
"color.gray": "رمادي",
"color.gunmetal": "رصاصي غامق",
"color.red": "أحمر",
"color.blue": "أزرق",
"color.navy": "كحلي",
"color.green": "أخضر",
"color.darkGreen": "أخضر داكن",
"color.beige": "بيج",
"color.brown": "بني",
"color.maroon": "خمري",
"color.burgundy": "برغندي",
"color.yellow": "أصفر",
"color.orange": "برتقالي",
"color.gold": "ذهبي",
"color.bronze": "برونزي",
"color.champagne": "شامبانيا",
"color.purple": "بنفسجي",
"Registration failed. Please try again.": "فشل التسجيل، حاول مجدداً.",
"An unexpected error occurred:": "حدث خطأ غير متوقع:",
'Your email not updated yet': "بريدك الإلكتروني لم يتم تحديثه بعد",
'Enter your email'
'''Types of Trips in Intaleq: