25-12-1/1
This commit is contained in:
@@ -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 {
|
||||||
|
|||||||
@@ -1,177 +1,106 @@
|
|||||||
<?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 ===== -->
|
||||||
|
<uses-permission android:name="android.permission.ACCESS_FINE_LOCATION"/>
|
||||||
<!-- ===== Permissions ===== -->
|
<uses-permission android:name="android.permission.ACCESS_COARSE_LOCATION"/>
|
||||||
<uses-permission android:name="android.permission.ACCESS_FINE_LOCATION" />
|
<uses-permission android:name="android.permission.ACCESS_BACKGROUND_LOCATION"/>
|
||||||
<uses-permission android:name="android.permission.ACCESS_COARSE_LOCATION" />
|
<uses-permission android:name="android.permission.FOREGROUND_SERVICE"/>
|
||||||
<uses-permission android:name="android.permission.ACCESS_BACKGROUND_LOCATION" />
|
<uses-permission android:name="android.permission.FOREGROUND_SERVICE_LOCATION"/>
|
||||||
<uses-permission android:name="android.permission.FOREGROUND_SERVICE" />
|
<uses-permission android:name="android.permission.FOREGROUND_SERVICE_TYPE_REMOTE_MESSAGING"/>
|
||||||
<uses-permission android:name="android.permission.FOREGROUND_SERVICE_LOCATION" />
|
<uses-permission android:name="android.permission.USE_BIOMETRIC"/>
|
||||||
<uses-permission android:name="android.permission.FOREGROUND_SERVICE_TYPE_REMOTE_MESSAGING" />
|
<uses-permission android:name="android.permission.VIBRATE"/>
|
||||||
<uses-permission android:name="android.permission.USE_BIOMETRIC" />
|
<uses-permission android:name="android.permission.ACCESS_NETWORK_STATE"/>
|
||||||
<uses-permission android:name="android.permission.VIBRATE" />
|
<uses-permission android:name="android.permission.CAMERA"/>
|
||||||
<uses-permission android:name="android.permission.ACCESS_NETWORK_STATE" />
|
<uses-permission android:name="android.permission.RECORD_AUDIO"/>
|
||||||
<uses-permission android:name="android.permission.CAMERA" />
|
<uses-permission android:name="android.permission.MODIFY_AUDIO_SETTINGS"/>
|
||||||
<uses-permission android:name="android.permission.RECORD_AUDIO" />
|
<uses-permission android:name="android.permission.WAKE_LOCK"/>
|
||||||
<uses-permission android:name="android.permission.MODIFY_AUDIO_SETTINGS" />
|
<uses-permission android:name="android.permission.WRITE_EXTERNAL_STORAGE"/>
|
||||||
<uses-permission android:name="android.permission.WAKE_LOCK" />
|
<uses-permission android:name="android.permission.POST_NOTIFICATIONS"/>
|
||||||
<uses-permission android:name="android.permission.WRITE_EXTERNAL_STORAGE" />
|
<uses-permission android:name="android.permission.READ_CONTACTS"/>
|
||||||
<uses-permission android:name="android.permission.POST_NOTIFICATIONS" />
|
<uses-permission android:name="android.permission.WRITE_CONTACTS"/>
|
||||||
<uses-permission android:name="android.permission.READ_CONTACTS" />
|
<uses-permission android:name="android.permission.SYSTEM_ALERT_WINDOW"/>
|
||||||
<uses-permission android:name="android.permission.WRITE_CONTACTS" />
|
<uses-permission android:name="android.permission.FOREGROUND_SERVICE_SPECIAL_USE"/>
|
||||||
<uses-permission android:name="android.permission.SYSTEM_ALERT_WINDOW" />
|
<uses-permission android:name="android.permission.READ_EXTERNAL_STORAGE"/>
|
||||||
<uses-permission android:name="android.permission.FOREGROUND_SERVICE_SPECIAL_USE" />
|
<uses-permission android:name="android.permission.SCHEDULE_EXACT_ALARM"/>
|
||||||
<uses-permission android:name="android.permission.READ_EXTERNAL_STORAGE" />
|
<uses-permission android:name="android.permission.RECEIVE_BOOT_COMPLETED"/>
|
||||||
<uses-permission android:name="android.permission.SCHEDULE_EXACT_ALARM" />
|
<uses-permission android:name="com.google.android.c2dm.permission.RECEIVE"/>
|
||||||
<uses-permission android:name="android.permission.RECEIVE_BOOT_COMPLETED" />
|
<uses-permission android:name="android.permission.QUICKBOOT_POWERON"/>
|
||||||
<uses-permission android:name="com.google.android.c2dm.permission.RECEIVE" />
|
<uses-permission android:name="com.htc.intent.action.QUICKBOOT_POWERON"/>
|
||||||
<uses-permission android:name="android.permission.QUICKBOOT_POWERON" />
|
<uses-permission android:name="android.permission.PICTURE_IN_PICTURE"/>
|
||||||
<uses-permission android:name="com.htc.intent.action.QUICKBOOT_POWERON" />
|
<uses-feature android:name="android.hardware.camera"/>
|
||||||
<uses-permission android:name="android.permission.PICTURE_IN_PICTURE" />
|
<uses-feature android:name="android.hardware.camera.autofocus"/>
|
||||||
<uses-feature android:name="android.hardware.camera" />
|
<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">
|
||||||
<uses-feature android:name="android.hardware.camera.autofocus" />
|
<!-- Flutter embedding v2 -->
|
||||||
|
<meta-data android:name="flutterEmbedding" android:value="2"/>
|
||||||
<application
|
<!-- تحديد نقطة دخول خلفية (للـ overlay / background executor) -->
|
||||||
android:name="${applicationName}"
|
<!-- <meta-data
|
||||||
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 -->
|
|
||||||
<meta-data
|
|
||||||
android:name="flutterEmbedding"
|
|
||||||
android:value="2" />
|
|
||||||
|
|
||||||
<!-- تحديد نقطة دخول خلفية (للـ overlay / background executor) -->
|
|
||||||
<!-- <meta-data
|
|
||||||
android:name="io.flutter.embedding.android.BackgroundExecutor.DART_ENTRYPOINT"
|
android:name="io.flutter.embedding.android.BackgroundExecutor.DART_ENTRYPOINT"
|
||||||
android:value="overlayMain" />
|
android:value="overlayMain" />
|
||||||
<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 android:name="com.google.android.geo.API_KEY" android:value="@string/api_key"/>
|
||||||
<meta-data
|
<meta-data android:name="com.google.firebase.messaging.default_notification_channel_id" android:value="@string/default_notification_channel_id"/>
|
||||||
android:name="com.google.android.geo.API_KEY"
|
<meta-data android:name="io.flutter.embedding.android.EnableImpeller" android:value="false"/>
|
||||||
android:value="@string/api_key" />
|
<!-- Main Activity -->
|
||||||
<meta-data
|
<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="com.google.firebase.messaging.default_notification_channel_id"
|
<meta-data android:name="io.flutter.embedding.android.NormalTheme" android:resource="@style/NormalTheme"/>
|
||||||
android:value="@string/default_notification_channel_id" />
|
<intent-filter>
|
||||||
<meta-data
|
<action android:name="android.intent.action.MAIN"/>
|
||||||
android:name="io.flutter.embedding.android.EnableImpeller"
|
<category android:name="android.intent.category.LAUNCHER"/>
|
||||||
android:value="false" />
|
</intent-filter>
|
||||||
<!-- Main Activity -->
|
<!-- Deep Link: intaleqapp://... -->
|
||||||
<activity
|
<intent-filter>
|
||||||
android:name=".MainActivity"
|
<action android:name="android.intent.action.VIEW"/>
|
||||||
android:configChanges="orientation|keyboardHidden|screenSize|locale|layoutDirection|fontScale|screenLayout|density|uiMode"
|
<category android:name="android.intent.category.DEFAULT"/>
|
||||||
android:exported="true"
|
<category android:name="android.intent.category.BROWSABLE"/>
|
||||||
android:hardwareAccelerated="true"
|
<data android:scheme="intaleqapp"/>
|
||||||
android:launchMode="singleTask"
|
</intent-filter>
|
||||||
android:theme="@style/LaunchTheme"
|
</activity>
|
||||||
android:windowSoftInputMode="adjustResize">
|
<!-- أنشطة ومكوّنات إضافية -->
|
||||||
|
<activity android:name="com.yalantis.ucrop.UCropActivity" android:screenOrientation="portrait" android:theme="@style/Theme.AppCompat.Light.NoActionBar"/>
|
||||||
<meta-data
|
<!-- خدماتك الخاصة -->
|
||||||
android:name="io.flutter.embedding.android.NormalTheme"
|
<service android:name=".MyFirebaseMessagingService" android:exported="false"/>
|
||||||
android:resource="@style/NormalTheme" />
|
<service android:name=".LocationUpdatesService" android:exported="false" android:foregroundServiceType="location"/>
|
||||||
|
<!-- خدمة Firebase الرسمية لاستقبال رسائل FCM -->
|
||||||
<intent-filter>
|
<service android:name="com.google.firebase.messaging.FirebaseMessagingService" android:exported="false" tools:replace="android:exported">
|
||||||
<action android:name="android.intent.action.MAIN" />
|
<intent-filter>
|
||||||
<category android:name="android.intent.category.LAUNCHER" />
|
<action android:name="com.google.firebase.MESSAGING_EVENT"/>
|
||||||
</intent-filter>
|
</intent-filter>
|
||||||
|
</service>
|
||||||
<!-- Deep Link: intaleqapp://... -->
|
<!-- خدمة overlay للمكتبة الأولى (إن كنت تستخدمها) -->
|
||||||
<intent-filter>
|
<!-- <service
|
||||||
<action android:name="android.intent.action.VIEW" />
|
|
||||||
<category android:name="android.intent.category.DEFAULT" />
|
|
||||||
<category android:name="android.intent.category.BROWSABLE" />
|
|
||||||
<data android:scheme="intaleqapp" />
|
|
||||||
</intent-filter>
|
|
||||||
</activity>
|
|
||||||
|
|
||||||
<!-- أنشطة ومكوّنات إضافية -->
|
|
||||||
<activity
|
|
||||||
android:name="com.yalantis.ucrop.UCropActivity"
|
|
||||||
android:screenOrientation="portrait"
|
|
||||||
android:theme="@style/Theme.AppCompat.Light.NoActionBar" />
|
|
||||||
|
|
||||||
<!-- خدماتك الخاصة -->
|
|
||||||
<service
|
|
||||||
android:name=".MyFirebaseMessagingService"
|
|
||||||
android:exported="false" />
|
|
||||||
|
|
||||||
<service
|
|
||||||
android:name=".LocationUpdatesService"
|
|
||||||
android:exported="false"
|
|
||||||
android:foregroundServiceType="location" />
|
|
||||||
|
|
||||||
<!-- خدمة Firebase الرسمية لاستقبال رسائل FCM -->
|
|
||||||
<service
|
|
||||||
android:name="com.google.firebase.messaging.FirebaseMessagingService"
|
|
||||||
android:exported="false"
|
|
||||||
tools:replace="android:exported">
|
|
||||||
<intent-filter>
|
|
||||||
<action android:name="com.google.firebase.MESSAGING_EVENT" />
|
|
||||||
</intent-filter>
|
|
||||||
</service>
|
|
||||||
|
|
||||||
<!-- خدمة overlay للمكتبة الأولى (إن كنت تستخدمها) -->
|
|
||||||
<!-- <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"
|
<!-- خدمة overlay الخاصة بمكتبة flutter_overlay_window -->
|
||||||
android:exported="false"
|
<!-- استقبال توكن/رسائل قديمة (توافقية) -->
|
||||||
android:foregroundServiceType="specialUse" />
|
<receiver android:name="com.google.firebase.iid.FirebaseInstanceIdReceiver" android:exported="true" android:permission="com.google.android.c2dm.permission.SEND">
|
||||||
<!-- خدمة overlay الخاصة بمكتبة flutter_overlay_window -->
|
<intent-filter>
|
||||||
|
<action android:name="com.google.android.c2dm.intent.RECEIVE"/>
|
||||||
|
<category android:name="com.intaleq_driver"/>
|
||||||
<!-- استقبال توكن/رسائل قديمة (توافقية) -->
|
</intent-filter>
|
||||||
<receiver
|
</receiver>
|
||||||
android:name="com.google.firebase.iid.FirebaseInstanceIdReceiver"
|
<!-- خدمة الفقاعة الخاصة بك -->
|
||||||
android:exported="true"
|
<service android:name="com.dsaved.bubblehead.bubble.BubbleHeadService" android:enabled="true" android:exported="false">
|
||||||
android:permission="com.google.android.c2dm.permission.SEND">
|
<intent-filter>
|
||||||
<intent-filter>
|
<action android:name="intent.bring.app.to.foreground"/>
|
||||||
<action android:name="com.google.android.c2dm.intent.RECEIVE" />
|
<category android:name="android.intent.category.DEFAULT"/>
|
||||||
<category android:name="com.intaleq_driver" />
|
</intent-filter>
|
||||||
</intent-filter>
|
</service>
|
||||||
</receiver>
|
<!-- Notif schedulers -->
|
||||||
|
<receiver android:name="com.dexterous.flutterlocalnotifications.ScheduledNotificationReceiver" android:exported="false"/>
|
||||||
<!-- خدمة الفقاعة الخاصة بك -->
|
<receiver android:name="com.dexterous.flutterlocalnotifications.ScheduledNotificationBootReceiver" android:exported="false">
|
||||||
<service
|
<intent-filter>
|
||||||
android:name="com.dsaved.bubblehead.bubble.BubbleHeadService"
|
<action android:name="android.intent.action.BOOT_COMPLETED"/>
|
||||||
android:enabled="true"
|
<action android:name="android.intent.action.MY_PACKAGE_REPLACED"/>
|
||||||
android:exported="false">
|
<action android:name="android.intent.action.QUICKBOOT_POWERON"/>
|
||||||
<intent-filter>
|
<action android:name="com.htc.intent.action.QUICKBOOT_POWERON"/>
|
||||||
<action android:name="intent.bring.app.to.foreground" />
|
</intent-filter>
|
||||||
<category android:name="android.intent.category.DEFAULT" />
|
</receiver>
|
||||||
</intent-filter>
|
<!-- مستقبل برودكاست خاص بك -->
|
||||||
</service>
|
<receiver android:name=".YourBroadcastReceiver" android:exported="false"/>
|
||||||
|
</application>
|
||||||
<!-- Notif schedulers -->
|
<uses-permission android:name="android.permission.INTERNET"/>
|
||||||
<receiver
|
|
||||||
android:name="com.dexterous.flutterlocalnotifications.ScheduledNotificationReceiver"
|
|
||||||
android:exported="false" />
|
|
||||||
<receiver
|
|
||||||
android:name="com.dexterous.flutterlocalnotifications.ScheduledNotificationBootReceiver"
|
|
||||||
android:exported="false">
|
|
||||||
<intent-filter>
|
|
||||||
<action android:name="android.intent.action.BOOT_COMPLETED" />
|
|
||||||
<action android:name="android.intent.action.MY_PACKAGE_REPLACED" />
|
|
||||||
<action android:name="android.intent.action.QUICKBOOT_POWERON" />
|
|
||||||
<action android:name="com.htc.intent.action.QUICKBOOT_POWERON" />
|
|
||||||
</intent-filter>
|
|
||||||
</receiver>
|
|
||||||
|
|
||||||
<!-- مستقبل برودكاست خاص بك -->
|
|
||||||
<receiver
|
|
||||||
android:name=".YourBroadcastReceiver"
|
|
||||||
android:exported="false" />
|
|
||||||
|
|
||||||
</application>
|
|
||||||
</manifest>
|
</manifest>
|
||||||
BIN
assets/images/shamcashsend.png
Normal file
BIN
assets/images/shamcashsend.png
Normal file
Binary file not shown.
|
After Width: | Height: | Size: 31 KiB |
@@ -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";
|
||||||
|
|||||||
@@ -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,
|
||||||
"driverId": box.read(BoxName.driverID),
|
payload: {
|
||||||
"inviterPassengerPhone": formattedPhoneNumber,
|
"driverId": box.read(BoxName.driverID),
|
||||||
});
|
"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'
|
||||||
|
|||||||
@@ -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(
|
||||||
|
|||||||
@@ -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.');
|
||||||
|
|||||||
@@ -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');
|
||||||
// }
|
// }
|
||||||
|
|||||||
@@ -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 driver’s license has expired.',
|
// Get.snackbar('Expired License', 'Your driver’s 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'); // عدّل حسب منطقك
|
||||||
|
|||||||
@@ -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
|
||||||
|
|||||||
@@ -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
|
||||||
|
|||||||
@@ -8,11 +8,32 @@ 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,
|
||||||
);
|
);
|
||||||
await launchUrl(launchUri);
|
|
||||||
|
if (await canLaunchUrl(launchUri)) {
|
||||||
|
await launchUrl(launchUri);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
void launchCommunication(
|
void launchCommunication(
|
||||||
|
|||||||
@@ -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();
|
||||||
|
|||||||
@@ -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,
|
||||||
|
|||||||
@@ -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,
|
||||||
|
|||||||
@@ -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') {
|
||||||
|
|||||||
@@ -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)
|
|
||||||
controller.animateCamera(
|
|
||||||
CameraUpdate.newLatLng(Get.find<LocationController>().myLocation),
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
// قم بإنشائه مباشرة
|
// Check actual location before moving camera
|
||||||
// final MapController mapController = MapController();
|
var currentLoc = locationController.myLocation;
|
||||||
// bool isMapReady = false;
|
if (currentLoc.latitude != 0 && currentLoc.longitude != 0) {
|
||||||
// void onMapReady() {
|
controller.animateCamera(
|
||||||
// isMapReady = true;
|
CameraUpdate.newLatLng(currentLoc),
|
||||||
// print("Map is ready to be moved!");
|
);
|
||||||
// }
|
} else {
|
||||||
|
// Optional: Move to default city view instead of ocean
|
||||||
|
controller.animateCamera(
|
||||||
|
CameraUpdate.newLatLngZoom(myLocation, 10),
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
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) {
|
||||||
mapHomeCaptainController!.animateCamera(
|
var loc = locationController.myLocation;
|
||||||
CameraUpdate.newCameraPosition(
|
|
||||||
CameraPosition(
|
if (loc.latitude != 0 && loc.longitude != 0) {
|
||||||
target: locationController.myLocation, // الموقع الجديد
|
mapHomeCaptainController!.animateCamera(
|
||||||
zoom: 17.5,
|
CameraUpdate.newCameraPosition(
|
||||||
tilt: 50.0,
|
CameraPosition(
|
||||||
bearing: locationController.heading, // اتجاه السيارة
|
target: loc,
|
||||||
|
zoom: 17.5,
|
||||||
|
tilt: 50.0,
|
||||||
|
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
@@ -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,
|
||||||
|
|||||||
@@ -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",
|
||||||
|
|||||||
@@ -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":
|
||||||
|
|||||||
@@ -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(
|
||||||
actions: <Widget>[
|
'الخروج الآن سيؤدي لإلغاء متابعة عملية الدفع.',
|
||||||
TextButton(
|
textAlign: TextAlign.right),
|
||||||
onPressed: () => Navigator.of(context).pop(false),
|
actions: <Widget>[
|
||||||
child: const Text('البقاء'),
|
TextButton(
|
||||||
|
onPressed: () => Navigator.of(context).pop(false),
|
||||||
|
child: const Text('البقاء')),
|
||||||
|
TextButton(
|
||||||
|
onPressed: () => Navigator.of(context).pop(true),
|
||||||
|
child: const Text('خروج',
|
||||||
|
style: TextStyle(color: Colors.red))),
|
||||||
|
],
|
||||||
),
|
),
|
||||||
TextButton(
|
)) ??
|
||||||
onPressed: () => Navigator.of(context).pop(true),
|
false;
|
||||||
child: const Text('الخروج'),
|
|
||||||
),
|
|
||||||
],
|
|
||||||
),
|
|
||||||
);
|
|
||||||
|
|
||||||
// إذا وافق المستخدم على الخروج، قم بإغلاق الشاشة
|
|
||||||
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(
|
child: Column(
|
||||||
|
crossAxisAlignment: CrossAxisAlignment.center,
|
||||||
children: [
|
children: [
|
||||||
Image.asset(widget.providerLogo, width: 96),
|
// 1. المبلغ (تصميم مميز)
|
||||||
const SizedBox(height: 16),
|
Container(
|
||||||
Text("تعليمات الدفع", style: Theme.of(context).textTheme.titleLarge),
|
width: double.infinity,
|
||||||
const SizedBox(height: 12),
|
padding: const EdgeInsets.symmetric(vertical: 25, horizontal: 15),
|
||||||
Card(
|
decoration: BoxDecoration(
|
||||||
elevation: 1.5,
|
gradient: LinearGradient(
|
||||||
shape:
|
colors: [Colors.blue.shade800, Colors.blue.shade600]),
|
||||||
RoundedRectangleBorder(borderRadius: BorderRadius.circular(12)),
|
borderRadius: BorderRadius.circular(16),
|
||||||
child: Padding(
|
boxShadow: [
|
||||||
padding: const EdgeInsets.all(16),
|
BoxShadow(
|
||||||
|
color: Colors.blue.withOpacity(0.25),
|
||||||
|
blurRadius: 15,
|
||||||
|
offset: const Offset(0, 8))
|
||||||
|
],
|
||||||
|
),
|
||||||
|
child: Column(
|
||||||
|
children: [
|
||||||
|
const Text("المبلغ المطلوب",
|
||||||
|
style: TextStyle(color: Colors.white70, fontSize: 14)),
|
||||||
|
const SizedBox(height: 8),
|
||||||
|
Text(
|
||||||
|
"${currencyFormat.format(widget.amount)} ل.س",
|
||||||
|
style: const TextStyle(
|
||||||
|
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,
|
||||||
|
letterSpacing: 1.5)),
|
||||||
|
],
|
||||||
|
),
|
||||||
|
const Icon(Icons.copy_rounded,
|
||||||
|
color: Colors.blue, size: 24),
|
||||||
|
],
|
||||||
|
),
|
||||||
|
),
|
||||||
|
),
|
||||||
|
],
|
||||||
|
),
|
||||||
|
),
|
||||||
|
|
||||||
|
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(
|
child: Column(
|
||||||
children: [
|
children: [
|
||||||
_StepTile(number: 1, text: "افتح تطبيق محفظتك الإلكترونية."),
|
Image.asset(
|
||||||
_StepTile(number: 2, text: "اختر خدمة تحويل الأموال."),
|
widget.qrImagePath,
|
||||||
_StepTile(
|
width: 180,
|
||||||
number: 3,
|
height: 180,
|
||||||
text:
|
fit: BoxFit.contain,
|
||||||
"أدخل المبلغ المطلوب: ${currencyFormat.format(widget.amount)} ل.س"),
|
errorBuilder: (c, o, s) => const Icon(Icons.qr_code_2,
|
||||||
_StepTile(number: 4, text: "حوّل إلى الرقم التالي:"),
|
size: 100, color: Colors.grey),
|
||||||
// --- التعديل هنا ---
|
|
||||||
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(
|
const Text("اضغط للتكبير",
|
||||||
number: 5,
|
style: TextStyle(fontSize: 10, color: Colors.grey)),
|
||||||
text: "هام: انسخ رقم القسيمة والصقه في خانة \"البيان\"."),
|
|
||||||
ListTile(
|
|
||||||
contentPadding: EdgeInsets.zero,
|
|
||||||
title: Text(invoiceText,
|
|
||||||
style: const TextStyle(
|
|
||||||
fontSize: 20,
|
|
||||||
fontWeight: FontWeight.bold,
|
|
||||||
letterSpacing: 1.5)),
|
|
||||||
trailing: OutlinedButton.icon(
|
|
||||||
onPressed: _invoiceNumber == null
|
|
||||||
? null
|
|
||||||
: () async {
|
|
||||||
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: 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("تم إضافة الرصيد والمكافأة إلى حسابك",
|
||||||
onPressed: () => Navigator.of(context).pop(),
|
style: TextStyle(color: Colors.grey)),
|
||||||
child: const Text("العودة"),
|
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(),
|
||||||
|
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),
|
||||||
textAlign: TextAlign.center,
|
|
||||||
),
|
),
|
||||||
const SizedBox(height: 8),
|
const SizedBox(height: 15),
|
||||||
const Text("يرجى المحاولة مرة أخرى.", style: TextStyle(fontSize: 16)),
|
const Padding(
|
||||||
const SizedBox(height: 20),
|
padding: EdgeInsets.symmetric(horizontal: 30),
|
||||||
ElevatedButton(
|
child: Text(
|
||||||
onPressed: _createAndPollInvoice,
|
"لم يصلنا إشعار الدفع. هل تأكدت من وضع (رقم البيان) في الملاحظات؟",
|
||||||
child: const Text("المحاولة مرة أخرى"),
|
textAlign: TextAlign.center,
|
||||||
|
style: TextStyle(color: Colors.grey, height: 1.5)),
|
||||||
),
|
),
|
||||||
|
const SizedBox(height: 40),
|
||||||
|
SizedBox(
|
||||||
|
width: double.infinity,
|
||||||
|
child: ElevatedButton.icon(
|
||||||
|
style: ElevatedButton.styleFrom(
|
||||||
|
padding: const EdgeInsets.symmetric(vertical: 15),
|
||||||
|
shape: RoundedRectangleBorder(
|
||||||
|
borderRadius: BorderRadius.circular(12))),
|
||||||
|
onPressed: _createAndPollInvoice,
|
||||||
|
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),
|
|
||||||
);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|||||||
@@ -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) {
|
||||||
|
|||||||
@@ -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 it’s 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(
|
||||||
|
|||||||
@@ -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),
|
||||||
|
|||||||
@@ -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,114 +16,169 @@ 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
|
||||||
body: SafeArea(
|
return PopScope(
|
||||||
child: Stack(
|
canPop: false, // Prevents immediate popping
|
||||||
children: [
|
onPopInvokedWithResult: (didPop, result) async {
|
||||||
// 1. الخريطة في الخلفية
|
if (didPop) {
|
||||||
GoogleDriverMap(locationController: locationController),
|
return;
|
||||||
|
}
|
||||||
|
// Show dialog
|
||||||
|
final shouldExit = await showExitDialog();
|
||||||
|
if (shouldExit) {
|
||||||
|
Get.back(); // Manually pop if confirmed
|
||||||
|
}
|
||||||
|
},
|
||||||
|
child: Scaffold(
|
||||||
|
body: SafeArea(
|
||||||
|
child: Stack(
|
||||||
|
children: [
|
||||||
|
// 1. Map
|
||||||
|
GoogleDriverMap(locationController: locationController),
|
||||||
|
|
||||||
// 2. شريط تعليمات الطريق في الأعلى
|
// 2. Instructions
|
||||||
const InstructionsOfRoads(),
|
const InstructionsOfRoads(),
|
||||||
|
|
||||||
// 4. نافذة معلومات الراكب في الأسفل (تظهر قبل بدء الرحلة)
|
// 3. Passenger Info
|
||||||
|
Positioned(
|
||||||
|
top: 0,
|
||||||
|
left: 0,
|
||||||
|
right: 0,
|
||||||
|
child: PassengerInfoWindow(),
|
||||||
|
),
|
||||||
|
|
||||||
PassengerInfoWindow(),
|
// 4. Cancel Widget
|
||||||
// 3. زر إلغاء الرحلة في الأعلى يسارًا
|
CancelWidget(mapDriverController: mapDriverController),
|
||||||
|
|
||||||
CancelWidget(mapDriverController: mapDriverController),
|
// 5. End Ride Bar
|
||||||
// Changed: تم تعديل تصميم زر الإلغاء ليكون أيقونة بسيطة في الأعلى
|
driverEndRideBar(),
|
||||||
// 5. شريط معلومات وإنهاء الرحلة (يظهر بعد بدء الرحلة)
|
|
||||||
driverEndRideBar(),
|
|
||||||
|
|
||||||
// 6. أزرار الطوارئ والاتصال
|
// 6. SOS
|
||||||
SosConnect(),
|
SosConnect(),
|
||||||
|
|
||||||
// 7. دائرة عرض السرعة
|
// 7. Speed
|
||||||
speedCircle(),
|
speedCircle(),
|
||||||
GoogleMapApp(),
|
|
||||||
// 8. نافذة عرض السعر النهائي (تظهر بعد انتهاء الرحلة)
|
// 8. External Map
|
||||||
const PricesWindow(),
|
Positioned(
|
||||||
],
|
bottom: 100,
|
||||||
),
|
right: 10,
|
||||||
));
|
child: GoogleMapApp(),
|
||||||
|
),
|
||||||
|
|
||||||
|
// 9. Prices Window
|
||||||
|
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) =>
|
bottom: 10,
|
||||||
// يتم إظهار التعليمات فقط إذا كانت متوفرة
|
left: MediaQuery.of(context).size.width * 0.15,
|
||||||
controller.currentInstruction.isNotEmpty
|
right: MediaQuery.of(context).size.width * 0.15,
|
||||||
? Positioned(
|
child: GetBuilder<MapDriverController>(
|
||||||
bottom: 10,
|
builder: (controller) => controller.currentInstruction.isNotEmpty
|
||||||
left: MediaQuery.of(context).size.width * 0.15,
|
? AnimatedContainer(
|
||||||
right: MediaQuery.of(context).size.width * 0.15,
|
duration: const Duration(milliseconds: 300),
|
||||||
child: AnimatedContainer(
|
padding:
|
||||||
duration: const Duration(milliseconds: 300),
|
const EdgeInsets.symmetric(horizontal: 16, vertical: 12),
|
||||||
padding: const EdgeInsets.symmetric(
|
decoration: BoxDecoration(
|
||||||
horizontal: 16, vertical: 12),
|
color: Colors.white,
|
||||||
decoration: BoxDecoration(
|
borderRadius: BorderRadius.circular(30),
|
||||||
color: Colors.white,
|
boxShadow: [
|
||||||
borderRadius: BorderRadius.circular(30),
|
BoxShadow(
|
||||||
boxShadow: [
|
color: Colors.black.withOpacity(0.2),
|
||||||
BoxShadow(
|
blurRadius: 8,
|
||||||
color: Colors.black.withOpacity(0.2),
|
offset: const Offset(0, 4),
|
||||||
blurRadius: 8,
|
|
||||||
offset: const Offset(0, 4),
|
|
||||||
),
|
|
||||||
],
|
|
||||||
),
|
),
|
||||||
child: Row(
|
],
|
||||||
mainAxisAlignment: MainAxisAlignment.center,
|
),
|
||||||
children: [
|
child: Row(
|
||||||
const Icon(Icons.directions,
|
mainAxisAlignment: MainAxisAlignment.center,
|
||||||
color: AppColor.primaryColor),
|
children: [
|
||||||
const SizedBox(width: 10),
|
const Icon(Icons.directions, color: AppColor.primaryColor),
|
||||||
Expanded(
|
const SizedBox(width: 10),
|
||||||
child: Text(
|
Expanded(
|
||||||
controller.currentInstruction,
|
child: Text(
|
||||||
style: AppStyle.title.copyWith(fontSize: 16),
|
controller.currentInstruction,
|
||||||
textAlign: TextAlign.center,
|
style: AppStyle.title.copyWith(fontSize: 16),
|
||||||
overflow: TextOverflow.ellipsis,
|
textAlign: TextAlign.center,
|
||||||
),
|
overflow: TextOverflow.ellipsis,
|
||||||
),
|
),
|
||||||
],
|
|
||||||
),
|
),
|
||||||
),
|
const SizedBox(width: 10),
|
||||||
)
|
InkWell(
|
||||||
: const SizedBox(), // في حالة عدم وجود تعليمات، لا يظهر شيء
|
onTap: () {
|
||||||
|
controller.toggleTts();
|
||||||
|
},
|
||||||
|
child: Icon(
|
||||||
|
controller.isTtsEnabled
|
||||||
|
? Icons.volume_up
|
||||||
|
: Icons.volume_off,
|
||||||
|
color: controller.isTtsEnabled
|
||||||
|
? AppColor.greenColor
|
||||||
|
: Colors.grey,
|
||||||
|
),
|
||||||
|
),
|
||||||
|
],
|
||||||
|
),
|
||||||
|
)
|
||||||
|
: 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,
|
||||||
|
|||||||
@@ -621,45 +621,95 @@ 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,
|
// جعلنا الزر يظهر في المنتصف أو يمتد ليكون واضحاً جداً
|
||||||
child: AnimatedContainer(
|
right: 20,
|
||||||
duration: const Duration(microseconds: 200),
|
left: 20,
|
||||||
width: homeCaptainController.widthMapTypeAndTraffic,
|
child: Center(
|
||||||
decoration: BoxDecoration(
|
child: AnimatedContainer(
|
||||||
border: Border.all(color: AppColor.blueColor),
|
duration: const Duration(
|
||||||
color: AppColor.secondaryColor,
|
milliseconds:
|
||||||
borderRadius: BorderRadius.circular(15)),
|
200), // تم تصحيح microseconds إلى milliseconds لحركة أنعم
|
||||||
child: GestureDetector(
|
// أزلنا العرض الثابت homeCaptainController.widthMapTypeAndTraffic لكي يتسع للنص
|
||||||
onLongPress: () {
|
// width: homeCaptainController.widthMapTypeAndTraffic,
|
||||||
box.write(BoxName.rideStatus, 'delete');
|
decoration: BoxDecoration(
|
||||||
homeCaptainController.update();
|
border: Border.all(
|
||||||
},
|
color: AppColor.blueColor,
|
||||||
child: IconButton(
|
width: 2), // تعريض الإطار قليلاً
|
||||||
onPressed: () {
|
color: AppColor.secondaryColor, // لون الخلفية
|
||||||
box.read(BoxName.rideStatus) == 'Applied'
|
borderRadius: BorderRadius.circular(
|
||||||
? {
|
30), // تدوير الحواف ليشبه الأزرار الحديثة
|
||||||
Get.to(() => PassengerLocationMapPage(),
|
boxShadow: [
|
||||||
arguments:
|
BoxShadow(
|
||||||
box.read(BoxName.rideArguments)),
|
color: Colors.black.withOpacity(0.2),
|
||||||
Get.put(MapDriverController())
|
blurRadius: 8,
|
||||||
.changeRideToBeginToPassenger()
|
offset: const Offset(0, 4),
|
||||||
}
|
)
|
||||||
: {
|
]),
|
||||||
Get.to(() => PassengerLocationMapPage(),
|
child: Material(
|
||||||
arguments:
|
color: Colors.transparent,
|
||||||
box.read(BoxName.rideArguments)),
|
child: InkWell(
|
||||||
Get.put(MapDriverController())
|
borderRadius: BorderRadius.circular(30),
|
||||||
.startRideFromStartApp()
|
onLongPress: () {
|
||||||
};
|
// وظيفة الحذف عند الضغط الطويل (للطوارئ)
|
||||||
},
|
box.write(BoxName.rideStatus, 'delete');
|
||||||
icon: const Icon(
|
homeCaptainController.update();
|
||||||
Icons.directions_rounded,
|
},
|
||||||
size: 29,
|
onTap: () {
|
||||||
color: AppColor.blueColor,
|
// نفس منطقك الأصلي للانتقال
|
||||||
|
if (box.read(BoxName.rideStatus) == 'Applied') {
|
||||||
|
Get.to(() => PassengerLocationMapPage(),
|
||||||
|
arguments: box.read(BoxName.rideArguments));
|
||||||
|
Get.put(MapDriverController())
|
||||||
|
.changeRideToBeginToPassenger();
|
||||||
|
} else {
|
||||||
|
Get.to(() => PassengerLocationMapPage(),
|
||||||
|
arguments: box.read(BoxName.rideArguments));
|
||||||
|
Get.put(MapDriverController())
|
||||||
|
.startRideFromStartApp();
|
||||||
|
}
|
||||||
|
},
|
||||||
|
child: Padding(
|
||||||
|
padding: const EdgeInsets.symmetric(
|
||||||
|
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,
|
||||||
|
),
|
||||||
|
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)
|
||||||
|
]
|
||||||
|
],
|
||||||
|
),
|
||||||
|
),
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
|
|||||||
@@ -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);
|
||||||
|
|||||||
@@ -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,82 +12,88 @@ 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(
|
|
||||||
duration: const Duration(milliseconds: 300),
|
|
||||||
// New: يظهر الشريط من الأعلى عندما تبدأ الرحلة
|
|
||||||
top: controller.isRideStarted ? 0 : -200,
|
|
||||||
left: 0,
|
|
||||||
right: 0,
|
|
||||||
child: Card(
|
|
||||||
margin: EdgeInsets.zero,
|
|
||||||
elevation: 10,
|
|
||||||
shape: const RoundedRectangleBorder(
|
|
||||||
borderRadius: BorderRadius.vertical(bottom: Radius.circular(24)),
|
|
||||||
),
|
|
||||||
child: Padding(
|
|
||||||
padding: const EdgeInsets.symmetric(horizontal: 16.0, vertical: 12.0),
|
|
||||||
child: Column(
|
|
||||||
children: [
|
|
||||||
// -- معلومات الرحلة --
|
|
||||||
if (controller.carType != 'Mishwar Vip')
|
|
||||||
Row(
|
|
||||||
mainAxisAlignment: MainAxisAlignment.spaceEvenly,
|
|
||||||
children: [
|
|
||||||
_buildInfoColumn(
|
|
||||||
icon: Icons.social_distance,
|
|
||||||
text: '${controller.distance} ${'KM'.tr}',
|
|
||||||
label: 'Distance'.tr,
|
|
||||||
),
|
|
||||||
_buildInfoColumn(
|
|
||||||
icon: Icons.timelapse,
|
|
||||||
text: controller.hours > 1
|
|
||||||
? '${controller.hours}h ${controller.minutes}m'
|
|
||||||
: '${controller.minutes}m',
|
|
||||||
label: 'Time'.tr,
|
|
||||||
),
|
|
||||||
_buildInfoColumn(
|
|
||||||
icon: Icons.money_sharp,
|
|
||||||
text: '${controller.paymentAmount} ${'SYP'.tr}',
|
|
||||||
label: 'Price'.tr,
|
|
||||||
),
|
|
||||||
],
|
|
||||||
),
|
|
||||||
if (controller.carType != 'Mishwar Vip')
|
|
||||||
const Divider(height: 20),
|
|
||||||
|
|
||||||
// -- مؤقت الرحلة المتبقي (إن وجد) --
|
Widget driverEndRideBar() {
|
||||||
_builtTimerAndCarType(),
|
// 1. Positioned هي الوالد المباشر (لأنها داخل Stack في الصفحة الرئيسية)
|
||||||
|
return Positioned(
|
||||||
|
top: 0,
|
||||||
|
left: 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(
|
||||||
|
margin: EdgeInsets.zero,
|
||||||
|
elevation: 10,
|
||||||
|
shape: const RoundedRectangleBorder(
|
||||||
|
borderRadius: BorderRadius.vertical(bottom: Radius.circular(24)),
|
||||||
|
),
|
||||||
|
child: Padding(
|
||||||
|
padding:
|
||||||
|
const EdgeInsets.symmetric(horizontal: 16.0, vertical: 12.0),
|
||||||
|
child: Column(
|
||||||
|
children: [
|
||||||
|
if (controller.carType != 'Mishwar Vip')
|
||||||
|
Row(
|
||||||
|
mainAxisAlignment: MainAxisAlignment.spaceEvenly,
|
||||||
|
children: [
|
||||||
|
_buildInfoColumn(
|
||||||
|
icon: Icons.social_distance,
|
||||||
|
text: '${controller.distance} ${'KM'.tr}',
|
||||||
|
label: 'Distance'.tr,
|
||||||
|
),
|
||||||
|
_buildInfoColumn(
|
||||||
|
icon: Icons.timelapse,
|
||||||
|
text: controller.hours > 1
|
||||||
|
? '${controller.hours}h ${controller.minutes}m'
|
||||||
|
: '${controller.minutes}m',
|
||||||
|
label: 'Time'.tr,
|
||||||
|
),
|
||||||
|
_buildInfoColumn(
|
||||||
|
icon: Icons.money_sharp,
|
||||||
|
text:
|
||||||
|
'${NumberFormat('#,##0').format(double.tryParse(controller.paymentAmount.toString()) ?? 0)} ${'SYP'.tr}',
|
||||||
|
label: 'Price'.tr,
|
||||||
|
),
|
||||||
|
],
|
||||||
|
),
|
||||||
|
|
||||||
const SizedBox(height: 12),
|
// ... بقية الكود كما هو (الأزرار والمؤقت)
|
||||||
|
if (controller.carType != 'Mishwar Vip')
|
||||||
// -- زر إنهاء الرحلة المنزلق --
|
const Divider(height: 20),
|
||||||
SlideAction(
|
const _builtTimerAndCarType(),
|
||||||
height: 55,
|
const SizedBox(height: 12),
|
||||||
borderRadius: 15,
|
SlideAction(
|
||||||
elevation: 4,
|
height: 55,
|
||||||
text: 'Slide to End Trip'.tr,
|
borderRadius: 15,
|
||||||
textStyle: AppStyle.title.copyWith(
|
elevation: 4,
|
||||||
fontSize: 18,
|
text: 'Slide to End Trip'.tr,
|
||||||
fontWeight: FontWeight.bold,
|
textStyle: AppStyle.title.copyWith(
|
||||||
color: Colors.white,
|
fontSize: 18,
|
||||||
|
fontWeight: FontWeight.bold,
|
||||||
|
color: Colors.white,
|
||||||
|
),
|
||||||
|
outerColor: AppColor.redColor,
|
||||||
|
innerColor: Colors.white,
|
||||||
|
sliderButtonIcon: const Icon(
|
||||||
|
Icons.arrow_forward_ios,
|
||||||
|
color: AppColor.redColor,
|
||||||
|
size: 24,
|
||||||
|
),
|
||||||
|
sliderRotate: false,
|
||||||
|
onSubmit: () {
|
||||||
|
HapticFeedback.mediumImpact();
|
||||||
|
controller.finishRideFromDriver();
|
||||||
|
return null;
|
||||||
|
},
|
||||||
),
|
),
|
||||||
outerColor: AppColor.redColor,
|
],
|
||||||
innerColor: Colors.white,
|
),
|
||||||
sliderButtonIcon: const Icon(
|
|
||||||
Icons.arrow_forward_ios,
|
|
||||||
color: AppColor.redColor,
|
|
||||||
size: 24,
|
|
||||||
),
|
|
||||||
sliderRotate: false,
|
|
||||||
onSubmit: () {
|
|
||||||
HapticFeedback.mediumImpact();
|
|
||||||
controller.finishRideFromDriver();
|
|
||||||
return null; // New: onSubmit now returns null
|
|
||||||
},
|
|
||||||
),
|
|
||||||
],
|
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
@@ -116,100 +123,119 @@ class _builtTimerAndCarType extends StatelessWidget {
|
|||||||
|
|
||||||
@override
|
@override
|
||||||
Widget build(BuildContext context) {
|
Widget build(BuildContext context) {
|
||||||
final controller = Get.find<MapDriverController>();
|
// نستخدم GetBuilder هنا لضمان تحديث العداد في كل ثانية
|
||||||
return Row(
|
return GetBuilder<MapDriverController>(builder: (controller) {
|
||||||
mainAxisAlignment: MainAxisAlignment.center,
|
return Row(
|
||||||
children: [
|
mainAxisAlignment: MainAxisAlignment.center,
|
||||||
// -- نوع السيارة --
|
children: [
|
||||||
Container(
|
// -- نوع السيارة --
|
||||||
decoration: AppStyle.boxDecoration1.copyWith(color: Colors.grey[200]),
|
Container(
|
||||||
padding: const EdgeInsets.symmetric(horizontal: 16, vertical: 8),
|
decoration:
|
||||||
child: Text(
|
AppStyle.boxDecoration1.copyWith(color: Colors.grey[200]),
|
||||||
controller.carType,
|
padding: const EdgeInsets.symmetric(horizontal: 16, vertical: 8),
|
||||||
style: AppStyle.title.copyWith(fontWeight: FontWeight.bold),
|
child: Text(
|
||||||
),
|
controller.carType.tr,
|
||||||
),
|
style: AppStyle.title.copyWith(fontWeight: FontWeight.bold),
|
||||||
// -- مؤقت الرحلة --
|
|
||||||
if (controller.carType != 'Comfort' &&
|
|
||||||
controller.carType != 'Mishwar Vip' &&
|
|
||||||
controller.carType != 'Lady') ...[
|
|
||||||
const SizedBox(width: 10),
|
|
||||||
Expanded(
|
|
||||||
child: Container(
|
|
||||||
height: 40,
|
|
||||||
decoration: BoxDecoration(
|
|
||||||
borderRadius: BorderRadius.circular(12),
|
|
||||||
gradient: LinearGradient(
|
|
||||||
colors: [
|
|
||||||
controller.remainingTimeTimerRideBegin < 60
|
|
||||||
? AppColor.redColor.withOpacity(0.8)
|
|
||||||
: AppColor.greenColor.withOpacity(0.8),
|
|
||||||
controller.remainingTimeTimerRideBegin < 60
|
|
||||||
? AppColor.redColor
|
|
||||||
: AppColor.greenColor,
|
|
||||||
],
|
|
||||||
),
|
|
||||||
),
|
|
||||||
child: ClipRRect(
|
|
||||||
borderRadius: BorderRadius.circular(12),
|
|
||||||
child: Stack(
|
|
||||||
alignment: Alignment.center,
|
|
||||||
children: [
|
|
||||||
LinearProgressIndicator(
|
|
||||||
backgroundColor: Colors.transparent,
|
|
||||||
valueColor: AlwaysStoppedAnimation<Color>(
|
|
||||||
Colors.white.withOpacity(0.2)),
|
|
||||||
minHeight: 40,
|
|
||||||
value: controller.progressTimerRideBegin.toDouble(),
|
|
||||||
),
|
|
||||||
Text(
|
|
||||||
controller.stringRemainingTimeRideBegin,
|
|
||||||
style: AppStyle.title.copyWith(
|
|
||||||
color: Colors.white, fontWeight: FontWeight.bold),
|
|
||||||
),
|
|
||||||
],
|
|
||||||
),
|
|
||||||
),
|
|
||||||
),
|
),
|
||||||
)
|
),
|
||||||
|
|
||||||
|
// -- مؤقت الرحلة --
|
||||||
|
if (controller.carType != 'Comfort' &&
|
||||||
|
controller.carType != 'Mishwar Vip' &&
|
||||||
|
controller.carType != 'Lady') ...[
|
||||||
|
const SizedBox(width: 10),
|
||||||
|
Expanded(
|
||||||
|
child: Container(
|
||||||
|
height: 40,
|
||||||
|
decoration: BoxDecoration(
|
||||||
|
borderRadius: BorderRadius.circular(12),
|
||||||
|
gradient: LinearGradient(
|
||||||
|
colors: [
|
||||||
|
controller.remainingTimeTimerRideBegin < 60
|
||||||
|
? AppColor.redColor.withOpacity(0.8)
|
||||||
|
: AppColor.greenColor.withOpacity(0.8),
|
||||||
|
controller.remainingTimeTimerRideBegin < 60
|
||||||
|
? AppColor.redColor
|
||||||
|
: AppColor.greenColor,
|
||||||
|
],
|
||||||
|
),
|
||||||
|
),
|
||||||
|
child: ClipRRect(
|
||||||
|
borderRadius: BorderRadius.circular(12),
|
||||||
|
child: Stack(
|
||||||
|
alignment: Alignment.center,
|
||||||
|
children: [
|
||||||
|
LinearProgressIndicator(
|
||||||
|
backgroundColor: Colors.transparent,
|
||||||
|
valueColor: AlwaysStoppedAnimation<Color>(
|
||||||
|
Colors.white.withOpacity(0.2)),
|
||||||
|
minHeight: 40,
|
||||||
|
// تأكد من أن هذه القيمة بين 0.0 و 1.0 في الكونترولر
|
||||||
|
value: controller.progressTimerRideBegin.toDouble(),
|
||||||
|
),
|
||||||
|
Text(
|
||||||
|
controller.stringRemainingTimeRideBegin,
|
||||||
|
style: AppStyle.title.copyWith(
|
||||||
|
color: Colors.white, fontWeight: FontWeight.bold),
|
||||||
|
),
|
||||||
|
],
|
||||||
|
),
|
||||||
|
),
|
||||||
|
),
|
||||||
|
)
|
||||||
|
],
|
||||||
],
|
],
|
||||||
],
|
);
|
||||||
);
|
});
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// Changed: تم تعديل مكان ومظهر دائرة السرعة
|
// Changed: تم تعديل مكان ومظهر دائرة السرعة
|
||||||
GetBuilder<MapDriverController> speedCircle() {
|
// غيرنا نوع الإرجاع إلى Widget بدلاً من GetBuilder
|
||||||
if (Get.find<MapDriverController>().speed > 100) {
|
Widget speedCircle() {
|
||||||
if (Platform.isIOS) {
|
// التحقق من السرعة يمكن أن يبقى هنا أو داخل الـ builder
|
||||||
HapticFeedback.selectionClick();
|
// لكن التنبيهات (Vibration/Dialog) يفضل أن تكون داخل الـ builder لتجنب تكرارها أثناء إعادة البناء الخارجية
|
||||||
} else {
|
|
||||||
Vibration.vibrate(duration: 1000);
|
return Positioned(
|
||||||
}
|
// New: Positioned الآن هي الوالد المباشر (يجب وضع هذه الدالة داخل Stack في الصفحة الرئيسية)
|
||||||
Get.defaultDialog(
|
bottom: 25,
|
||||||
barrierDismissible: false,
|
left: 3,
|
||||||
titleStyle: AppStyle.title,
|
child: GetBuilder<MapDriverController>(
|
||||||
title: 'Speed Over'.tr,
|
builder: (controller) {
|
||||||
middleText: 'Please slow down'.tr,
|
// التحقق من التنبيهات هنا
|
||||||
middleTextStyle: AppStyle.title,
|
if (controller.speed > 100) {
|
||||||
confirm: MyElevatedButton(
|
// نستخدم addPostFrameCallback لضمان عدم استدعاء الـ Dialog أثناء عملية البناء
|
||||||
title: 'I will slow down'.tr,
|
WidgetsBinding.instance.addPostFrameCallback((_) {
|
||||||
onPressed: () => Get.back(),
|
if (!Get.isDialogOpen!) {
|
||||||
),
|
// تجنب فتح أكثر من نافذة
|
||||||
);
|
if (Platform.isIOS) {
|
||||||
}
|
HapticFeedback.selectionClick();
|
||||||
return GetBuilder<MapDriverController>(
|
} else {
|
||||||
builder: (controller) {
|
Vibration.vibrate(duration: 1000);
|
||||||
return controller.isRideStarted
|
}
|
||||||
? Positioned(
|
Get.defaultDialog(
|
||||||
// New: تم وضع دائرة السرعة في الأسفل يمينًا
|
barrierDismissible: false,
|
||||||
bottom: 25,
|
titleStyle: AppStyle.title,
|
||||||
left: 3,
|
title: 'Speed Over'.tr,
|
||||||
child: Container(
|
middleText: 'Please slow down'.tr,
|
||||||
|
middleTextStyle: AppStyle.title,
|
||||||
|
confirm: MyElevatedButton(
|
||||||
|
title: 'I will slow down'.tr,
|
||||||
|
onPressed: () => Get.back(),
|
||||||
|
),
|
||||||
|
);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
return controller.isRideStarted
|
||||||
|
? 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(); // إذا لم تبدأ الرحلة نخفي العنصر وهو داخل الـ Positioned
|
||||||
: const SizedBox();
|
},
|
||||||
},
|
),
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -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,144 +122,164 @@ 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(
|
||||||
style: TextStyle(
|
text,
|
||||||
color: AppColor.primaryColor, fontWeight: FontWeight.bold)),
|
style: TextStyle(
|
||||||
|
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
|
||||||
style: ElevatedButton.styleFrom(
|
child: ElevatedButton(
|
||||||
backgroundColor: AppColor.yellowColor,
|
style: ElevatedButton.styleFrom(
|
||||||
foregroundColor: Colors.black,
|
backgroundColor: AppColor.yellowColor,
|
||||||
shape: RoundedRectangleBorder(
|
foregroundColor: Colors.black,
|
||||||
borderRadius: BorderRadius.circular(10)),
|
padding: EdgeInsets.zero, // Reduce padding to fit text
|
||||||
),
|
shape: RoundedRectangleBorder(
|
||||||
onPressed: () async {
|
borderRadius: BorderRadius.circular(10)),
|
||||||
controller.getRoute(
|
),
|
||||||
origin: controller.latLngPassengerLocation,
|
onPressed: () async {
|
||||||
destination: controller.latLngPassengerDestination,
|
// LOGIC FIX: Check distance FIRST
|
||||||
routeColor: Colors.blue // أو أي لون
|
double distance = await controller
|
||||||
);
|
.calculateDistanceBetweenDriverAndPassengerLocation();
|
||||||
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(
|
if (distance < 140) {
|
||||||
target: controller.tokenPassenger.toString(),
|
// Only draw route and send notif if close enough
|
||||||
title: 'Hi ,I Arrive your site'.tr,
|
controller.getRoute(
|
||||||
body: 'I Arrive at your site'.tr,
|
origin: controller.latLngPassengerLocation,
|
||||||
isTopic: false, // Important: this is a token
|
destination: controller.latLngPassengerDestination,
|
||||||
tone: 'ding',
|
routeColor: Colors.blue);
|
||||||
driverList: [], category: 'Hi ,I Arrive your site',
|
|
||||||
);
|
NotificationService.sendNotification(
|
||||||
controller.startTimerToShowDriverWaitPassengerDuration();
|
target: controller.tokenPassenger.toString(),
|
||||||
controller.isArrivedSend = false;
|
title: 'Hi ,I Arrive your site'.tr,
|
||||||
} else {
|
body: 'I Arrive at your site'.tr,
|
||||||
MyDialog().getDialog(
|
isTopic: false,
|
||||||
'You are not near the passenger location'.tr,
|
tone: 'ding',
|
||||||
'Please go to the pickup location exactly'.tr,
|
driverList: [],
|
||||||
() => Get.back());
|
category: 'Hi ,I Arrive your site',
|
||||||
}
|
);
|
||||||
},
|
controller.startTimerToShowDriverWaitPassengerDuration();
|
||||||
|
controller.isArrivedSend = false;
|
||||||
|
} else {
|
||||||
|
MyDialog().getDialog(
|
||||||
|
'You are not near'.tr, // Shortened title
|
||||||
|
'Please go to the pickup location exactly'.tr,
|
||||||
|
() => 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,
|
shape: RoundedRectangleBorder(
|
||||||
shape: RoundedRectangleBorder(
|
borderRadius: BorderRadius.circular(10)),
|
||||||
borderRadius: BorderRadius.circular(10)),
|
),
|
||||||
|
onPressed: () {
|
||||||
|
MyDialog().getDialog(
|
||||||
|
"Is the Passenger in your Car?".tr,
|
||||||
|
"Don't start trip if passenger not in your car".tr,
|
||||||
|
() async {
|
||||||
|
await controller.startRideFromDriver();
|
||||||
|
Get.back();
|
||||||
|
},
|
||||||
|
);
|
||||||
|
},
|
||||||
|
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))),
|
||||||
|
],
|
||||||
|
),
|
||||||
),
|
),
|
||||||
onPressed: () {
|
|
||||||
MyDialog().getDialog(
|
|
||||||
"Is the Passenger in your Car?".tr,
|
|
||||||
"Don't start trip if passenger not in your car".tr,
|
|
||||||
() async {
|
|
||||||
await controller.startRideFromDriver();
|
|
||||||
Get.back();
|
|
||||||
},
|
|
||||||
);
|
|
||||||
},
|
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
],
|
],
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
// Changed: مؤشر الانتظار الآن أكثر دمجًا
|
|
||||||
Widget _buildWaitingIndicator(MapDriverController controller) {
|
Widget _buildWaitingIndicator(MapDriverController controller) {
|
||||||
return ClipRRect(
|
return Column(
|
||||||
borderRadius: BorderRadius.circular(10),
|
children: [
|
||||||
child: Stack(
|
ClipRRect(
|
||||||
alignment: Alignment.center,
|
borderRadius: BorderRadius.circular(10),
|
||||||
children: [
|
child: LinearProgressIndicator(
|
||||||
LinearProgressIndicator(
|
backgroundColor: AppColor.greyColor.withOpacity(0.2),
|
||||||
backgroundColor: AppColor.greyColor.withOpacity(0.3),
|
// 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(),
|
||||||
),
|
),
|
||||||
Text(
|
),
|
||||||
controller.stringRemainingTimeWaitingPassenger,
|
const SizedBox(height: 4),
|
||||||
style: AppStyle.title.copyWith(
|
Text(
|
||||||
color: Colors.white,
|
"${'Waiting'.tr}: ${controller.stringRemainingTimeWaitingPassenger}",
|
||||||
fontWeight: FontWeight.bold,
|
style: AppStyle.title.copyWith(
|
||||||
fontSize: 13,
|
color: Colors.grey[700],
|
||||||
shadows: [
|
fontWeight: FontWeight.bold,
|
||||||
Shadow(color: Colors.black.withOpacity(0.5), blurRadius: 2)
|
fontSize: 12,
|
||||||
]),
|
|
||||||
),
|
),
|
||||||
],
|
),
|
||||||
),
|
],
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
// 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();
|
||||||
|
|||||||
@@ -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,13 +214,14 @@ 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",
|
||||||
'id': orderData!.orderId,
|
payload: {
|
||||||
'rideTimeStart': DateTime.now().toString(),
|
'id': orderData!.orderId,
|
||||||
'status': 'Apply',
|
'rideTimeStart': DateTime.now().toString(),
|
||||||
'driver_id': box.read(BoxName.driverID),
|
'status': 'Apply',
|
||||||
});
|
'driver_id': box.read(BoxName.driverID),
|
||||||
|
});
|
||||||
List<String> bodyToPassenger = [
|
List<String> bodyToPassenger = [
|
||||||
_getData(6).toString(),
|
_getData(6).toString(),
|
||||||
_getData(8).toString(),
|
_getData(8).toString(),
|
||||||
@@ -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(
|
||||||
|
|||||||
@@ -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,
|
||||||
// '${box.read(BoxName.nameDriver)} ${'is reviewing your order. They may need more information or a higher price.'.tr}',
|
// body:
|
||||||
// controller.myList[9].toString(),
|
// '${box.read(BoxName.nameDriver)} ${'is reviewing your order. They may need more information or a higher price.'.tr}',
|
||||||
// bodyToPassenger,
|
// isTopic: false, // Important: this is a token
|
||||||
// 'notification');
|
// tone: 'start',
|
||||||
NotificationService.sendNotification(
|
// driverList: bodyToPassenger,
|
||||||
target: controller.myList[9].toString(),
|
// category: 'Order Under Review',
|
||||||
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(),
|
||||||
|
|||||||
File diff suppressed because it is too large
Load Diff
@@ -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(
|
bool isAuthSupported =
|
||||||
barrierDismissible: false,
|
await LocalAuthentication().isDeviceSupported();
|
||||||
title: 'Insert Wallet phone number'.tr,
|
|
||||||
content: Form(
|
if (isAuthSupported) {
|
||||||
key: paymentController.formKey,
|
bool didAuthenticate =
|
||||||
child: MyTextForm(
|
await LocalAuthentication().authenticate(
|
||||||
controller:
|
localizedReason:
|
||||||
paymentController.walletphoneController,
|
'استخدم بصمة الإصبع أو الوجه لتأكيد الدفع',
|
||||||
label: 'Insert Wallet phone number'.tr,
|
);
|
||||||
hint: '963941234567',
|
|
||||||
type: TextInputType.phone)),
|
if (!didAuthenticate) {
|
||||||
confirm: MyElevatedButton(
|
print("❌ User did not authenticate with biometrics");
|
||||||
title: 'OK'.tr,
|
return;
|
||||||
onPressed: () async {
|
}
|
||||||
Get.back();
|
}
|
||||||
if (paymentController.formKey.currentState!
|
|
||||||
.validate()) {
|
// الانتقال مباشرة لإنشاء الفاتورة بعد النجاح بالبصمة
|
||||||
box.write(
|
Get.to(
|
||||||
BoxName.phoneWallet,
|
() => PaymentScreenSmsProvider(amount: pricePoint));
|
||||||
paymentController
|
|
||||||
.walletphoneController.text);
|
|
||||||
// await payWithSyriaTelWallet(
|
|
||||||
// context, pricePoint.toString(), 'SYP');
|
|
||||||
bool isAuthSupported =
|
|
||||||
await LocalAuthentication()
|
|
||||||
.isDeviceSupported();
|
|
||||||
if (isAuthSupported) {
|
|
||||||
bool didAuthenticate =
|
|
||||||
await LocalAuthentication()
|
|
||||||
.authenticate(
|
|
||||||
localizedReason:
|
|
||||||
'استخدم بصمة الإصبع أو الوجه لتأكيد الدفع',
|
|
||||||
);
|
|
||||||
if (!didAuthenticate) {
|
|
||||||
if (Get.isDialogOpen ?? false) Get.back();
|
|
||||||
print(
|
|
||||||
"❌ User did not authenticate with biometrics");
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
Get.to(() => PaymentScreenSmsProvider(
|
|
||||||
amount: pricePoint));
|
|
||||||
}
|
|
||||||
}));
|
|
||||||
},
|
},
|
||||||
child: Row(
|
child: Row(
|
||||||
mainAxisAlignment: MainAxisAlignment.spaceBetween,
|
mainAxisAlignment: MainAxisAlignment.spaceBetween,
|
||||||
|
|||||||
@@ -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),
|
||||||
|
|||||||
@@ -283,22 +283,14 @@ 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'],
|
link: '${AppLink.endPoint}rides/updateStausFromSpeed.php',
|
||||||
'rideTimeStart': DateTime.now().toString(),
|
payload: {
|
||||||
'status': 'Apply',
|
'id': rideInfo['id'],
|
||||||
'driver_id': box.read(BoxName.driverID),
|
'rideTimeStart': DateTime.now().toString(),
|
||||||
});
|
'status': 'Apply',
|
||||||
if (AppLink.endPoint.toString() != AppLink.seferCairoServer) {
|
'driver_id': box.read(BoxName.driverID),
|
||||||
CRUD().post(
|
});
|
||||||
link: '${AppLink.endPoint}rides/updateStausFromSpeed.php',
|
|
||||||
payload: {
|
|
||||||
'id': rideInfo['id'],
|
|
||||||
'rideTimeStart': DateTime.now().toString(),
|
|
||||||
'status': 'Apply',
|
|
||||||
'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();
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -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,
|
||||||
|
|||||||
@@ -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>
|
||||||
|
|||||||
@@ -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
14
shorebird.yaml
Normal 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
|
||||||
Reference in New Issue
Block a user