25-12-1/1

This commit is contained in:
Hamza-Ayed
2025-12-01 07:52:54 +03:00
parent b1b8efdd7d
commit 9b1008a0bf
40 changed files with 2471 additions and 2039 deletions

View File

@@ -42,10 +42,10 @@ android {
// Merged the two defaultConfig sections into one. This is the correct way. // Merged the two defaultConfig sections into one. This is the correct way.
defaultConfig { defaultConfig {
applicationId = "com.intaleq_driver" applicationId = "com.intaleq_driver"
minSdk = 23 minSdkVersion = 23
targetSdk = 36 targetSdk = 36
versionCode = 36 versionCode = 47
versionName = '1.0.36' // I've used the higher version name versionName = '1.0.47' // I've used the higher version name
multiDexEnabled = true multiDexEnabled = true
ndk { ndk {

View File

@@ -1,7 +1,5 @@
<?xml version="1.0" encoding="utf-8"?> <?xml version="1.0" encoding="utf-8"?>
<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">
<!-- ===== Permissions ===== --> <!-- ===== Permissions ===== -->
<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"/>
@@ -31,23 +29,9 @@
<uses-permission android:name="android.permission.PICTURE_IN_PICTURE"/> <uses-permission android:name="android.permission.PICTURE_IN_PICTURE"/>
<uses-feature android:name="android.hardware.camera"/> <uses-feature android:name="android.hardware.camera"/>
<uses-feature android:name="android.hardware.camera.autofocus"/> <uses-feature android:name="android.hardware.camera.autofocus"/>
<application android:name="${applicationName}" android:icon="@mipmap/launcher_icon" android:label="@string/label" android:enableOnBackInvokedCallback="true" android:allowBackup="false" android:fullBackupContent="false" android:networkSecurityConfig="@xml/network_security_config" android:usesCleartextTraffic="false" android:theme="@style/LaunchTheme">
<application
android:name="${applicationName}"
android:icon="@mipmap/launcher_icon"
android:label="@string/label"
android:enableOnBackInvokedCallback="true"
android:allowBackup="false"
android:fullBackupContent="false"
android:networkSecurityConfig="@xml/network_security_config"
android:usesCleartextTraffic="false"
android:theme="@style/LaunchTheme">
<!-- Flutter embedding v2 --> <!-- Flutter embedding v2 -->
<meta-data <meta-data android:name="flutterEmbedding" android:value="2"/>
android:name="flutterEmbedding"
android:value="2" />
<!-- تحديد نقطة دخول خلفية (للـ overlay / background executor) --> <!-- تحديد نقطة دخول خلفية (للـ overlay / background executor) -->
<!-- <meta-data <!-- <meta-data
android:name="io.flutter.embedding.android.BackgroundExecutor.DART_ENTRYPOINT" android:name="io.flutter.embedding.android.BackgroundExecutor.DART_ENTRYPOINT"
@@ -55,36 +39,17 @@
<meta-data <meta-data
android:name="io.flutter.embedding.android.BackgroundExecutor.DART_LIBRARY_URI" android:name="io.flutter.embedding.android.BackgroundExecutor.DART_LIBRARY_URI"
android:value="main.dart" /> --> android:value="main.dart" /> -->
<!-- خرائط + إشعارات فFirebase (قناة افتراضية) --> <!-- خرائط + إشعارات فFirebase (قناة افتراضية) -->
<meta-data <meta-data android:name="com.google.android.geo.API_KEY" android:value="@string/api_key"/>
android:name="com.google.android.geo.API_KEY" <meta-data android:name="com.google.firebase.messaging.default_notification_channel_id" android:value="@string/default_notification_channel_id"/>
android:value="@string/api_key" /> <meta-data android:name="io.flutter.embedding.android.EnableImpeller" android:value="false"/>
<meta-data
android:name="com.google.firebase.messaging.default_notification_channel_id"
android:value="@string/default_notification_channel_id" />
<meta-data
android:name="io.flutter.embedding.android.EnableImpeller"
android:value="false" />
<!-- Main Activity --> <!-- Main Activity -->
<activity <activity android:name=".MainActivity" android:configChanges="orientation|keyboardHidden|screenSize|locale|layoutDirection|fontScale|screenLayout|density|uiMode" android:exported="true" android:hardwareAccelerated="true" android:launchMode="singleTask" android:theme="@style/LaunchTheme" android:windowSoftInputMode="adjustResize">
android:name=".MainActivity" <meta-data android:name="io.flutter.embedding.android.NormalTheme" android:resource="@style/NormalTheme"/>
android:configChanges="orientation|keyboardHidden|screenSize|locale|layoutDirection|fontScale|screenLayout|density|uiMode"
android:exported="true"
android:hardwareAccelerated="true"
android:launchMode="singleTask"
android:theme="@style/LaunchTheme"
android:windowSoftInputMode="adjustResize">
<meta-data
android:name="io.flutter.embedding.android.NormalTheme"
android:resource="@style/NormalTheme" />
<intent-filter> <intent-filter>
<action android:name="android.intent.action.MAIN"/> <action android:name="android.intent.action.MAIN"/>
<category android:name="android.intent.category.LAUNCHER"/> <category android:name="android.intent.category.LAUNCHER"/>
</intent-filter> </intent-filter>
<!-- Deep Link: intaleqapp://... --> <!-- Deep Link: intaleqapp://... -->
<intent-filter> <intent-filter>
<action android:name="android.intent.action.VIEW"/> <action android:name="android.intent.action.VIEW"/>
@@ -93,73 +58,40 @@
<data android:scheme="intaleqapp"/> <data android:scheme="intaleqapp"/>
</intent-filter> </intent-filter>
</activity> </activity>
<!-- أنشطة ومكوّنات إضافية --> <!-- أنشطة ومكوّنات إضافية -->
<activity <activity android:name="com.yalantis.ucrop.UCropActivity" android:screenOrientation="portrait" android:theme="@style/Theme.AppCompat.Light.NoActionBar"/>
android:name="com.yalantis.ucrop.UCropActivity"
android:screenOrientation="portrait"
android:theme="@style/Theme.AppCompat.Light.NoActionBar" />
<!-- خدماتك الخاصة --> <!-- خدماتك الخاصة -->
<service <service android:name=".MyFirebaseMessagingService" android:exported="false"/>
android:name=".MyFirebaseMessagingService" <service android:name=".LocationUpdatesService" android:exported="false" android:foregroundServiceType="location"/>
android:exported="false" />
<service
android:name=".LocationUpdatesService"
android:exported="false"
android:foregroundServiceType="location" />
<!-- خدمة Firebase الرسمية لاستقبال رسائل FCM --> <!-- خدمة Firebase الرسمية لاستقبال رسائل FCM -->
<service <service android:name="com.google.firebase.messaging.FirebaseMessagingService" android:exported="false" tools:replace="android:exported">
android:name="com.google.firebase.messaging.FirebaseMessagingService"
android:exported="false"
tools:replace="android:exported">
<intent-filter> <intent-filter>
<action android:name="com.google.firebase.MESSAGING_EVENT"/> <action android:name="com.google.firebase.MESSAGING_EVENT"/>
</intent-filter> </intent-filter>
</service> </service>
<!-- خدمة overlay للمكتبة الأولى (إن كنت تستخدمها) --> <!-- خدمة overlay للمكتبة الأولى (إن كنت تستخدمها) -->
<!-- <service <!-- <service
android:name="com.phan_tech.flutter_overlay_apps.OverlayService" android:name="com.phan_tech.flutter_overlay_apps.OverlayService"
android:exported="false" /> --> android:exported="false" /> -->
<service <service android:name="flutter.overlay.window.flutter_overlay_window.OverlayService" android:exported="false" android:foregroundServiceType="specialUse"/>
android:name="flutter.overlay.window.flutter_overlay_window.OverlayService"
android:exported="false"
android:foregroundServiceType="specialUse" />
<!-- خدمة overlay الخاصة بمكتبة flutter_overlay_window --> <!-- خدمة overlay الخاصة بمكتبة flutter_overlay_window -->
<!-- استقبال توكن/رسائل قديمة (توافقية) --> <!-- استقبال توكن/رسائل قديمة (توافقية) -->
<receiver <receiver android:name="com.google.firebase.iid.FirebaseInstanceIdReceiver" android:exported="true" android:permission="com.google.android.c2dm.permission.SEND">
android:name="com.google.firebase.iid.FirebaseInstanceIdReceiver"
android:exported="true"
android:permission="com.google.android.c2dm.permission.SEND">
<intent-filter> <intent-filter>
<action android:name="com.google.android.c2dm.intent.RECEIVE"/> <action android:name="com.google.android.c2dm.intent.RECEIVE"/>
<category android:name="com.intaleq_driver"/> <category android:name="com.intaleq_driver"/>
</intent-filter> </intent-filter>
</receiver> </receiver>
<!-- خدمة الفقاعة الخاصة بك --> <!-- خدمة الفقاعة الخاصة بك -->
<service <service android:name="com.dsaved.bubblehead.bubble.BubbleHeadService" android:enabled="true" android:exported="false">
android:name="com.dsaved.bubblehead.bubble.BubbleHeadService"
android:enabled="true"
android:exported="false">
<intent-filter> <intent-filter>
<action android:name="intent.bring.app.to.foreground"/> <action android:name="intent.bring.app.to.foreground"/>
<category android:name="android.intent.category.DEFAULT"/> <category android:name="android.intent.category.DEFAULT"/>
</intent-filter> </intent-filter>
</service> </service>
<!-- Notif schedulers --> <!-- Notif schedulers -->
<receiver <receiver android:name="com.dexterous.flutterlocalnotifications.ScheduledNotificationReceiver" android:exported="false"/>
android:name="com.dexterous.flutterlocalnotifications.ScheduledNotificationReceiver" <receiver android:name="com.dexterous.flutterlocalnotifications.ScheduledNotificationBootReceiver" android:exported="false">
android:exported="false" />
<receiver
android:name="com.dexterous.flutterlocalnotifications.ScheduledNotificationBootReceiver"
android:exported="false">
<intent-filter> <intent-filter>
<action android:name="android.intent.action.BOOT_COMPLETED"/> <action android:name="android.intent.action.BOOT_COMPLETED"/>
<action android:name="android.intent.action.MY_PACKAGE_REPLACED"/> <action android:name="android.intent.action.MY_PACKAGE_REPLACED"/>
@@ -167,11 +99,8 @@
<action android:name="com.htc.intent.action.QUICKBOOT_POWERON"/> <action android:name="com.htc.intent.action.QUICKBOOT_POWERON"/>
</intent-filter> </intent-filter>
</receiver> </receiver>
<!-- مستقبل برودكاست خاص بك --> <!-- مستقبل برودكاست خاص بك -->
<receiver <receiver android:name=".YourBroadcastReceiver" android:exported="false"/>
android:name=".YourBroadcastReceiver"
android:exported="false" />
</application> </application>
<uses-permission android:name="android.permission.INTERNET"/>
</manifest> </manifest>

Binary file not shown.

After

Width:  |  Height:  |  Size: 31 KiB

View File

@@ -11,6 +11,7 @@ class BoxName {
"rideArgumentsFromBackground"; "rideArgumentsFromBackground";
static const String FCM_PRIVATE_KEY = "FCM_PRIVATE_KEY"; static const String FCM_PRIVATE_KEY = "FCM_PRIVATE_KEY";
static const String hmac = "hmac"; static const String hmac = "hmac";
static const String ttsEnabled = "ttsEnabled";
static const String walletType = "walletType"; static const String walletType = "walletType";
static const String fingerPrint = "fingerPrint"; static const String fingerPrint = "fingerPrint";
static const String updateInterval = "updateInterval"; static const String updateInterval = "updateInterval";

View File

@@ -326,15 +326,45 @@ Download the Intaleq app now and enjoy your ride!
return input; // Fallback for unrecognized formats return input; // Fallback for unrecognized formats
} }
String normalizeSyrianPhone(String input) {
String phone = input.trim();
// احذف كل شيء غير أرقام
phone = phone.replaceAll(RegExp(r'[^0-9]'), '');
// إذا يبدأ بـ 0 → احذفها
if (phone.startsWith('0')) {
phone = phone.substring(1);
}
// إذا يبدأ بـ 963 مكررة → احذف التكرار
while (phone.startsWith('963963')) {
phone = phone.substring(3);
}
// إذا يبدأ بـ 963 ولكن داخله كمان 963 → خليه مرة واحدة فقط
if (phone.startsWith('963') && phone.length > 12) {
phone = phone.substring(phone.length - 9); // آخر 9 أرقام
}
// الآن إذا كان بلا 963 → أضفها
if (!phone.startsWith('963')) {
phone = '963' + phone;
}
return phone;
}
/// Sends an invitation to a potential new driver. /// Sends an invitation to a potential new driver.
void sendInvite() async { void sendInvite() async {
if (invitePhoneController.text.isEmpty) { if (invitePhoneController.text.isEmpty) {
mySnackeBarError('Please enter a phone number'.tr); mySnackeBarError('Please enter a phone number'.tr);
return; return;
} }
// Format Syrian phone number: remove leading 0 and add +963
String formattedPhoneNumber = String formattedPhoneNumber =
_formatSyrianPhoneNumber(invitePhoneController.text); normalizeSyrianPhone(invitePhoneController.text);
if (formattedPhoneNumber.length < 13) { if (formattedPhoneNumber.length != 12) {
mySnackeBarError('Please enter a correct phone'.tr); mySnackeBarError('Please enter a correct phone'.tr);
return; return;
} }
@@ -370,22 +400,32 @@ Download the Intaleq app now and enjoy your ride!
mySnackeBarError('Please enter a phone number'.tr); mySnackeBarError('Please enter a phone number'.tr);
return; return;
} }
String formattedPhoneNumber =
_formatSyrianPhoneNumber(invitePhoneController.text); // Format Syrian phone number: remove leading 0 and add +963
if (formattedPhoneNumber.length < 13) { String formattedPhoneNumber = invitePhoneController.text.trim();
if (formattedPhoneNumber.startsWith('0')) {
formattedPhoneNumber = formattedPhoneNumber.substring(1);
}
formattedPhoneNumber = '+963$formattedPhoneNumber';
if (formattedPhoneNumber.length < 12) {
// +963 + 9 digits = 12+
mySnackeBarError('Please enter a correct phone'.tr); mySnackeBarError('Please enter a correct phone'.tr);
return; return;
} }
var response = var response = await CRUD().post(
await CRUD().post(link: AppLink.addInvitationPassenger, payload: { link: AppLink.addInvitationPassenger,
payload: {
"driverId": box.read(BoxName.driverID), "driverId": box.read(BoxName.driverID),
"inviterPassengerPhone": formattedPhoneNumber, "inviterPassengerPhone": formattedPhoneNumber,
}); },
);
if (response != 'failure') { if (response != 'failure') {
var d = (response); var d = response;
mySnackbarSuccess('Invite sent successfully'.tr); mySnackbarSuccess('Invite sent successfully'.tr);
String message = '${'*Intaleq APP CODE*'.tr}\n\n' String message = '${'*Intaleq APP CODE*'.tr}\n\n'
'${"Use this code in registration".tr}\n\n' '${"Use this code in registration".tr}\n\n'
'${"To get a gift for both".tr}\n\n' '${"To get a gift for both".tr}\n\n'

View File

@@ -26,6 +26,7 @@ import '../../../views/auth/captin/otp_page.dart';
import '../../../views/auth/captin/otp_token_page.dart'; import '../../../views/auth/captin/otp_token_page.dart';
import '../../../views/auth/syria/pending_driver_page.dart'; import '../../../views/auth/syria/pending_driver_page.dart';
import '../../firebase/firbase_messge.dart'; import '../../firebase/firbase_messge.dart';
import '../../firebase/local_notification.dart';
import '../../firebase/notification_service.dart'; import '../../firebase/notification_service.dart';
import '../../functions/encrypt_decrypt.dart'; import '../../functions/encrypt_decrypt.dart';
import '../../functions/package_info.dart'; import '../../functions/package_info.dart';
@@ -177,11 +178,11 @@ class LoginDriverController extends GetxController {
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}');
final jwt = decodedResponse1['jwt']; final jwt = decodedResponse1['jwt'];
box.write(BoxName.jwt, c(jwt)); box.write(BoxName.jwt, c(jwt));
@@ -253,6 +254,40 @@ class LoginDriverController extends GetxController {
.join(''); .join('');
} }
bool isInviteDriverFound = false;
Future updateInvitationCodeFromRegister() async {
var res = await CRUD().post(
link: AppLink.updateDriverInvitationDirectly,
payload: {
"inviterDriverPhone": box.read(BoxName.phoneDriver).toString(),
// "driverId": box.read(BoxName.driverID).toString(),
},
);
Log.print('invite: ${res}');
if (res['status'] != 'failure') {
isInviteDriverFound = true;
update();
// mySnackbarSuccess("Code approved".tr); // Localized success message
box.write(BoxName.isInstall, '1');
NotificationController().showNotification(
"Code approved".tr, "Code approved".tr, 'tone2', '');
NotificationService.sendNotification(
target: (res)['message'][0]['token'].toString(),
title: 'You have received a gift token!'.tr,
body: 'for '.tr + box.read(BoxName.phoneDriver).toString(),
isTopic: false, // Important: this is a token
tone: 'tone2',
driverList: [], category: 'You have received a gift token!',
);
} else {
// mySnackeBarError(
// "You dont have invitation code".tr); // Localized error message
}
}
loginWithGoogleCredential(String driverID, email) async { loginWithGoogleCredential(String driverID, email) async {
isloading = true; isloading = true;
update(); update();
@@ -263,7 +298,7 @@ class LoginDriverController extends GetxController {
// 'email': email ?? 'yet', // 'email': email ?? 'yet',
'id': driverID, 'id': driverID,
}); });
// Log.print('res: ${res}'); Log.print('loginWithGoogleCredential: ${res}');
if (res == 'failure') { if (res == 'failure') {
await isPhoneVerified(); await isPhoneVerified();
isloading = false; // <--- أضفت هذا أيضاً isloading = false; // <--- أضفت هذا أيضاً
@@ -314,6 +349,13 @@ class LoginDriverController extends GetxController {
} else if (int.parse(d['year'].toString()) < 2002) { } else if (int.parse(d['year'].toString()) < 2002) {
box.write(BoxName.carTypeOfDriver, 'Awfar Car'); box.write(BoxName.carTypeOfDriver, 'Awfar Car');
} }
// add invitations
if (box.read(BoxName.isInstall) == null ||
box.read(BoxName.isInstall).toString() == '0') {
updateInvitationCodeFromRegister();
}
// updateAppTester(AppInformation.appName); // updateAppTester(AppInformation.appName);
if (d['status'].toString() != 'yet') { if (d['status'].toString() != 'yet') {
var token = await CRUD().get( var token = await CRUD().get(

View File

@@ -18,14 +18,77 @@ class PhoneAuthHelper {
static final String _sendOtpUrl = '${_baseUrl}sendWhatsAppDriver.php'; static final String _sendOtpUrl = '${_baseUrl}sendWhatsAppDriver.php';
static final String _verifyOtpUrl = '${_baseUrl}verifyOtp.php'; static final String _verifyOtpUrl = '${_baseUrl}verifyOtp.php';
static final String _registerUrl = '${_baseUrl}register_driver.php'; static final String _registerUrl = '${_baseUrl}register_driver.php';
static String formatSyrianPhone(String phone) {
// Remove spaces, symbols, +, -, ()
phone = phone.replaceAll(RegExp(r'[ \-\(\)\+]'), '').trim();
// Normalize 00963 → 963
if (phone.startsWith('00963')) {
phone = phone.replaceFirst('00963', '963');
}
// Normalize 0963 → 963
if (phone.startsWith('0963')) {
phone = phone.replaceFirst('0963', '963');
}
if (phone.startsWith('096309')) {
phone = phone.replaceFirst('096309', '963');
}
// NEW: Fix 96309xxxx → 9639xxxx
if (phone.startsWith('96309')) {
phone = '9639' + phone.substring(5); // remove the "0" after 963
}
// If starts with 9630 → correct to 9639
if (phone.startsWith('9630')) {
phone = '9639' + phone.substring(4);
}
// If already in correct format: 9639xxxxxxxx
if (phone.startsWith('9639') && phone.length == 12) {
return phone;
}
// If starts with 963 but missing the 9
if (phone.startsWith('963') && phone.length > 3) {
// Ensure it begins with 9639
if (!phone.startsWith('9639')) {
phone = '9639' + phone.substring(3);
}
return phone;
}
// If starts with 09xxxxxxxx → 9639xxxxxxxx
if (phone.startsWith('09')) {
return '963' + phone.substring(1);
}
// If 9xxxxxxxx (9 digits)
if (phone.startsWith('9') && phone.length == 9) {
return '963' + phone;
}
// If starts with incorrect 0xxxxxxx → assume Syrian and fix
if (phone.startsWith('0') && phone.length == 10) {
return '963' + phone.substring(1);
}
return phone;
}
/// Sends an OTP to the provided phone number. /// Sends an OTP to the provided phone number.
static Future<bool> sendOtp(String phoneNumber) async { static Future<bool> sendOtp(String phoneNumber) async {
try { try {
final fixedPhone = formatSyrianPhone(phoneNumber);
Log.print('fixedPhone: $fixedPhone');
final response = await CRUD().post( final response = await CRUD().post(
link: _sendOtpUrl, link: _sendOtpUrl,
payload: {'receiver': phoneNumber}, payload: {'receiver': fixedPhone},
); );
Log.print('fixedPhone: ${fixedPhone}');
if (response != 'failure') { if (response != 'failure') {
final data = (response); final data = (response);
Log.print('data: ${data}'); Log.print('data: ${data}');
@@ -49,10 +112,12 @@ class PhoneAuthHelper {
/// Verifies the OTP and logs the user in. /// Verifies the OTP and logs the user in.
static Future<void> verifyOtp(String phoneNumber) async { static Future<void> verifyOtp(String phoneNumber) async {
try { try {
final fixedPhone = formatSyrianPhone(phoneNumber);
Log.print('fixedPhone: $fixedPhone');
final response = await CRUD().post( final response = await CRUD().post(
link: _verifyOtpUrl, link: _verifyOtpUrl,
payload: { payload: {
'phone_number': phoneNumber, 'phone_number': fixedPhone,
}, },
); );
@@ -80,7 +145,7 @@ class PhoneAuthHelper {
// ✅ رقم الهاتف تم التحقق منه لكن السائق غير مسجل // ✅ رقم الهاتف تم التحقق منه لكن السائق غير مسجل
// mySnackbarSuccess('Phone verified. Please complete registration.'); // mySnackbarSuccess('Phone verified. Please complete registration.');
// Get.offAll(() => SyrianCardAI()); // Get.offAll(() => SyrianCardAI());
Get.offAll(() => RegistrationView()); Get.to(() => RegistrationView());
} }
} else { } else {
mySnackeBarError(data['message'] ?? 'Verification failed.'); mySnackeBarError(data['message'] ?? 'Verification failed.');

View File

@@ -280,7 +280,7 @@ class RegisterCaptainController extends GetxController {
// box.read(BoxName.emailDriver).toString(), // box.read(BoxName.emailDriver).toString(),
// ); // );
// Get.offAll(() => SyrianCardAI()); // Get.offAll(() => SyrianCardAI());
Get.offAll(() => RegistrationView()); Get.to(() => RegistrationView());
// } else { // } else {
// Get.snackbar('title', 'message'); // Get.snackbar('title', 'message');
// } // }

View File

@@ -58,6 +58,7 @@ class RegistrationController extends GetxController {
final firstNameController = TextEditingController(); final firstNameController = TextEditingController();
final lastNameController = TextEditingController(); final lastNameController = TextEditingController();
final nationalIdController = TextEditingController(); final nationalIdController = TextEditingController();
final bithdateController = TextEditingController();
final phoneController = TextEditingController(); // You can pre-fill this final phoneController = TextEditingController(); // You can pre-fill this
final driverLicenseExpiryController = TextEditingController(); final driverLicenseExpiryController = TextEditingController();
DateTime? driverLicenseExpiryDate; DateTime? driverLicenseExpiryDate;
@@ -101,14 +102,15 @@ class RegistrationController extends GetxController {
isValid = driverInfoFormKey.currentState!.validate(); isValid = driverInfoFormKey.currentState!.validate();
if (isValid) { if (isValid) {
// Optional: Check if license is expired // Optional: Check if license is expired
if (driverLicenseExpiryDate != null && // if (driverLicenseExpiryDate != null &&
driverLicenseExpiryDate!.isBefore(DateTime.now())) { // driverLicenseExpiryDate!.isBefore(DateTime.now())) {
Get.snackbar('Expired License', 'Your drivers license has expired.', // Get.snackbar('Expired License', 'Your drivers license has expired.'.tr
snackPosition: SnackPosition.BOTTOM, // ,
backgroundColor: Colors.red, // snackPosition: SnackPosition.BOTTOM,
colorText: Colors.white); // backgroundColor: Colors.red,
return; // Stop progression // colorText: Colors.white);
} // return; // Stop progression
// }
} }
} else if (currentPage.value == 1) { } else if (currentPage.value == 1) {
// Validate Step 2 // Validate Step 2
@@ -495,6 +497,7 @@ class RegistrationController extends GetxController {
_addField(fields, 'last_name', lastNameController.text); _addField(fields, 'last_name', lastNameController.text);
_addField(fields, 'phone', box.read(BoxName.phoneDriver) ?? ''); _addField(fields, 'phone', box.read(BoxName.phoneDriver) ?? '');
_addField(fields, 'national_number', nationalIdController.text); _addField(fields, 'national_number', nationalIdController.text);
_addField(fields, 'birthdate', bithdateController.text);
_addField(fields, 'expiry_date', driverLicenseExpiryController.text); _addField(fields, 'expiry_date', driverLicenseExpiryController.text);
_addField( _addField(
fields, 'password', 'generate_your_password_here'); // عدّل حسب منطقك fields, 'password', 'generate_your_password_here'); // عدّل حسب منطقك

View File

@@ -1,6 +1,7 @@
import 'dart:convert'; import 'dart:convert';
import 'dart:io'; import 'dart:io';
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/Captin/orderCaptin/order_speed_request.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:firebase_messaging/firebase_messaging.dart'; import 'package:firebase_messaging/firebase_messaging.dart';
@@ -117,7 +118,7 @@ class FirebaseMessagesController extends GetxController {
driverToken = myList[14].toString(); driverToken = myList[14].toString();
Get.put(HomeCaptainController()).changeRideId(); Get.put(HomeCaptainController()).changeRideId();
update(); update();
Get.to(() => OrderRequestPage(), arguments: { Get.to(() => OrderSpeedRequest(), arguments: {
'myListString': myListString, 'myListString': myListString,
'DriverList': myList, 'DriverList': myList,
'body': body 'body': body

View File

@@ -114,7 +114,7 @@ class AI extends GetxController {
// 'tone2', // Type of notification // 'tone2', // Type of notification
// ); // );
NotificationService.sendNotification( NotificationService.sendNotification(
target: jsonDecode(res)['message'][0]['token'].toString(), target: (res)['message'][0]['token'].toString(),
title: 'You have received a gift token!'.tr, title: 'You have received a gift token!'.tr,
body: 'for '.tr + box.read(BoxName.phoneDriver).toString(), body: 'for '.tr + box.read(BoxName.phoneDriver).toString(),
isTopic: false, // Important: this is a token isTopic: false, // Important: this is a token

View File

@@ -8,12 +8,33 @@ void showInBrowser(String url) async {
} }
Future<void> makePhoneCall(String phoneNumber) async { Future<void> makePhoneCall(String phoneNumber) async {
// 1. تنظيف الرقم (إزالة المسافات والفواصل)
String formattedNumber = phoneNumber.replaceAll(RegExp(r'\s+'), '');
// 2. التحقق من طول الرقم لتحديد طريقة التنسيق
if (formattedNumber.length > 6) {
// --- التعديل المطلوب ---
if (formattedNumber.startsWith('09')) {
// إذا كان يبدأ بـ 09 (رقم موبايل سوري محلي)
// نحذف أول خانة (الصفر) ونضيف +963
formattedNumber = '+963${formattedNumber.substring(1)}';
} else if (!formattedNumber.startsWith('+')) {
// إذا لم يكن يبدأ بـ + (ولم يكن يبدأ بـ 09)، نضيف + في البداية
// هذا للحفاظ على منطقك القديم للأرقام الدولية الأخرى
formattedNumber = '+$formattedNumber';
}
}
// 3. التنفيذ (Launch)
final Uri launchUri = Uri( final Uri launchUri = Uri(
scheme: 'tel', scheme: 'tel',
path: phoneNumber, path: formattedNumber,
); );
if (await canLaunchUrl(launchUri)) {
await launchUrl(launchUri); await launchUrl(launchUri);
} }
}
void launchCommunication( void launchCommunication(
String method, String contactInfo, String message) async { String method, String contactInfo, String message) async {

View File

@@ -1,5 +1,3 @@
import 'dart:io';
import 'package:sefer_driver/views/home/on_boarding_page.dart'; import 'package:sefer_driver/views/home/on_boarding_page.dart';
import 'package:sefer_driver/views/widgets/error_snakbar.dart'; import 'package:sefer_driver/views/widgets/error_snakbar.dart';
import 'package:flutter/material.dart'; import 'package:flutter/material.dart';
@@ -9,12 +7,10 @@ import 'package:sefer_driver/constant/colors.dart';
import 'package:sefer_driver/constant/links.dart'; import 'package:sefer_driver/constant/links.dart';
import 'package:sefer_driver/controller/functions/crud.dart'; import 'package:sefer_driver/controller/functions/crud.dart';
import 'package:sefer_driver/main.dart'; import 'package:sefer_driver/main.dart';
import 'package:sefer_driver/onbording_page.dart';
import 'package:sefer_driver/views/widgets/elevated_btn.dart'; import 'package:sefer_driver/views/widgets/elevated_btn.dart';
import 'package:sefer_driver/views/widgets/my_textField.dart'; import 'package:sefer_driver/views/widgets/my_textField.dart';
import '../../constant/style.dart'; import '../../constant/style.dart';
import 'encrypt_decrypt.dart';
class LogOutController extends GetxController { class LogOutController extends GetxController {
TextEditingController checkTxtController = TextEditingController(); TextEditingController checkTxtController = TextEditingController();

View File

@@ -48,7 +48,7 @@ Future<String> getPackageInfo() async {
void showUpdateDialog(BuildContext context) { void showUpdateDialog(BuildContext context) {
final String storeUrl = Platform.isAndroid final String storeUrl = Platform.isAndroid
? 'https://play.google.com/store/apps/details?id=com.intaleq_driver' ? 'https://play.google.com/store/apps/details?id=com.intaleq_driver'
: 'https://apps.apple.com/ae/app/intaleq-driver/id6502189302'; : 'https://apps.apple.com/jo/app/intaleq-driver/id6482995159';
showGeneralDialog( showGeneralDialog(
context: context, context: context,

View File

@@ -52,31 +52,58 @@ class DriverBehaviorController extends GetxController {
double totalSpeed = 0; double totalSpeed = 0;
int hardBrakes = 0; int hardBrakes = 0;
double totalDistance = 0; double totalDistance = 0;
// متغيرات للمقارنة مع النقطة السابقة
double? prevLat, prevLng; double? prevLat, prevLng;
DateTime? prevTime;
// ترتيب البيانات حسب الوقت لضمان دقة الحساب (اختياري لكن مفضل)
// data.sort((a, b) => a['created_at'].compareTo(b['created_at']));
for (var item in data) { for (var item in data) {
double speed = item['speed'] ?? 0; // 1. قراءة البيانات بالأسماء الصحيحة من الجدول
double lat = item['lat'] ?? 0; double lat = item['latitude'] ?? item['lat'] ?? 0.0;
double lng = item['lng'] ?? 0; double lng = item['longitude'] ?? item['lng'] ?? 0.0;
double acc = item['acceleration'] ?? 0; double acc = item['acceleration'] ?? 0.0;
if (speed > maxSpeed) maxSpeed = speed; // قراءة الوقت لحساب السرعة
totalSpeed += speed; DateTime currentTime =
DateTime.tryParse(item['created_at'].toString()) ?? DateTime.now();
// ✅ Hard brake threshold double currentSpeed = 0;
// 2. حساب السرعة والمسافة إذا وجدت نقطة سابقة
if (prevLat != null && prevLng != null && prevTime != null) {
double distKm = _calculateDistance(prevLat, prevLng, lat, lng);
int timeDiffSeconds = currentTime.difference(prevTime).inSeconds;
if (timeDiffSeconds > 0) {
// السرعة (كم/س) = (المسافة بالكيلومتر * 3600) / الزمن بالثواني
currentSpeed = (distKm * 3600) / timeDiffSeconds;
}
totalDistance += distKm;
}
// تحديث القيم الإحصائية
if (currentSpeed > maxSpeed) maxSpeed = currentSpeed;
totalSpeed += currentSpeed;
// حساب الفرملة القوية (يعتمد على التسارع المحفوظ مسبقاً)
if (acc.abs() > 3.0) hardBrakes++; if (acc.abs() > 3.0) hardBrakes++;
// ✅ Distance between points // حفظ النقطة الحالية لتكون هي "السابقة" في الدورة التالية
if (prevLat != null && prevLng != null) {
totalDistance += _calculateDistance(prevLat, prevLng, lat, lng);
}
prevLat = lat; prevLat = lat;
prevLng = lng; prevLng = lng;
prevTime = currentTime;
} }
double avgSpeed = totalSpeed / data.length; // تجنب القسمة على صفر
double avgSpeed = (data.length > 1) ? totalSpeed / (data.length - 1) : 0;
// حساب تقييم السلوك
double behaviorScore = 100 - (hardBrakes * 5) - ((maxSpeed > 100) ? 10 : 0); double behaviorScore = 100 - (hardBrakes * 5) - ((maxSpeed > 100) ? 10 : 0);
behaviorScore = behaviorScore.clamp(0, 100); behaviorScore = behaviorScore.clamp(0.0, 100.0);
return { return {
'max_speed': maxSpeed, 'max_speed': maxSpeed,

View File

@@ -35,7 +35,7 @@ class DurationController extends GetxController {
getStaticDriver() async { getStaticDriver() async {
isLoading = true; isLoading = true;
update(); update();
var res = await CRUD().getWallet( var res = await CRUD().get(
link: AppLink.driverStatistic, link: AppLink.driverStatistic,
payload: {'driverID': box.read(BoxName.driverID)}); payload: {'driverID': box.read(BoxName.driverID)});
if (res == 'failure') { if (res == 'failure') {

View File

@@ -51,7 +51,7 @@ class HomeCaptainController extends GetxController {
String totalMoneyInSEFER = '0'; String totalMoneyInSEFER = '0';
String totalDurationToday = '0'; String totalDurationToday = '0';
Timer? timer; Timer? timer;
late LatLng myLocation = const LatLng(32, 36); late LatLng myLocation = const LatLng(33.5138, 36.2765);
String totalPoints = '0'; String totalPoints = '0';
String countRefuse = '0'; String countRefuse = '0';
bool mapType = false; bool mapType = false;
@@ -99,7 +99,7 @@ class HomeCaptainController extends GetxController {
isActive = !isActive; isActive = !isActive;
if (isActive) { if (isActive) {
if (double.parse(totalPoints) > -300) { if (double.parse(totalPoints) > -30000) {
locationController.startLocationUpdates(); locationController.startLocationUpdates();
HapticFeedback.heavyImpact(); HapticFeedback.heavyImpact();
// locationBackController.startBackLocation(); // locationBackController.startBackLocation();
@@ -188,22 +188,25 @@ class HomeCaptainController extends GetxController {
// late GoogleMapController mapHomeCaptainController; // late GoogleMapController mapHomeCaptainController;
GoogleMapController? mapHomeCaptainController; GoogleMapController? mapHomeCaptainController;
// final locationController = Get.find<LocationController>();
// --- FIX 2: Smart Map Creation ---
void onMapCreated(GoogleMapController controller) { void onMapCreated(GoogleMapController controller) {
mapHomeCaptainController = controller; mapHomeCaptainController = controller;
controller.getVisibleRegion();
// Animate camera to user location (optional) // Check actual location before moving camera
var currentLoc = locationController.myLocation;
if (currentLoc.latitude != 0 && currentLoc.longitude != 0) {
controller.animateCamera( controller.animateCamera(
CameraUpdate.newLatLng(Get.find<LocationController>().myLocation), CameraUpdate.newLatLng(currentLoc),
);
} else {
// Optional: Move to default city view instead of ocean
controller.animateCamera(
CameraUpdate.newLatLngZoom(myLocation, 10),
); );
} }
}
// قم بإنشائه مباشرة
// final MapController mapController = MapController();
// bool isMapReady = false;
// void onMapReady() {
// isMapReady = true;
// print("Map is ready to be moved!");
// }
void savePeriod(Duration period) { void savePeriod(Duration period) {
final periods = box.read<List<dynamic>>(BoxName.periods) ?? []; final periods = box.read<List<dynamic>>(BoxName.periods) ?? [];
@@ -234,7 +237,14 @@ class HomeCaptainController extends GetxController {
getlocation() async { getlocation() async {
isLoading = true; isLoading = true;
update(); update();
// This ensures we try to get a fix, but map doesn't crash if it fails
await Get.find<LocationController>().getLocation(); await Get.find<LocationController>().getLocation();
var loc = Get.find<LocationController>().myLocation;
if (loc.latitude != 0) {
myLocation = loc;
}
isLoading = false; isLoading = false;
update(); update();
} }
@@ -267,7 +277,7 @@ class HomeCaptainController extends GetxController {
void onInit() async { void onInit() async {
// await locationBackController.requestLocationPermission(); // await locationBackController.requestLocationPermission();
Get.put(FirebaseMessagesController()); Get.put(FirebaseMessagesController());
// addToken(); addToken();
await getlocation(); await getlocation();
onButtonSelected(); onButtonSelected();
getDriverRate(); getDriverRate();
@@ -283,61 +293,27 @@ class HomeCaptainController extends GetxController {
getRefusedOrderByCaptain(); getRefusedOrderByCaptain();
box.write(BoxName.statusDriverLocation, 'off'); box.write(BoxName.statusDriverLocation, 'off');
locationController.addListener(() { locationController.addListener(() {
// فقط إذا كان السائق "متصل" والخريطة جاهزة // Only animate if active, map is ready, AND location is valid (not 0,0)
if (isActive && mapHomeCaptainController != null) { if (isActive && mapHomeCaptainController != null) {
var loc = locationController.myLocation;
if (loc.latitude != 0 && loc.longitude != 0) {
mapHomeCaptainController!.animateCamera( mapHomeCaptainController!.animateCamera(
CameraUpdate.newCameraPosition( CameraUpdate.newCameraPosition(
CameraPosition( CameraPosition(
target: locationController.myLocation, // الموقع الجديد target: loc,
zoom: 17.5, zoom: 17.5,
tilt: 50.0, tilt: 50.0,
bearing: locationController.heading, // اتجاه السيارة bearing: locationController.heading,
), ),
), ),
); );
} }
}
}); });
// LocationController().getLocation(); // LocationController().getLocation();
super.onInit(); super.onInit();
} }
// void getRefusedOrderByCaptain() async {
// // Get today's date in YYYY-MM-DD format
// String today = DateTime.now().toString().substring(0, 10);
// String driverId = box.read(BoxName.driverID).toString();
// String customQuery = '''
// SELECT COUNT(*) AS count
// FROM ${TableName.driverOrdersRefuse}
// WHERE driver_id = '$driverId'
// AND DATE(created_at) = '$today'
// ''';
// try {
// List<Map<String, dynamic>> results =
// await sql.getCustomQuery(customQuery);
// countRefuse = results[0]['count'].toString();
// update();
// if (int.parse(countRefuse) > 3) {
// box.write(BoxName.statusDriverLocation, 'on');
// locationController.stopLocationUpdates();
// Get.defaultDialog(
// // backgroundColor: CupertinoColors.destructiveRed,
// barrierDismissible: false,
// title: 'You Are Stopped For this Day !'.tr,
// content: Text(
// 'You Refused 3 Rides this Day that is the reason \nSee you Tomorrow!'
// .tr,
// style: AppStyle.title,
// ),
// confirm: MyElevatedButton(
// title: 'Ok , See you Tomorrow'.tr,
// onPressed: () => Get.back()));
// } else {
// box.write(BoxName.statusDriverLocation, 'off');
// }
// } catch (e) {}
// }
addToken() async { addToken() async {
String? fingerPrint = await storage.read(key: BoxName.fingerPrint); String? fingerPrint = await storage.read(key: BoxName.fingerPrint);
@@ -346,14 +322,8 @@ class HomeCaptainController extends GetxController {
'captain_id': (box.read(BoxName.driverID)).toString(), 'captain_id': (box.read(BoxName.driverID)).toString(),
'fingerPrint': (fingerPrint).toString() 'fingerPrint': (fingerPrint).toString()
}; };
Log.print('payload: ${payload}'); // Log.print('payload: ${payload}');
CRUD().post(link: AppLink.addTokensDriver, payload: payload); CRUD().post(link: AppLink.addTokensDriver, payload: payload);
await CRUD().post(
link: "${AppLink.paymentServer}/ride/firebase/addDriver.php",
payload: payload);
// MapDriverController().driverCallPassenger();
// box.write(BoxName.statusDriverLocation, 'off');
} }
getPaymentToday() async { getPaymentToday() async {
@@ -468,6 +438,7 @@ class HomeCaptainController extends GetxController {
void dispose() { void dispose() {
activeTimer?.cancel(); activeTimer?.cancel();
stopTimer(); stopTimer();
mapHomeCaptainController?.dispose(); // Dispose controller
super.dispose(); super.dispose();
} }
} }

File diff suppressed because it is too large Load Diff

View File

@@ -188,7 +188,7 @@ class OrderRequestController extends GetxController {
if (remainingTime == 0 && _timerActive) { if (remainingTime == 0 && _timerActive) {
if (applied == false) { if (applied == false) {
endTimer(); endTimer();
refuseOrder(orderID); //refuseOrder(orderID);
} }
} }
} }
@@ -211,29 +211,6 @@ class OrderRequestController extends GetxController {
} }
} }
void refuseOrder(
orderID,
) async {
await CRUD().postFromDialogue(link: AppLink.addDriverOrder, payload: {
'driver_id': box.read(BoxName.driverID),
'order_id': (orderID),
'status': 'Refused'
});
await CRUD().post(link: AppLink.updateRides, payload: {
'id': (orderID),
'status': 'Refused',
'driver_id': box.read(BoxName.driverID),
});
// if (AppLink.endPoint != AppLink.seferCairoServer) {
// CRUD().post(link: '${AppLink.endPoint}/rides/update.php', payload: {
// 'id': (orderID),
// 'status': 'Refused',
// 'driver_id': box.read(BoxName.driverID),
// });
// }
update();
}
addRideToNotificationDriverString( addRideToNotificationDriverString(
orderID, orderID,
String startLocation, String startLocation,

View File

@@ -6603,7 +6603,7 @@ const List<Country> countries = [
code: "SY", code: "SY",
dialCode: "963", dialCode: "963",
minLength: 9, minLength: 9,
maxLength: 9, maxLength: 10,
), ),
Country( Country(
name: "Taiwan", name: "Taiwan",

View File

@@ -59,7 +59,16 @@ class MyTranslation extends Translations {
'witout zero': 'بدون صفر', 'witout zero': 'بدون صفر',
'You Can Cancel the Trip and get Cost From ': 'You Can Cancel the Trip and get Cost From ':
'يمكنك إلغاء الرحلة واسترداد التكلفة من ', 'يمكنك إلغاء الرحلة واسترداد التكلفة من ',
'Please enter a correct phone': 'يرجى إدخال رقم هاتف صحيح',
'Only Syrian phone numbers are allowed':
'يسمح بأرقام الهواتف السورية فقط',
'Go to passenger:': 'اذهب إلى الراكب:',
'Birth year must be 4 digits':
'يجب أن يكون سنة الميلاد مكونة من 4 أرقام',
'Required field': 'حقل مطلوب',
'You are not near': 'أنت لست بالقرب من',
'Please enter your phone number': 'يرجى إدخال رقم هاتفك', 'Please enter your phone number': 'يرجى إدخال رقم هاتفك',
'Enter a valid year': 'أدخل سنة صحيحة',
'Phone number seems too short': 'يبدو أن رقم الهاتف قصير جدًا', 'Phone number seems too short': 'يبدو أن رقم الهاتف قصير جدًا',
'You have upload Criminal documents': 'لقد قمت بتحميل وثائق جنائية', 'You have upload Criminal documents': 'لقد قمت بتحميل وثائق جنائية',
'Close': 'إغلاق', 'Close': 'إغلاق',
@@ -658,7 +667,17 @@ Raih Gai: For same-day return trips longer than 50km.
"phone number of driver": "رقم هاتف السائق", "phone number of driver": "رقم هاتف السائق",
"Transfer budget": "نقل الميزانية", "Transfer budget": "نقل الميزانية",
"Comfort": "كمفورت", "Comfort": "كمفورت",
"Speed": "سبيد", "Speed": "سعر ثابت",
'Insert Emergency Number': 'أدخل رقم الطوارئ',
'Emergency Number': 'رقم الطوارئ',
'Save': 'حفظ',
'Stay': 'ابقى',
'Exit': 'خروج',
'Waiting': 'انتظار',
'Your data will be erased after 2 weeks\nAnd you will can\'t return to use app after 1 month ':
',سيتم مسح بياناتك بعد أسبوعين\nولن تتمكن من العودة لاستخدام التطبيق بعد شهر واحد ',
"You are in an active ride. Leaving this screen might stop tracking. Are you sure you want to exit?":
"أنت في رحلة نشطة. قد يؤدي مغادرة هذه الشاشة إلى إيقاف التتبع. هل أنت متأكد أنك تريد الخروج؟",
"Lady": "ليدي", "Lady": "ليدي",
"Permission denied": "تم رفض الإذن", "Permission denied": "تم رفض الإذن",
"Contact permission is required to pick a contact": "Contact permission is required to pick a contact":

View File

@@ -1,74 +1,54 @@
// لإضافة هذه الحزمة، قم بتشغيل الأمر التالي في الـ Terminal
// flutter pub add intl
import 'dart:async'; import 'dart:async';
import 'dart:convert';
import 'package:flutter/material.dart'; import 'package:flutter/material.dart';
import 'package:flutter/services.dart'; import 'package:flutter/services.dart';
import 'package:http/http.dart' as http;
import 'package:intl/intl.dart'; import 'package:intl/intl.dart';
import 'package:sefer_driver/constant/links.dart'; import 'package:sefer_driver/constant/links.dart';
import 'package:sefer_driver/controller/functions/crud.dart'; import 'package:sefer_driver/controller/functions/crud.dart';
import '../../../constant/box_name.dart'; import '../../../constant/box_name.dart';
import '../../../main.dart'; import '../../../main.dart';
import '../../../print.dart';
/// خدمة لإدارة عمليات الدفع المتعلقة بنظام الدفع عبر الرسائل القصيرة // ... (PaymentService class remains unchanged) ...
class PaymentService { class PaymentService {
final String _baseUrl = "${AppLink.paymentServer}/sms_webhook"; final String _baseUrl = "${AppLink.paymentServer}/ride/shamcash";
Future<String?> createInvoice({ Future<String?> createInvoice({required double amount}) async {
required String userPhone, final url = "$_baseUrl/create_invoice_shamcash.php";
required double amount,
}) async {
final url = "$_baseUrl/create_invoice.php";
try { try {
final response = await CRUD().postWallet( final response = await CRUD().postWallet(
link: url, link: url,
payload: { payload: {
'user_phone': userPhone.toString(),
'driverID': box.read(BoxName.driverID), 'driverID': box.read(BoxName.driverID),
'amount': amount.toString(), 'amount': amount.toString(),
}, },
).timeout(const Duration(seconds: 15)); // إضافة مهلة للطلب ).timeout(const Duration(seconds: 15));
if (response != 'failure') { if (response != 'failure') {
final data = (response); final data = response;
if (data['status'] == 'success' && data['invoice_number'] != null) { if (data['status'] == 'success' && data['invoice_number'] != null) {
debugPrint(
"تم إنشاء الفاتورة بنجاح. الرقم: ${data['invoice_number']}");
return data['invoice_number'].toString(); return data['invoice_number'].toString();
} else {
debugPrint("فشل في إنشاء الفاتورة من السيرفر: ${data['message']}");
return null;
} }
} else {
debugPrint("خطأ في السيرفر عند إنشاء الفاتورة: ${response.statusCode}");
return null;
} }
return null;
} catch (e) { } catch (e) {
debugPrint("حدث استثناء عند إنشاء الفاتورة: $e");
return null; return null;
} }
} }
/// دالة للتحقق من حالة فاتورة واحدة
Future<bool> checkInvoiceStatus(String invoiceNumber) async { Future<bool> checkInvoiceStatus(String invoiceNumber) async {
final url = "$_baseUrl/check_invoice_status.php"; final url = "$_baseUrl/check_status.php";
try { try {
final response = await CRUD().postWallet(link: url, payload: { final response = await CRUD().postWallet(link: url, payload: {
'invoice_number': invoiceNumber, 'invoice_number': invoiceNumber,
}).timeout(const Duration(seconds: 10)); // مهلة للشبكة }).timeout(const Duration(seconds: 10));
if (response != 'failure') { if (response != 'failure') {
final data = (response); final data = response;
return data['status'] == 'success' && return data['status'] == 'success' &&
data['invoice_status'] == 'completed'; data['invoice_status'] == 'completed';
} }
return false; return false;
} catch (e) { } catch (e) {
debugPrint("خطأ أثناء التحقق من الفاتورة: $e");
return false; return false;
} }
} }
@@ -86,14 +66,15 @@ class PaymentScreenSmsProvider extends StatefulWidget {
final double amount; final double amount;
final String providerName; final String providerName;
final String providerLogo; final String providerLogo;
final String paymentPhoneNumber; final String qrImagePath;
const PaymentScreenSmsProvider({ const PaymentScreenSmsProvider({
super.key, super.key,
required this.amount, required this.amount,
this.providerName = 'شام كاش', this.providerName = 'شام كاش',
this.providerLogo = 'assets/images/shamCash.png', this.providerLogo = 'assets/images/shamCash.png',
this.paymentPhoneNumber = '963942542053', this.qrImagePath = 'assets/images/shamcashsend.png',
// removed paymentPhoneNumber
}); });
@override @override
@@ -106,7 +87,6 @@ class _PaymentScreenSmsProviderState extends State<PaymentScreenSmsProvider> {
Timer? _pollingTimer; Timer? _pollingTimer;
PaymentStatus _status = PaymentStatus.creatingInvoice; PaymentStatus _status = PaymentStatus.creatingInvoice;
String? _invoiceNumber; String? _invoiceNumber;
final String phone = box.read(BoxName.phoneWallet);
@override @override
void initState() { void initState() {
@@ -116,17 +96,14 @@ class _PaymentScreenSmsProviderState extends State<PaymentScreenSmsProvider> {
@override @override
void dispose() { void dispose() {
_pollingTimer?.cancel(); // مهم جداً: إلغاء المؤقت عند الخروج من الشاشة _pollingTimer?.cancel();
super.dispose(); super.dispose();
} }
void _createAndPollInvoice() async { void _createAndPollInvoice() async {
setState(() => _status = PaymentStatus.creatingInvoice); setState(() => _status = PaymentStatus.creatingInvoice);
final invoiceNumber =
final invoiceNumber = await _paymentService.createInvoice( await _paymentService.createInvoice(amount: widget.amount);
userPhone: phone,
amount: widget.amount,
);
if (invoiceNumber != null && mounted) { if (invoiceNumber != null && mounted) {
setState(() { setState(() {
@@ -140,7 +117,7 @@ class _PaymentScreenSmsProviderState extends State<PaymentScreenSmsProvider> {
} }
void _startPolling(String invoiceNumber) { void _startPolling(String invoiceNumber) {
const timeoutDuration = Duration(minutes: 3); const timeoutDuration = Duration(minutes: 5);
var elapsed = Duration.zero; var elapsed = Duration.zero;
_pollingTimer = Timer.periodic(const Duration(seconds: 5), (timer) async { _pollingTimer = Timer.periodic(const Duration(seconds: 5), (timer) async {
@@ -150,64 +127,57 @@ class _PaymentScreenSmsProviderState extends State<PaymentScreenSmsProvider> {
if (mounted) setState(() => _status = PaymentStatus.paymentTimeout); if (mounted) setState(() => _status = PaymentStatus.paymentTimeout);
return; return;
} }
debugPrint("Polling... Checking invoice status for: $invoiceNumber");
final isCompleted = final isCompleted =
await _paymentService.checkInvoiceStatus(invoiceNumber); await _paymentService.checkInvoiceStatus(invoiceNumber);
if (isCompleted && mounted) { if (isCompleted && mounted) {
timer.cancel(); timer.cancel();
setState(() => _status = PaymentStatus.paymentSuccess); setState(() => _status = PaymentStatus.paymentSuccess);
// TODO: تحديث رصيد المستخدم أو تنفيذ الإجراءات اللازمة
} }
}); });
} }
/// دالة جديدة لمعالجة محاولة الرجوع للخلف Future<bool> _onPopInvoked() async {
void _onPopInvoked(bool didPop) async {
// إذا كان الرجوع قد تم بالفعل (مثلاً من خلال Navigator.pop)، لا تفعل شيئاً
if (didPop) return;
// إذا كان المستخدم ينتظر الدفع، أظهر له حوار التأكيد
if (_status == PaymentStatus.waitingForPayment) { if (_status == PaymentStatus.waitingForPayment) {
final shouldPop = await showDialog<bool>( return (await showDialog<bool>(
context: context, context: context,
builder: (context) => AlertDialog( builder: (context) => AlertDialog(
title: const Text('هل أنت متأكد؟'), title: const Text('إلغاء العملية؟', textAlign: TextAlign.right),
content: const Text('إذا خرجت الآن، سيتم إلغاء عملية الدفع الحالية.'), content: const Text(
'الخروج الآن سيؤدي لإلغاء متابعة عملية الدفع.',
textAlign: TextAlign.right),
actions: <Widget>[ actions: <Widget>[
TextButton( TextButton(
onPressed: () => Navigator.of(context).pop(false), onPressed: () => Navigator.of(context).pop(false),
child: const Text('البقاء'), child: const Text('البقاء')),
),
TextButton( TextButton(
onPressed: () => Navigator.of(context).pop(true), onPressed: () => Navigator.of(context).pop(true),
child: const Text('الخروج'), child: const Text('خروج',
), style: TextStyle(color: Colors.red))),
], ],
), ),
); )) ??
false;
// إذا وافق المستخدم على الخروج، قم بإغلاق الشاشة
if (shouldPop ?? false) {
Navigator.of(context).pop();
}
} }
return true;
} }
@override @override
Widget build(BuildContext context) { Widget build(BuildContext context) {
// استخدام PopScope بدلاً من WillPopScope return WillPopScope(
return PopScope( onWillPop: _onPopInvoked,
// منع الرجوع التلقائي فقط في حالة انتظار الدفع
canPop: _status != PaymentStatus.waitingForPayment,
// استدعاء دالة التحقق عند محاولة الرجوع
onPopInvoked: _onPopInvoked,
child: Scaffold( child: Scaffold(
appBar: AppBar(title: Text("الدفع عبر ${widget.providerName}")), backgroundColor: Colors.grey[50],
body: Padding( appBar: AppBar(
padding: const EdgeInsets.all(16.0), title: Text("دفع عبر ${widget.providerName}"),
child: Center( centerTitle: true,
child: _buildContentByStatus(), elevation: 0,
backgroundColor: Colors.white,
foregroundColor: Colors.black,
),
body: SafeArea(
child: Padding(
padding: const EdgeInsets.all(20.0),
child: Center(child: _buildContentByStatus()),
), ),
), ),
), ),
@@ -222,7 +192,7 @@ class _PaymentScreenSmsProviderState extends State<PaymentScreenSmsProvider> {
children: [ children: [
CircularProgressIndicator(), CircularProgressIndicator(),
SizedBox(height: 20), SizedBox(height: 20),
Text("جاري إنشاء فاتورة الدفع...", style: TextStyle(fontSize: 16)), Text("جاري إنشاء رقم البيان...", style: TextStyle(fontSize: 16)),
], ],
); );
case PaymentStatus.waitingForPayment: case PaymentStatus.waitingForPayment:
@@ -237,94 +207,195 @@ class _PaymentScreenSmsProviderState extends State<PaymentScreenSmsProvider> {
Widget _buildWaitingForPaymentUI() { Widget _buildWaitingForPaymentUI() {
final currencyFormat = NumberFormat.decimalPattern('ar_SY'); final currencyFormat = NumberFormat.decimalPattern('ar_SY');
final invoiceText = _invoiceNumber ?? '------'; final invoiceText = _invoiceNumber ?? '---';
return SingleChildScrollView( return SingleChildScrollView(
physics: const BouncingScrollPhysics(),
child: Column(
crossAxisAlignment: CrossAxisAlignment.center,
children: [
// 1. المبلغ (تصميم مميز)
Container(
width: double.infinity,
padding: const EdgeInsets.symmetric(vertical: 25, horizontal: 15),
decoration: BoxDecoration(
gradient: LinearGradient(
colors: [Colors.blue.shade800, Colors.blue.shade600]),
borderRadius: BorderRadius.circular(16),
boxShadow: [
BoxShadow(
color: Colors.blue.withOpacity(0.25),
blurRadius: 15,
offset: const Offset(0, 8))
],
),
child: Column( child: Column(
children: [ children: [
Image.asset(widget.providerLogo, width: 96), const Text("المبلغ المطلوب",
const SizedBox(height: 16), style: TextStyle(color: Colors.white70, fontSize: 14)),
Text("تعليمات الدفع", style: Theme.of(context).textTheme.titleLarge),
const SizedBox(height: 12),
Card(
elevation: 1.5,
shape:
RoundedRectangleBorder(borderRadius: BorderRadius.circular(12)),
child: Padding(
padding: const EdgeInsets.all(16),
child: Column(
children: [
_StepTile(number: 1, text: "افتح تطبيق محفظتك الإلكترونية."),
_StepTile(number: 2, text: "اختر خدمة تحويل الأموال."),
_StepTile(
number: 3,
text:
"أدخل المبلغ المطلوب: ${currencyFormat.format(widget.amount)} ل.س"),
_StepTile(number: 4, text: "حوّل إلى الرقم التالي:"),
// --- التعديل هنا ---
ListTile(
contentPadding: EdgeInsets.zero,
title: Text(
widget.paymentPhoneNumber,
style: const TextStyle(
fontSize: 20,
fontWeight: FontWeight.bold,
letterSpacing: 1.2),
),
trailing: OutlinedButton.icon(
onPressed: () async {
await Clipboard.setData(
ClipboardData(text: widget.paymentPhoneNumber));
if (mounted) {
ScaffoldMessenger.of(context).showSnackBar(
const SnackBar(
content: Text("تم نسخ رقم الهاتف")));
}
},
icon: const Icon(Icons.copy, size: 18),
label: const Text("نسخ"),
),
),
// --- نهاية التعديل ---
const SizedBox(height: 8), const SizedBox(height: 8),
_StepTile( Text(
number: 5, "${currencyFormat.format(widget.amount)} ل.س",
text: "هام: انسخ رقم القسيمة والصقه في خانة \"البيان\"."),
ListTile(
contentPadding: EdgeInsets.zero,
title: Text(invoiceText,
style: const TextStyle( style: const TextStyle(
fontSize: 20, color: Colors.white,
fontSize: 32,
fontWeight: FontWeight.bold),
),
],
),
),
const SizedBox(height: 30),
// 2. التعليمات والنسخ (الجزء الأهم)
Container(
padding: const EdgeInsets.all(20),
decoration: BoxDecoration(
color: Colors.white,
borderRadius: BorderRadius.circular(16),
border: Border.all(color: Colors.grey.shade200),
boxShadow: [
BoxShadow(
color: Colors.grey.shade100,
blurRadius: 10,
offset: const Offset(0, 4))
],
),
child: Column(
children: [
Row(
children: [
Container(
padding: const EdgeInsets.all(8),
decoration: BoxDecoration(
color: Colors.orange.shade50, shape: BoxShape.circle),
child: Icon(Icons.priority_high_rounded,
color: Colors.orange.shade800, size: 20),
),
const SizedBox(width: 12),
const Expanded(
child: Text(
"انسخ الرقم أدناه وضعه في خانة (الملاحظات) عند الدفع.",
style: TextStyle(
fontSize: 14, fontWeight: FontWeight.w600),
),
),
],
),
const SizedBox(height: 20),
InkWell(
onTap: () {
Clipboard.setData(ClipboardData(text: invoiceText));
ScaffoldMessenger.of(context).showSnackBar(
SnackBar(
content: const Text("تم نسخ رقم البيان ✅",
textAlign: TextAlign.center),
backgroundColor: Colors.green.shade600,
behavior: SnackBarBehavior.floating,
shape: RoundedRectangleBorder(
borderRadius: BorderRadius.circular(10)),
margin: const EdgeInsets.all(20),
),
);
},
borderRadius: BorderRadius.circular(12),
child: Container(
padding: const EdgeInsets.symmetric(
horizontal: 15, vertical: 12),
decoration: BoxDecoration(
color: Colors.grey.shade50,
borderRadius: BorderRadius.circular(12),
border:
Border.all(color: Colors.blue.shade200, width: 1.5),
),
child: Row(
mainAxisAlignment: MainAxisAlignment.spaceBetween,
children: [
Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
const Text("رقم البيان (Invoice ID)",
style: TextStyle(
fontSize: 12, color: Colors.grey)),
Text(invoiceText,
style: const TextStyle(
fontSize: 22,
fontWeight: FontWeight.bold, fontWeight: FontWeight.bold,
letterSpacing: 1.5)), letterSpacing: 1.5)),
trailing: OutlinedButton.icon( ],
onPressed: _invoiceNumber == null ),
? null const Icon(Icons.copy_rounded,
: () async { color: Colors.blue, size: 24),
await Clipboard.setData( ],
ClipboardData(text: invoiceText)); ),
if (mounted) {
ScaffoldMessenger.of(context).showSnackBar(
const SnackBar(
content: Text("تم نسخ رقم القسيمة")));
}
},
icon: const Icon(Icons.copy, size: 18),
label: const Text("نسخ"),
), ),
), ),
], ],
), ),
), ),
const SizedBox(height: 30),
// 3. الـ QR Code (قابل للاختيار/الضغط)
const Text("امسح الرمز للدفع",
style: TextStyle(
fontSize: 16,
fontWeight: FontWeight.bold,
color: Colors.black87)),
const SizedBox(height: 15),
GestureDetector(
onTap: () {
// تأثير بصري بسيط عند الضغط (أو تكبير الصورة في Dialog)
showDialog(
context: context,
builder: (ctx) => Dialog(
backgroundColor: Colors.transparent,
child: InteractiveViewer(
child: Image.asset(widget.qrImagePath),
), ),
),
);
},
child: Container(
padding: const EdgeInsets.all(15),
decoration: BoxDecoration(
color: Colors.white,
borderRadius: BorderRadius.circular(20),
border: Border.all(color: Colors.grey.shade300),
boxShadow: [
BoxShadow(
color: Colors.grey.shade200,
blurRadius: 10,
spreadRadius: 2)
],
),
child: Column(
children: [
Image.asset(
widget.qrImagePath,
width: 180,
height: 180,
fit: BoxFit.contain,
errorBuilder: (c, o, s) => const Icon(Icons.qr_code_2,
size: 100, color: Colors.grey),
),
const SizedBox(height: 8),
const Text("اضغط للتكبير",
style: TextStyle(fontSize: 10, color: Colors.grey)),
],
),
),
),
const SizedBox(height: 40),
// مؤشر الانتظار
const LinearProgressIndicator(backgroundColor: Colors.white),
const SizedBox(height: 10),
const Text("ننتظر إشعار الدفع تلقائياً...",
style: TextStyle(color: Colors.grey, fontSize: 12)),
const SizedBox(height: 20), const SizedBox(height: 20),
const LinearProgressIndicator(minHeight: 2),
const SizedBox(height: 12),
Text("بانتظار تأكيد الدفع...",
style: TextStyle(color: Colors.grey.shade700)),
const SizedBox(height: 4),
const Text("هذه الشاشة ستتحدث تلقائيًا",
style: TextStyle(color: Colors.grey)),
], ],
), ),
); );
@@ -334,14 +405,26 @@ class _PaymentScreenSmsProviderState extends State<PaymentScreenSmsProvider> {
return Column( return Column(
mainAxisAlignment: MainAxisAlignment.center, mainAxisAlignment: MainAxisAlignment.center,
children: [ children: [
const Icon(Icons.check_circle, color: Colors.green, size: 80), const Icon(Icons.verified_rounded, color: Colors.green, size: 100),
const SizedBox(height: 20), const SizedBox(height: 20),
const Text("تم الدفع بنجاح!", const Text("تم الدفع بنجاح!",
style: TextStyle(fontSize: 22, fontWeight: FontWeight.bold)), style: TextStyle(fontSize: 24, fontWeight: FontWeight.bold)),
const SizedBox(height: 20), const SizedBox(height: 10),
ElevatedButton( const Text("تم إضافة الرصيد والمكافأة إلى حسابك",
style: TextStyle(color: Colors.grey)),
const SizedBox(height: 40),
SizedBox(
width: double.infinity,
child: ElevatedButton(
style: ElevatedButton.styleFrom(
backgroundColor: Colors.green,
foregroundColor: Colors.white,
padding: const EdgeInsets.symmetric(vertical: 16),
shape: RoundedRectangleBorder(
borderRadius: BorderRadius.circular(12))),
onPressed: () => Navigator.of(context).pop(), onPressed: () => Navigator.of(context).pop(),
child: const Text("العودة"), child: const Text("متابعة", style: TextStyle(fontSize: 18)),
),
), ),
], ],
); );
@@ -351,47 +434,41 @@ class _PaymentScreenSmsProviderState extends State<PaymentScreenSmsProvider> {
return Column( return Column(
mainAxisAlignment: MainAxisAlignment.center, mainAxisAlignment: MainAxisAlignment.center,
children: [ children: [
const Icon(Icons.error, color: Colors.red, size: 80), Icon(Icons.error_outline_rounded, color: Colors.red.shade400, size: 80),
const SizedBox(height: 20), const SizedBox(height: 20),
Text( Text(
_status == PaymentStatus.paymentTimeout _status == PaymentStatus.paymentTimeout
? "انتهى الوقت المحدد للدفع" ? "انتهى الوقت"
: "حدث خطأ ما", : "لم يتم التحقق",
style: const TextStyle(fontSize: 22, fontWeight: FontWeight.bold), style: const TextStyle(fontSize: 22, fontWeight: FontWeight.bold),
),
const SizedBox(height: 15),
const Padding(
padding: EdgeInsets.symmetric(horizontal: 30),
child: Text(
"لم يصلنا إشعار الدفع. هل تأكدت من وضع (رقم البيان) في الملاحظات؟",
textAlign: TextAlign.center, textAlign: TextAlign.center,
style: TextStyle(color: Colors.grey, height: 1.5)),
), ),
const SizedBox(height: 8), const SizedBox(height: 40),
const Text("يرجى المحاولة مرة أخرى.", style: TextStyle(fontSize: 16)), SizedBox(
const SizedBox(height: 20), width: double.infinity,
ElevatedButton( child: ElevatedButton.icon(
style: ElevatedButton.styleFrom(
padding: const EdgeInsets.symmetric(vertical: 15),
shape: RoundedRectangleBorder(
borderRadius: BorderRadius.circular(12))),
onPressed: _createAndPollInvoice, onPressed: _createAndPollInvoice,
child: const Text("المحاولة مرة أخرى"), icon: const Icon(Icons.refresh),
label: const Text("حاول مرة أخرى"),
), ),
),
const SizedBox(height: 15),
TextButton(
onPressed: () => Navigator.of(context).pop(),
child: const Text("إلغاء", style: TextStyle(color: Colors.grey)),
)
], ],
); );
} }
} }
// ويدجت مساعد لعرض خطوات التعليمات بشكل أنيق
class _StepTile extends StatelessWidget {
final int number;
final String text;
const _StepTile({required this.number, required this.text});
@override
Widget build(BuildContext context) {
return ListTile(
contentPadding: EdgeInsets.zero,
leading: CircleAvatar(
radius: 12,
backgroundColor: Theme.of(context).primaryColor,
child: Text("$number",
style: const TextStyle(
fontSize: 12,
color: Colors.white,
fontWeight: FontWeight.bold)),
),
title: Text(text),
);
}
}

View File

@@ -356,9 +356,14 @@ class _PhoneNumberScreenState extends State<PhoneNumberScreen> {
validator: (phone) { validator: (phone) {
if (phone == null || phone.number.isEmpty) { if (phone == null || phone.number.isEmpty) {
return 'Please enter your phone number'.tr; return 'Please enter your phone number'.tr;
} // Check if the number is a Syrian number
if (phone.countryISOCode != 'SY') {
return 'Only Syrian phone numbers are allowed'.tr;
} }
// Check if the national number part starts with '0' // Check if the national number part starts with '0'
if (phone.number.startsWith('0')) { if (phone.completeNumber.startsWith('96309') ||
phone.completeNumber.startsWith('+9630') ||
phone.completeNumber.startsWith('09')) {
return 'Please enter the number without the leading 0'.tr; return 'Please enter the number without the leading 0'.tr;
} }
if (phone.completeNumber.length < 10) { if (phone.completeNumber.length < 10) {

View File

@@ -121,6 +121,29 @@ class RegistrationView extends StatelessWidget {
}, },
), ),
const SizedBox(height: 16), const SizedBox(height: 16),
TextFormField(
controller: c.bithdateController,
decoration: InputDecoration(
labelText: 'سنة الميلاد'.tr,
hintText: '1999'.tr,
border: const OutlineInputBorder(),
),
keyboardType: TextInputType.number,
validator: (v) {
if (v == null || v.isEmpty) {
return 'Required field'.tr;
}
if (v.length != 4) {
return 'Birth year must be 4 digits'.tr;
}
// Optional: check if its a valid number
if (int.tryParse(v) == null) {
return 'Enter a valid year'.tr;
}
return null;
},
),
const SizedBox(height: 16),
TextFormField( TextFormField(
controller: c.driverLicenseExpiryController, controller: c.driverLicenseExpiryController,
decoration: InputDecoration( decoration: InputDecoration(

View File

@@ -39,18 +39,18 @@ class SettingsCaptain extends StatelessWidget {
subtitle: 'Change the app language'.tr, subtitle: 'Change the app language'.tr,
onTap: () => Get.to(() => const Language()), onTap: () => Get.to(() => const Language()),
), ),
_buildListTile( // _buildListTile(
icon: Icons.flag_outlined, // icon: Icons.flag_outlined,
title: 'Change Country'.tr, // title: 'Change Country'.tr,
subtitle: 'Get features for your country'.tr, // subtitle: 'Get features for your country'.tr,
onTap: () => Get.to( // onTap: () => Get.to(
() => MyScafolld( // () => MyScafolld(
title: 'Change Country'.tr, // title: 'Change Country'.tr,
body: [CountryPickerFromSetting()], // body: [CountryPickerFromSetting()],
isleading: true, // isleading: true,
), // ),
), // ),
), // ),
], ],
), ),
const SizedBox(height: 20), const SizedBox(height: 20),

View File

@@ -1,3 +1,4 @@
import 'package:sefer_driver/constant/box_name.dart';
import 'package:sefer_driver/constant/style.dart'; import 'package:sefer_driver/constant/style.dart';
import 'package:sefer_driver/views/widgets/elevated_btn.dart'; import 'package:sefer_driver/views/widgets/elevated_btn.dart';
import 'package:flutter/material.dart'; import 'package:flutter/material.dart';
@@ -6,6 +7,7 @@ import 'package:sefer_driver/controller/home/captin/map_driver_controller.dart';
import '../../../constant/colors.dart'; import '../../../constant/colors.dart';
import '../../../controller/functions/location_controller.dart'; import '../../../controller/functions/location_controller.dart';
import '../../../main.dart';
import '../../Rate/rate_passenger.dart'; import '../../Rate/rate_passenger.dart';
import '../../widgets/my_textField.dart'; import '../../widgets/my_textField.dart';
import 'mapDriverWidgets/driver_end_ride_bar.dart'; import 'mapDriverWidgets/driver_end_ride_bar.dart';
@@ -14,79 +16,122 @@ import 'mapDriverWidgets/google_map_app.dart';
import 'mapDriverWidgets/passenger_info_window.dart'; import 'mapDriverWidgets/passenger_info_window.dart';
import 'mapDriverWidgets/sos_connect.dart'; import 'mapDriverWidgets/sos_connect.dart';
// Changed: تم إعادة بناء الصفحة بالكامل لتكون أكثر تنظيمًا
class PassengerLocationMapPage extends StatelessWidget { class PassengerLocationMapPage extends StatelessWidget {
PassengerLocationMapPage({super.key}); PassengerLocationMapPage({super.key});
final LocationController locationController = Get.put(LocationController()); final LocationController locationController = Get.put(LocationController());
final MapDriverController mapDriverController = final MapDriverController mapDriverController =
Get.put(MapDriverController()); Get.put(MapDriverController());
// Helper function to show exit confirmation dialog
Future<bool> showExitDialog() async {
bool? result = await Get.defaultDialog(
title: "Warning".tr,
titleStyle: AppStyle.title.copyWith(color: AppColor.redColor),
middleText:
"You are in an active ride. Leaving this screen might stop tracking. Are you sure you want to exit?"
.tr,
middleTextStyle: AppStyle.title,
barrierDismissible: false,
confirm: MyElevatedButton(
title: 'Stay'.tr,
kolor: AppColor.greenColor,
onPressed: () => Get.back(result: false), // Return false (Don't pop)
),
cancel: MyElevatedButton(
title: 'Exit'.tr,
kolor: AppColor.redColor,
onPressed: () => Get.back(result: true), // Return true (Allow pop)
),
);
return result ?? false;
}
@override @override
Widget build(BuildContext context) { Widget build(BuildContext context) {
// New: استخدام addPostFrameCallback لضمان أن تحميل البيانات يتم بعد بناء الواجهة
WidgetsBinding.instance.addPostFrameCallback((_) { WidgetsBinding.instance.addPostFrameCallback((_) {
if (Get.arguments != null && Get.arguments is Map<String, dynamic>) { if (Get.arguments != null && Get.arguments is Map<String, dynamic>) {
mapDriverController.argumentLoading(); mapDriverController.argumentLoading();
mapDriverController.startTimerToShowPassengerInfoWindowFromDriver(); mapDriverController.startTimerToShowPassengerInfoWindowFromDriver();
} else {
// في حال عدم وجود arguments، يتم التعامل مع هذا الخطأ
Get.snackbar("Error", "No order data found.");
Get.back();
} }
}); });
return Scaffold( // ✅ Added PopScope to intercept back button
return PopScope(
canPop: false, // Prevents immediate popping
onPopInvokedWithResult: (didPop, result) async {
if (didPop) {
return;
}
// Show dialog
final shouldExit = await showExitDialog();
if (shouldExit) {
Get.back(); // Manually pop if confirmed
}
},
child: Scaffold(
body: SafeArea( body: SafeArea(
child: Stack( child: Stack(
children: [ children: [
// 1. الخريطة في الخلفية // 1. Map
GoogleDriverMap(locationController: locationController), GoogleDriverMap(locationController: locationController),
// 2. شريط تعليمات الطريق في الأعلى // 2. Instructions
const InstructionsOfRoads(), const InstructionsOfRoads(),
// 4. نافذة معلومات الراكب في الأسفل (تظهر قبل بدء الرحلة) // 3. Passenger Info
Positioned(
PassengerInfoWindow(), top: 0,
// 3. زر إلغاء الرحلة في الأعلى يسارًا left: 0,
right: 0,
child: PassengerInfoWindow(),
),
// 4. Cancel Widget
CancelWidget(mapDriverController: mapDriverController), CancelWidget(mapDriverController: mapDriverController),
// Changed: تم تعديل تصميم زر الإلغاء ليكون أيقونة بسيطة في الأعلى
// 5. شريط معلومات وإنهاء الرحلة (يظهر بعد بدء الرحلة) // 5. End Ride Bar
driverEndRideBar(), driverEndRideBar(),
// 6. أزرار الطوارئ والاتصال // 6. SOS
SosConnect(), SosConnect(),
// 7. دائرة عرض السرعة // 7. Speed
speedCircle(), speedCircle(),
GoogleMapApp(),
// 8. نافذة عرض السعر النهائي (تظهر بعد انتهاء الرحلة) // 8. External Map
Positioned(
bottom: 100,
right: 10,
child: GoogleMapApp(),
),
// 9. Prices Window
const PricesWindow(), const PricesWindow(),
], ],
), ),
)); )),
);
} }
} }
// New: تصميم جديد لشريط تعليمات الطريق في أعلى الشاشة // ... The rest of your widgets (InstructionsOfRoads, CancelWidget, etc.) remain unchanged ...
// ... Keep the code below exactly as you had it in the previous snippet ...
class InstructionsOfRoads extends StatelessWidget { class InstructionsOfRoads extends StatelessWidget {
const InstructionsOfRoads({super.key}); const InstructionsOfRoads({super.key});
@override @override
Widget build(BuildContext context) { Widget build(BuildContext context) {
return GetBuilder<MapDriverController>( return Positioned(
builder: (controller) =>
// يتم إظهار التعليمات فقط إذا كانت متوفرة
controller.currentInstruction.isNotEmpty
? Positioned(
bottom: 10, bottom: 10,
left: MediaQuery.of(context).size.width * 0.15, left: MediaQuery.of(context).size.width * 0.15,
right: MediaQuery.of(context).size.width * 0.15, right: MediaQuery.of(context).size.width * 0.15,
child: AnimatedContainer( child: GetBuilder<MapDriverController>(
builder: (controller) => controller.currentInstruction.isNotEmpty
? AnimatedContainer(
duration: const Duration(milliseconds: 300), duration: const Duration(milliseconds: 300),
padding: const EdgeInsets.symmetric( padding:
horizontal: 16, vertical: 12), const EdgeInsets.symmetric(horizontal: 16, vertical: 12),
decoration: BoxDecoration( decoration: BoxDecoration(
color: Colors.white, color: Colors.white,
borderRadius: BorderRadius.circular(30), borderRadius: BorderRadius.circular(30),
@@ -101,8 +146,7 @@ class InstructionsOfRoads extends StatelessWidget {
child: Row( child: Row(
mainAxisAlignment: MainAxisAlignment.center, mainAxisAlignment: MainAxisAlignment.center,
children: [ children: [
const Icon(Icons.directions, const Icon(Icons.directions, color: AppColor.primaryColor),
color: AppColor.primaryColor),
const SizedBox(width: 10), const SizedBox(width: 10),
Expanded( Expanded(
child: Text( child: Text(
@@ -112,16 +156,29 @@ class InstructionsOfRoads extends StatelessWidget {
overflow: TextOverflow.ellipsis, overflow: TextOverflow.ellipsis,
), ),
), ),
const SizedBox(width: 10),
InkWell(
onTap: () {
controller.toggleTts();
},
child: Icon(
controller.isTtsEnabled
? Icons.volume_up
: Icons.volume_off,
color: controller.isTtsEnabled
? AppColor.greenColor
: Colors.grey,
),
),
], ],
), ),
),
) )
: const SizedBox(), // في حالة عدم وجود تعليمات، لا يظهر شيء : const SizedBox(),
),
); );
} }
} }
// Changed: تم تعديل تصميم وموضع زر الإلغاء ليكون أيقونة بسيطة في الأعلى
class CancelWidget extends StatelessWidget { class CancelWidget extends StatelessWidget {
const CancelWidget({ const CancelWidget({
super.key, super.key,
@@ -137,7 +194,6 @@ class CancelWidget extends StatelessWidget {
left: 10, left: 10,
child: GetBuilder<MapDriverController>( child: GetBuilder<MapDriverController>(
builder: (controller) { builder: (controller) {
// يظهر زر الإلغاء فقط قبل انتهاء الرحلة
if (controller.isRideFinished) return const SizedBox.shrink(); if (controller.isRideFinished) return const SizedBox.shrink();
return GestureDetector( return GestureDetector(
@@ -199,7 +255,6 @@ class CancelWidget extends StatelessWidget {
} }
} }
// Changed: تم تعديل تصميم نافذة السعر لتكون أكثر وضوحًا
class PricesWindow extends StatelessWidget { class PricesWindow extends StatelessWidget {
const PricesWindow({ const PricesWindow({
super.key, super.key,

View File

@@ -621,46 +621,96 @@ class FloatingActionButtons extends StatelessWidget {
const SizedBox( const SizedBox(
height: 5, height: 5,
), ),
// هذا الكود يوضع داخل الـ Stack في ملف الواجهة (HomeCaptain View)
box.read(BoxName.rideStatus) == 'Applied' || box.read(BoxName.rideStatus) == 'Applied' ||
box.read(BoxName.rideStatus) == 'Begin' box.read(BoxName.rideStatus) == 'Begin'
? Positioned( ? Positioned(
bottom: Get.height * .2, bottom: Get.height * .2,
right: 6, // جعلنا الزر يظهر في المنتصف أو يمتد ليكون واضحاً جداً
right: 20,
left: 20,
child: Center(
child: AnimatedContainer( child: AnimatedContainer(
duration: const Duration(microseconds: 200), duration: const Duration(
width: homeCaptainController.widthMapTypeAndTraffic, milliseconds:
200), // تم تصحيح microseconds إلى milliseconds لحركة أنعم
// أزلنا العرض الثابت homeCaptainController.widthMapTypeAndTraffic لكي يتسع للنص
// width: homeCaptainController.widthMapTypeAndTraffic,
decoration: BoxDecoration( decoration: BoxDecoration(
border: Border.all(color: AppColor.blueColor), border: Border.all(
color: AppColor.secondaryColor, color: AppColor.blueColor,
borderRadius: BorderRadius.circular(15)), width: 2), // تعريض الإطار قليلاً
child: GestureDetector( color: AppColor.secondaryColor, // لون الخلفية
borderRadius: BorderRadius.circular(
30), // تدوير الحواف ليشبه الأزرار الحديثة
boxShadow: [
BoxShadow(
color: Colors.black.withOpacity(0.2),
blurRadius: 8,
offset: const Offset(0, 4),
)
]),
child: Material(
color: Colors.transparent,
child: InkWell(
borderRadius: BorderRadius.circular(30),
onLongPress: () { onLongPress: () {
// وظيفة الحذف عند الضغط الطويل (للطوارئ)
box.write(BoxName.rideStatus, 'delete'); box.write(BoxName.rideStatus, 'delete');
homeCaptainController.update(); homeCaptainController.update();
}, },
child: IconButton( onTap: () {
onPressed: () { // نفس منطقك الأصلي للانتقال
box.read(BoxName.rideStatus) == 'Applied' if (box.read(BoxName.rideStatus) == 'Applied') {
? {
Get.to(() => PassengerLocationMapPage(), Get.to(() => PassengerLocationMapPage(),
arguments: arguments: box.read(BoxName.rideArguments));
box.read(BoxName.rideArguments)),
Get.put(MapDriverController()) Get.put(MapDriverController())
.changeRideToBeginToPassenger() .changeRideToBeginToPassenger();
} else {
Get.to(() => PassengerLocationMapPage(),
arguments: box.read(BoxName.rideArguments));
Get.put(MapDriverController())
.startRideFromStartApp();
} }
: {
Get.to(() => PassengerLocationMapPage(),
arguments:
box.read(BoxName.rideArguments)),
Get.put(MapDriverController())
.startRideFromStartApp()
};
}, },
icon: const Icon( child: Padding(
Icons.directions_rounded, padding: const EdgeInsets.symmetric(
size: 29, horizontal: 20, vertical: 12),
child: Row(
mainAxisSize:
MainAxisSize.min, // حجم الزر على قد المحتوى
mainAxisAlignment: MainAxisAlignment.center,
children: [
const Icon(
Icons
.directions_car_filled_rounded, // تغيير الأيقونة لسيارة أو اتجاهات لتكون معبرة أكثر
size: 24,
color: AppColor.blueColor, color: AppColor.blueColor,
), ),
const SizedBox(
width: 10), // مسافة بين الأيقونة والنص
Text(
"متابعة الرحلة", // النص الواضح للسائق
style: const TextStyle(
color: AppColor.blueColor,
fontSize: 16,
fontWeight: FontWeight.bold,
fontFamily:
'Cairo', // تأكد من نوع الخط المستخدم عندك
),
),
if (box.read(BoxName.rideStatus) ==
'Begin') ...[
const SizedBox(width: 5),
// إضافة مؤشر صغير (نقطة حمراء) إذا كانت الرحلة قد بدأت بالفعل (اختياري)
const Icon(Icons.circle,
size: 8, color: Colors.green)
]
],
),
),
),
), ),
), ),
), ),

View File

@@ -219,9 +219,10 @@ Future<void> checkForPendingOrderFromServer() async {
link: AppLink.getArgumentAfterAppliedFromBackground, link: AppLink.getArgumentAfterAppliedFromBackground,
payload: {'driver_id': driverId}, payload: {'driver_id': driverId},
); );
Log.print('response: ${response}');
// Assuming the server returns order data if found, or 'failure'/'none' if not // Assuming the server returns order data if found, or 'failure'/'none' if not
if (response != 'failure') { if (response['status'] == 'success') {
final Map<String, dynamic> orderInfoFromServer = response['message']; final Map<String, dynamic> orderInfoFromServer = response['message'];
final Map<String, dynamic> rideArguments = final Map<String, dynamic> rideArguments =
_transformServerDataToAppArguments(orderInfoFromServer); _transformServerDataToAppArguments(orderInfoFromServer);

View File

@@ -1,6 +1,7 @@
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';
import 'package:intl/intl.dart';
import 'package:slide_to_act/slide_to_act.dart'; import 'package:slide_to_act/slide_to_act.dart';
import 'package:vibration/vibration.dart'; import 'package:vibration/vibration.dart';
import 'dart:io'; import 'dart:io';
@@ -11,14 +12,21 @@ import '../../../../controller/home/captin/map_driver_controller.dart';
import '../../../widgets/elevated_btn.dart'; import '../../../widgets/elevated_btn.dart';
// Changed: إعادة تصميم كاملة للشريط ليصبح شريطًا علويًا عند بدء الرحلة // Changed: إعادة تصميم كاملة للشريط ليصبح شريطًا علويًا عند بدء الرحلة
GetBuilder<MapDriverController> driverEndRideBar() { // ملف: driver_end_ride_bar.dart
return GetBuilder<MapDriverController>(
builder: (controller) => AnimatedPositioned( Widget driverEndRideBar() {
duration: const Duration(milliseconds: 300), // 1. Positioned هي الوالد المباشر (لأنها داخل Stack في الصفحة الرئيسية)
// New: يظهر الشريط من الأعلى عندما تبدأ الرحلة return Positioned(
top: controller.isRideStarted ? 0 : -200, top: 0,
left: 0, left: 0,
right: 0, right: 0,
// 2. GetBuilder يكون في الداخل
child: GetBuilder<MapDriverController>(
builder: (controller) => AnimatedContainer(
duration: const Duration(milliseconds: 300),
// 3. نستخدم التحريك (Translation) لإخفاء الشريط وإظهاره بدلاً من تغيير الـ top
transform: Matrix4.translationValues(
0, controller.isRideStarted ? 0 : -250, 0),
child: Card( child: Card(
margin: EdgeInsets.zero, margin: EdgeInsets.zero,
elevation: 10, elevation: 10,
@@ -26,10 +34,10 @@ GetBuilder<MapDriverController> driverEndRideBar() {
borderRadius: BorderRadius.vertical(bottom: Radius.circular(24)), borderRadius: BorderRadius.vertical(bottom: Radius.circular(24)),
), ),
child: Padding( child: Padding(
padding: const EdgeInsets.symmetric(horizontal: 16.0, vertical: 12.0), padding:
const EdgeInsets.symmetric(horizontal: 16.0, vertical: 12.0),
child: Column( child: Column(
children: [ children: [
// -- معلومات الرحلة --
if (controller.carType != 'Mishwar Vip') if (controller.carType != 'Mishwar Vip')
Row( Row(
mainAxisAlignment: MainAxisAlignment.spaceEvenly, mainAxisAlignment: MainAxisAlignment.spaceEvenly,
@@ -48,20 +56,18 @@ GetBuilder<MapDriverController> driverEndRideBar() {
), ),
_buildInfoColumn( _buildInfoColumn(
icon: Icons.money_sharp, icon: Icons.money_sharp,
text: '${controller.paymentAmount} ${'SYP'.tr}', text:
'${NumberFormat('#,##0').format(double.tryParse(controller.paymentAmount.toString()) ?? 0)} ${'SYP'.tr}',
label: 'Price'.tr, label: 'Price'.tr,
), ),
], ],
), ),
// ... بقية الكود كما هو (الأزرار والمؤقت)
if (controller.carType != 'Mishwar Vip') if (controller.carType != 'Mishwar Vip')
const Divider(height: 20), const Divider(height: 20),
const _builtTimerAndCarType(),
// -- مؤقت الرحلة المتبقي (إن وجد) --
_builtTimerAndCarType(),
const SizedBox(height: 12), const SizedBox(height: 12),
// -- زر إنهاء الرحلة المنزلق --
SlideAction( SlideAction(
height: 55, height: 55,
borderRadius: 15, borderRadius: 15,
@@ -83,7 +89,7 @@ GetBuilder<MapDriverController> driverEndRideBar() {
onSubmit: () { onSubmit: () {
HapticFeedback.mediumImpact(); HapticFeedback.mediumImpact();
controller.finishRideFromDriver(); controller.finishRideFromDriver();
return null; // New: onSubmit now returns null return null;
}, },
), ),
], ],
@@ -91,6 +97,7 @@ GetBuilder<MapDriverController> driverEndRideBar() {
), ),
), ),
), ),
),
); );
} }
@@ -116,19 +123,22 @@ class _builtTimerAndCarType extends StatelessWidget {
@override @override
Widget build(BuildContext context) { Widget build(BuildContext context) {
final controller = Get.find<MapDriverController>(); // نستخدم GetBuilder هنا لضمان تحديث العداد في كل ثانية
return GetBuilder<MapDriverController>(builder: (controller) {
return Row( return Row(
mainAxisAlignment: MainAxisAlignment.center, mainAxisAlignment: MainAxisAlignment.center,
children: [ children: [
// -- نوع السيارة -- // -- نوع السيارة --
Container( Container(
decoration: AppStyle.boxDecoration1.copyWith(color: Colors.grey[200]), decoration:
AppStyle.boxDecoration1.copyWith(color: Colors.grey[200]),
padding: const EdgeInsets.symmetric(horizontal: 16, vertical: 8), padding: const EdgeInsets.symmetric(horizontal: 16, vertical: 8),
child: Text( child: Text(
controller.carType, controller.carType.tr,
style: AppStyle.title.copyWith(fontWeight: FontWeight.bold), style: AppStyle.title.copyWith(fontWeight: FontWeight.bold),
), ),
), ),
// -- مؤقت الرحلة -- // -- مؤقت الرحلة --
if (controller.carType != 'Comfort' && if (controller.carType != 'Comfort' &&
controller.carType != 'Mishwar Vip' && controller.carType != 'Mishwar Vip' &&
@@ -160,6 +170,7 @@ class _builtTimerAndCarType extends StatelessWidget {
valueColor: AlwaysStoppedAnimation<Color>( valueColor: AlwaysStoppedAnimation<Color>(
Colors.white.withOpacity(0.2)), Colors.white.withOpacity(0.2)),
minHeight: 40, minHeight: 40,
// تأكد من أن هذه القيمة بين 0.0 و 1.0 في الكونترولر
value: controller.progressTimerRideBegin.toDouble(), value: controller.progressTimerRideBegin.toDouble(),
), ),
Text( Text(
@@ -175,12 +186,28 @@ class _builtTimerAndCarType extends StatelessWidget {
], ],
], ],
); );
});
} }
} }
// Changed: تم تعديل مكان ومظهر دائرة السرعة // Changed: تم تعديل مكان ومظهر دائرة السرعة
GetBuilder<MapDriverController> speedCircle() { // غيرنا نوع الإرجاع إلى Widget بدلاً من GetBuilder
if (Get.find<MapDriverController>().speed > 100) { Widget speedCircle() {
// التحقق من السرعة يمكن أن يبقى هنا أو داخل الـ builder
// لكن التنبيهات (Vibration/Dialog) يفضل أن تكون داخل الـ builder لتجنب تكرارها أثناء إعادة البناء الخارجية
return Positioned(
// New: Positioned الآن هي الوالد المباشر (يجب وضع هذه الدالة داخل Stack في الصفحة الرئيسية)
bottom: 25,
left: 3,
child: GetBuilder<MapDriverController>(
builder: (controller) {
// التحقق من التنبيهات هنا
if (controller.speed > 100) {
// نستخدم addPostFrameCallback لضمان عدم استدعاء الـ Dialog أثناء عملية البناء
WidgetsBinding.instance.addPostFrameCallback((_) {
if (!Get.isDialogOpen!) {
// تجنب فتح أكثر من نافذة
if (Platform.isIOS) { if (Platform.isIOS) {
HapticFeedback.selectionClick(); HapticFeedback.selectionClick();
} else { } else {
@@ -198,18 +225,17 @@ GetBuilder<MapDriverController> speedCircle() {
), ),
); );
} }
return GetBuilder<MapDriverController>( });
builder: (controller) { }
return controller.isRideStarted return controller.isRideStarted
? Positioned( ? Container(
// New: تم وضع دائرة السرعة في الأسفل يمينًا
bottom: 25,
left: 3,
child: Container(
decoration: BoxDecoration( decoration: BoxDecoration(
shape: BoxShape.circle, shape: BoxShape.circle,
color: Colors.white, color: Colors.white,
boxShadow: [BoxShadow(blurRadius: 5, color: Colors.black26)], boxShadow: const [
BoxShadow(blurRadius: 5, color: Colors.black26)
],
border: Border.all( border: Border.all(
width: 4, width: 4,
color: controller.speed > 100 color: controller.speed > 100
@@ -227,13 +253,13 @@ GetBuilder<MapDriverController> speedCircle() {
controller.speed.toStringAsFixed(0), controller.speed.toStringAsFixed(0),
style: AppStyle.number.copyWith(fontSize: 24), style: AppStyle.number.copyWith(fontSize: 24),
), ),
Text("km/h", style: TextStyle(fontSize: 10)), const Text("km/h", style: TextStyle(fontSize: 10)),
], ],
), ),
), ),
),
) )
: const SizedBox(); : const SizedBox(); // إذا لم تبدأ الرحلة نخفي العنصر وهو داخل الـ Positioned
}, },
),
); );
} }

View File

@@ -1,37 +1,43 @@
import 'package:sefer_driver/views/widgets/mydialoug.dart';
import 'package:flutter/material.dart'; import 'package:flutter/material.dart';
import 'package:get/get.dart'; import 'package:get/get.dart';
import 'package:sefer_driver/constant/colors.dart'; import 'package:sefer_driver/constant/colors.dart';
import 'package:sefer_driver/constant/info.dart';
import 'package:sefer_driver/controller/firebase/firbase_messge.dart';
import 'package:sefer_driver/controller/home/captin/map_driver_controller.dart'; import 'package:sefer_driver/controller/home/captin/map_driver_controller.dart';
import 'package:sefer_driver/views/widgets/elevated_btn.dart'; import 'package:sefer_driver/views/widgets/elevated_btn.dart';
import '../../../../constant/box_name.dart'; import '../../../../constant/box_name.dart';
import '../../../../constant/style.dart'; import '../../../../constant/style.dart';
import '../../../../controller/firebase/notification_service.dart'; import '../../../../controller/firebase/notification_service.dart';
import '../../../../main.dart'; import '../../../../main.dart';
import '../../../../print.dart'; import 'package:sefer_driver/views/widgets/mydialoug.dart';
// Changed: إعادة تصميم كاملة لتصبح شريط معلومات علوي مدمج
class PassengerInfoWindow extends StatelessWidget { class PassengerInfoWindow extends StatelessWidget {
PassengerInfoWindow({super.key}); PassengerInfoWindow({super.key});
final fcm = Get.isRegistered<FirebaseMessagesController>()
? Get.find<FirebaseMessagesController>() // Optimization: defining static styles here avoids rebuilding them every frame
: Get.put(FirebaseMessagesController()); final TextStyle _labelStyle =
AppStyle.title.copyWith(color: Colors.grey[600], fontSize: 13);
final TextStyle _valueStyle =
AppStyle.title.copyWith(fontWeight: FontWeight.bold, fontSize: 18);
@override @override
Widget build(BuildContext context) { Widget build(BuildContext context) {
// Get safe area top padding (for Notches/Status bars)
final double topPadding = MediaQuery.of(context).padding.top;
final double topMargin = topPadding + 10; // Safe area + 10px spacing
return GetBuilder<MapDriverController>( return GetBuilder<MapDriverController>(
builder: (controller) => AnimatedPositioned( builder: (controller) => AnimatedPositioned(
duration: const Duration(milliseconds: 400), duration: const Duration(milliseconds: 400),
curve: Curves.easeInOut, curve: Curves.easeInOut,
// Changed: تم تغيير الموضع من الأسفل إلى الأعلى // FIX: Use calculated top margin to avoid hiding behind status bar
top: controller.isPassengerInfoWindow ? 15.0 : -200.0, top: controller.isPassengerInfoWindow ? topMargin : -250.0,
left: 15.0, left: 15.0,
right: 15.0, right: 15.0,
child: Card( child: Card(
elevation: 8, // Optimization: Lower elevation slightly for smoother animation on cheap phones
shadowColor: Colors.black.withOpacity(0.3), elevation: 4,
shadowColor: Colors.black.withOpacity(0.2),
color: Colors.white,
surfaceTintColor: Colors.white, // Fix for Material 3 tinting
shape: RoundedRectangleBorder( shape: RoundedRectangleBorder(
borderRadius: BorderRadius.circular(16), borderRadius: BorderRadius.circular(16),
), ),
@@ -41,14 +47,12 @@ class PassengerInfoWindow extends StatelessWidget {
child: Column( child: Column(
mainAxisSize: MainAxisSize.min, mainAxisSize: MainAxisSize.min,
children: [ children: [
// New: صف علوي للمعلومات الأساسية
_buildTopInfoRow(controller), _buildTopInfoRow(controller),
const Divider(height: 16), const Divider(height: 16),
// Changed: الأزرار الآن في صف أفقي ومدمج
if (!controller.isRideBegin) _buildActionButtons(controller), if (!controller.isRideBegin) _buildActionButtons(controller),
// New: مؤشر انتظار الراكب المدمج // Optimization: Only render linear indicator if needed
if (controller.remainingTimeInPassengerLocatioWait < 300 && if (controller.remainingTimeInPassengerLocatioWait < 300 &&
controller.remainingTimeInPassengerLocatioWait != 0 && controller.remainingTimeInPassengerLocatioWait != 0 &&
!controller.isRideBegin) ...[ !controller.isRideBegin) ...[
@@ -56,7 +60,6 @@ class PassengerInfoWindow extends StatelessWidget {
_buildWaitingIndicator(controller), _buildWaitingIndicator(controller),
], ],
// زر الإلغاء بعد انتهاء وقت الانتظار
if (controller.isdriverWaitTimeEnd && if (controller.isdriverWaitTimeEnd &&
!controller.isRideBegin) ...[ !controller.isRideBegin) ...[
const SizedBox(height: 10), const SizedBox(height: 10),
@@ -70,35 +73,33 @@ class PassengerInfoWindow extends StatelessWidget {
); );
} }
// New: ودجت لعرض المعلومات العلوية بشكل مدمج
Widget _buildTopInfoRow(MapDriverController controller) { Widget _buildTopInfoRow(MapDriverController controller) {
return Row( return Row(
mainAxisAlignment: MainAxisAlignment.spaceBetween, mainAxisAlignment: MainAxisAlignment.spaceBetween,
crossAxisAlignment: CrossAxisAlignment.start, // Align top
children: [ children: [
// معلومات الراكب
Expanded( Expanded(
child: Column( child: Column(
crossAxisAlignment: CrossAxisAlignment.start, crossAxisAlignment: CrossAxisAlignment.start,
children: [ children: [
Text('Go to passenger:'.tr, style: _labelStyle),
const SizedBox(height: 2),
Text( Text(
'Go to passenger:'.tr, controller.passengerName ?? 'loading...',
style: AppStyle.title style: _valueStyle,
.copyWith(color: Colors.grey[600], fontSize: 13), maxLines: 1,
),
Text(
controller.passengerName,
style: AppStyle.title
.copyWith(fontWeight: FontWeight.bold, fontSize: 18),
overflow: TextOverflow.ellipsis, overflow: TextOverflow.ellipsis,
), ),
], ],
), ),
), ),
// معلومات المسافة والزمن const SizedBox(width: 10), // Spacing between name and chips
Row( Column(
// Changed to Column for better layout on small screens
crossAxisAlignment: CrossAxisAlignment.end,
children: [ children: [
_buildInfoChip(Icons.map_outlined, '${controller.distance} km'), _buildInfoChip(Icons.map_outlined, '${controller.distance} km'),
const SizedBox(width: 8), const SizedBox(height: 6), // Vertical spacing
_buildInfoChip( _buildInfoChip(
Icons.timer_outlined, Icons.timer_outlined,
controller.hours > 1 controller.hours > 1
@@ -111,10 +112,9 @@ class PassengerInfoWindow extends StatelessWidget {
); );
} }
// New: ودجت مخصص لعرض المعلومات بشكل أنيق
Widget _buildInfoChip(IconData icon, String text) { Widget _buildInfoChip(IconData icon, String text) {
return Container( return Container(
padding: const EdgeInsets.symmetric(horizontal: 8, vertical: 4), padding: const EdgeInsets.symmetric(horizontal: 10, vertical: 5),
decoration: BoxDecoration( decoration: BoxDecoration(
color: AppColor.primaryColor.withOpacity(0.1), color: AppColor.primaryColor.withOpacity(0.1),
borderRadius: BorderRadius.circular(20), borderRadius: BorderRadius.circular(20),
@@ -122,76 +122,88 @@ class PassengerInfoWindow extends StatelessWidget {
child: Row( child: Row(
mainAxisSize: MainAxisSize.min, mainAxisSize: MainAxisSize.min,
children: [ children: [
Icon(icon, color: AppColor.primaryColor, size: 16), Icon(icon, color: AppColor.primaryColor, size: 14), // Smaller icon
const SizedBox(width: 4), const SizedBox(width: 6),
Text(text, Text(
text,
style: TextStyle( style: TextStyle(
color: AppColor.primaryColor, fontWeight: FontWeight.bold)), color: AppColor.primaryColor,
fontWeight: FontWeight.bold,
fontSize: 12 // Slightly smaller font for chips
),
),
], ],
), ),
); );
} }
// Changed: إعادة تصميم أزرار الإجراءات لتكون أكثر دمجًا
Widget _buildActionButtons(MapDriverController controller) { Widget _buildActionButtons(MapDriverController controller) {
return Row( return Row(
children: [ children: [
if (controller.isArrivedSend) if (controller.isArrivedSend)
Expanded( Expanded(
child: ElevatedButton.icon( flex: 1,
icon: const Icon(Icons.location_on, size: 18), child: SizedBox(
label: Text('I Arrive'.tr), height: 45, // Fixed height for consistency
child: ElevatedButton(
style: ElevatedButton.styleFrom( style: ElevatedButton.styleFrom(
backgroundColor: AppColor.yellowColor, backgroundColor: AppColor.yellowColor,
foregroundColor: Colors.black, foregroundColor: Colors.black,
padding: EdgeInsets.zero, // Reduce padding to fit text
shape: RoundedRectangleBorder( shape: RoundedRectangleBorder(
borderRadius: BorderRadius.circular(10)), borderRadius: BorderRadius.circular(10)),
), ),
onPressed: () async { onPressed: () async {
// LOGIC FIX: Check distance FIRST
double distance = await controller
.calculateDistanceBetweenDriverAndPassengerLocation();
if (distance < 140) {
// Only draw route and send notif if close enough
controller.getRoute( controller.getRoute(
origin: controller.latLngPassengerLocation, origin: controller.latLngPassengerLocation,
destination: controller.latLngPassengerDestination, destination: controller.latLngPassengerDestination,
routeColor: Colors.blue // أو أي لون routeColor: Colors.blue);
);
if (await controller
.calculateDistanceBetweenDriverAndPassengerLocation() <
140) {
// fcm.sendNotificationToDriverMAP(
// 'Hi ,I Arrive your site',
// 'I Arrive at your site'.tr,
// controller.tokenPassenger,
// [],
// 'ding.wav',
// );
Log.print(
'controller.tokenPassenger: ${controller.tokenPassenger}');
NotificationService.sendNotification( NotificationService.sendNotification(
target: controller.tokenPassenger.toString(), target: controller.tokenPassenger.toString(),
title: 'Hi ,I Arrive your site'.tr, title: 'Hi ,I Arrive your site'.tr,
body: 'I Arrive at your site'.tr, body: 'I Arrive at your site'.tr,
isTopic: false, // Important: this is a token isTopic: false,
tone: 'ding', tone: 'ding',
driverList: [], category: 'Hi ,I Arrive your site', driverList: [],
category: 'Hi ,I Arrive your site',
); );
controller.startTimerToShowDriverWaitPassengerDuration(); controller.startTimerToShowDriverWaitPassengerDuration();
controller.isArrivedSend = false; controller.isArrivedSend = false;
} else { } else {
MyDialog().getDialog( MyDialog().getDialog(
'You are not near the passenger location'.tr, 'You are not near'.tr, // Shortened title
'Please go to the pickup location exactly'.tr, 'Please go to the pickup location exactly'.tr,
() => Get.back()); () => Get.back());
} }
}, },
// Using Row instead of .icon constructor for better control
child: Row(
mainAxisAlignment: MainAxisAlignment.center,
children: [
const Icon(Icons.location_on, size: 16),
const SizedBox(width: 4),
Flexible(
child: Text('I Arrive'.tr,
overflow: TextOverflow.ellipsis,
style: const TextStyle(fontSize: 12))),
],
),
),
), ),
), ),
if (controller.isArrivedSend) const SizedBox(width: 8), if (controller.isArrivedSend) const SizedBox(width: 8),
Expanded( Expanded(
flex: 2, flex: 2, // Give "Start" button more space
child: ElevatedButton.icon( child: SizedBox(
icon: const Icon(Icons.play_arrow_rounded, size: 20), height: 45,
label: Text('Start the Ride'.tr, child: ElevatedButton(
style: const TextStyle(fontWeight: FontWeight.bold)),
style: ElevatedButton.styleFrom( style: ElevatedButton.styleFrom(
backgroundColor: AppColor.greenColor, backgroundColor: AppColor.greenColor,
foregroundColor: Colors.white, foregroundColor: Colors.white,
@@ -208,58 +220,66 @@ class PassengerInfoWindow extends StatelessWidget {
}, },
); );
}, },
child: Row(
mainAxisAlignment: MainAxisAlignment.center,
children: [
const Icon(Icons.play_arrow_rounded, size: 22),
const SizedBox(width: 6),
Flexible(
child: Text('Start the Ride'.tr,
overflow: TextOverflow.ellipsis,
style: const TextStyle(fontWeight: FontWeight.bold))),
],
),
),
), ),
), ),
], ],
); );
} }
// Changed: مؤشر الانتظار الآن أكثر دمجًا
Widget _buildWaitingIndicator(MapDriverController controller) { Widget _buildWaitingIndicator(MapDriverController controller) {
return ClipRRect( return Column(
borderRadius: BorderRadius.circular(10),
child: Stack(
alignment: Alignment.center,
children: [ children: [
LinearProgressIndicator( ClipRRect(
backgroundColor: AppColor.greyColor.withOpacity(0.3), borderRadius: BorderRadius.circular(10),
child: LinearProgressIndicator(
backgroundColor: AppColor.greyColor.withOpacity(0.2),
// Ternary for color is fine
color: controller.remainingTimeInPassengerLocatioWait < 60 color: controller.remainingTimeInPassengerLocatioWait < 60
? AppColor.redColor ? AppColor.redColor
: AppColor.greenColor, : AppColor.greenColor,
minHeight: 25, minHeight: 8, // Thinner looks more modern
value: controller.progressInPassengerLocationFromDriver.toDouble(), value: controller.progressInPassengerLocationFromDriver.toDouble(),
), ),
),
const SizedBox(height: 4),
Text( Text(
controller.stringRemainingTimeWaitingPassenger, "${'Waiting'.tr}: ${controller.stringRemainingTimeWaitingPassenger}",
style: AppStyle.title.copyWith( style: AppStyle.title.copyWith(
color: Colors.white, color: Colors.grey[700],
fontWeight: FontWeight.bold, fontWeight: FontWeight.bold,
fontSize: 13, fontSize: 12,
shadows: [ ),
Shadow(color: Colors.black.withOpacity(0.5), blurRadius: 2)
]),
), ),
], ],
),
); );
} }
// New: زر الإلغاء بعد انتهاء الانتظار
Widget _buildCancelAfterWaitButton(MapDriverController controller) { Widget _buildCancelAfterWaitButton(MapDriverController controller) {
return MyElevatedButton( return MyElevatedButton(
title: 'You Can Cancel the Trip and get Cost From '.tr + title: 'Cancel Trip & Get Cost'.tr, // Shortened text
AppInformation.appName.tr,
kolor: AppColor.gold, kolor: AppColor.gold,
onPressed: () { onPressed: () {
MyDialog().getDialog('Are you sure to cancel?'.tr, '', () async { MyDialog().getDialog('Are you sure to cancel?'.tr, '', () async {
NotificationService.sendNotification( NotificationService.sendNotification(
target: controller.tokenPassenger.toString(), target: controller.tokenPassenger.toString(),
title: 'Driver Cancelled Your Trip'.tr, title: 'Driver Cancelled Your Trip'.tr,
body: body: 'You will need to pay the cost...',
'You will need to pay the cost to the driver, or it will be deducted from your next trip', isTopic: false,
isTopic: false, // Important: this is a token
tone: 'cancel', tone: 'cancel',
driverList: [], category: 'Driver Cancelled Your Trip', driverList: [],
category: 'Driver Cancelled Your Trip',
); );
box.write(BoxName.rideStatus, 'Cancel'); box.write(BoxName.rideStatus, 'Cancel');
await controller.addWaitingTimeCostFromPassengerToDriverWallet(); await controller.addWaitingTimeCostFromPassengerToDriverWallet();

View File

@@ -2,6 +2,7 @@ import 'dart:async';
import 'package:flutter/material.dart'; import 'package:flutter/material.dart';
import 'package:flutter_overlay_window/flutter_overlay_window.dart'; import 'package:flutter_overlay_window/flutter_overlay_window.dart';
import 'package:get/get.dart'; import 'package:get/get.dart';
import 'package:intl/intl.dart';
import 'package:just_audio/just_audio.dart'; import 'package:just_audio/just_audio.dart';
import 'package:sefer_driver/constant/api_key.dart'; import 'package:sefer_driver/constant/api_key.dart';
import '../../../../constant/box_name.dart'; import '../../../../constant/box_name.dart';
@@ -213,8 +214,9 @@ class _OrderOverlayState extends State<OrderOverlay>
await _closeOverlay(); await _closeOverlay();
return; return;
} }
var res = await CRUD().post(
var res = await CRUD().post(link: AppLink.updateStausFromSpeed, payload: { link: "${AppLink.ride}/rides/updateStausFromSpeed.php",
payload: {
'id': orderData!.orderId, 'id': orderData!.orderId,
'rideTimeStart': DateTime.now().toString(), 'rideTimeStart': DateTime.now().toString(),
'status': 'Apply', 'status': 'Apply',
@@ -340,16 +342,12 @@ class _OrderOverlayState extends State<OrderOverlay>
_log("Driver ID is null, cannot refuse order"); _log("Driver ID is null, cannot refuse order");
return; return;
} }
await _crud.post(link: AppLink.addDriverOrder, payload: { _crud.post(link: AppLink.addDriverOrder, payload: {
'driver_id': driverId, 'driver_id': driverId,
'order_id': orderID, 'order_id': orderID,
'status': 'Refused' 'status': 'Refused'
}); });
await _crud.post(link: AppLink.updateRides, payload: {
'id': orderID,
'status': 'Refused',
'driver_id': driverId,
});
_log("Order $orderID refused successfully"); _log("Order $orderID refused successfully");
} catch (e) { } catch (e) {
_log("Error in _apiRefuseOrder for $orderID: $e"); _log("Error in _apiRefuseOrder for $orderID: $e");
@@ -509,9 +507,16 @@ class _OrderOverlayState extends State<OrderOverlay>
children: [ children: [
Expanded( Expanded(
flex: 3, flex: 3,
child: _buildHighlightInfo("\$${order.price}", "السعر".tr, child: _buildHighlightInfo(
Icons.monetization_on_rounded, AppColors.priceHighlight, // التعديل هنا 👇
isLarge: true), "${NumberFormat('#,##0').format(order.price)} ل.س",
// أو يمكنك استخدام "SYP" بدلاً من "ل.س"
"السعر".tr,
Icons.monetization_on_rounded,
AppColors.priceHighlight,
isLarge: true,
),
), ),
const SizedBox(width: 12), const SizedBox(width: 12),
Expanded( Expanded(

View File

@@ -248,38 +248,31 @@ class _OrderRequestPageState extends State<OrderRequestPage> {
kolor: AppColor.greenColor, kolor: AppColor.greenColor,
title: 'Accept Order'.tr, title: 'Accept Order'.tr,
onPressed: () async { onPressed: () async {
Get.put(HomeCaptainController()).changeRideId();
box.write(BoxName.statusDriverLocation, 'on');
controller.endTimer();
controller.changeApplied();
var res = await CRUD().post( var res = await CRUD().post(
link: AppLink.updateStausFromSpeed,
payload: {
'id': (controller.myList[16]),
'rideTimeStart': DateTime.now().toString(),
'status': 'Apply',
'driver_id': box.read(BoxName.driverID),
});
CRUD().post(
link: link:
"${AppLink.endPoint}/ride/rides/updateStausFromSpeed.php", "${AppLink.ride}/rides/updateStausFromSpeed.php",
payload: { payload: {
'id': (controller.myList[16]), 'id': (controller.myList[16]),
'rideTimeStart': DateTime.now().toString(), 'rideTimeStart': DateTime.now().toString(),
'status': 'Apply', 'status': 'Apply',
'driver_id': box.read(BoxName.driverID), 'driver_id': box.read(BoxName.driverID),
}); });
if (res == 'failure') { if (res == 'failure') {
MyDialog().getDialog( MyDialog().getDialog(
"This ride is already applied by another driver." "This ride is already applied by another driver."
.tr, .tr,
'', () { '', () {
Get.back(); Get.back();
// Get.back(); Get.back();
}); });
} else { } else {
await CRUD().postFromDialogue( Get.put(HomeCaptainController()).changeRideId();
box.write(BoxName.statusDriverLocation, 'on');
controller.endTimer();
controller.changeApplied();
CRUD().postFromDialogue(
link: AppLink.addDriverOrder, link: AppLink.addDriverOrder,
payload: { payload: {
'driver_id': 'driver_id':
@@ -386,33 +379,26 @@ class _OrderRequestPageState extends State<OrderRequestPage> {
title: 'Refuse Order'.tr, title: 'Refuse Order'.tr,
onPressed: () async { onPressed: () async {
controller.endTimer(); controller.endTimer();
List<String> bodyToPassenger = [ // List<String> bodyToPassenger = [
box.read(BoxName.driverID).toString(), // box.read(BoxName.driverID).toString(),
box.read(BoxName.nameDriver).toString(), // box.read(BoxName.nameDriver).toString(),
box.read(BoxName.tokenDriver).toString(), // box.read(BoxName.tokenDriver).toString(),
]; // ];
// FirebaseMessagesController() // NotificationService.sendNotification(
// .sendNotificationToPassengerToken( // target: controller.myList[9].toString(),
// 'Order Under Review'.tr, // title: 'Order Under Review'.tr,
// body:
// '${box.read(BoxName.nameDriver)} ${'is reviewing your order. They may need more information or a higher price.'.tr}', // '${box.read(BoxName.nameDriver)} ${'is reviewing your order. They may need more information or a higher price.'.tr}',
// controller.myList[9].toString(), // isTopic: false, // Important: this is a token
// bodyToPassenger, // tone: 'start',
// 'notification'); // driverList: bodyToPassenger,
NotificationService.sendNotification( // category: 'Order Under Review',
target: controller.myList[9].toString(), // );
title: 'Order Under Review'.tr,
body:
'${box.read(BoxName.nameDriver)} ${'is reviewing your order. They may need more information or a higher price.'.tr}',
isTopic: false, // Important: this is a token
tone: 'start',
driverList: [], category: 'Order Under Review',
);
controller.refuseOrder( // controller.refuseOrder(
EncryptionHelper.instance.encryptData( // (controller.myList[16].toString()),
controller.myList[16].toString()), // );
);
controller.addRideToNotificationDriverString( controller.addRideToNotificationDriverString(
controller.myList[16].toString(), controller.myList[16].toString(),
controller.myList[29].toString(), controller.myList[29].toString(),

View File

@@ -1,21 +1,25 @@
import 'dart:convert'; // Though not directly used in this version's UI logic, often kept for model interactions. import 'dart:convert';
import 'package:sefer_driver/controller/home/captin/home_captain_controller.dart';
import 'package:flutter/material.dart'; import 'package:flutter/material.dart';
import 'package:get/get.dart'; import 'package:get/get.dart';
import 'package:google_maps_flutter/google_maps_flutter.dart';
import 'package:sefer_driver/controller/home/captin/home_captain_controller.dart';
import 'package:sefer_driver/constant/box_name.dart'; import 'package:sefer_driver/constant/box_name.dart';
import 'package:sefer_driver/controller/firebase/firbase_messge.dart'; import 'package:sefer_driver/controller/firebase/firbase_messge.dart';
import 'package:sefer_driver/main.dart'; // For `box` import 'package:sefer_driver/main.dart';
import 'package:sefer_driver/print.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:google_maps_flutter/google_maps_flutter.dart';
import '../../../../constant/colors.dart'; // Your AppColor import '../../../../constant/colors.dart';
import '../../../../constant/links.dart'; // Your AppLink import '../../../../constant/links.dart';
import '../../../../constant/style.dart'; // Your AppStyle import '../../../../constant/style.dart';
import '../../../../controller/firebase/notification_service.dart'; import '../../../../controller/firebase/notification_service.dart';
import '../../../../controller/functions/crud.dart'; import '../../../../controller/functions/crud.dart';
import '../../../../controller/functions/launch.dart'; import '../../../../controller/functions/launch.dart';
import '../../../../controller/home/captin/order_request_controller.dart'; import '../../../../controller/home/captin/order_request_controller.dart';
import '../../../widgets/elevated_btn.dart'; // Your MyElevatedButton import '../../../widgets/elevated_btn.dart';
import '../../../widgets/mydialoug.dart';
class OrderSpeedRequest extends StatelessWidget { class OrderSpeedRequest extends StatelessWidget {
OrderSpeedRequest({super.key}); OrderSpeedRequest({super.key});
@@ -23,7 +27,7 @@ class OrderSpeedRequest extends StatelessWidget {
final OrderRequestController orderRequestController = final OrderRequestController orderRequestController =
Get.put(OrderRequestController()); Get.put(OrderRequestController());
// Helper to make myList access more readable and safer // دالة مساعدة لاستخراج البيانات بأمان (Null Safety)
String _getData(int index, {String defaultValue = ''}) { String _getData(int index, {String defaultValue = ''}) {
if (orderRequestController.myList.length > index && if (orderRequestController.myList.length > index &&
orderRequestController.myList[index] != null) { orderRequestController.myList[index] != null) {
@@ -34,39 +38,9 @@ class OrderSpeedRequest extends StatelessWidget {
@override @override
Widget build(BuildContext context) { Widget build(BuildContext context) {
// Define AppBar first to get its height for body calculations
final appBar = AppBar(
title: Text('Speed Order'.tr),
leading: IconButton(
icon: const Icon(Icons.arrow_back),
onPressed: () => Get.back(),
),
backgroundColor: AppColor.primaryColor, // Example color, adjust as needed
elevation: 2.0,
);
final double appBarHeight = appBar.preferredSize.height;
final MediaQueryData mediaQueryData = MediaQuery.of(context);
final double screenHeight = mediaQueryData.size.height;
final double statusBarHeight = mediaQueryData.padding.top;
final double bottomSystemPadding = mediaQueryData.padding.bottom;
// Calculate available height for the Scaffold's body content
// Subtracting status bar, app bar, and bottom system padding (like navigation bar)
final double availableBodyHeight =
screenHeight - appBarHeight - statusBarHeight - bottomSystemPadding;
// Define overall padding for the body content
const EdgeInsets bodyContentPadding =
EdgeInsets.symmetric(horizontal: 10.0, vertical: 8.0);
// Calculate the height for the main content Column, considering the body's own padding
double mainColumnHeight = availableBodyHeight - bodyContentPadding.vertical;
if (mainColumnHeight < 0) mainColumnHeight = 0;
return GetBuilder<OrderRequestController>( return GetBuilder<OrderRequestController>(
builder: (controller) { builder: (controller) {
// Pre-extract data for readability and safety // --- استخراج البيانات بشكل نظيف ---
final String price = final String price =
double.tryParse(_getData(2))?.toStringAsFixed(2) ?? 'N/A'; double.tryParse(_getData(2))?.toStringAsFixed(2) ?? 'N/A';
final bool isComfortTrip = _getData(31) == 'Comfort'; final bool isComfortTrip = _getData(31) == 'Comfort';
@@ -75,48 +49,46 @@ class OrderSpeedRequest extends StatelessWidget {
final String pickupName = _getData(12); final String pickupName = _getData(12);
final String pickupDetails = '(${_getData(11)})'; final String pickupDetails = '(${_getData(11)})';
final String pickupFullAddress = _getData(29); final String pickupFullAddress = _getData(29);
final String dropoffName = _getData(5); final String dropoffName = _getData(5);
final String dropoffDetails = '(${_getData(4)})'; final String dropoffDetails = '(${_getData(4)})';
final String dropoffFullAddress = _getData(30); final String dropoffFullAddress = _getData(30);
final String passengerName = _getData(8); final String passengerName = _getData(8);
final String passengerRating = _getData(33); final String passengerRating = _getData(33);
final bool isVisaPayment = _getData(13) == 'true'; final bool isVisaPayment = _getData(13) == 'true';
final bool hasSteps = _getData(20) == 'haveSteps'; final bool hasSteps = _getData(20) == 'haveSteps';
final String mapUrl = final String mapUrl =
'https://www.google.com/maps/dir/${_getData(0)}/${_getData(1)}/'; 'https://www.google.com/maps/dir/${_getData(0)}/${_getData(1)}/';
final String rideId = _getData(16); // Commonly used ID final String rideId = _getData(16);
return Scaffold( return Scaffold(
appBar: appBar, appBar: AppBar(
backgroundColor: AppColor.secondaryColor ?? title: Text('Speed Order'.tr),
Colors.grey[100], // Background for the page leading: IconButton(
icon: const Icon(Icons.arrow_back),
onPressed: () => Get.back(),
),
backgroundColor: AppColor.primaryColor,
elevation: 2.0,
),
backgroundColor: AppColor.secondaryColor ?? Colors.grey[100],
body: SafeArea( body: SafeArea(
// Ensures content is not obscured by system UI
child: Padding( child: Padding(
padding: padding:
bodyContentPadding, // Apply overall padding to the body's content area const EdgeInsets.symmetric(horizontal: 10.0, vertical: 8.0),
child: SizedBox(
// Constrain the height of the main layout Column
height: mainColumnHeight,
child: Column( child: Column(
mainAxisAlignment: MainAxisAlignment.spaceBetween,
crossAxisAlignment: CrossAxisAlignment.stretch,
children: [ children: [
// --- MAP SECTION --- // 1. قسم الخريطة (ارتفاع ثابت)
SizedBox( SizedBox(
height: height: Get.height * 0.28,
Get.height * 0.28, // Relative to total screen height
child: ClipRRect( child: ClipRRect(
borderRadius: BorderRadius.circular(15.0), borderRadius: BorderRadius.circular(15.0),
child: GoogleMap( child: GoogleMap(
initialCameraPosition: CameraPosition( initialCameraPosition: CameraPosition(
zoom: 12, zoom: 12,
target: target: Get.find<HomeCaptainController>().myLocation,
Get.find<HomeCaptainController>().myLocation), ),
cameraTargetBounds: cameraTargetBounds:
CameraTargetBounds(controller.bounds), CameraTargetBounds(controller.bounds),
myLocationButtonEnabled: false, myLocationButtonEnabled: false,
@@ -127,8 +99,7 @@ class OrderSpeedRequest extends StatelessWidget {
markers: { markers: {
Marker( Marker(
markerId: MarkerId('MyLocation'.tr), markerId: MarkerId('MyLocation'.tr),
position: LatLng( position: LatLng(controller.latPassengerLocation,
controller.latPassengerLocation,
controller.lngPassengerLocation), controller.lngPassengerLocation),
icon: controller.startIcon), icon: controller.startIcon),
Marker( Marker(
@@ -155,62 +126,20 @@ class OrderSpeedRequest extends StatelessWidget {
), ),
), ),
), ),
const SizedBox(height: 8), const SizedBox(height: 8),
// --- PRICE & TRIP TYPE SECTION --- // 2. بطاقة السعر
Card( _buildPriceCard(price, carType, isComfortTrip),
elevation: 3,
shape: RoundedRectangleBorder(
borderRadius: BorderRadius.circular(12)),
child: Padding(
padding: const EdgeInsets.symmetric(
vertical: 12.0, horizontal: 16.0),
child: Row(
mainAxisAlignment: MainAxisAlignment.spaceBetween,
children: [
Text(
price,
style: AppStyle.headTitle.copyWith(
color: AppColor.primaryColor,
fontWeight: FontWeight.bold,
fontSize: 28),
),
Column(
crossAxisAlignment: CrossAxisAlignment.end,
children: [
Text(
carType,
style: AppStyle.title.copyWith(
color: AppColor.greenColor,
fontWeight: FontWeight.bold),
),
if (isComfortTrip)
Row(
children: [
const Icon(Icons.ac_unit,
color: AppColor.blueColor, size: 18),
const SizedBox(width: 4),
Text('Air condition Trip'.tr,
style: AppStyle.subtitle
.copyWith(fontSize: 13)),
],
),
],
),
],
),
),
),
const SizedBox(height: 8), const SizedBox(height: 8),
// --- EXPANDED SECTION FOR SCROLLABLE (BUT NOT USER-SCROLLABLE) CONTENT --- // 3. التفاصيل القابلة للتمرير (تأخذ المساحة المتبقية)
Expanded( Expanded(
child: SingleChildScrollView( child: SingleChildScrollView(
physics: physics: const BouncingScrollPhysics(),
const NeverScrollableScrollPhysics(), // Prevents user scrolling
child: Column( child: Column(
mainAxisSize: MainAxisSize mainAxisSize: MainAxisSize.min,
.min, // Takes minimum vertical space needed
children: [ children: [
_buildLocationCard( _buildLocationCard(
icon: Icons.arrow_circle_up, icon: Icons.arrow_circle_up,
@@ -228,273 +157,35 @@ class OrderSpeedRequest extends StatelessWidget {
fullAddress: dropoffFullAddress, fullAddress: dropoffFullAddress,
), ),
const SizedBox(height: 8), const SizedBox(height: 8),
// --- PAYMENT, STEPS & DIRECTIONS INFO --- _buildInfoCard(isVisaPayment, hasSteps, mapUrl),
Card(
elevation: 2,
shape: RoundedRectangleBorder(
borderRadius: BorderRadius.circular(10)),
child: Padding(
padding: const EdgeInsets.all(10.0),
child: Row(
mainAxisAlignment:
MainAxisAlignment.spaceBetween,
children: [
Row(
children: [
Icon(
isVisaPayment
? Icons.credit_card
: Icons
.payments_outlined, // Using payments_outlined for cash
color: isVisaPayment
? AppColor.deepPurpleAccent
: AppColor.greenColor,
size: 24,
),
const SizedBox(width: 8),
Text(
isVisaPayment ? 'Visa'.tr : 'Cash'.tr,
style: AppStyle.title.copyWith(
fontWeight: FontWeight.w600),
),
],
),
if (hasSteps)
Expanded(
child: Row(
mainAxisAlignment:
MainAxisAlignment.center,
children: [
const Icon(
Icons
.format_list_numbered_rtl_outlined,
color: AppColor.bronze,
size: 24),
const SizedBox(width: 4),
Flexible(
child: Text(
'Trip has Steps'.tr,
style: AppStyle.title.copyWith(
color: AppColor.bronze,
fontSize: 13),
overflow: TextOverflow.ellipsis,
)),
],
),
),
TextButton.icon(
style: TextButton.styleFrom(
padding: EdgeInsets.zero,
tapTargetSize:
MaterialTapTargetSize.shrinkWrap,
alignment: Alignment.centerRight,
),
onPressed: () => showInBrowser(mapUrl),
icon: const Icon(
Icons.directions_outlined,
color: AppColor.blueColor,
size: 20),
label: Text("Directions".tr,
style: AppStyle.subtitle.copyWith(
color: AppColor.blueColor,
fontSize: 13)),
),
],
),
),
),
const SizedBox(height: 8), const SizedBox(height: 8),
// --- PASSENGER INFO --- _buildPassengerCard(passengerName, passengerRating),
Card(
elevation: 2,
shape: RoundedRectangleBorder(
borderRadius: BorderRadius.circular(10)),
child: Padding(
padding: const EdgeInsets.symmetric(
horizontal: 16.0, vertical: 10.0),
child: Row(
children: [
const Icon(Icons.person_outline,
color: AppColor.greyColor, size: 22),
const SizedBox(width: 10),
Expanded(
child: Text(
passengerName,
style: AppStyle.title,
overflow: TextOverflow.ellipsis,
),
),
const SizedBox(width: 10),
const Icon(Icons.star_rounded,
color: Colors.amber, size: 20),
Text(
passengerRating,
style: AppStyle.title.copyWith(
fontWeight: FontWeight.bold),
),
], ],
), ),
), ),
), ),
],
),
),
),
// const SizedBox(height: 8), // Spacer before action buttons if needed
// --- ACTION BUTTONS & TIMER --- // 4. الأزرار والمؤقت (مثبتة في الأسفل)
Padding( Padding(
padding: const EdgeInsets.only(top: 8.0, bottom: 5.0), padding: const EdgeInsets.only(top: 8.0),
child: Row( child: Row(
mainAxisAlignment: MainAxisAlignment.spaceBetween, mainAxisAlignment: MainAxisAlignment.spaceBetween,
children: [ children: [
// زر القبول
Expanded( Expanded(
child: MyElevatedButton( child: MyElevatedButton(
kolor: AppColor.greenColor, kolor: AppColor.greenColor,
title: 'Accept Order'.tr, title: 'Accept Order'.tr,
onPressed: () async { onPressed: () => _handleAcceptOrder(controller),
Get.put(HomeCaptainController()).changeRideId();
box.write(BoxName.statusDriverLocation, 'on');
var res = await CRUD().post(
link: AppLink.updateStausFromSpeed,
payload: {
'id': rideId,
'rideTimeStart': DateTime.now().toString(),
'status': 'Apply',
'driver_id': box.read(BoxName.driverID),
},
);
CRUD().post(
link:
"${AppLink.server}/ride/rides/updateStausFromSpeed.php",
payload: {
'id': rideId,
'rideTimeStart': DateTime.now().toString(),
'status': 'Apply',
'driver_id': box.read(BoxName.driverID),
},
);
if (res != "failure") {
box.write(BoxName.statusDriverLocation, 'on');
controller.changeApplied();
List<String> bodyToPassenger = [
box.read(BoxName.driverID).toString(),
box.read(BoxName.nameDriver).toString(),
box.read(BoxName.tokenDriver).toString(),
rideId.toString(),
];
// Get.put(FirebaseMessagesController())
// .sendNotificationToDriverMAP(
// 'Accepted Ride',
// 'your ride is applied'.tr,
// controller.arguments?['DriverList']
// ?[9]
// ?.toString() ??
// _getData(9), // Safer access
// bodyToPassenger,
// 'start.wav');
NotificationService.sendNotification(
target: controller.arguments?['DriverList']
?[9]
?.toString() ??
_getData(9),
title: 'Accepted Ride'.tr,
body: 'your ride is applied'.tr,
isTopic:
false, // Important: this is a token
tone: 'start',
driverList: [], category: 'Accepted Ride',
);
// Using rideId (_getData(16)) for order_id consistently
CRUD().postFromDialogue(
link: AppLink.addDriverOrder,
payload: {
'driver_id': _getData(
6), // Driver ID from the order data
'order_id': rideId,
'status': 'Apply'
});
CRUD().post(
link:
"${AppLink.rideServer}/driver_order/add.php",
payload: {
'driver_id': _getData(6),
'order_id': rideId,
'status': 'Apply'
});
Get.back(); // Go back from order request screen
box.write(BoxName.rideArguments, {
'passengerLocation': _getData(0),
'passengerDestination': _getData(1),
'Duration': _getData(4),
'totalCost': _getData(26),
'Distance': _getData(5),
'name': _getData(8),
'phone': _getData(10),
'email': _getData(28),
'WalletChecked': _getData(13),
'tokenPassenger': _getData(9),
'direction': mapUrl,
'DurationToPassenger': _getData(15),
'rideId': rideId,
'passengerId': _getData(7),
'driverId': box
.read(BoxName.driverID)
.toString(), // Current driver accepting
'durationOfRideValue': _getData(19),
'paymentAmount': _getData(2),
'paymentMethod': _getData(13) == 'true'
? 'visa'
: 'cash',
'isHaveSteps': _getData(20),
'step0': _getData(21),
'step1': _getData(22),
'step2': _getData(23),
'step3': _getData(24),
'step4': _getData(25),
'passengerWalletBurc': _getData(26),
'timeOfOrder': DateTime.now().toString(),
'totalPassenger': _getData(
2), // This is likely trip cost for passenger
'carType': _getData(31),
'kazan':
_getData(32), // Driver's commission/cut
'startNameLocation': _getData(29),
'endNameLocation': _getData(30),
});
Get.to(() => PassengerLocationMapPage(),
arguments:
box.read(BoxName.rideArguments));
} else {
Get.defaultDialog(
title:
"This ride is already taken by another driver."
.tr,
middleText: '',
titleStyle: AppStyle.title,
middleTextStyle: AppStyle.title,
confirm: MyElevatedButton(
title: 'Ok'.tr,
onPressed: () {
Get.back(); // Close dialog
Get.back(); // Close order request screen
}));
}
},
), ),
), ),
const SizedBox(width: 10), const SizedBox(width: 10),
// --- TIMER ---
// المؤقت
GetBuilder<OrderRequestController>( GetBuilder<OrderRequestController>(
id: 'timerUpdate', // Ensure controller calls update(['timerUpdate']) for this id: 'timerUpdate',
builder: (timerCtrl) { builder: (timerCtrl) {
final isNearEnd = final isNearEnd = timerCtrl.remainingTimeSpeed <= 5;
timerCtrl.remainingTimeSpeed <= 5;
return SizedBox( return SizedBox(
width: 60, width: 60,
height: 60, height: 60,
@@ -509,25 +200,343 @@ class OrderSpeedRequest extends StatelessWidget {
strokeWidth: 5, strokeWidth: 5,
backgroundColor: Colors.grey.shade300, backgroundColor: Colors.grey.shade300,
), ),
Text('${timerCtrl.remainingTimeSpeed}', Text(
'${timerCtrl.remainingTimeSpeed}',
style: AppStyle.headTitle2.copyWith( style: AppStyle.headTitle2.copyWith(
color: isNearEnd color: isNearEnd
? Colors.redAccent ? Colors.redAccent
: AppColor.writeColor ?? : AppColor.writeColor ?? Colors.black,
Colors.black, ),
)), ),
], ],
), ),
); );
}, },
), ),
const SizedBox(width: 10), const SizedBox(width: 10),
// زر الرفض
Expanded( Expanded(
child: MyElevatedButton( child: MyElevatedButton(
title: 'Refuse Order'.tr, title: 'Refuse Order'.tr,
onPressed: () async { onPressed: () =>
_handleRefuseOrder(controller, rideId),
kolor: AppColor.redColor,
),
),
],
),
),
],
),
),
),
);
},
);
}
// --- WIDGET BUILDERS (لبناء الواجهة بشكل نظيف) ---
Widget _buildPriceCard(String price, String carType, bool isComfortTrip) {
return Card(
elevation: 3,
shape: RoundedRectangleBorder(borderRadius: BorderRadius.circular(12)),
child: Padding(
padding: const EdgeInsets.symmetric(vertical: 12.0, horizontal: 16.0),
child: Row(
mainAxisAlignment: MainAxisAlignment.spaceBetween,
children: [
Text(
price,
style: AppStyle.headTitle.copyWith(
color: AppColor.primaryColor,
fontWeight: FontWeight.bold,
fontSize: 28),
),
Column(
crossAxisAlignment: CrossAxisAlignment.end,
children: [
Text(
carType,
style: AppStyle.title.copyWith(
color: AppColor.greenColor, fontWeight: FontWeight.bold),
),
if (isComfortTrip)
Row(
children: [
const Icon(Icons.ac_unit,
color: AppColor.blueColor, size: 18),
const SizedBox(width: 4),
Text('Air condition Trip'.tr,
style: AppStyle.subtitle.copyWith(fontSize: 13)),
],
),
],
),
],
),
),
);
}
Widget _buildInfoCard(bool isVisaPayment, bool hasSteps, String mapUrl) {
return Card(
elevation: 2,
shape: RoundedRectangleBorder(borderRadius: BorderRadius.circular(10)),
child: Padding(
padding: const EdgeInsets.all(10.0),
child: Row(
mainAxisAlignment: MainAxisAlignment.spaceBetween,
children: [
Row(
children: [
Icon(
isVisaPayment ? Icons.credit_card : Icons.payments_outlined,
color: isVisaPayment
? AppColor.deepPurpleAccent
: AppColor.greenColor,
size: 24,
),
const SizedBox(width: 8),
Text(
isVisaPayment ? 'Visa'.tr : 'Cash'.tr,
style: AppStyle.title.copyWith(fontWeight: FontWeight.w600),
),
],
),
if (hasSteps)
Expanded(
child: Row(
mainAxisAlignment: MainAxisAlignment.center,
children: [
const Icon(Icons.format_list_numbered_rtl_outlined,
color: AppColor.bronze, size: 24),
const SizedBox(width: 4),
Flexible(
child: Text(
'Trip has Steps'.tr,
style: AppStyle.title
.copyWith(color: AppColor.bronze, fontSize: 13),
overflow: TextOverflow.ellipsis,
)),
],
),
),
TextButton.icon(
style: TextButton.styleFrom(
padding: EdgeInsets.zero,
tapTargetSize: MaterialTapTargetSize.shrinkWrap,
alignment: Alignment.centerRight,
),
onPressed: () => showInBrowser(mapUrl),
icon: const Icon(Icons.directions_outlined,
color: AppColor.blueColor, size: 20),
label: Text("Directions".tr,
style: AppStyle.subtitle
.copyWith(color: AppColor.blueColor, fontSize: 13)),
),
],
),
),
);
}
Widget _buildPassengerCard(String name, String rating) {
return Card(
elevation: 2,
shape: RoundedRectangleBorder(borderRadius: BorderRadius.circular(10)),
child: Padding(
padding: const EdgeInsets.symmetric(horizontal: 16.0, vertical: 10.0),
child: Row(
children: [
const Icon(Icons.person_outline,
color: AppColor.greyColor, size: 22),
const SizedBox(width: 10),
Expanded(
child: Text(
name,
style: AppStyle.title,
overflow: TextOverflow.ellipsis,
),
),
const SizedBox(width: 10),
const Icon(Icons.star_rounded, color: Colors.amber, size: 20),
Text(
rating,
style: AppStyle.title.copyWith(fontWeight: FontWeight.bold),
),
],
),
),
);
}
Widget _buildLocationCard(
{required IconData icon,
required Color iconColor,
required String title,
required String subtitle,
required String fullAddress}) {
return Card(
elevation: 2,
shape: RoundedRectangleBorder(borderRadius: BorderRadius.circular(10)),
margin: const EdgeInsets.symmetric(vertical: 4),
child: Padding(
padding: const EdgeInsets.all(10.0),
child: Row(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
Icon(icon, color: iconColor, size: 28),
const SizedBox(width: 12),
Expanded(
child: Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
Text(
"$title $subtitle".trim(),
style: AppStyle.title.copyWith(fontWeight: FontWeight.w600),
maxLines: 1,
overflow: TextOverflow.ellipsis,
),
if (fullAddress.isNotEmpty) ...[
const SizedBox(height: 3),
Text(
fullAddress,
style: AppStyle.subtitle
.copyWith(fontSize: 13, color: AppColor.greyColor),
maxLines: 2,
overflow: TextOverflow.ellipsis,
),
]
],
),
),
],
),
),
);
}
// --- منطق التعامل مع الطلبات (Logic Handlers) ---
Future<void> _handleAcceptOrder(OrderRequestController controller) async {
// 1. محاولة تحديث الحالة في السيرفر
// 1. إظهار لودينج وإيقاف التفاعل
Get.dialog(const Center(child: CircularProgressIndicator()),
barrierDismissible: false);
// هذا الرابط يجب أن يكون لملف PHP الآمن الذي يحتوي على rowCount
var res = await CRUD().post(link: AppLink.updateStausFromSpeed, payload: {
'id': (controller.myList[16]),
'rideTimeStart': DateTime.now().toString(),
'status': 'Apply',
'driver_id': box.read(BoxName.driverID),
});
Log.print('oreder response update: ${res}');
// 2. إغلاق اللودينج بمجرد وصول الرد
Get.back(); // إغلاق اللودينج
// 2. معالجة الفشل (Robust Error Handling)
// نفحص إذا كانت النتيجة فشل سواء وصلت كنص أو كـ JSON
bool isFailed = false;
if (res == 'failure') isFailed = true;
if (res is Map && res['status'] == 'failure') isFailed = true;
if (isFailed) {
MyDialog().getDialog(
"This ride is already applied by another driver.".tr, '', () {
Get.back(); // يغلق نافذة التنبيه (Dialog)
Get.back(); // يغلق صفحة الطلب بالكامل (Screen) ويرجع للخريطة
});
return; // توقف تام للكود هنا، لن يتم تنفيذ أي سطر بالأسفل
}
// 3. معالجة النجاح (Success Handling)
// إيقاف المؤقت وتحديث الواجهة
controller.endTimer(); controller.endTimer();
controller.refuseOrder(rideId); controller.changeApplied();
// تحديث حالة السائق في التطبيق
Get.put(HomeCaptainController()).changeRideId();
box.write(BoxName.statusDriverLocation, 'on');
// *هام*: تم حذف استدعاء الـ API الثاني المكرر هنا لأنه غير ضروري وقد يسبب مشاكل
// تسجيل الطلب في سجل السائقين (Driver History)
CRUD().postFromDialogue(link: AppLink.addDriverOrder, payload: {
'driver_id': (controller.myList[6].toString()),
'order_id': (controller.myList[16].toString()),
'status': 'Apply'
});
// إرسال إشعار للراكب
List<String> bodyToPassenger = [
controller.myList[6].toString(),
controller.myList[8].toString(),
controller.myList[9].toString(),
];
NotificationService.sendNotification(
target: controller.myList[9].toString(),
title: "Accepted Ride".tr,
body: 'your ride is Accepted'.tr,
isTopic: false,
tone: 'start',
driverList: bodyToPassenger,
category: 'Accepted Ride',
);
// حفظ البيانات في الصندوق (Box) للانتقال للصفحة التالية
box.write(BoxName.rideArguments, {
'passengerLocation': controller.myList[0].toString(),
'passengerDestination': controller.myList[1].toString(),
'Duration': controller.myList[4].toString(),
'totalCost': controller.myList[26].toString(),
'Distance': controller.myList[5].toString(),
'name': controller.myList[8].toString(),
'phone': controller.myList[10].toString(),
'email': controller.myList[28].toString(),
'WalletChecked': controller.myList[13].toString(),
'tokenPassenger': controller.myList[9].toString(),
'direction':
'https://www.google.com/maps/dir/${controller.myList[0]}/${controller.myList[1]}/',
'DurationToPassenger': controller.myList[15].toString(),
'rideId': (controller.myList[16].toString()),
'passengerId': (controller.myList[7].toString()),
'driverId': (controller.myList[18].toString()),
'durationOfRideValue': controller.myList[19].toString(),
'paymentAmount': controller.myList[2].toString(),
'paymentMethod':
controller.myList[13].toString() == 'true' ? 'visa' : 'cash',
'isHaveSteps': controller.myList[20].toString(),
'step0': controller.myList[21].toString(),
'step1': controller.myList[22].toString(),
'step2': controller.myList[23].toString(),
'step3': controller.myList[24].toString(),
'step4': controller.myList[25].toString(),
'passengerWalletBurc': controller.myList[26].toString(),
'timeOfOrder': DateTime.now().toString(),
'totalPassenger': controller.myList[2].toString(),
'carType': controller.myList[31].toString(),
'kazan': controller.myList[32].toString(),
'startNameLocation': controller.myList[29].toString(),
'endNameLocation': controller.myList[30].toString(),
});
// الانتقال لصفحة تتبع الراكب
Get.back(); // يغلق صفحة الطلب الحالية
Get.to(() => PassengerLocationMapPage(),
arguments: box.read(BoxName.rideArguments));
Log.print(
'box.read(BoxName.rideArguments): ${box.read(BoxName.rideArguments)}');
}
void _handleRefuseOrder(OrderRequestController controller, String rideId) {
controller.endTimer();
// controller.refuseOrder(rideId);
// تسجيل الرفض في الإشعارات المحلية للسائق
controller.addRideToNotificationDriverString( controller.addRideToNotificationDriverString(
rideId, rideId,
_getData(29), _getData(29),
@@ -542,71 +551,8 @@ class OrderSpeedRequest extends StatelessWidget {
_getData(2), _getData(2),
_getData(5), _getData(5),
_getData(4)); _getData(4));
// Get.back(); // refuseOrder or endTimer should handle navigation if needed.
// If not, add Get.back(); here.
},
kolor: AppColor.redColor,
),
),
],
),
),
],
),
),
),
),
);
},
);
}
// Helper widget for location cards to reduce repetition and improve readability // الخروج من الصفحة بعد الرفض
Widget _buildLocationCard( Get.back();
{required IconData icon,
required Color iconColor,
required String title,
required String subtitle,
required String fullAddress}) {
return Card(
elevation: 2,
shape: RoundedRectangleBorder(borderRadius: BorderRadius.circular(10)),
margin: const EdgeInsets.symmetric(
vertical: 4), // Add a little vertical margin between cards
child: Padding(
padding: const EdgeInsets.all(10.0),
child: Row(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
Icon(icon, color: iconColor, size: 28),
const SizedBox(width: 12),
Expanded(
child: Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
Text(
"$title $subtitle"
.trim(), // Trim to avoid extra spaces if subtitle is empty
style: AppStyle.title.copyWith(fontWeight: FontWeight.w600),
maxLines: 1,
overflow: TextOverflow.ellipsis,
),
if (fullAddress.isNotEmpty) ...[
const SizedBox(height: 3),
Text(
fullAddress,
style: AppStyle.subtitle
.copyWith(fontSize: 13, color: AppColor.greyColor),
maxLines: 2, // Allow up to 2 lines for address
overflow: TextOverflow.ellipsis,
),
]
],
),
),
],
),
),
);
} }
} }

View File

@@ -66,48 +66,48 @@ class PointsCaptain extends StatelessWidget {
color: AppColor.blueColor, size: 70), color: AppColor.blueColor, size: 70),
], ],
)), )),
GestureDetector( // GestureDetector(
onTap: () async { // onTap: () async {
Get.back(); // Get.back();
Get.defaultDialog( // Get.defaultDialog(
barrierDismissible: false, // barrierDismissible: false,
title: 'Insert Wallet phone number'.tr, // title: 'Insert Wallet phone number'.tr,
content: Form( // content: Form(
key: paymentController.formKey, // key: paymentController.formKey,
child: MyTextForm( // child: MyTextForm(
controller: // controller:
paymentController.walletphoneController, // paymentController.walletphoneController,
label: 'Insert Wallet phone number'.tr, // label: 'Insert Wallet phone number'.tr,
hint: '963941234567', // hint: '963941234567',
type: TextInputType.phone)), // type: TextInputType.phone)),
confirm: MyElevatedButton( // confirm: MyElevatedButton(
title: 'OK'.tr, // title: 'OK'.tr,
onPressed: () async { // onPressed: () async {
Get.back(); // Get.back();
if (paymentController.formKey.currentState! // if (paymentController.formKey.currentState!
.validate()) { // .validate()) {
box.write( // box.write(
BoxName.phoneWallet, // BoxName.phoneWallet,
paymentController // paymentController
.walletphoneController.text); // .walletphoneController.text);
await payWithMTNWallet( // await payWithMTNWallet(
context, pricePoint.toString(), 'SYP'); // context, pricePoint.toString(), 'SYP');
} // }
})); // }));
}, // },
child: Row( // child: Row(
mainAxisAlignment: MainAxisAlignment.spaceBetween, // mainAxisAlignment: MainAxisAlignment.spaceBetween,
children: [ // children: [
Text('Pay by MTN Wallet'.tr), // Text('Pay by MTN Wallet'.tr),
const SizedBox(width: 10), // const SizedBox(width: 10),
Image.asset( // Image.asset(
'assets/images/cashMTN.png', // 'assets/images/cashMTN.png',
width: 70, // width: 70,
height: 70, // height: 70,
fit: BoxFit.fill, // fit: BoxFit.fill,
), // ),
], // ],
)), // )),
GestureDetector( GestureDetector(
onTap: () async { onTap: () async {
@@ -153,51 +153,26 @@ class PointsCaptain extends StatelessWidget {
)), )),
GestureDetector( GestureDetector(
onTap: () async { onTap: () async {
Get.back(); // التحقق بالبصمة قبل أي شيء
Get.defaultDialog(
barrierDismissible: false,
title: 'Insert Wallet phone number'.tr,
content: Form(
key: paymentController.formKey,
child: MyTextForm(
controller:
paymentController.walletphoneController,
label: 'Insert Wallet phone number'.tr,
hint: '963941234567',
type: TextInputType.phone)),
confirm: MyElevatedButton(
title: 'OK'.tr,
onPressed: () async {
Get.back();
if (paymentController.formKey.currentState!
.validate()) {
box.write(
BoxName.phoneWallet,
paymentController
.walletphoneController.text);
// await payWithSyriaTelWallet(
// context, pricePoint.toString(), 'SYP');
bool isAuthSupported = bool isAuthSupported =
await LocalAuthentication() await LocalAuthentication().isDeviceSupported();
.isDeviceSupported();
if (isAuthSupported) { if (isAuthSupported) {
bool didAuthenticate = bool didAuthenticate =
await LocalAuthentication() await LocalAuthentication().authenticate(
.authenticate(
localizedReason: localizedReason:
'استخدم بصمة الإصبع أو الوجه لتأكيد الدفع', 'استخدم بصمة الإصبع أو الوجه لتأكيد الدفع',
); );
if (!didAuthenticate) { if (!didAuthenticate) {
if (Get.isDialogOpen ?? false) Get.back(); print("❌ User did not authenticate with biometrics");
print(
"❌ User did not authenticate with biometrics");
return; return;
} }
} }
Get.to(() => PaymentScreenSmsProvider(
amount: pricePoint)); // الانتقال مباشرة لإنشاء الفاتورة بعد النجاح بالبصمة
} Get.to(
})); () => PaymentScreenSmsProvider(amount: pricePoint));
}, },
child: Row( child: Row(
mainAxisAlignment: MainAxisAlignment.spaceBetween, mainAxisAlignment: MainAxisAlignment.spaceBetween,

View File

@@ -113,9 +113,11 @@ class WalletCaptainRefactored extends StatelessWidget {
child: Column( child: Column(
children: [ children: [
Text( Text(
'${'Total Points is'.tr} 💎', 'رصيد التشغيل 💎',
style: AppStyle.headTitle2 style: AppStyle.headTitle2.copyWith(
.copyWith(color: Colors.white, fontWeight: FontWeight.bold), color: Colors.white,
fontWeight: FontWeight.bold,
),
textAlign: TextAlign.center, textAlign: TextAlign.center,
), ),
const SizedBox(height: 8), const SizedBox(height: 8),

View File

@@ -283,14 +283,7 @@ class RideAvailableCard extends StatelessWidget {
// --- Ride Acceptance Logic --- // --- Ride Acceptance Logic ---
// This logic is copied exactly from your original code. // This logic is copied exactly from your original code.
void _acceptRide() async { void _acceptRide() async {
var res = await CRUD().post(link: AppLink.updateStausFromSpeed, payload: { var res = await CRUD().post(
'id': rideInfo['id'],
'rideTimeStart': DateTime.now().toString(),
'status': 'Apply',
'driver_id': box.read(BoxName.driverID),
});
if (AppLink.endPoint.toString() != AppLink.seferCairoServer) {
CRUD().post(
link: '${AppLink.endPoint}rides/updateStausFromSpeed.php', link: '${AppLink.endPoint}rides/updateStausFromSpeed.php',
payload: { payload: {
'id': rideInfo['id'], 'id': rideInfo['id'],
@@ -298,7 +291,6 @@ class RideAvailableCard extends StatelessWidget {
'status': 'Apply', 'status': 'Apply',
'driver_id': box.read(BoxName.driverID), 'driver_id': box.read(BoxName.driverID),
}); });
}
if (res != "failure") { if (res != "failure") {
List<String> bodyToPassenger = [ List<String> bodyToPassenger = [
@@ -312,46 +304,23 @@ class RideAvailableCard extends StatelessWidget {
'order_id': rideInfo['id'], 'order_id': rideInfo['id'],
'status': 'Apply' 'status': 'Apply'
}); });
await CRUD().post(link: AppLink.updateRides, payload: { // await CRUD().post(link: AppLink.updateRides, payload: {
'id': rideInfo['id'], // 'id': rideInfo['id'],
'DriverIsGoingToPassenger': DateTime.now().toString(), // 'DriverIsGoingToPassenger': DateTime.now().toString(),
'status': 'Applied' // 'status': 'Applied'
}); // });
await CRUD().post( await CRUD().post(
link: AppLink.updateWaitingRide, link: AppLink.updateWaitingRide,
payload: {'id': rideInfo['id'], 'status': 'Applied'}); payload: {'id': rideInfo['id'], 'status': 'Applied'});
if (AppLink.endPoint.toString() != AppLink.seferCairoServer) { // if (AppLink.endPoint.toString() != AppLink.seferCairoServer) {
CRUD().postFromDialogue(
link: '${AppLink.endPoint}/driver_order/add.php',
payload: {
'driver_id': box.read(BoxName.driverID),
'order_id': rideInfo['id'],
'status': 'Apply'
});
CRUD().post(link: '${AppLink.endPoint}/rides/update.php', payload: {
'id': rideInfo['id'],
'DriverIsGoingToPassenger': DateTime.now().toString(),
'status': 'Applied'
});
CRUD().post(
link:
"${AppLink.endPoint}/ride/notificationCaptain/updateWaitingTrip.php",
payload: {'id': rideInfo['id'], 'status': 'Applied'});
}
// FirebaseMessagesController().sendNotificationToPassengerToken(
// "Accepted Ride".tr,
// 'your ride is Accepted'.tr,
// rideInfo['passengerToken'].toString(),
// bodyToPassenger,
// 'start.wav');
NotificationService.sendNotification( NotificationService.sendNotification(
target: rideInfo['passengerToken'].toString(), target: rideInfo['passengerToken'].toString(),
title: 'Accepted Ride'.tr, title: 'Accepted Ride'.tr,
body: 'your ride is Accepted'.tr, body: 'your ride is Accepted'.tr,
isTopic: false, // Important: this is a token isTopic: false, // Important: this is a token
tone: 'start', tone: 'start',
driverList: [], category: 'Accepted Ride', driverList: bodyToPassenger, category: 'Accepted Ride',
); );
Get.back(); Get.back();
Get.to(() => PassengerLocationMapPage(), arguments: { Get.to(() => PassengerLocationMapPage(), arguments: {
@@ -385,18 +354,15 @@ class RideAvailableCard extends StatelessWidget {
'totalPassenger': rideInfo['price'].toString(), 'totalPassenger': rideInfo['price'].toString(),
'carType': rideInfo['carType'].toString(), 'carType': rideInfo['carType'].toString(),
'kazan': Get.find<HomeCaptainController>().kazan.toString(), 'kazan': Get.find<HomeCaptainController>().kazan.toString(),
'startNameLocation': rideInfo['startName'].toString(),
'endNameLocation': rideInfo['endName'].toString(),
}); });
} else { } else {
MyDialog().getDialog( MyDialog().getDialog(
"This ride is already taken by another driver.".tr, '', () { "This ride is already taken by another driver.".tr, '', () {
CRUD().post( CRUD().post(
link: AppLink.deleteAvailableRide, payload: {'id': rideInfo['id']}); link: AppLink.deleteAvailableRide, payload: {'id': rideInfo['id']});
if (AppLink.endPoint.toString() != AppLink.seferCairoServer) {
CRUD().post(
link:
'${AppLink.endPoint}/ride/notificationCaptain/deleteAvailableRide.php',
payload: {'id': rideInfo['id']});
}
Get.back(); Get.back();
}); });
} }

View File

@@ -52,7 +52,7 @@ class MyDialog extends GetxController {
title: Column( title: Column(
children: [ children: [
Text( Text(
title, title.tr,
style: AppStyle.title.copyWith( style: AppStyle.title.copyWith(
fontSize: 20, fontSize: 20,
fontWeight: FontWeight.w700, fontWeight: FontWeight.w700,

View File

@@ -4,5 +4,9 @@
<dict> <dict>
<key>com.apple.security.app-sandbox</key> <key>com.apple.security.app-sandbox</key>
<true/> <true/>
<key>com.apple.security.cs.allow-unsigned-executable-memory</key>
<true/>
<key>com.apple.security.network.client</key>
<true/>
</dict> </dict>
</plist> </plist>

View File

@@ -144,6 +144,7 @@ flutter:
- assets/ - assets/
- assets/images/ - assets/images/
- assets/fonts/ - assets/fonts/
- shorebird.yaml
fonts: fonts:
# - family: mohanad # - family: mohanad

14
shorebird.yaml Normal file
View File

@@ -0,0 +1,14 @@
# This file is used to configure the Shorebird updater used by your app.
# Learn more at https://docs.shorebird.dev
# This file does not contain any sensitive information and should be checked into version control.
# Your app_id is the unique identifier assigned to your app.
# It is used to identify your app when requesting patches from Shorebird's servers.
# It is not a secret and can be shared publicly.
app_id: eceba4d4-b399-4a26-a9ab-25f6e328bf7d
# auto_update controls if Shorebird should automatically update in the background on launch.
# If auto_update: false, you will need to use package:shorebird_code_push to trigger updates.
# https://pub.dev/packages/shorebird_code_push
# Uncomment the following line to disable automatic updates.
# auto_update: false