25-8-9-1
This commit is contained in:
@@ -46,8 +46,8 @@ android {
|
|||||||
// For more information, see: https://flutter.dev/to/review-gradle-config.
|
// For more information, see: https://flutter.dev/to/review-gradle-config.
|
||||||
minSdk = 23
|
minSdk = 23
|
||||||
targetSdk = flutter.targetSdkVersion
|
targetSdk = flutter.targetSdkVersion
|
||||||
versionCode = 7
|
versionCode = 8
|
||||||
versionName = '1.0.7'
|
versionName = '1.0.8'
|
||||||
multiDexEnabled =true
|
multiDexEnabled =true
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -2,7 +2,6 @@
|
|||||||
<manifest xmlns:android="http://schemas.android.com/apk/res/android"
|
<manifest xmlns:android="http://schemas.android.com/apk/res/android"
|
||||||
xmlns:tools="http://schemas.android.com/tools">
|
xmlns:tools="http://schemas.android.com/tools">
|
||||||
|
|
||||||
<uses-permission android:name="android.permission.INTERNET" />
|
|
||||||
<uses-permission android:name="android.permission.ACCESS_FINE_LOCATION" />
|
<uses-permission android:name="android.permission.ACCESS_FINE_LOCATION" />
|
||||||
<uses-permission android:name="android.permission.ACCESS_COARSE_LOCATION" />
|
<uses-permission android:name="android.permission.ACCESS_COARSE_LOCATION" />
|
||||||
<uses-permission android:name="android.permission.ACCESS_BACKGROUND_LOCATION" />
|
<uses-permission android:name="android.permission.ACCESS_BACKGROUND_LOCATION" />
|
||||||
|
|||||||
Executable
BIN
Binary file not shown.
|
After Width: | Height: | Size: 61 KiB |
Executable
BIN
Binary file not shown.
Executable
BIN
Binary file not shown.
BIN
Binary file not shown.
Executable
BIN
Binary file not shown.
Executable
BIN
Binary file not shown.
Executable
BIN
Binary file not shown.
Executable
BIN
Binary file not shown.
Executable
BIN
Binary file not shown.
Executable
BIN
Binary file not shown.
@@ -79,12 +79,14 @@ class LoginDriverController extends GetxController {
|
|||||||
var res = await CRUD().get(
|
var res = await CRUD().get(
|
||||||
link: AppLink.getTesterApp,
|
link: AppLink.getTesterApp,
|
||||||
payload: {'appPlatform': AppInformation.appName});
|
payload: {'appPlatform': AppInformation.appName});
|
||||||
|
Log.print('res: ${res}');
|
||||||
if (res != 'failure') {
|
if (res != 'failure') {
|
||||||
var d = jsonDecode(res);
|
var d = jsonDecode(res);
|
||||||
isTest = d['message'][0]['isTest'];
|
isTest = d['message'][0]['isTest'];
|
||||||
|
Log.print('isTest: ${isTest}');
|
||||||
box.write(BoxName.isTest, isTest);
|
box.write(BoxName.isTest, isTest);
|
||||||
|
|
||||||
Log.print('isTest: ${box.read(BoxName.isTest)}');
|
// Log.print('isTest: ${box.read(BoxName.isTest)}');
|
||||||
update();
|
update();
|
||||||
} else {
|
} else {
|
||||||
isTest = 0;
|
isTest = 0;
|
||||||
@@ -164,22 +166,22 @@ class LoginDriverController extends GetxController {
|
|||||||
|
|
||||||
getJWT() async {
|
getJWT() async {
|
||||||
dev = Platform.isAndroid ? 'android' : 'ios';
|
dev = Platform.isAndroid ? 'android' : 'ios';
|
||||||
Log.print(
|
// Log.print(
|
||||||
'box.read(BoxName.firstTimeLoadKey): ${box.read(BoxName.firstTimeLoadKey)}');
|
// 'box.read(BoxName.firstTimeLoadKey): ${box.read(BoxName.firstTimeLoadKey)}');
|
||||||
if (box.read(BoxName.firstTimeLoadKey).toString() != 'false') {
|
if (box.read(BoxName.firstTimeLoadKey).toString() != 'false') {
|
||||||
var payload = {
|
var payload = {
|
||||||
'id': box.read(BoxName.driverID) ?? AK.newId,
|
'id': box.read(BoxName.driverID) ?? AK.newId,
|
||||||
'password': AK.passnpassenger,
|
'password': AK.passnpassenger,
|
||||||
'aud': '${AK.allowed}$dev',
|
'aud': '${AK.allowed}$dev',
|
||||||
};
|
};
|
||||||
Log.print('payload: ${payload}');
|
// Log.print('payload: ${payload}');
|
||||||
|
|
||||||
var response0 = await http.post(
|
var response0 = await http.post(
|
||||||
Uri.parse(AppLink.loginFirstTimeDriver),
|
Uri.parse(AppLink.loginFirstTimeDriver),
|
||||||
body: payload,
|
body: payload,
|
||||||
);
|
);
|
||||||
Log.print('response0: ${response0.body}');
|
// Log.print('response0: ${response0.body}');
|
||||||
Log.print('request: ${response0.request}');
|
// Log.print('request: ${response0.request}');
|
||||||
if (response0.statusCode == 200) {
|
if (response0.statusCode == 200) {
|
||||||
final decodedResponse1 = jsonDecode(response0.body);
|
final decodedResponse1 = jsonDecode(response0.body);
|
||||||
// Log.print('decodedResponse1: ${decodedResponse1}');
|
// Log.print('decodedResponse1: ${decodedResponse1}');
|
||||||
@@ -207,13 +209,13 @@ class LoginDriverController extends GetxController {
|
|||||||
'password': box.read(BoxName.emailDriver),
|
'password': box.read(BoxName.emailDriver),
|
||||||
'aud': '${AK.allowed}$dev',
|
'aud': '${AK.allowed}$dev',
|
||||||
};
|
};
|
||||||
print(payload);
|
// print(payload);
|
||||||
var response1 = await http.post(
|
var response1 = await http.post(
|
||||||
Uri.parse(AppLink.loginJwtDriver),
|
Uri.parse(AppLink.loginJwtDriver),
|
||||||
body: payload,
|
body: payload,
|
||||||
);
|
);
|
||||||
print(response1.request);
|
// print(response1.request);
|
||||||
print(response1.body);
|
// print(response1.body);
|
||||||
|
|
||||||
if (response1.statusCode == 200) {
|
if (response1.statusCode == 200) {
|
||||||
final decodedResponse1 = jsonDecode(response1.body);
|
final decodedResponse1 = jsonDecode(response1.body);
|
||||||
@@ -265,7 +267,7 @@ class LoginDriverController extends GetxController {
|
|||||||
isloading = true;
|
isloading = true;
|
||||||
update();
|
update();
|
||||||
await SecurityHelper.performSecurityChecks();
|
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: {
|
var res = await CRUD().get(link: AppLink.loginFromGoogleCaptin, payload: {
|
||||||
'email': email ?? 'yet',
|
'email': email ?? 'yet',
|
||||||
@@ -329,8 +331,8 @@ class LoginDriverController extends GetxController {
|
|||||||
String fingerPrint = await DeviceHelper.getDeviceFingerprint();
|
String fingerPrint = await DeviceHelper.getDeviceFingerprint();
|
||||||
await storage.write(
|
await storage.write(
|
||||||
key: BoxName.fingerPrint, value: fingerPrint.toString());
|
key: BoxName.fingerPrint, value: fingerPrint.toString());
|
||||||
print(jsonDecode(token)['data'][0]['token'].toString());
|
// print(jsonDecode(token)['data'][0]['token'].toString());
|
||||||
print(box.read(BoxName.tokenDriver).toString());
|
// print(box.read(BoxName.tokenDriver).toString());
|
||||||
if (email == '962798583052@intaleqapp.com') {
|
if (email == '962798583052@intaleqapp.com') {
|
||||||
} else {
|
} else {
|
||||||
if (token != 'failure') {
|
if (token != 'failure') {
|
||||||
@@ -379,7 +381,7 @@ class LoginDriverController extends GetxController {
|
|||||||
isloading = true;
|
isloading = true;
|
||||||
update();
|
update();
|
||||||
await SecurityHelper.performSecurityChecks();
|
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: {
|
var res = await CRUD().get(link: AppLink.loginFromGoogleCaptin, payload: {
|
||||||
'email': email ?? 'yet',
|
'email': email ?? 'yet',
|
||||||
|
|||||||
@@ -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 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<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();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -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');
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -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]}'
|
'Bearer ${X.r(X.r(X.r(box.read(BoxName.jwt), cn), cC), cs).toString().split(AppInformation.addd)[0]}'
|
||||||
},
|
},
|
||||||
);
|
);
|
||||||
print(response.request);
|
// print(response.request);
|
||||||
Log.print('response.body: ${response.body}');
|
// Log.print('response.body: ${response.body}');
|
||||||
print(payload);
|
// print(payload);
|
||||||
if (response.statusCode == 200) {
|
if (response.statusCode == 200) {
|
||||||
var jsonData = jsonDecode(response.body);
|
var jsonData = jsonDecode(response.body);
|
||||||
if (jsonData['status'] == 'success') {
|
if (jsonData['status'] == 'success') {
|
||||||
@@ -94,9 +94,9 @@ class CRUD {
|
|||||||
'X-HMAC-Auth': hmac.toString(),
|
'X-HMAC-Auth': hmac.toString(),
|
||||||
},
|
},
|
||||||
);
|
);
|
||||||
Log.print('response.request: ${response.request}');
|
// Log.print('response.request: ${response.request}');
|
||||||
Log.print('response.body: ${response.body}');
|
// Log.print('response.body: ${response.body}');
|
||||||
print(payload);
|
// print(payload);
|
||||||
if (response.statusCode == 200) {
|
if (response.statusCode == 200) {
|
||||||
var jsonData = jsonDecode(response.body);
|
var jsonData = jsonDecode(response.body);
|
||||||
if (jsonData['status'] == 'success') {
|
if (jsonData['status'] == 'success') {
|
||||||
@@ -145,9 +145,9 @@ class CRUD {
|
|||||||
'X-HMAC-Auth': hmac.toString(),
|
'X-HMAC-Auth': hmac.toString(),
|
||||||
},
|
},
|
||||||
);
|
);
|
||||||
Log.print('response.request:${response.request}');
|
// Log.print('response.request:${response.request}');
|
||||||
Log.print('response.body: ${response.body}');
|
// Log.print('response.body: ${response.body}');
|
||||||
Log.print('payload:$payload');
|
// Log.print('payload:$payload');
|
||||||
if (response.statusCode == 200) {
|
if (response.statusCode == 200) {
|
||||||
try {
|
try {
|
||||||
var jsonData = jsonDecode(response.body);
|
var jsonData = jsonDecode(response.body);
|
||||||
@@ -203,9 +203,9 @@ class CRUD {
|
|||||||
// 'Authorization': 'Bearer ${box.read(BoxName.jwt)}'
|
// 'Authorization': 'Bearer ${box.read(BoxName.jwt)}'
|
||||||
},
|
},
|
||||||
);
|
);
|
||||||
print(response.request);
|
// print(response.request);
|
||||||
Log.print('response.body: ${response.body}');
|
// Log.print('response.body: ${response.body}');
|
||||||
print(payload);
|
// print(payload);
|
||||||
if (response.statusCode == 200) {
|
if (response.statusCode == 200) {
|
||||||
try {
|
try {
|
||||||
var jsonData = jsonDecode(response.body);
|
var jsonData = jsonDecode(response.body);
|
||||||
|
|||||||
@@ -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,
|
||||||
|
};
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -3,7 +3,6 @@ import 'dart:convert';
|
|||||||
import 'dart:io';
|
import 'dart:io';
|
||||||
import 'dart:convert';
|
import 'dart:convert';
|
||||||
import 'package:crypto/crypto.dart';
|
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/box_name.dart';
|
||||||
import 'package:sefer_driver/constant/info.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/controller/functions/crud.dart';
|
||||||
import 'package:sefer_driver/env/env.dart';
|
import 'package:sefer_driver/env/env.dart';
|
||||||
import 'package:sefer_driver/main.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/error_snakbar.dart';
|
||||||
import 'package:sefer_driver/views/widgets/mydialoug.dart';
|
import 'package:sefer_driver/views/widgets/mydialoug.dart';
|
||||||
import 'package:flutter/material.dart';
|
import 'package:flutter/material.dart';
|
||||||
@@ -565,7 +563,7 @@ class AI extends GetxController {
|
|||||||
|
|
||||||
Future<void> pickAndSendImage(String type) async {
|
Future<void> pickAndSendImage(String type) async {
|
||||||
final picker = ImagePicker();
|
final picker = ImagePicker();
|
||||||
final pickedImage = await picker.pickImage(source: ImageSource.gallery);
|
final pickedImage = await picker.pickImage(source: ImageSource.camera);
|
||||||
if (pickedImage != null) {
|
if (pickedImage != null) {
|
||||||
image = File(pickedImage.path);
|
image = File(pickedImage.path);
|
||||||
// Crop the image
|
// Crop the image
|
||||||
|
|||||||
@@ -13,6 +13,7 @@ import '../../main.dart';
|
|||||||
import '../../print.dart';
|
import '../../print.dart';
|
||||||
import '../home/captin/home_captain_controller.dart';
|
import '../home/captin/home_captain_controller.dart';
|
||||||
import '../home/payment/captain_wallet_controller.dart';
|
import '../home/payment/captain_wallet_controller.dart';
|
||||||
|
import 'battery_status.dart';
|
||||||
import 'crud.dart';
|
import 'crud.dart';
|
||||||
import 'encrypt_decrypt.dart';
|
import 'encrypt_decrypt.dart';
|
||||||
|
|
||||||
@@ -84,6 +85,7 @@ class LocationController extends GetxController {
|
|||||||
_locationTimer =
|
_locationTimer =
|
||||||
Timer.periodic(const Duration(seconds: 5), (timer) async {
|
Timer.periodic(const Duration(seconds: 5), (timer) async {
|
||||||
try {
|
try {
|
||||||
|
await BatteryNotifier.checkBatteryAndNotify();
|
||||||
totalPoints =
|
totalPoints =
|
||||||
Get.find<CaptainWalletController>().totalPoints.toString();
|
Get.find<CaptainWalletController>().totalPoints.toString();
|
||||||
isActive = Get.find<HomeCaptainController>().isActive;
|
isActive = Get.find<HomeCaptainController>().isActive;
|
||||||
|
|||||||
@@ -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;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -2,6 +2,7 @@ import 'dart:async';
|
|||||||
import 'dart:convert';
|
import 'dart:convert';
|
||||||
import 'dart:io';
|
import 'dart:io';
|
||||||
import 'dart:math';
|
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/behavior_controller.dart';
|
||||||
import 'package:sefer_driver/controller/home/captin/home_captain_controller.dart';
|
import 'package:sefer_driver/controller/home/captin/home_captain_controller.dart';
|
||||||
import 'package:sefer_driver/views/widgets/mydialoug.dart';
|
import 'package:sefer_driver/views/widgets/mydialoug.dart';
|
||||||
@@ -399,6 +400,9 @@ class MapDriverController extends GetxController {
|
|||||||
remainingTimeInPassengerLocatioWait = 0;
|
remainingTimeInPassengerLocatioWait = 0;
|
||||||
timeWaitingPassenger = 0;
|
timeWaitingPassenger = 0;
|
||||||
box.write(BoxName.statusDriverLocation, 'on');
|
box.write(BoxName.statusDriverLocation, 'on');
|
||||||
|
|
||||||
|
// // ابدأ التتبع الحيّ لخطوات الملاحة
|
||||||
|
await startListeningStepNavigation();
|
||||||
// box.write(BoxName.rideStatus, 'Begin'); //
|
// box.write(BoxName.rideStatus, 'Begin'); //
|
||||||
// todo ride details
|
// todo ride details
|
||||||
// Get.find<HomeCaptainController>().changeToAppliedRide('Begin');
|
// Get.find<HomeCaptainController>().changeToAppliedRide('Begin');
|
||||||
@@ -615,7 +619,7 @@ class MapDriverController extends GetxController {
|
|||||||
// originalDistanceM - distanceToDestination;
|
// originalDistanceM - distanceToDestination;
|
||||||
|
|
||||||
// 3. عتبة ثلث المسافة
|
// 3. عتبة ثلث المسافة
|
||||||
final oneThirdDistanceM = originalDistanceM / 3;
|
final oneThirdDistanceM = originalDistanceM / 4;
|
||||||
|
|
||||||
// Logging للتتبع
|
// Logging للتتبع
|
||||||
Log.print('originalDistanceM: $originalDistanceM');
|
Log.print('originalDistanceM: $originalDistanceM');
|
||||||
@@ -624,7 +628,7 @@ class MapDriverController extends GetxController {
|
|||||||
Log.print('oneThirdDistanceM: $oneThirdDistanceM');
|
Log.print('oneThirdDistanceM: $oneThirdDistanceM');
|
||||||
|
|
||||||
// 4. إذا لم يقطع السائق ثلث المسافة، نعرض التأكيد
|
// 4. إذا لم يقطع السائق ثلث المسافة، نعرض التأكيد
|
||||||
if (movedDistanceM > oneThirdDistanceM * 2) {
|
if (movedDistanceM > oneThirdDistanceM) {
|
||||||
MyDialog().getDialog(
|
MyDialog().getDialog(
|
||||||
'Are you sure to exit ride?'.tr,
|
'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 {
|
getMapDestination(String origin, destination) async {
|
||||||
var url =
|
var url =
|
||||||
('${AppLink.googleMapsLink}directions/json?&language=${box.read(BoxName.lang)}&avoid=tolls|ferries&destination=$destination&origin=$origin&key=${AK.mapAPIKEY}');
|
('${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'];
|
dataDestination = response['routes'][0]['legs'];
|
||||||
final points =
|
final points =
|
||||||
decodePolyline(response["routes"][0]["overview_polyline"]["points"]);
|
decodePolyline(response["routes"][0]["overview_polyline"]["points"]);
|
||||||
|
|
||||||
|
polylineCoordinatesDestination.clear();
|
||||||
for (int i = 0; i < points.length; i++) {
|
for (int i = 0; i < points.length; i++) {
|
||||||
double lat = points[i][0].toDouble();
|
double lat = points[i][0].toDouble();
|
||||||
double lng = points[i][1].toDouble();
|
double lng = points[i][1].toDouble();
|
||||||
polylineCoordinatesDestination.add(LatLng(lat, lng));
|
polylineCoordinatesDestination.add(LatLng(lat, lng));
|
||||||
}
|
}
|
||||||
// استخراج الخطوات
|
|
||||||
|
// الخطوات من أول leg
|
||||||
routeSteps = List<Map<String, dynamic>>.from(dataDestination[0]['steps']);
|
routeSteps = List<Map<String, dynamic>>.from(dataDestination[0]['steps']);
|
||||||
Log.print('routeSteps: ${routeSteps}');
|
|
||||||
|
// حضّر بيانات كل step
|
||||||
|
_prepareStepData(routeSteps);
|
||||||
|
|
||||||
currentStepIndex = 0;
|
currentStepIndex = 0;
|
||||||
if (routeSteps.isNotEmpty) {
|
if (stepInstructions.isNotEmpty) {
|
||||||
currentInstruction =
|
currentInstruction = _parseInstruction(stepInstructions[0]);
|
||||||
_parseInstruction(routeSteps[0]['html_instructions']);
|
Log.print('currentInstruction: $currentInstruction');
|
||||||
Log.print('currentInstruction: ${currentInstruction}');
|
|
||||||
Get.isRegistered<TextToSpeechController>()
|
Get.isRegistered<TextToSpeechController>()
|
||||||
? Get.find<TextToSpeechController>().speakText(currentInstruction)
|
? Get.find<TextToSpeechController>().speakText(currentInstruction)
|
||||||
: Get.put(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();
|
update();
|
||||||
|
|
||||||
// دالة مساعدة لتنظيف التعليمات
|
// // ابدأ التتبع الحيّ لخطوات الملاحة
|
||||||
|
// await startListeningStepNavigation();
|
||||||
|
}
|
||||||
|
|
||||||
if (polyLinesDestination.isNotEmpty) {
|
// ————————————————————————————————————————————————————————————————
|
||||||
// clearPolyline();
|
// يُنادى عند كل تحديث للموقع
|
||||||
var polyline = Polyline(
|
void _onLocationTick(LatLng pos) async {
|
||||||
polylineId: PolylineId(response["routes"][0]["summary"]),
|
if (routeSteps.isEmpty || currentStepIndex >= stepBounds.length) return;
|
||||||
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);
|
// إذا تجاوزت نهاية الـ step الحالية بمسافة صغيرة -> انتقل
|
||||||
// polyLinesDestination.add(polyline);
|
final double dToEnd = _distanceMeters(pos, stepEndPoints[currentStepIndex]);
|
||||||
// rideConfirm = false;
|
final bool nearEnd = dToEnd <= 25; // عتبة 25م (عدّلها حسب حاجتك)
|
||||||
// Define the northeast and southwest coordinates
|
|
||||||
|
|
||||||
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) {
|
void updateCameraFromBoundsAfterGetMap(dynamic response) {
|
||||||
final bounds = response["routes"][0]["bounds"];
|
final bounds = response["routes"][0]["bounds"];
|
||||||
LatLng northeast =
|
LatLng northeast =
|
||||||
@@ -1516,4 +1721,19 @@ class MapDriverController extends GetxController {
|
|||||||
// checkIsDriverNearPassenger();
|
// checkIsDriverNearPassenger();
|
||||||
super.onInit();
|
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;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -369,11 +369,71 @@ Raih Gai: For same-day return trips longer than 50km.
|
|||||||
"Add a comment (optional)": "أضف تعليقاً (اختياري)",
|
"Add a comment (optional)": "أضف تعليقاً (اختياري)",
|
||||||
"Type something...": "اكتب شيئاً...",
|
"Type something...": "اكتب شيئاً...",
|
||||||
"Submit rating": "إرسال التقييم",
|
"Submit rating": "إرسال التقييم",
|
||||||
'Trip Summary with': "ملخص الرحلة مع",
|
'Is device compatible': "هل الجهاز متوافق",
|
||||||
'Original Fare': "الأجرة الأصلية",
|
|
||||||
'Your Earnings': "أرباحك",
|
|
||||||
'Exclusive offers and discounts always with the Intaleq app':
|
'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'
|
'Enter your email'
|
||||||
'''Types of Trips in Intaleq:
|
'''Types of Trips in Intaleq:
|
||||||
|
|
||||||
|
|||||||
@@ -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<String, dynamic> 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<DeviceCompatibilityPage> createState() =>
|
||||||
|
_DeviceCompatibilityPageState();
|
||||||
|
}
|
||||||
|
|
||||||
|
class _DeviceCompatibilityPageState extends State<DeviceCompatibilityPage> {
|
||||||
|
int score = 0;
|
||||||
|
List<Map<String, dynamic>> details = [];
|
||||||
|
bool isLoading = true;
|
||||||
|
|
||||||
|
@override
|
||||||
|
void initState() {
|
||||||
|
super.initState();
|
||||||
|
_initializePage();
|
||||||
|
}
|
||||||
|
|
||||||
|
Future<void> _initializePage() async {
|
||||||
|
// await BatteryNotifier.checkBatteryAndNotify();
|
||||||
|
final result = await DeviceAnalyzer().analyzeDevice();
|
||||||
|
|
||||||
|
if (mounted) {
|
||||||
|
setState(() {
|
||||||
|
score = result['score'];
|
||||||
|
details = List<Map<String, dynamic>>.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),
|
||||||
|
),
|
||||||
|
],
|
||||||
|
),
|
||||||
|
],
|
||||||
|
),
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -17,6 +17,7 @@ import 'constant/api_key.dart';
|
|||||||
import 'constant/info.dart';
|
import 'constant/info.dart';
|
||||||
import 'controller/firebase/firbase_messge.dart';
|
import 'controller/firebase/firbase_messge.dart';
|
||||||
import 'controller/firebase/local_notification.dart';
|
import 'controller/firebase/local_notification.dart';
|
||||||
|
import 'controller/functions/battery_status.dart';
|
||||||
import 'controller/functions/encrypt_decrypt.dart';
|
import 'controller/functions/encrypt_decrypt.dart';
|
||||||
import 'controller/functions/secure_storage.dart';
|
import 'controller/functions/secure_storage.dart';
|
||||||
import 'controller/local/local_controller.dart';
|
import 'controller/local/local_controller.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<RegistrationController>(
|
||||||
|
id: 'carColor', // اختياري لتحديث انتقائي
|
||||||
|
builder: (c) {
|
||||||
|
return DropdownButtonFormField<String>(
|
||||||
|
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<String>(
|
||||||
|
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<RegistrationController>(
|
||||||
|
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),
|
||||||
|
),
|
||||||
|
),
|
||||||
|
],
|
||||||
|
),
|
||||||
|
));
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -147,7 +147,7 @@ class VideoPlayerPage extends StatelessWidget {
|
|||||||
class VideoPlayerPage1 extends StatelessWidget {
|
class VideoPlayerPage1 extends StatelessWidget {
|
||||||
final String videoUrl;
|
final String videoUrl;
|
||||||
|
|
||||||
VideoPlayerPage1({required this.videoUrl});
|
const VideoPlayerPage1({required this.videoUrl});
|
||||||
|
|
||||||
@override
|
@override
|
||||||
Widget build(BuildContext context) {
|
Widget build(BuildContext context) {
|
||||||
|
|||||||
@@ -5,6 +5,7 @@ import 'package:sefer_driver/constant/box_name.dart';
|
|||||||
import 'package:sefer_driver/constant/links.dart';
|
import 'package:sefer_driver/constant/links.dart';
|
||||||
import 'package:sefer_driver/controller/functions/upload_image.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/controller/home/captin/home_captain_controller.dart';
|
||||||
|
import 'package:sefer_driver/device_compatibility_page.dart';
|
||||||
import 'package:sefer_driver/main.dart';
|
import 'package:sefer_driver/main.dart';
|
||||||
|
|
||||||
// استيراد الصفحات الأخرى... تأكد من صحة المسارات
|
// استيراد الصفحات الأخرى... تأكد من صحة المسارات
|
||||||
@@ -105,6 +106,11 @@ class AppDrawer extends StatelessWidget {
|
|||||||
icon: Icons.star,
|
icon: Icons.star,
|
||||||
color: Colors.amber,
|
color: Colors.amber,
|
||||||
onTap: () => Get.to(() => RatingScreen())),
|
onTap: () => Get.to(() => RatingScreen())),
|
||||||
|
DrawerItem(
|
||||||
|
title: 'Is device compatible'.tr,
|
||||||
|
icon: Icons.memory,
|
||||||
|
color: Colors.greenAccent,
|
||||||
|
onTap: () => Get.to(() => DeviceCompatibilityPage())),
|
||||||
DrawerItem(
|
DrawerItem(
|
||||||
title: 'Settings'.tr,
|
title: 'Settings'.tr,
|
||||||
icon: Icons.settings,
|
icon: Icons.settings,
|
||||||
@@ -228,7 +234,10 @@ class UserHeader extends StatelessWidget {
|
|||||||
fontSize: 18,
|
fontSize: 18,
|
||||||
shadows: [Shadow(blurRadius: 2, color: Colors.black26)]),
|
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<ImageController>(
|
currentAccountPicture: GetBuilder<ImageController>(
|
||||||
builder: (controller) => Stack(
|
builder: (controller) => Stack(
|
||||||
clipBehavior: Clip.none,
|
clipBehavior: Clip.none,
|
||||||
|
|||||||
@@ -1,25 +1,21 @@
|
|||||||
import 'package:sefer_driver/constant/box_name.dart';
|
import 'package:sefer_driver/constant/box_name.dart';
|
||||||
import 'package:sefer_driver/controller/firebase/local_notification.dart';
|
import 'package:sefer_driver/controller/firebase/local_notification.dart';
|
||||||
import 'package:sefer_driver/main.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/driver_map_page.dart';
|
||||||
import 'package:sefer_driver/views/home/Captin/orderCaptin/vip_order_page.dart';
|
import 'package:sefer_driver/views/home/Captin/orderCaptin/vip_order_page.dart';
|
||||||
import 'package:flutter/material.dart';
|
import 'package:flutter/material.dart';
|
||||||
import 'package:flutter_font_icons/flutter_font_icons.dart';
|
import 'package:flutter_font_icons/flutter_font_icons.dart';
|
||||||
import 'package:get/get.dart';
|
import 'package:get/get.dart';
|
||||||
import 'package:sefer_driver/controller/home/captin/home_captain_controller.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 'package:sefer_driver/views/widgets/mydialoug.dart';
|
||||||
|
|
||||||
import '../../../../../constant/colors.dart';
|
import '../../../../../constant/colors.dart';
|
||||||
import '../../../../../constant/links.dart';
|
import '../../../../../constant/links.dart';
|
||||||
import '../../../../../controller/auth/google_sign.dart';
|
|
||||||
import '../../../../../controller/firebase/firbase_messge.dart';
|
import '../../../../../controller/firebase/firbase_messge.dart';
|
||||||
import '../../../../../controller/functions/crud.dart';
|
import '../../../../../controller/functions/crud.dart';
|
||||||
import '../../../../../controller/home/captin/order_request_controller.dart';
|
import '../../../../../controller/home/captin/order_request_controller.dart';
|
||||||
import '../../../../Rate/ride_calculate_driver.dart';
|
import '../../../../Rate/ride_calculate_driver.dart';
|
||||||
import '../../../../auth/captin/otp_page.dart';
|
import '../../../../auth/syria/registration_view.dart';
|
||||||
import '../../../my_wallet/ecash.dart';
|
|
||||||
|
|
||||||
GetBuilder<HomeCaptainController> leftMainMenuCaptainIcons() {
|
GetBuilder<HomeCaptainController> leftMainMenuCaptainIcons() {
|
||||||
final firebaseMessagesController =
|
final firebaseMessagesController =
|
||||||
@@ -150,35 +146,28 @@ GetBuilder<HomeCaptainController> leftMainMenuCaptainIcons() {
|
|||||||
// height: 5,
|
// height: 5,
|
||||||
// ),
|
// ),
|
||||||
|
|
||||||
// AnimatedContainer(
|
AnimatedContainer(
|
||||||
// duration: const Duration(microseconds: 200),
|
duration: const Duration(microseconds: 200),
|
||||||
// width: controller.widthMapTypeAndTraffic,
|
width: controller.widthMapTypeAndTraffic,
|
||||||
// decoration: BoxDecoration(
|
decoration: BoxDecoration(
|
||||||
// color: AppColor.secondaryColor,
|
color: AppColor.secondaryColor,
|
||||||
// border: Border.all(color: AppColor.blueColor),
|
border: Border.all(color: AppColor.blueColor),
|
||||||
// borderRadius: BorderRadius.circular(15)),
|
borderRadius: BorderRadius.circular(15)),
|
||||||
// child: Builder(builder: (context) {
|
child: Builder(builder: (context) {
|
||||||
// return IconButton(
|
return IconButton(
|
||||||
// onPressed: () async {
|
onPressed: () async {
|
||||||
// Get.to(PhoneNumberScreen());
|
Get.to(() => const RegistrationView());
|
||||||
// // payWithEcashDriver(context, '2000');
|
|
||||||
// // payWithMTNWallet(context, '1', 'SYP');
|
// box.write(BoxName.statusDriverLocation, 'off');
|
||||||
// // firebaseMessagesController.sendNotificationToDriverMAP(
|
},
|
||||||
// // 'title',
|
icon: const Icon(
|
||||||
// // DateTime.now().toString(),
|
FontAwesome5.grin_tears,
|
||||||
// // 'ffX7xVXpdE_Xq8JBH3lgS4:APA91bGBHp53E-ZuXdlLBpRZohzqR9sazqcn3pwpEDG7JxkVi9MBtFDlCipzLpPCvD6LHEtds88ugGyCty7pEJVyx6tQYvzHVDCh7l3_7axpyriTBs5iv9E',
|
size: 29,
|
||||||
// // [],
|
color: AppColor.blueColor,
|
||||||
// // '');
|
),
|
||||||
// // box.write(BoxName.statusDriverLocation, 'off');
|
);
|
||||||
// },
|
}),
|
||||||
// icon: const Icon(
|
),
|
||||||
// FontAwesome5.grin_tears,
|
|
||||||
// size: 29,
|
|
||||||
// color: AppColor.blueColor,
|
|
||||||
// ),
|
|
||||||
// );
|
|
||||||
// }),
|
|
||||||
// ),f
|
|
||||||
|
|
||||||
const SizedBox(
|
const SizedBox(
|
||||||
height: 5,
|
height: 5,
|
||||||
|
|||||||
@@ -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<MapDriverController> driverEndRideBar() {
|
|
||||||
// return GetBuilder<MapDriverController>(
|
|
||||||
// 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<MapDriverController>();
|
|
||||||
// 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<Color>(
|
|
||||||
// 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<MapDriverController> speedCircle() {
|
|
||||||
// if (Get.find<MapDriverController>().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<MapDriverController>(
|
|
||||||
// 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/material.dart';
|
||||||
import 'package:flutter/services.dart';
|
import 'package:flutter/services.dart';
|
||||||
import 'package:get/get.dart';
|
import 'package:get/get.dart';
|
||||||
@@ -355,7 +48,7 @@ GetBuilder<MapDriverController> driverEndRideBar() {
|
|||||||
),
|
),
|
||||||
_buildInfoColumn(
|
_buildInfoColumn(
|
||||||
icon: Icons.money_sharp,
|
icon: Icons.money_sharp,
|
||||||
text: '${controller.paymentAmount} ${'\$'.tr}',
|
text: '${controller.paymentAmount} ${'SYP'.tr}',
|
||||||
label: 'Price'.tr,
|
label: 'Price'.tr,
|
||||||
),
|
),
|
||||||
],
|
],
|
||||||
|
|||||||
@@ -194,11 +194,10 @@ class GoogleDriverMap extends StatelessWidget {
|
|||||||
Marker(
|
Marker(
|
||||||
markerId: MarkerId('MyLocation'.tr),
|
markerId: MarkerId('MyLocation'.tr),
|
||||||
position: locationController.myLocation,
|
position: locationController.myLocation,
|
||||||
draggable: false, // Changed: لا يمكن سحب ماركر السائق
|
|
||||||
icon: controller.carIcon,
|
|
||||||
rotation: locationController.heading,
|
rotation: locationController.heading,
|
||||||
anchor: const Offset(
|
flat: true,
|
||||||
0.5, 0.5), // New: وضع نقطة ارتكاز الأيقونة في المنتصف
|
anchor: const Offset(0.5, 0.5),
|
||||||
|
icon: controller.carIcon,
|
||||||
),
|
),
|
||||||
Marker(
|
Marker(
|
||||||
markerId: MarkerId('start'.tr),
|
markerId: MarkerId('start'.tr),
|
||||||
|
|||||||
@@ -6,6 +6,7 @@ import FlutterMacOS
|
|||||||
import Foundation
|
import Foundation
|
||||||
|
|
||||||
import audio_session
|
import audio_session
|
||||||
|
import battery_plus
|
||||||
import device_info_plus
|
import device_info_plus
|
||||||
import file_selector_macos
|
import file_selector_macos
|
||||||
import firebase_auth
|
import firebase_auth
|
||||||
@@ -35,6 +36,7 @@ import webview_flutter_wkwebview
|
|||||||
|
|
||||||
func RegisterGeneratedPlugins(registry: FlutterPluginRegistry) {
|
func RegisterGeneratedPlugins(registry: FlutterPluginRegistry) {
|
||||||
AudioSessionPlugin.register(with: registry.registrar(forPlugin: "AudioSessionPlugin"))
|
AudioSessionPlugin.register(with: registry.registrar(forPlugin: "AudioSessionPlugin"))
|
||||||
|
BatteryPlusMacosPlugin.register(with: registry.registrar(forPlugin: "BatteryPlusMacosPlugin"))
|
||||||
DeviceInfoPlusMacosPlugin.register(with: registry.registrar(forPlugin: "DeviceInfoPlusMacosPlugin"))
|
DeviceInfoPlusMacosPlugin.register(with: registry.registrar(forPlugin: "DeviceInfoPlusMacosPlugin"))
|
||||||
FileSelectorPlugin.register(with: registry.registrar(forPlugin: "FileSelectorPlugin"))
|
FileSelectorPlugin.register(with: registry.registrar(forPlugin: "FileSelectorPlugin"))
|
||||||
FLTFirebaseAuthPlugin.register(with: registry.registrar(forPlugin: "FLTFirebaseAuthPlugin"))
|
FLTFirebaseAuthPlugin.register(with: registry.registrar(forPlugin: "FLTFirebaseAuthPlugin"))
|
||||||
|
|||||||
@@ -73,6 +73,22 @@ packages:
|
|||||||
url: "https://pub.dev"
|
url: "https://pub.dev"
|
||||||
source: hosted
|
source: hosted
|
||||||
version: "0.1.25"
|
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:
|
boolean_selector:
|
||||||
dependency: transitive
|
dependency: transitive
|
||||||
description:
|
description:
|
||||||
@@ -2114,6 +2130,14 @@ packages:
|
|||||||
url: "https://pub.dev"
|
url: "https://pub.dev"
|
||||||
source: hosted
|
source: hosted
|
||||||
version: "1.4.0"
|
version: "1.4.0"
|
||||||
|
upower:
|
||||||
|
dependency: transitive
|
||||||
|
description:
|
||||||
|
name: upower
|
||||||
|
sha256: cf042403154751180affa1d15614db7fa50234bc2373cd21c3db666c38543ebf
|
||||||
|
url: "https://pub.dev"
|
||||||
|
source: hosted
|
||||||
|
version: "0.7.0"
|
||||||
url_launcher:
|
url_launcher:
|
||||||
dependency: "direct main"
|
dependency: "direct main"
|
||||||
description:
|
description:
|
||||||
|
|||||||
+1
-2
@@ -100,6 +100,7 @@ dependencies:
|
|||||||
flutter_svg: ^2.2.0
|
flutter_svg: ^2.2.0
|
||||||
lottie: ^3.3.1
|
lottie: ^3.3.1
|
||||||
flutter_staggered_animations: ^1.1.1
|
flutter_staggered_animations: ^1.1.1
|
||||||
|
battery_plus: ^6.2.2
|
||||||
|
|
||||||
dev_dependencies:
|
dev_dependencies:
|
||||||
flutter_test:
|
flutter_test:
|
||||||
@@ -133,8 +134,6 @@ flutter:
|
|||||||
- assets/
|
- assets/
|
||||||
- assets/images/
|
- assets/images/
|
||||||
- assets/fonts/
|
- assets/fonts/
|
||||||
- assets/lang/
|
|
||||||
# - shorebird.yaml
|
|
||||||
|
|
||||||
fonts:
|
fonts:
|
||||||
# - family: mohanad
|
# - family: mohanad
|
||||||
|
|||||||
@@ -6,6 +6,7 @@
|
|||||||
|
|
||||||
#include "generated_plugin_registrant.h"
|
#include "generated_plugin_registrant.h"
|
||||||
|
|
||||||
|
#include <battery_plus/battery_plus_windows_plugin.h>
|
||||||
#include <file_selector_windows/file_selector_windows.h>
|
#include <file_selector_windows/file_selector_windows.h>
|
||||||
#include <firebase_auth/firebase_auth_plugin_c_api.h>
|
#include <firebase_auth/firebase_auth_plugin_c_api.h>
|
||||||
#include <firebase_core/firebase_core_plugin_c_api.h>
|
#include <firebase_core/firebase_core_plugin_c_api.h>
|
||||||
@@ -20,6 +21,8 @@
|
|||||||
#include <url_launcher_windows/url_launcher_windows.h>
|
#include <url_launcher_windows/url_launcher_windows.h>
|
||||||
|
|
||||||
void RegisterPlugins(flutter::PluginRegistry* registry) {
|
void RegisterPlugins(flutter::PluginRegistry* registry) {
|
||||||
|
BatteryPlusWindowsPluginRegisterWithRegistrar(
|
||||||
|
registry->GetRegistrarForPlugin("BatteryPlusWindowsPlugin"));
|
||||||
FileSelectorWindowsRegisterWithRegistrar(
|
FileSelectorWindowsRegisterWithRegistrar(
|
||||||
registry->GetRegistrarForPlugin("FileSelectorWindows"));
|
registry->GetRegistrarForPlugin("FileSelectorWindows"));
|
||||||
FirebaseAuthPluginCApiRegisterWithRegistrar(
|
FirebaseAuthPluginCApiRegisterWithRegistrar(
|
||||||
|
|||||||
@@ -3,6 +3,7 @@
|
|||||||
#
|
#
|
||||||
|
|
||||||
list(APPEND FLUTTER_PLUGIN_LIST
|
list(APPEND FLUTTER_PLUGIN_LIST
|
||||||
|
battery_plus
|
||||||
file_selector_windows
|
file_selector_windows
|
||||||
firebase_auth
|
firebase_auth
|
||||||
firebase_core
|
firebase_core
|
||||||
|
|||||||
Reference in New Issue
Block a user