diff --git a/android/app/build.gradle b/android/app/build.gradle index 3faa35f..7891675 100644 --- a/android/app/build.gradle +++ b/android/app/build.gradle @@ -46,8 +46,8 @@ android { // For more information, see: https://flutter.dev/to/review-gradle-config. minSdk = 23 targetSdk = flutter.targetSdkVersion - versionCode = 7 - versionName = '1.0.7' + versionCode = 8 + versionName = '1.0.8' multiDexEnabled =true } diff --git a/android/app/src/main/AndroidManifest.xml b/android/app/src/main/AndroidManifest.xml index 5b329ca..6ae7deb 100644 --- a/android/app/src/main/AndroidManifest.xml +++ b/android/app/src/main/AndroidManifest.xml @@ -2,7 +2,6 @@ - diff --git a/android/app/src/main/res/raw/app_icon.png b/android/app/src/main/res/raw/app_icon.png new file mode 100755 index 0000000..96cc55a Binary files /dev/null and b/android/app/src/main/res/raw/app_icon.png differ diff --git a/android/app/src/main/res/raw/cancel.wav b/android/app/src/main/res/raw/cancel.wav new file mode 100755 index 0000000..dbe6e7c Binary files /dev/null and b/android/app/src/main/res/raw/cancel.wav differ diff --git a/android/app/src/main/res/raw/ding.wav b/android/app/src/main/res/raw/ding.wav new file mode 100755 index 0000000..c53cd50 Binary files /dev/null and b/android/app/src/main/res/raw/ding.wav differ diff --git a/android/app/src/main/res/raw/iphone_ringtone.wav b/android/app/src/main/res/raw/iphone_ringtone.wav new file mode 100755 index 0000000..aee314f Binary files /dev/null and b/android/app/src/main/res/raw/iphone_ringtone.wav differ diff --git a/android/app/src/main/res/raw/order.wav b/android/app/src/main/res/raw/order.wav new file mode 100755 index 0000000..b2b0ba2 Binary files /dev/null and b/android/app/src/main/res/raw/order.wav differ diff --git a/android/app/src/main/res/raw/order1.wav b/android/app/src/main/res/raw/order1.wav new file mode 100755 index 0000000..919fdda Binary files /dev/null and b/android/app/src/main/res/raw/order1.wav differ diff --git a/android/app/src/main/res/raw/promo.wav b/android/app/src/main/res/raw/promo.wav new file mode 100755 index 0000000..3bb2d03 Binary files /dev/null and b/android/app/src/main/res/raw/promo.wav differ diff --git a/android/app/src/main/res/raw/start.wav b/android/app/src/main/res/raw/start.wav new file mode 100755 index 0000000..0655506 Binary files /dev/null and b/android/app/src/main/res/raw/start.wav differ diff --git a/android/app/src/main/res/raw/tone1.mp3 b/android/app/src/main/res/raw/tone1.mp3 new file mode 100755 index 0000000..e32e61b Binary files /dev/null and b/android/app/src/main/res/raw/tone1.mp3 differ diff --git a/android/app/src/main/res/raw/tone2.wav b/android/app/src/main/res/raw/tone2.wav new file mode 100755 index 0000000..0582d47 Binary files /dev/null and b/android/app/src/main/res/raw/tone2.wav differ diff --git a/lib/controller/auth/captin/login_captin_controller.dart b/lib/controller/auth/captin/login_captin_controller.dart index 227521d..33c90e3 100755 --- a/lib/controller/auth/captin/login_captin_controller.dart +++ b/lib/controller/auth/captin/login_captin_controller.dart @@ -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', diff --git a/lib/controller/auth/syria/registration_controller.dart b/lib/controller/auth/syria/registration_controller.dart new file mode 100644 index 0000000..996cf3f --- /dev/null +++ b/lib/controller/auth/syria/registration_controller.dart @@ -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(); + final carInfoFormKey = GlobalKey(); + + // 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 driver’s 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 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> 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 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 = {}; + + // --- 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 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? json; + try { + json = jsonDecode(resp.body) as Map; + } 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 fields, String key, String? value) { + if (value != null && value.toString().trim().isNotEmpty) { + fields[key] = value.toString().trim(); + } + } +} diff --git a/lib/controller/functions/battery_status.dart b/lib/controller/functions/battery_status.dart new file mode 100644 index 0000000..a47470a --- /dev/null +++ b/lib/controller/functions/battery_status.dart @@ -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 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'); + } + } +} diff --git a/lib/controller/functions/crud.dart b/lib/controller/functions/crud.dart index 99aab26..afc0684 100755 --- a/lib/controller/functions/crud.dart +++ b/lib/controller/functions/crud.dart @@ -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); diff --git a/lib/controller/functions/device_analyzer.dart b/lib/controller/functions/device_analyzer.dart new file mode 100644 index 0000000..82c3fa0 --- /dev/null +++ b/lib/controller/functions/device_analyzer.dart @@ -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 _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 _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> analyzeDevice() async { + List> 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.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( + 0, (sum, item) => sum + (item['achieved_score'] as int)); + + return { + 'score': totalScore.clamp(0, 100), + 'details': details, + }; + } +} diff --git a/lib/controller/functions/gemeni.dart b/lib/controller/functions/gemeni.dart index 588194e..eee53df 100755 --- a/lib/controller/functions/gemeni.dart +++ b/lib/controller/functions/gemeni.dart @@ -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 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 diff --git a/lib/controller/functions/location_controller.dart b/lib/controller/functions/location_controller.dart index f4af1f8..995e297 100755 --- a/lib/controller/functions/location_controller.dart +++ b/lib/controller/functions/location_controller.dart @@ -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().totalPoints.toString(); isActive = Get.find().isActive; diff --git a/lib/controller/functions/performance_test.dart b/lib/controller/functions/performance_test.dart new file mode 100644 index 0000000..de86828 --- /dev/null +++ b/lib/controller/functions/performance_test.dart @@ -0,0 +1,43 @@ +import 'dart:io'; + +class PerformanceTester { + /// ✅ فحص سرعة الكتابة إلى التخزين (Storage Write Speed) بوحدة MB/s + static Future testStorageWriteSpeed() async { + try { + final tempDir = Directory.systemTemp; + final testFile = File('${tempDir.path}/speed_test.txt'); + final data = List.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 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; + } + } +} diff --git a/lib/controller/home/captin/map_driver_controller.dart b/lib/controller/home/captin/map_driver_controller.dart index e51c71e..2e8f918 100755 --- a/lib/controller/home/captin/map_driver_controller.dart +++ b/lib/controller/home/captin/map_driver_controller.dart @@ -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().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 stepBounds = []; + List stepEndPoints = []; + List stepInstructions = []; + StreamSubscription? _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>.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() ? Get.find().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 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 _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()) { + Get.find().speakText(currentInstruction); + } else { + Get.put(TextToSpeechController()).speakText(currentInstruction); + } + + // تركيز الكاميرا على باوند الخطوة الجديدة + _fitToBounds(stepBounds[currentStepIndex]); + update(); + } + + void _prepareStepData(List> 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 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>.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() +// ? Get.find().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 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; + } } diff --git a/lib/controller/local/translations.dart b/lib/controller/local/translations.dart index 98e1e85..a9fb46f 100755 --- a/lib/controller/local/translations.dart +++ b/lib/controller/local/translations.dart @@ -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 driver’s 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: diff --git a/lib/device_compatibility_page.dart b/lib/device_compatibility_page.dart new file mode 100644 index 0000000..6f3a02f --- /dev/null +++ b/lib/device_compatibility_page.dart @@ -0,0 +1,248 @@ +import 'package:flutter/material.dart'; +import 'package:get/get.dart'; +import 'package:fl_chart/fl_chart.dart'; +import 'controller/functions/device_analyzer.dart'; + +// --- CompatibilityDetailCard Widget (Updated to use 'max_score') --- +class CompatibilityDetailCard extends StatelessWidget { + final Map detail; + const CompatibilityDetailCard({super.key, required this.detail}); + + Color _getStatusColor(bool status, int achieved, int max) { + if (!status) return Colors.red.shade400; + if (achieved < max) return Colors.orange.shade600; + return Colors.teal; + } + + IconData _getIconForLabel(String label) { + if (label.contains('رام')) return Icons.memory; + if (label.contains('معالج') || label.contains('CPU')) { + return Icons.developer_board; + } + if (label.contains('تخزين') || label.contains('كتابة')) { + return Icons.sd_storage_outlined; + } + if (label.contains('أندرويد')) return Icons.android; + if (label.contains('خدمات')) return Icons.g_mobiledata; + if (label.contains('حساسات') || label.contains('Gyroscope')) { + return Icons.sensors; + } + return Icons.smartphone; + } + + @override + Widget build(BuildContext context) { + final bool status = detail['status'] ?? false; + final String label = detail['label'] ?? ""; + final int achievedScore = detail['achieved_score'] ?? 0; + // Corrected to use 'max_score' from the analyzer + final int maxScore = detail['max_score'] ?? 1; + final Color color = _getStatusColor(status, achievedScore, maxScore); + final double progress = + (maxScore > 0) ? (achievedScore / maxScore).clamp(0.0, 1.0) : 0.0; + + return Container( + margin: const EdgeInsets.symmetric(horizontal: 16, vertical: 7), + padding: const EdgeInsets.all(16), + decoration: BoxDecoration( + color: Colors.white, + borderRadius: BorderRadius.circular(16), + boxShadow: [ + BoxShadow( + color: Colors.grey.withOpacity(0.08), + blurRadius: 15, + offset: const Offset(0, 5), + ) + ], + ), + child: Column( + crossAxisAlignment: CrossAxisAlignment.start, + children: [ + Row( + children: [ + Icon(_getIconForLabel(label), + color: Colors.grey.shade600, size: 20), + const SizedBox(width: 8), + Expanded( + child: Text( + label, + style: TextStyle( + fontSize: 15, + color: Colors.grey.shade800, + fontWeight: FontWeight.w600), + ), + ), + // Corrected to display points out of max_score + Text( + "$achievedScore/$maxScore نقطة", + style: TextStyle( + color: color, fontWeight: FontWeight.bold, fontSize: 14), + ), + ], + ), + const SizedBox(height: 12), + LinearProgressIndicator( + value: progress, + backgroundColor: Colors.grey.shade200, + color: color, + minHeight: 6, + borderRadius: BorderRadius.circular(3), + ), + ], + ), + ); + } +} + +// --- Main Page Widget --- +class DeviceCompatibilityPage extends StatefulWidget { + const DeviceCompatibilityPage({super.key}); + @override + State createState() => + _DeviceCompatibilityPageState(); +} + +class _DeviceCompatibilityPageState extends State { + int score = 0; + List> details = []; + bool isLoading = true; + + @override + void initState() { + super.initState(); + _initializePage(); + } + + Future _initializePage() async { + // await BatteryNotifier.checkBatteryAndNotify(); + final result = await DeviceAnalyzer().analyzeDevice(); + + if (mounted) { + setState(() { + score = result['score']; + details = List>.from(result['details']); + isLoading = false; + }); + } + } + + Color _getColorForScore(int value) { + if (value >= 80) return Colors.teal; + if (value >= 60) return Colors.orange.shade700; + return Colors.red.shade600; + } + + String _getScoreMessage(int value) { + if (value >= 80) return "جهازك يقدم أداءً ممتازاً"; + if (value >= 60) return "جهازك جيد ومناسب جداً"; + if (value >= 40) return "متوافق، قد تلاحظ بعض البطء"; + return "قد لا يعمل التطبيق بالشكل الأمثل"; + } + + @override + Widget build(BuildContext context) { + return Scaffold( + backgroundColor: const Color(0xFFF7F8FC), + appBar: AppBar( + title: const Text("توافق الجهاز", + style: + TextStyle(color: Colors.black87, fontWeight: FontWeight.bold)), + centerTitle: true, + backgroundColor: Colors.transparent, + elevation: 0, + iconTheme: const IconThemeData(color: Colors.black87), + ), + body: isLoading + ? const Center(child: CircularProgressIndicator(color: Colors.teal)) + : Column( + children: [ + _buildScoreHeader(), + Expanded( + child: ListView.builder( + padding: const EdgeInsets.only(top: 10, bottom: 20), + itemCount: details.length, + itemBuilder: (context, i) => + CompatibilityDetailCard(detail: details[i]), + ), + ), + Padding( + padding: const EdgeInsets.fromLTRB(16, 8, 16, 24), + child: ElevatedButton( + style: ElevatedButton.styleFrom( + backgroundColor: Colors.teal, + minimumSize: const Size(double.infinity, 50), + shape: RoundedRectangleBorder( + borderRadius: BorderRadius.circular(12)), + elevation: 0, + ), + onPressed: () => Get.back(), + child: const Text("المتابعة إلى التطبيق", + style: TextStyle( + fontSize: 16, + fontWeight: FontWeight.bold, + color: Colors.white)), + ), + ), + ], + ), + ); + } + + /// ## Corrected Score Header Widget + /// This widget now uses a `Stack` to correctly place the text over the `PieChart`. + Widget _buildScoreHeader() { + return Container( + margin: const EdgeInsets.symmetric(horizontal: 16), + height: 220, // Give the container a fixed height + child: Stack( + alignment: Alignment.center, + children: [ + // Layer 1: The Pie Chart + PieChart( + PieChartData( + sectionsSpace: 4, + // This creates the "hole" in the middle. + centerSpaceRadius: 80, + startDegreeOffset: -90, + sections: [ + PieChartSectionData( + color: _getColorForScore(score), + value: score.toDouble(), + title: '', + radius: 25, + ), + PieChartSectionData( + color: Colors.grey.shade200, + value: (100 - score).toDouble().clamp(0, 100), + title: '', + radius: 25, + ), + ], + ), + ), + // Layer 2: The text and message, centered on top of the chart + Column( + mainAxisAlignment: MainAxisAlignment.center, + children: [ + Text( + "$score%", + style: TextStyle( + fontSize: 52, + fontWeight: FontWeight.bold, + color: _getColorForScore(score)), + ), + const SizedBox(height: 4), + Text( + _getScoreMessage(score), + style: TextStyle( + color: Colors.grey.shade700, + fontSize: 16, + fontWeight: FontWeight.w500), + ), + ], + ), + ], + ), + ); + } +} diff --git a/lib/main.dart b/lib/main.dart index 5b8f69e..5a3d0e9 100755 --- a/lib/main.dart +++ b/lib/main.dart @@ -17,6 +17,7 @@ import 'constant/api_key.dart'; import 'constant/info.dart'; import 'controller/firebase/firbase_messge.dart'; import 'controller/firebase/local_notification.dart'; +import 'controller/functions/battery_status.dart'; import 'controller/functions/encrypt_decrypt.dart'; import 'controller/functions/secure_storage.dart'; import 'controller/local/local_controller.dart'; diff --git a/lib/views/auth/syria/registration_view.dart b/lib/views/auth/syria/registration_view.dart new file mode 100644 index 0000000..88a23bc --- /dev/null +++ b/lib/views/auth/syria/registration_view.dart @@ -0,0 +1,371 @@ +import 'dart:io'; +import 'package:flutter/material.dart'; +import 'package:get/get.dart'; + +import '../../../controller/auth/syria/registration_controller.dart'; + +class RegistrationView extends StatelessWidget { + const RegistrationView({Key? key}) : super(key: key); + + @override + Widget build(BuildContext context) { + final RegistrationController controller = Get.put(RegistrationController()); + + return Scaffold( + appBar: AppBar( + title: Text('Driver Registration'.tr), + centerTitle: true, + ), + body: Column( + children: [ + SizedBox( + height: 90, + child: Obx( + () => Stepper( + currentStep: controller.currentPage.value, + type: StepperType.horizontal, + controlsBuilder: (_, __) => const SizedBox.shrink(), + steps: [ + Step( + title: Text('Driver'.tr), + content: const SizedBox.shrink()), + Step( + title: Text('Vehicle'.tr), + content: const SizedBox.shrink()), + Step( + title: Text('Docs'.tr), content: const SizedBox.shrink()), + ], + ), + ), + ), + Expanded( + child: PageView( + controller: controller.pageController, + physics: const NeverScrollableScrollPhysics(), + onPageChanged: (i) => controller.currentPage.value = i, + children: [ + _buildDriverInfoStep(context, controller), + _buildCarInfoStep(context, controller), + _buildDocumentUploadStep(context, controller), + ], + ), + ), + ], + ), + bottomNavigationBar: _buildBottomNavBar(controller), + ); + } + + // STEP 1 + Widget _buildDriverInfoStep(BuildContext ctx, RegistrationController c) { + return SingleChildScrollView( + padding: const EdgeInsets.all(16), + child: Form( + key: c.driverInfoFormKey, + child: Column( + crossAxisAlignment: CrossAxisAlignment.start, + children: [ + Text("Driver's Personal Information".tr, + style: + const TextStyle(fontSize: 20, fontWeight: FontWeight.bold)), + const SizedBox(height: 20), + TextFormField( + controller: c.firstNameController, + decoration: InputDecoration( + labelText: 'First Name'.tr, + border: const OutlineInputBorder()), + validator: (v) => + (v?.isEmpty ?? true) ? 'Required field'.tr : null, + ), + const SizedBox(height: 16), + TextFormField( + controller: c.lastNameController, + decoration: InputDecoration( + labelText: 'Last Name'.tr, + border: const OutlineInputBorder()), + validator: (v) => + (v?.isEmpty ?? true) ? 'Required field'.tr : null, + ), + const SizedBox(height: 16), + TextFormField( + controller: c.nationalIdController, + decoration: InputDecoration( + labelText: 'National ID Number'.tr, + border: const OutlineInputBorder()), + keyboardType: TextInputType.number, + validator: (v) => + (v?.isEmpty ?? true) ? 'Required field'.tr : null, + ), + const SizedBox(height: 16), + TextFormField( + controller: c.driverLicenseExpiryController, + decoration: InputDecoration( + labelText: 'License Expiry Date'.tr, + hintText: 'YYYY-MM-DD'.tr, + border: const OutlineInputBorder()), + readOnly: true, + onTap: () async { + DateTime? d = await showDatePicker( + context: ctx, + initialDate: DateTime.now(), + firstDate: DateTime(2000), + lastDate: DateTime(2101), + ); + if (d != null) { + c.driverLicenseExpiryDate = d; + c.driverLicenseExpiryController.text = + d.toLocal().toString().split(' ')[0]; + } + }, + validator: (v) => + (v?.isEmpty ?? true) ? 'Please select a date'.tr : null, + ), + ], + ), + ), + ); + } + + // STEP 2 + Widget _buildCarInfoStep(BuildContext ctx, RegistrationController c) { + return SingleChildScrollView( + padding: const EdgeInsets.all(16), + child: Form( + key: c.carInfoFormKey, + child: Column( + crossAxisAlignment: CrossAxisAlignment.start, + children: [ + Text('Vehicle Information'.tr, + style: + const TextStyle(fontSize: 20, fontWeight: FontWeight.bold)), + const SizedBox(height: 20), + TextFormField( + controller: c.carPlateController, + decoration: InputDecoration( + labelText: 'Car Plate Number'.tr, + border: const OutlineInputBorder()), + validator: (v) => + (v?.isEmpty ?? true) ? 'Required field'.tr : null, + ), + const SizedBox(height: 16), + TextFormField( + controller: c.carMakeController, + decoration: InputDecoration( + labelText: 'Car Make (e.g., Toyota)'.tr, + border: const OutlineInputBorder()), + validator: (v) => + (v?.isEmpty ?? true) ? 'Required field'.tr : null, + ), + const SizedBox(height: 16), + TextFormField( + controller: c.carModelController, + decoration: InputDecoration( + labelText: 'Car Model (e.g., Corolla)'.tr, + border: const OutlineInputBorder()), + validator: (v) => + (v?.isEmpty ?? true) ? 'Required field'.tr : null, + ), + const SizedBox(height: 16), + TextFormField( + controller: c.carYearController, + keyboardType: TextInputType.number, + decoration: InputDecoration( + labelText: 'Year of Manufacture'.tr, + border: const OutlineInputBorder()), + validator: (v) => + (v?.isEmpty ?? true) ? 'Required field'.tr : null, + ), + const SizedBox(height: 16), + // حقل اسم اللون (يبقى اختياري أو نملؤه تلقائيًا عند اختيار الهكس) + // TextFormField( + // controller: c.carColorController, + // decoration: InputDecoration( + // labelText: 'Car Color (Name)'.tr, + // border: const OutlineInputBorder(), + // ), + // validator: (v) => + // (v?.isEmpty ?? true) ? 'Required field'.tr : null, + // ), + // const SizedBox(height: 16), + +// الدروب داون للهكس + دائرة اللون + GetBuilder( + id: 'carColor', // اختياري لتحديث انتقائي + builder: (c) { + return DropdownButtonFormField( + value: (c.colorHex != null && c.colorHex!.isNotEmpty) + ? c.colorHex + : null, + isExpanded: true, + decoration: InputDecoration( + labelText: 'Car Color (Hex)'.tr, + border: const OutlineInputBorder(), + // prefixIcon: Padding( + // padding: + // const EdgeInsetsDirectional.only(start: 12, end: 8), + // child: Container( + // width: 18, + // height: 18, + // decoration: BoxDecoration( + // color: (c.colorHex?.isNotEmpty ?? false) + // ? c.hexToColor(c.colorHex!) + // : Colors.transparent, + // shape: BoxShape.circle, + // border: Border.all(color: Colors.black26), + // ), + // ), + // ), + ), + items: RegistrationController.kCarColorOptions.map((opt) { + final hex = opt['hex']!; + final key = opt['key']!; + return DropdownMenuItem( + value: hex, + child: Row( + children: [ + Container( + width: 18, + height: 18, + decoration: BoxDecoration( + color: c.hexToColor(hex), + shape: BoxShape.circle, + border: Border.all(color: Colors.black12), + ), + ), + const SizedBox(width: 8), + Expanded(child: Text(key.tr)), + ], + ), + ); + }).toList(), + onChanged: (hex) { + c.colorHex = hex; // خزّن الهكس + final key = RegistrationController.kCarColorOptions + .firstWhere((o) => o['hex'] == hex)['key']!; + c.carColorController.text = key.tr; + c.update([ + 'carColor' + ]); // <-- مهم: يعيد بناء الودجت ويحدّث الدائرة + }, + validator: (v) => + (v == null || v.isEmpty) ? 'Required field'.tr : null, + ); + }, + ) + ], + ), + ), + ); + } + + // STEP 3 + Widget _buildDocumentUploadStep(BuildContext ctx, RegistrationController c) { + return GetBuilder( + builder: (ctrl) => SingleChildScrollView( + padding: const EdgeInsets.all(16), + child: Column( + crossAxisAlignment: CrossAxisAlignment.start, + children: [ + Text('Upload Documents'.tr, + style: + const TextStyle(fontSize: 20, fontWeight: FontWeight.bold)), + const SizedBox(height: 20), + _buildImagePickerBox( + 'Driver License (Front)'.tr, + ctrl.driverLicenseFrontImage, + () => ctrl.pickImage(ImageType.driverLicenseFront), + ), + _buildImagePickerBox( + 'Driver License (Back)'.tr, + ctrl.driverLicenseBackImage, + () => ctrl.pickImage(ImageType.driverLicenseBack), + ), + _buildImagePickerBox( + 'Car Registration (Front)'.tr, + ctrl.carLicenseFrontImage, + () => ctrl.pickImage(ImageType.carLicenseFront), + ), + _buildImagePickerBox( + 'Car Registration (Back)'.tr, + ctrl.carLicenseBackImage, + () => ctrl.pickImage(ImageType.carLicenseBack), + ), + ], + ), + ), + ); + } + + Widget _buildImagePickerBox(String title, File? img, VoidCallback onTap) { + return Card( + margin: const EdgeInsets.only(bottom: 16), + child: InkWell( + onTap: onTap, + child: SizedBox( + height: 150, + width: double.infinity, + child: img != null + ? Image.file(img, fit: BoxFit.fill) + : Column( + mainAxisAlignment: MainAxisAlignment.center, + children: [ + Icon(Icons.camera_alt_outlined, + size: 40, color: Colors.grey[600]), + const SizedBox(height: 8), + Text(title, style: TextStyle(color: Colors.grey[700])), + Text('Tap to upload'.tr, + style: + const TextStyle(color: Colors.grey, fontSize: 12)), + ], + ), + ), + ), + ); + } + + Widget _buildBottomNavBar(RegistrationController c) { + return Obx(() => Padding( + padding: const EdgeInsets.all(16), + child: Row( + mainAxisAlignment: MainAxisAlignment.spaceBetween, + children: [ + if (c.currentPage.value > 0) + TextButton( + onPressed: c.goToPreviousStep, + child: Text('<< BACK'.tr), + ), + const Spacer(), + ElevatedButton( + style: ElevatedButton.styleFrom( + padding: + const EdgeInsets.symmetric(horizontal: 40, vertical: 12), + backgroundColor: c.currentPage.value == 2 + ? Colors.green + : Theme.of(Get.context!).primaryColor, + ), + onPressed: c.isLoading.value + ? null + : () { + if (c.currentPage.value == 2) { + c.submitRegistration(); + } else { + c.goToNextStep(); + } + }, + child: c.isLoading.value + ? const SizedBox( + width: 20, + height: 20, + child: CircularProgressIndicator( + color: Colors.white, strokeWidth: 2)) + : Text( + c.currentPage.value == 2 ? 'SUBMIT'.tr : 'NEXT >>'.tr, + style: + const TextStyle(fontSize: 16, color: Colors.white), + ), + ), + ], + ), + )); + } +} diff --git a/lib/views/home/Captin/About Us/video_page.dart b/lib/views/home/Captin/About Us/video_page.dart index 62b1c7c..734c40d 100755 --- a/lib/views/home/Captin/About Us/video_page.dart +++ b/lib/views/home/Captin/About Us/video_page.dart @@ -147,7 +147,7 @@ class VideoPlayerPage extends StatelessWidget { class VideoPlayerPage1 extends StatelessWidget { final String videoUrl; - VideoPlayerPage1({required this.videoUrl}); + const VideoPlayerPage1({required this.videoUrl}); @override Widget build(BuildContext context) { diff --git a/lib/views/home/Captin/home_captain/drawer_captain.dart b/lib/views/home/Captin/home_captain/drawer_captain.dart index d01779e..e99024f 100755 --- a/lib/views/home/Captin/home_captain/drawer_captain.dart +++ b/lib/views/home/Captin/home_captain/drawer_captain.dart @@ -5,6 +5,7 @@ import 'package:sefer_driver/constant/box_name.dart'; import 'package:sefer_driver/constant/links.dart'; import 'package:sefer_driver/controller/functions/upload_image.dart'; import 'package:sefer_driver/controller/home/captin/home_captain_controller.dart'; +import 'package:sefer_driver/device_compatibility_page.dart'; import 'package:sefer_driver/main.dart'; // استيراد الصفحات الأخرى... تأكد من صحة المسارات @@ -105,6 +106,11 @@ class AppDrawer extends StatelessWidget { icon: Icons.star, color: Colors.amber, onTap: () => Get.to(() => RatingScreen())), + DrawerItem( + title: 'Is device compatible'.tr, + icon: Icons.memory, + color: Colors.greenAccent, + onTap: () => Get.to(() => DeviceCompatibilityPage())), DrawerItem( title: 'Settings'.tr, icon: Icons.settings, @@ -228,7 +234,10 @@ class UserHeader extends StatelessWidget { fontSize: 18, shadows: [Shadow(blurRadius: 2, color: Colors.black26)]), ), - accountEmail: Text(box.read(BoxName.emailDriver)), + accountEmail: + box.read(BoxName.emailDriver).toString().contains('intaleqapp') + ? Text('Your email not updated yet'.tr) + : Text(box.read(BoxName.emailDriver)), currentAccountPicture: GetBuilder( builder: (controller) => Stack( clipBehavior: Clip.none, diff --git a/lib/views/home/Captin/home_captain/widget/left_menu_map_captain.dart b/lib/views/home/Captin/home_captain/widget/left_menu_map_captain.dart index 4bc3964..ce8de89 100755 --- a/lib/views/home/Captin/home_captain/widget/left_menu_map_captain.dart +++ b/lib/views/home/Captin/home_captain/widget/left_menu_map_captain.dart @@ -1,25 +1,21 @@ import 'package:sefer_driver/constant/box_name.dart'; import 'package:sefer_driver/controller/firebase/local_notification.dart'; import 'package:sefer_driver/main.dart'; -import 'package:sefer_driver/views/auth/captin/login_captin.dart'; import 'package:sefer_driver/views/home/Captin/driver_map_page.dart'; import 'package:sefer_driver/views/home/Captin/orderCaptin/vip_order_page.dart'; import 'package:flutter/material.dart'; import 'package:flutter_font_icons/flutter_font_icons.dart'; import 'package:get/get.dart'; import 'package:sefer_driver/controller/home/captin/home_captain_controller.dart'; -import 'package:sefer_driver/views/home/my_wallet/points_captain.dart'; import 'package:sefer_driver/views/widgets/mydialoug.dart'; import '../../../../../constant/colors.dart'; import '../../../../../constant/links.dart'; -import '../../../../../controller/auth/google_sign.dart'; import '../../../../../controller/firebase/firbase_messge.dart'; import '../../../../../controller/functions/crud.dart'; import '../../../../../controller/home/captin/order_request_controller.dart'; import '../../../../Rate/ride_calculate_driver.dart'; -import '../../../../auth/captin/otp_page.dart'; -import '../../../my_wallet/ecash.dart'; +import '../../../../auth/syria/registration_view.dart'; GetBuilder leftMainMenuCaptainIcons() { final firebaseMessagesController = @@ -150,35 +146,28 @@ GetBuilder leftMainMenuCaptainIcons() { // height: 5, // ), - // AnimatedContainer( - // duration: const Duration(microseconds: 200), - // width: controller.widthMapTypeAndTraffic, - // decoration: BoxDecoration( - // color: AppColor.secondaryColor, - // border: Border.all(color: AppColor.blueColor), - // borderRadius: BorderRadius.circular(15)), - // child: Builder(builder: (context) { - // return IconButton( - // onPressed: () async { - // Get.to(PhoneNumberScreen()); - // // payWithEcashDriver(context, '2000'); - // // payWithMTNWallet(context, '1', 'SYP'); - // // firebaseMessagesController.sendNotificationToDriverMAP( - // // 'title', - // // DateTime.now().toString(), - // // 'ffX7xVXpdE_Xq8JBH3lgS4:APA91bGBHp53E-ZuXdlLBpRZohzqR9sazqcn3pwpEDG7JxkVi9MBtFDlCipzLpPCvD6LHEtds88ugGyCty7pEJVyx6tQYvzHVDCh7l3_7axpyriTBs5iv9E', - // // [], - // // ''); - // // box.write(BoxName.statusDriverLocation, 'off'); - // }, - // icon: const Icon( - // FontAwesome5.grin_tears, - // size: 29, - // color: AppColor.blueColor, - // ), - // ); - // }), - // ),f + AnimatedContainer( + duration: const Duration(microseconds: 200), + width: controller.widthMapTypeAndTraffic, + decoration: BoxDecoration( + color: AppColor.secondaryColor, + border: Border.all(color: AppColor.blueColor), + borderRadius: BorderRadius.circular(15)), + child: Builder(builder: (context) { + return IconButton( + onPressed: () async { + Get.to(() => const RegistrationView()); + + // box.write(BoxName.statusDriverLocation, 'off'); + }, + icon: const Icon( + FontAwesome5.grin_tears, + size: 29, + color: AppColor.blueColor, + ), + ); + }), + ), const SizedBox( height: 5, diff --git a/lib/views/home/Captin/mapDriverWidgets/driver_end_ride_bar.dart b/lib/views/home/Captin/mapDriverWidgets/driver_end_ride_bar.dart index 5ea34bf..32f12d3 100755 --- a/lib/views/home/Captin/mapDriverWidgets/driver_end_ride_bar.dart +++ b/lib/views/home/Captin/mapDriverWidgets/driver_end_ride_bar.dart @@ -1,310 +1,3 @@ -// import 'dart:io'; - -// import 'package:flutter/material.dart'; -// import 'package:flutter/services.dart'; -// import 'package:get/get.dart'; -// import 'package:slide_to_act/slide_to_act.dart'; -// import 'package:vibration/vibration.dart'; - -// import '../../../../constant/colors.dart'; -// import '../../../../constant/style.dart'; -// import '../../../../controller/home/captin/map_driver_controller.dart'; -// import '../../../widgets/elevated_btn.dart'; - -// GetBuilder driverEndRideBar() { -// return GetBuilder( -// builder: (mapDriverController) => mapDriverController.isRideStarted -// ? Positioned( -// left: 5, -// top: 5, -// right: 5, -// child: Container( -// decoration: AppStyle.boxDecoration1.copyWith( -// borderRadius: BorderRadius.circular(15), -// boxShadow: [ -// BoxShadow( -// color: Colors.black.withOpacity(0.1), -// blurRadius: 10, -// offset: Offset(0, 5), -// ), -// ], -// ), -// padding: const EdgeInsets.all(10), -// height: mapDriverController.remainingTimeTimerRideBegin < 60 -// ? mapDriverController.driverEndPage = 190 -// : mapDriverController.carType == 'Mishwar Vip' -// ? 120 -// : 170, -// child: Column( -// mainAxisAlignment: MainAxisAlignment.spaceAround, -// children: [ -// if (mapDriverController.carType != 'Mishwar Vip') -// Row( -// mainAxisAlignment: MainAxisAlignment.spaceEvenly, -// children: [ -// _buildInfoColumn( -// icon: Icons.social_distance, -// text: '${mapDriverController.distance} ${'KM'.tr}', -// ), -// _buildInfoColumn( -// icon: Icons.timelapse, -// text: mapDriverController.hours > 1 -// ? '${mapDriverController.hours} ${'H and'.tr} ${mapDriverController.minutes} m' -// : '${mapDriverController.minutes} ${'m'.tr}', -// ), -// _buildInfoColumn( -// icon: Icons.money_sharp, -// text: -// '${mapDriverController.paymentAmount} ${'\$'.tr}', -// ), -// ], -// ), -// if (mapDriverController.carType != 'Speed' && -// mapDriverController.carType != 'Awfar Car' && -// mapDriverController.carType != 'Scooter') -// Row( -// mainAxisAlignment: MainAxisAlignment.spaceAround, -// children: [ -// _buildInfoBox( -// icon: Icons.timer, -// text: -// mapDriverController.stringRemainingTimeRideBegin1, -// ), -// _buildInfoBox( -// icon: Icons.location_on, -// text: -// '${mapDriverController.recentDistanceToDash.toStringAsFixed(0)} ${'KM'.tr}', -// ), -// _buildInfoBox( -// icon: Icons.attach_money, -// text: mapDriverController.price.toStringAsFixed(2), -// ), -// ], -// ), -// _builtTimerAndCarType(), -// Container( -// width: Get.width * 0.8, -// decoration: BoxDecoration( -// borderRadius: BorderRadius.circular(15), -// boxShadow: [ -// BoxShadow( -// color: AppColor.redColor.withOpacity(0.3), -// blurRadius: 8, -// offset: Offset(0, 4), -// ), -// ], -// ), -// child: SlideAction( -// height: 50, -// borderRadius: 15, -// elevation: 4, -// text: 'Slide to End Trip'.tr, -// textStyle: AppStyle.title.copyWith( -// fontSize: 18, -// fontWeight: FontWeight.bold, -// color: Colors.white, -// ), -// outerColor: AppColor.redColor, -// innerColor: Colors.white, -// sliderButtonIcon: const Icon( -// Icons.arrow_forward_ios, -// color: AppColor.redColor, -// size: 24, -// ), -// sliderRotate: false, -// onSubmit: () { -// HapticFeedback.mediumImpact(); -// mapDriverController.finishRideFromDriver(); -// }, -// ), -// ) -// ], -// ), -// ), -// ) -// : const SizedBox(), -// ); -// } - -// class _builtTimerAndCarType extends StatelessWidget { -// const _builtTimerAndCarType({ -// super.key, -// }); - -// @override -// Widget build(BuildContext context) { -// final mapDriverController = Get.find(); -// return Row( -// mainAxisAlignment: MainAxisAlignment.spaceAround, -// children: [ -// Container( -// decoration: AppStyle.boxDecoration1.copyWith( -// boxShadow: [ -// BoxShadow( -// color: AppColor.accentColor.withOpacity(0.2), -// blurRadius: 8, -// offset: Offset(0, 4), -// ), -// ], -// ), -// padding: const EdgeInsets.symmetric(horizontal: 16, vertical: 8), -// child: Text( -// mapDriverController.carType, -// style: AppStyle.title, -// ), -// ), -// if (mapDriverController.carType != 'Comfort' && -// mapDriverController.carType != 'Mishwar Vip' && -// mapDriverController.carType != 'Lady') -// Container( -// width: Get.width * 0.6, -// decoration: BoxDecoration( -// borderRadius: BorderRadius.circular(12), -// gradient: LinearGradient( -// colors: [ -// mapDriverController.remainingTimeTimerRideBegin < 60 -// ? AppColor.redColor.withOpacity(0.8) -// : AppColor.greenColor.withOpacity(0.8), -// mapDriverController.remainingTimeTimerRideBegin < 60 -// ? AppColor.redColor -// : AppColor.greenColor, -// ], -// begin: Alignment.centerLeft, -// end: Alignment.centerRight, -// ), -// boxShadow: [ -// BoxShadow( -// color: (mapDriverController.remainingTimeTimerRideBegin < 60 -// ? AppColor.redColor -// : AppColor.greenColor) -// .withOpacity(0.3), -// blurRadius: 8, -// offset: Offset(0, 4), -// ), -// ], -// ), -// child: ClipRRect( -// borderRadius: BorderRadius.circular(12), -// child: Stack( -// children: [ -// LinearProgressIndicator( -// backgroundColor: Colors.white.withOpacity(0.2), -// valueColor: AlwaysStoppedAnimation( -// Colors.white.withOpacity(0.5), -// ), -// minHeight: 40, -// value: -// mapDriverController.progressTimerRideBegin.toDouble(), -// ), -// Center( -// child: AnimatedDefaultTextStyle( -// duration: Duration(milliseconds: 300), -// style: AppStyle.title.copyWith( -// color: Colors.white, -// fontWeight: FontWeight.bold, -// fontSize: -// mapDriverController.remainingTimeTimerRideBegin < 60 -// ? 18 -// : 16, -// shadows: [ -// Shadow( -// color: Colors.black26, -// offset: Offset(0, 2), -// blurRadius: 4, -// ), -// ], -// ), -// child: Text( -// mapDriverController.stringRemainingTimeRideBegin, -// ), -// ), -// ), -// ], -// ), -// ), -// ), -// ], -// ); -// } -// } - -// Widget _buildInfoColumn({required IconData icon, required String text}) { -// return Column( -// children: [ -// Icon(icon), -// Text( -// text, -// style: AppStyle.title, -// ), -// ], -// ); -// } - -// Widget _buildInfoBox({required IconData icon, required String text}) { -// return Container( -// width: Get.width * .2, -// decoration: AppStyle.boxDecoration1, -// padding: const EdgeInsets.all(4), -// child: Row( -// children: [ -// Icon(icon), -// SizedBox(width: 4), -// Text( -// text, -// style: AppStyle.number, -// ), -// ], -// ), -// ); -// } - -// GetBuilder speedCircle() { -// if (Get.find().speed > 100) { -// if (Platform.isIOS) { -// HapticFeedback.selectionClick(); -// } else { -// Vibration.vibrate(duration: 1000); -// } -// Get.defaultDialog( -// barrierDismissible: false, -// titleStyle: AppStyle.title, -// title: 'Speed Over'.tr, -// middleText: 'Please slow down'.tr, -// middleTextStyle: AppStyle.title, -// confirm: MyElevatedButton( -// title: 'I will slow down'.tr, -// onPressed: () => Get.back(), -// ), -// ); -// } -// return GetBuilder( -// builder: (mapDriverController) { -// return mapDriverController.isRideStarted -// ? Positioned( -// bottom: 25, -// right: 100, -// child: Container( -// decoration: BoxDecoration( -// shape: BoxShape.circle, -// color: mapDriverController.speed > 100 -// ? Colors.red -// : AppColor.secondaryColor, -// border: Border.all(width: 3, color: AppColor.redColor), -// ), -// height: 60, -// width: 60, -// child: Center( -// child: Text( -// mapDriverController.speed.toStringAsFixed(0), -// style: AppStyle.number, -// ), -// ), -// ), -// ) -// : const SizedBox(); -// }, -// ); -// } import 'package:flutter/material.dart'; import 'package:flutter/services.dart'; import 'package:get/get.dart'; @@ -355,7 +48,7 @@ GetBuilder driverEndRideBar() { ), _buildInfoColumn( icon: Icons.money_sharp, - text: '${controller.paymentAmount} ${'\$'.tr}', + text: '${controller.paymentAmount} ${'SYP'.tr}', label: 'Price'.tr, ), ], diff --git a/lib/views/home/Captin/mapDriverWidgets/google_driver_map_page.dart b/lib/views/home/Captin/mapDriverWidgets/google_driver_map_page.dart index e8f15bb..3e9c585 100755 --- a/lib/views/home/Captin/mapDriverWidgets/google_driver_map_page.dart +++ b/lib/views/home/Captin/mapDriverWidgets/google_driver_map_page.dart @@ -194,11 +194,10 @@ class GoogleDriverMap extends StatelessWidget { Marker( markerId: MarkerId('MyLocation'.tr), position: locationController.myLocation, - draggable: false, // Changed: لا يمكن سحب ماركر السائق - icon: controller.carIcon, rotation: locationController.heading, - anchor: const Offset( - 0.5, 0.5), // New: وضع نقطة ارتكاز الأيقونة في المنتصف + flat: true, + anchor: const Offset(0.5, 0.5), + icon: controller.carIcon, ), Marker( markerId: MarkerId('start'.tr), diff --git a/macos/Flutter/GeneratedPluginRegistrant.swift b/macos/Flutter/GeneratedPluginRegistrant.swift index f06987e..c18d8f2 100644 --- a/macos/Flutter/GeneratedPluginRegistrant.swift +++ b/macos/Flutter/GeneratedPluginRegistrant.swift @@ -6,6 +6,7 @@ import FlutterMacOS import Foundation import audio_session +import battery_plus import device_info_plus import file_selector_macos import firebase_auth @@ -35,6 +36,7 @@ import webview_flutter_wkwebview func RegisterGeneratedPlugins(registry: FlutterPluginRegistry) { AudioSessionPlugin.register(with: registry.registrar(forPlugin: "AudioSessionPlugin")) + BatteryPlusMacosPlugin.register(with: registry.registrar(forPlugin: "BatteryPlusMacosPlugin")) DeviceInfoPlusMacosPlugin.register(with: registry.registrar(forPlugin: "DeviceInfoPlusMacosPlugin")) FileSelectorPlugin.register(with: registry.registrar(forPlugin: "FileSelectorPlugin")) FLTFirebaseAuthPlugin.register(with: registry.registrar(forPlugin: "FLTFirebaseAuthPlugin")) diff --git a/pubspec.lock b/pubspec.lock index 63b2f3e..1dc9b93 100644 --- a/pubspec.lock +++ b/pubspec.lock @@ -73,6 +73,22 @@ packages: url: "https://pub.dev" source: hosted version: "0.1.25" + battery_plus: + dependency: "direct main" + description: + name: battery_plus + sha256: fb794c34cee2e4ea31005fb17ff15e1d904951ec7f15eedead741021870ee834 + url: "https://pub.dev" + source: hosted + version: "6.2.2" + battery_plus_platform_interface: + dependency: transitive + description: + name: battery_plus_platform_interface + sha256: e8342c0f32de4b1dfd0223114b6785e48e579bfc398da9471c9179b907fa4910 + url: "https://pub.dev" + source: hosted + version: "2.0.1" boolean_selector: dependency: transitive description: @@ -2114,6 +2130,14 @@ packages: url: "https://pub.dev" source: hosted version: "1.4.0" + upower: + dependency: transitive + description: + name: upower + sha256: cf042403154751180affa1d15614db7fa50234bc2373cd21c3db666c38543ebf + url: "https://pub.dev" + source: hosted + version: "0.7.0" url_launcher: dependency: "direct main" description: diff --git a/pubspec.yaml b/pubspec.yaml index a631bc7..b168d32 100755 --- a/pubspec.yaml +++ b/pubspec.yaml @@ -100,6 +100,7 @@ dependencies: flutter_svg: ^2.2.0 lottie: ^3.3.1 flutter_staggered_animations: ^1.1.1 + battery_plus: ^6.2.2 dev_dependencies: flutter_test: @@ -133,8 +134,6 @@ flutter: - assets/ - assets/images/ - assets/fonts/ - - assets/lang/ - # - shorebird.yaml fonts: # - family: mohanad diff --git a/windows/flutter/generated_plugin_registrant.cc b/windows/flutter/generated_plugin_registrant.cc index 5baf75d..54cc472 100644 --- a/windows/flutter/generated_plugin_registrant.cc +++ b/windows/flutter/generated_plugin_registrant.cc @@ -6,6 +6,7 @@ #include "generated_plugin_registrant.h" +#include #include #include #include @@ -20,6 +21,8 @@ #include void RegisterPlugins(flutter::PluginRegistry* registry) { + BatteryPlusWindowsPluginRegisterWithRegistrar( + registry->GetRegistrarForPlugin("BatteryPlusWindowsPlugin")); FileSelectorWindowsRegisterWithRegistrar( registry->GetRegistrarForPlugin("FileSelectorWindows")); FirebaseAuthPluginCApiRegisterWithRegistrar( diff --git a/windows/flutter/generated_plugins.cmake b/windows/flutter/generated_plugins.cmake index 769cfd2..163d6e9 100644 --- a/windows/flutter/generated_plugins.cmake +++ b/windows/flutter/generated_plugins.cmake @@ -3,6 +3,7 @@ # list(APPEND FLUTTER_PLUGIN_LIST + battery_plus file_selector_windows firebase_auth firebase_core