25-12-1/1

This commit is contained in:
Hamza-Ayed
2025-12-01 07:53:52 +03:00
parent 1a0bf1ee32
commit 11dfe94bbb
49 changed files with 19013 additions and 15915 deletions

1
.env
View File

@@ -5,6 +5,7 @@ accountSIDTwillo=QFx0qy456juj383n9xuy2194q629q1fj0y7XrXlBl
serverAPI=QQQQobSrrFi:QVQ87xU7zwCvmZzZdaxuS2f23Y4mz7MzyOzr8od2br6KYyeFaTVLG3K3hx5ZaUyx7eYvAYpAVdKk-286NTRi3zs9iSOnXtXRIxswg3KecBmsl3VxJ9wO-vIpwu4Pv7dkHkXniuxMSDgWXrXlBl serverAPI=QQQQobSrrFi:QVQ87xU7zwCvmZzZdaxuS2f23Y4mz7MzyOzr8od2br6KYyeFaTVLG3K3hx5ZaUyx7eYvAYpAVdKk-286NTRi3zs9iSOnXtXRIxswg3KecBmsl3VxJ9wO-vIpwu4Pv7dkHkXniuxMSDgWXrXlBl
# mapAPIKEY=AIzaSyCFsWBqvkXzk1Gb-bCGxwqTwJQKIeHjH64 # mapAPIKEY=AIzaSyCFsWBqvkXzk1Gb-bCGxwqTwJQKIeHjH64
mapAPIKEY=AIzaSyAPFR_XbRN0XZ5Iz3AYDjNYHGJG2s2QWwM mapAPIKEY=AIzaSyAPFR_XbRN0XZ5Iz3AYDjNYHGJG2s2QWwM
mapKeyOsm=maldev@route-dollars
mapAPIKEYIOS=AIzaSyDdqkLMCrqjVrn7XmadIqynyoBa7P27OeM mapAPIKEYIOS=AIzaSyDdqkLMCrqjVrn7XmadIqynyoBa7P27OeM
twilloRecoveryCode=CAU79DHPH1BjE9PUH4ETXTSXZXrXlBl twilloRecoveryCode=CAU79DHPH1BjE9PUH4ETXTSXZXrXlBl
apiKeyHere=g_WNUb5L-tripz7-F8omHpUmgIzH7ETeH9xZ8RwGG9_G8zX9A apiKeyHere=g_WNUb5L-tripz7-F8omHpUmgIzH7ETeH9xZ8RwGG9_G8zX9A

View File

@@ -45,10 +45,10 @@ android {
applicationId = "com.Intaleq.intaleq" applicationId = "com.Intaleq.intaleq"
// You can update the following values to match your application needs. // You can update the following values to match your application needs.
// For more information, see: https://flutter.dev/to/review-gradle-config. // For more information, see: https://flutter.dev/to/review-gradle-config.
minSdk = 29 minSdk = 23
targetSdk = 36 targetSdk = 36
versionCode = 25 versionCode = 45
versionName = '1.0.25' versionName = '1.0.45'
multiDexEnabled = true multiDexEnabled = true
ndk { ndk {
abiFilters "armeabi-v7a", "arm64-v8a" abiFilters "armeabi-v7a", "arm64-v8a"

View File

@@ -20,7 +20,7 @@
<uses-feature android:name="android.hardware.camera.autofocus" /> <uses-feature android:name="android.hardware.camera.autofocus" />
<application <application
android:label="Intaleq" android:label="@string/label"
android:name="${applicationName}" android:name="${applicationName}"
android:allowBackup="false" android:allowBackup="false"
android:fullBackupContent="false" android:fullBackupContent="false"

View File

@@ -1,6 +1,8 @@
<resources> <resources>
<string name="security_warning_title">تحذير أمني</string> <string name="security_warning_title">تحذير أمني</string>
<string name="security_warning_message">تم اكتشاف مشكلة أمنية أو تعديل على هذا الجهاز. لا يمكن تشغيل التطبيق على هذا الجهاز.</string> <string name="security_warning_message">تم اكتشاف مشكلة أمنية أو تعديل على هذا الجهاز. لا يمكن
تشغيل التطبيق على هذا الجهاز.</string>
<string name="exit_button">إغلاق التطبيق</string> <string name="exit_button">إغلاق التطبيق</string>
<string name="device_secure">الجهاز آمن. الاستمرار بشكل طبيعي.</string> <string name="device_secure">الجهاز آمن. الاستمرار بشكل طبيعي.</string>
<string name="label">انطلق</string>
</resources> </resources>

View File

@@ -12,5 +12,5 @@
this device. The app cannot run on this device.</string> this device. The app cannot run on this device.</string>
<string name="exit_button">Exit App</string> <string name="exit_button">Exit App</string>
<string name="device_secure">Device is secure. Proceeding normally.</string> <string name="device_secure">Device is secure. Proceeding normally.</string>
<string name="label">Intaleq</string>
</resources> </resources>

View File

@@ -33,11 +33,11 @@
<key>CFBundlePackageType</key> <key>CFBundlePackageType</key>
<string>APPL</string> <string>APPL</string>
<key>CFBundleShortVersionString</key> <key>CFBundleShortVersionString</key>
<string>12</string> <string>23</string>
<key>CFBundleSignature</key> <key>CFBundleSignature</key>
<string>????</string> <string>????</string>
<key>CFBundleVersion</key> <key>CFBundleVersion</key>
<string>1.0.12</string> <string>1.0.23</string>
<key>FirebaseAppDelegateProxyEnabled</key> <key>FirebaseAppDelegateProxyEnabled</key>
<string>NO</string> <string>NO</string>
<key>GMSApiKey</key> <key>GMSApiKey</key>

View File

@@ -27,7 +27,7 @@ class AppColor {
/// **Grey Color:** A neutral grey for secondary text, borders, dividers, /// **Grey Color:** A neutral grey for secondary text, borders, dividers,
/// and disabled states. /// and disabled states.
static const Color greyColor = Color(0xFF8E8E93); static const Color grayColor = Color(0xFF8E8E93);
/// **Red Color (Error):** A clear, attention-grabbing red for error messages and alerts. /// **Red Color (Error):** A clear, attention-grabbing red for error messages and alerts.
static const Color redColor = Color(0xFFD32F2F); static const Color redColor = Color(0xFFD32F2F);

View File

@@ -0,0 +1,107 @@
// في ملف: constant/country_polygons.dart
import 'package:google_maps_flutter/google_maps_flutter.dart';
class CountryPolygons {
// ==========================================================
// 1. الأردن: تغطية الممر الحضري الرئيسي (من إربد شمالاً حتى العقبة جنوباً)
// حوالي 12 نقطة
// ==========================================================
static final List<LatLng> jordanBoundary = [
// شمال إربد (قرب الحدود)
const LatLng(32.65, 35.80),
// شمال شرق المفرق
const LatLng(32.35, 37.00),
// شرق الزرقاء / الأزرق
const LatLng(31.85, 36.80),
// جنوب شرق (نهاية الزحف السكاني)
const LatLng(31.00, 36.50),
// جنوب / معان
const LatLng(30.30, 35.75),
// العقبة
const LatLng(29.50, 35.00),
// البحر الأحمر / الحدود الغربية
const LatLng(29.50, 34.85),
// غرب وادي عربة
const LatLng(30.80, 35.25),
// منطقة البحر الميت / السلط
const LatLng(32.00, 35.50),
// العودة عبر وادي الأردن إلى الشمال
const LatLng(32.45, 35.60),
// العودة لنقطة إربد
const LatLng(32.65, 35.80),
];
// ==========================================================
// 2. سوريا: تغطية الممر الغربي والساحلي (درعا، دمشق، حمص، حماة، حلب، الساحل)
// حوالي 14 نقطة
// ==========================================================
static final List<LatLng> syriaBoundary = [
// درعا / الجنوب
const LatLng(32.65, 35.95),
// شرق السويداء (حدود المنطقة المأهولة)
const LatLng(32.85, 37.10),
// أطراف دمشق الشرقية
const LatLng(33.50, 36.65),
// تدمر (أقصى امتداد شرقي للمضلع)
const LatLng(34.50, 38.30),
// الرقة (شمال شرق)
const LatLng(35.95, 38.80),
// حلب (الشمال)
const LatLng(36.45, 37.15),
// الحدود الشمالية الغربية (إدلب / تركيا)
const LatLng(36.50, 36.50),
// اللاذقية (الساحل)
const LatLng(35.50, 35.75),
// طرطوس (الساحل)
const LatLng(34.80, 35.85),
// حمص
const LatLng(34.70, 36.70),
// حماة
const LatLng(35.10, 36.70),
// العودة إلى منطقة دمشق
const LatLng(33.40, 36.30),
// العودة إلى درعا
const LatLng(32.65, 35.95),
];
// ==========================================================
// 3. مصر: تغطية القاهرة الكبرى، الدلتا، والإسكندرية والإسماعيلية
// حوالي 10 نقاط
// ==========================================================
static final List<LatLng> egyptBoundary = [
// جنوب الفيوم (أقصى امتداد جنوبي غربي)
const LatLng(29.20, 30.60),
// جنوب القاهرة (العياط)
const LatLng(29.80, 31.30),
// شرق السويس
const LatLng(29.95, 32.70),
// الإسماعيلية / القناة
const LatLng(30.60, 32.25),
// بورسعيد / أطراف الدلتا الشمالية الشرقية
const LatLng(31.30, 31.80),
// دمياط / ساحل الدلتا
const LatLng(31.50, 31.25),
// الإسكندرية (أقصى الشمال الغربي)
const LatLng(31.20, 29.80),
// غرب الدلتا
const LatLng(30.50, 30.20),
// العودة لنقطة البداية
const LatLng(29.20, 30.60),
];
// دالة تُرجع رابط API بناءً على اسم الدولة
static String getRoutingApiUrl(String countryName) {
switch (countryName) {
case 'Jordan':
return 'https://routec.intaleq.xyz/route-jo';
case 'Syria':
return 'https://routec.intaleq.xyz/route';
case 'Egypt':
return 'https://routec.intaleq.xyz/route-eg';
default:
// الافتراضي في حالة لم يقع الموقع ضمن أي من المضلعات
return 'https://routec.intaleq.xyz/route';
}
}
}

View File

@@ -1,33 +1,18 @@
import 'package:Intaleq/constant/box_name.dart'; import 'package:Intaleq/constant/box_name.dart';
import 'package:Intaleq/env/env.dart';
import 'package:Intaleq/main.dart'; import 'package:Intaleq/main.dart';
class AppLink { class AppLink {
// static final String seferPaymentServer0 = Env.seferPaymentServer;
// static final String seferPaymentServer = '${Env.seferPaymentServer}/ride';
// static final String seferAlexandriaServer = Env.seferAlexandriaServer;
// static final String seferCairoServer = Env.seferCairoServer;
static String serverPHP = box.read('serverPHP'); static String serverPHP = box.read('serverPHP');
// static String seferCairoServer =
// box.read(BoxName.locationName)[0]['server_link'];
// static String seferGizaServer = box.read(BoxName.serverChosen);
// static String seferAlexandriaServer = box.read(BoxName.serverChosen);
// static String seferPaymentServer = box.read('seferPaymentServer');
static String seferPaymentServer = static String paymentServer = 'https://walletintaleq.intaleq.xyz/v1/main';
// box.read(BoxName.paymentLink); static String location = 'https://api.intaleq.xyz/intaleq/ride/location';
'https://walletintaleq.intaleq.xyz/v1/main';
static String seferPaymentServer0 = box.read('seferPaymentServer'); static String seferPaymentServer0 = box.read('seferPaymentServer');
// static const String seferGizaServer = 'https://gizasefer.online/sefer';
// static final String seferGizaServer = Env.seferGizaServer;
// static final String endPoint = box.read(BoxName.serverChosen);
// static final String server = endPoint;
// static final String server = Env.serverPHP;
static final String endPoint = 'https://intaleq.xyz/intaleq'; static final String endPoint = 'https://api.intaleq.xyz/intaleq';
static final String ride = 'https://rides.intaleq.xyz/intaleq';
// box.read(BoxName.serverChosen) ?? box.read(BoxName.basicLink); // box.read(BoxName.serverChosen) ?? box.read(BoxName.basicLink);
// 'https://server.sefer.click/sefer.click/sefer'; static final String server = 'https://api.intaleq.xyz/intaleq';
static final String server = endPoint;
static String IntaleqSyriaServer = endPoint; static String IntaleqSyriaServer = endPoint;
static String IntaleqGizaServer = box.read('Giza'); static String IntaleqGizaServer = box.read('Giza');
static String IntaleqAlexandriaServer = box.read('Alexandria'); static String IntaleqAlexandriaServer = box.read('Alexandria');
@@ -44,30 +29,30 @@ class AppLink {
static String getTokens = "$server/ride/firebase/get.php"; static String getTokens = "$server/ride/firebase/get.php";
static String getTokenParent = "$server/ride/firebase/getTokenParent.php"; static String getTokenParent = "$server/ride/firebase/getTokenParent.php";
static String addTokens = "$server/ride/firebase/add.php"; static String addTokens = "$server/ride/firebase/add.php";
static String addFingerPrint = "$seferPaymentServer/ride/firebase/add.php"; static String addFingerPrint = "$paymentServer/ride/firebase/add.php";
static String addTokensDriver = "$server/ride/firebase/addDriver.php"; static String addTokensDriver = "$server/ride/firebase/addDriver.php";
static String packageInfo = "$server/auth/packageInfo.php"; static String packageInfo = "$server/auth/packageInfo.php";
//=======================Wallet=================== //=======================Wallet===================
static String wallet = '$seferPaymentServer/ride/passengerWallet'; static String wallet = '$paymentServer/ride/passengerWallet';
static String walletDriver = '$seferPaymentServer/ride/driverWallet'; static String walletDriver = '$paymentServer/ride/driverWallet';
static String getAllPassengerTransaction = static String getAllPassengerTransaction =
"$wallet/getAllPassengerTransaction.php"; "$wallet/getAllPassengerTransaction.php";
static String getWalletByPassenger = "$wallet/getWalletByPassenger.php"; static String getWalletByPassenger = "$wallet/getWalletByPassenger.php";
static String getPassengersWallet = "$wallet/get.php"; static String getPassengersWallet = "$wallet/get.php";
static String payWithPayMobWalletPasenger = static String payWithPayMobWalletPasenger =
'$seferPaymentServer/ride/payMob/wallet/payWithPayMob.php'; '$paymentServer/ride/payMob/wallet/payWithPayMob.php';
static String payWithPayMobCardPassenger = static String payWithPayMobCardPassenger =
'$seferPaymentServer/ride/payMob/payWithPayMob.php'; '$paymentServer/ride/payMob/payWithPayMob.php';
static String payWithEcash = "$seferPaymentServer/ecash/payWithEcash.php"; static String payWithEcash = "$paymentServer/ecash/payWithEcash.php";
static String paymetVerifyPassenger = static String paymetVerifyPassenger =
"$seferPaymentServer/ride/payMob/paymet_verfy.php"; "$paymentServer/ride/payMob/paymet_verfy.php";
static String getPassengerWalletArchive = static String getPassengerWalletArchive =
"$wallet/getPassengerWalletArchive.php"; "$wallet/getPassengerWalletArchive.php";
static String addDrivePayment = "$seferPaymentServer/ride/payment/add.php"; static String addDrivePayment = "$paymentServer/ride/payment/add.php";
static String addSeferWallet = "$seferPaymentServer/ride/seferWallet/add.php"; static String addSeferWallet = "$paymentServer/ride/seferWallet/add.php";
static String addPassengersWallet = "$wallet/add.php"; static String addPassengersWallet = "$wallet/add.php";
static String deletePassengersWallet = "$wallet/delete.php"; static String deletePassengersWallet = "$wallet/delete.php";
static String updatePassengersWallet = "$wallet/update.php"; static String updatePassengersWallet = "$wallet/update.php";
@@ -92,80 +77,79 @@ class AppLink {
static String getPhones = "$server/ride/egyptPhones/get.php"; static String getPhones = "$server/ride/egyptPhones/get.php";
////=======================cancelRide=================== ////=======================cancelRide===================
static String ride = '$server/ride'; // static String ride = '$server/ride';
static String addCancelRideFromPassenger = "$server/ride/cancelRide/add.php"; static String addCancelRideFromPassenger = "$ride/cancelRide/add.php";
static String cancelRide = "$server/ride/cancelRide/get.php"; static String cancelRide = "$ride/cancelRide/get.php";
//-----------------ridessss------------------ //-----------------ridessss------------------
static String addRides = "$ride/rides/add.php"; static String addRides = "$ride/ride/rides/add.php";
static String getRides = "$endPoint/ride/rides/get.php"; static String getRides = "$ride/ride/rides/get.php";
static String getRideOrderID = "$endPoint/ride/rides/getRideOrderID.php"; static String getRideOrderID = "$ride/ride/rides/getRideOrderID.php";
static String getRideStatus = "$endPoint/ride/rides/getRideStatus.php"; static String getRideStatus = "$ride/ride/rides/getRideStatus.php";
static String getRideStatusBegin = static String getRideStatusBegin = "$ride/ride/rides/getRideStatusBegin.php";
"$endPoint/ride/rides/getRideStatusBegin.php";
static String getRideStatusFromStartApp = static String getRideStatusFromStartApp =
"$ride/rides/getRideStatusFromStartApp.php"; "$server/ride/rides/getRideStatusFromStartApp.php";
static String updateRides = "$server/ride/rides/update.php"; static String updateRides = "$ride/ride/rides/update.php";
static String updateStausFromSpeed = static String updateStausFromSpeed =
"$server/ride/rides/updateStausFromSpeed.php"; "$ride/ride/rides/updateStausFromSpeed.php";
static String deleteRides = "$server/ride/rides/delete.php"; static String deleteRides = "$ride/ride/rides/delete.php";
//-----------------DriverPayment------------------ //-----------------DriverPayment------------------
static String adddriverScam = "$ride/driver_scam/add.php"; static String adddriverScam = "$server/driver_scam/add.php";
static String getdriverScam = "$ride/driver_scam/get.php"; static String getdriverScam = "$server/ride/driver_scam/get.php";
/////////---getKazanPercent===//////////// /////////---getKazanPercent===////////////
static String getKazanPercent = "$ride/kazan/get.php"; static String getKazanPercent = "$server/ride/kazan/get.php";
static String addKazanPercent = "$ride/kazan/add.php"; static String addKazanPercent = "$server/ride/kazan/add.php";
////-----------------DriverPayment------------------ ////-----------------DriverPayment------------------
static String addDriverpayment = "$seferPaymentServer/ride/payment/add.php"; static String addDriverpayment = "$paymentServer/ride/payment/add.php";
static String addDriverPaymentPoints = static String addDriverPaymentPoints =
"$seferPaymentServer/ride/driverPayment/add.php"; "$paymentServer/ride/driverPayment/add.php";
static String addPaymentTokenPassenger = static String addPaymentTokenPassenger =
"$seferPaymentServer/ride/passengerWallet/addPaymentTokenPassenger.php"; "$paymentServer/ride/passengerWallet/addPaymentTokenPassenger.php";
static String addPaymentTokenDriver = static String addPaymentTokenDriver =
"$seferPaymentServer/ride/driverWallet/addPaymentToken.php"; "$paymentServer/ride/driverWallet/addPaymentToken.php";
static String getDriverPaymentPoints = static String getDriverPaymentPoints =
"$seferPaymentServer/ride/driverWallet/get.php"; "$paymentServer/ride/driverWallet/get.php";
static String payWithEcashPassenger = static String payWithEcashPassenger =
"$seferPaymentServer/ride/ecash/passenger/payWithEcash.php"; "$paymentServer/ride/ecash/passenger/payWithEcash.php";
static String payWithMTNConfirm = static String payWithMTNConfirm =
"$seferPaymentServer/ride/mtn/passenger/mtn_confirm.php"; "$paymentServer/ride/mtn/passenger/mtn_confirm.php";
static String payWithMTNStart = static String payWithMTNStart =
"$seferPaymentServer/ride/mtn/passenger/mtn_start.php"; "$paymentServer/ride/mtn/passenger/mtn_start.php";
static String payWithSyriatelConfirm = static String payWithSyriatelConfirm =
"$seferPaymentServer/ride/syriatel/passenger/confirm_payment.php"; "$paymentServer/ride/syriatel/passenger/confirm_payment.php";
static String payWithSyriatelStart = static String payWithSyriatelStart =
"$seferPaymentServer/ride/syriatel/passenger/start_payment.php"; "$paymentServer/ride/syriatel/passenger/start_payment.php";
static String getDriverpaymentToday = static String getDriverpaymentToday = "$paymentServer/ride/payment/get.php";
"$seferPaymentServer/ride/payment/get.php"; static String getCountRide = "$paymentServer/ride/payment/getCountRide.php";
static String getCountRide =
"$seferPaymentServer/ride/payment/getCountRide.php";
static String getAllPaymentFromRide = static String getAllPaymentFromRide =
"$seferPaymentServer/ride/payment/getAllPayment.php"; "$paymentServer/ride/payment/getAllPayment.php";
static String getAllPaymentVisa = static String getAllPaymentVisa =
"$seferPaymentServer/ride/payment/getAllPaymentVisa.php"; "$paymentServer/ride/payment/getAllPaymentVisa.php";
//-----------------Passenger NotificationCaptain------------------ //-----------------Passenger NotificationCaptain------------------
static String addNotificationPassenger = static String addNotificationPassenger =
"$ride/notificationPassenger/add.php"; "$server/ride/notificationPassenger/add.php";
static String getNotificationPassenger = static String getNotificationPassenger =
"$ride/notificationPassenger/get.php"; "$server/ride/notificationPassenger/get.php";
static String updateNotificationPassenger = static String updateNotificationPassenger =
"$ride/notificationPassenger/update.php"; "$server/ride/notificationPassenger/update.php";
//-----------------Driver NotificationCaptain------------------ //-----------------Driver NotificationCaptain------------------
static String addNotificationCaptain = "$ride/notificationCaptain/add.php"; static String addNotificationCaptain =
"$server/ride/notificationCaptain/add.php";
static String addWaitingRide = static String addWaitingRide =
"$server/ride/notificationCaptain/addWaitingRide.php"; "$server/ride/notificationCaptain/addWaitingRide.php";
static String updateWaitingTrip = static String updateWaitingTrip =
"$server/ride/notificationCaptain/updateWaitingTrip.php"; "$server/ride/notificationCaptain/updateWaitingTrip.php";
static String getRideWaiting = static String getRideWaiting =
"$endPoint/ride/notificationCaptain/getRideWaiting.php"; "$endPoint/ride/notificationCaptain/getRideWaiting.php";
static String getNotificationCaptain = "$ride/notificationCaptain/get.php"; static String getNotificationCaptain =
"$server/ride/notificationCaptain/get.php";
static String updateNotificationCaptain = static String updateNotificationCaptain =
"$ride/notificationCaptain/update.php"; "$server/ride/notificationCaptain/update.php";
static String deleteNotificationCaptain = static String deleteNotificationCaptain =
"$ride/notificationCaptain/delete.php"; "$server/ride/notificationCaptain/delete.php";
//-----------------invitor------------------ //-----------------invitor------------------
static String addInviteDriver = "$server/ride/invitor/add.php"; static String addInviteDriver = "$server/ride/invitor/add.php";
@@ -178,70 +162,70 @@ class AppLink {
static String updatePassengerGift = static String updatePassengerGift =
"$server/ride/invitor/updatePassengerGift.php"; "$server/ride/invitor/updatePassengerGift.php";
//-----------------Api Key------------------ //-----------------Api Key------------------
static String addApiKey = "$ride/apiKey/add.php"; static String addApiKey = "$server/ride/apiKey/add.php";
static String getApiKey = "$ride/apiKey/get.php"; static String getApiKey = "$server/ride/apiKey/get.php";
static String getCnMap = "$server/auth/cnMap.php"; static String getCnMap = "$server/auth/cnMap.php";
static String updateApiKey = "$ride/apiKey/update.php"; static String updateApiKey = "$server/ride/apiKey/update.php";
static String deleteApiKey = "$ride/apiKey/delete.php"; static String deleteApiKey = "$server/ride/apiKey/delete.php";
static String getPlacesSyria = "$ride/places_syria/get.php"; static String getPlacesSyria = "$server/ride/places_syria/get.php";
//-----------------Feed Back------------------ //-----------------Feed Back------------------
static String addFeedBack = "$ride/feedBack/add.php"; static String addFeedBack = "$server/ride/feedBack/add.php";
static String add_solve_all = "$ride/feedBack/add_solve_all.php"; static String add_solve_all = "$server/ride/feedBack/add_solve_all.php";
static String uploadAudio = "$ride/feedBack/upload_audio.php"; static String uploadAudio = "$server/ride/feedBack/upload_audio.php";
static String getFeedBack = "$ride/feedBack/get.php"; static String getFeedBack = "$server/ride/feedBack/get.php";
static String updateFeedBack = "$ride/feedBack/updateFeedBack.php"; static String updateFeedBack = "$server/ride/feedBack/updateFeedBack.php";
//-----------------Tips------------------ //-----------------Tips------------------
static String addTips = "$ride/tips/add.php"; static String addTips = "$server/ride/tips/add.php";
static String getTips = "$ride/tips/get.php"; static String getTips = "$server/ride/tips/get.php";
static String updateTips = "$ride/tips/update.php"; static String updateTips = "$server/ride/tips/update.php";
//-----------------Help Center------------------ //-----------------Help Center------------------
static String addhelpCenter = "$ride/helpCenter/add.php"; static String addhelpCenter = "$server/ride/helpCenter/add.php";
static String gethelpCenter = "$ride/helpCenter/get.php"; static String gethelpCenter = "$server/ride/helpCenter/get.php";
static String getByIdhelpCenter = "$ride/helpCenter/getById.php"; static String getByIdhelpCenter = "$server/ride/helpCenter/getById.php";
static String updatehelpCenter = "$ride/helpCenter/update.php"; static String updatehelpCenter = "$server/ride/helpCenter/update.php";
static String deletehelpCenter = "$ride/helpCenter/delete.php"; static String deletehelpCenter = "$server/ride/helpCenter/delete.php";
//-----------------license------------------ //-----------------license------------------
static String addLicense = "$ride/license/add.php"; static String addLicense = "$server/ride/license/add.php";
static String getLicense = "$ride/license/get.php"; static String getLicense = "$server/ride/license/get.php";
static String updateLicense = "$ride/license/updateFeedBack.php"; static String updateLicense = "$server/ride/license/updateFeedBack.php";
//-----------------RegisrationCar------------------ //-----------------RegisrationCar------------------
static String addRegisrationCar = "$ride/RegisrationCar/add.php"; static String addRegisrationCar = "$server/ride/RegisrationCar/add.php";
static String getRegisrationCar = static String getRegisrationCar =
"${box.read(BoxName.serverChosen)}/ride/RegisrationCar/get.php"; "${box.read(BoxName.serverChosen)}/server/ride/RegisrationCar/get.php";
static String selectDriverAndCarForMishwariTrip = static String selectDriverAndCarForMishwariTrip =
"$ride/RegisrationCar/selectDriverAndCarForMishwariTrip.php"; "$server/ride/RegisrationCar/selectDriverAndCarForMishwariTrip.php";
static String updateRegisrationCar = "$ride/RegisrationCar/update.php"; static String updateRegisrationCar = "$server/ride/RegisrationCar/update.php";
//-----------------mishwari------------------ //-----------------mishwari------------------
static String addMishwari = "$ride/mishwari/add.php"; static String addMishwari = "$server/ride/mishwari/add.php";
static String cancelMishwari = "$ride/mishwari/cancel.php"; static String cancelMishwari = "$server/ride/mishwari/cancel.php";
static String getMishwari = "$ride/mishwari/get.php"; static String getMishwari = "$server/ride/mishwari/get.php";
//-----------------DriverOrder------------------ //-----------------DriverOrder------------------
static String addDriverOrder = "$ride/driver_order/add.php"; static String addDriverOrder = "$server/ride/driver_order/add.php";
static String getDriverOrder = "$ride/driver_order/get.php"; static String getDriverOrder = "$server/ride/driver_order/get.php";
static String getOrderCancelStatus = static String getOrderCancelStatus =
"$ride/driver_order/getOrderCancelStatus.php"; "$server/ride/driver_order/getOrderCancelStatus.php";
static String updateDriverOrder = "$ride/driver_order/update.php"; static String updateDriverOrder = "$server/ride/driver_order/update.php";
static String deleteDriverOrder = "$ride/driver_order/delete.php"; static String deleteDriverOrder = "$server/ride/driver_order/delete.php";
// ===================================== // =====================================
static String addRateToPassenger = "$ride/rate/add.php"; static String addRateToPassenger = "$server/ride/rate/add.php";
static String savePlacesServer = "$ride/places/add.php"; static String savePlacesServer = "$server/ride/places/add.php";
static String getapiKey = "$ride/apiKey/get.php"; static String getapiKey = "$server/ride/apiKey/get.php";
static String addRateToDriver = "$ride/rate/addRateToDriver.php"; static String addRateToDriver = "$server/ride/rate/addRateToDriver.php";
static String getDriverRate = "$ride/rate/getDriverRate.php"; static String getDriverRate = "$server/ride/rate/getDriverRate.php";
static String getPassengerRate = "$ride/rate/getPassengerRate.php"; static String getPassengerRate = "$server/ride/rate/getPassengerRate.php";
////////////////emails ============// ////////////////emails ============//
static String sendEmailToPassengerForTripDetails = static String sendEmailToPassengerForTripDetails =
"$ride/rides/emailToPassengerTripDetail.php"; "$server/ride/rides/emailToPassengerTripDetail.php";
// =========================================== // ===========================================
static String pathImage = "$server/upload/types/"; static String pathImage = "$server/upload/types/";
@@ -255,11 +239,11 @@ class AppLink {
static String uploadEgypt = "$server/uploadEgypt.php"; static String uploadEgypt = "$server/uploadEgypt.php";
//==================certifcate========== //==================certifcate==========
static String location = '${box.read(BoxName.serverChosen)}/ride/location'; // static String location = '${box.read(BoxName.serverChosen)}/ride/location';
static String getCarsLocationByPassenger = "$location/get.php"; static String getCarsLocationByPassenger = "$location/get.php";
static String getLocationAreaLinks = static String getLocationAreaLinks =
'$ride/location/get_location_area_links.php'; '$server/ride/location/get_location_area_links.php';
static String addpassengerLocation = "$location/addpassengerLocation.php"; static String addpassengerLocation = "$location/addpassengerLocation.php";
static String getCarsLocationByPassengerSpeed = "$location/getSpeed.php"; static String getCarsLocationByPassengerSpeed = "$location/getSpeed.php";
static String getCarsLocationByPassengerComfort = "$location/getComfort.php"; static String getCarsLocationByPassengerComfort = "$location/getComfort.php";
@@ -268,6 +252,8 @@ class AppLink {
"$location/getElectric.php"; "$location/getElectric.php";
static String getCarsLocationByPassengerPinkBike = static String getCarsLocationByPassengerPinkBike =
"$location/getPinkBike.php"; "$location/getPinkBike.php";
static String getCarsLocationByPassengerVan =
"$location/getCarsLocationByPassengerVan.php";
static String getCarsLocationByPassengerDelivery = static String getCarsLocationByPassengerDelivery =
"$location/getDelivery.php"; "$location/getDelivery.php";
static String getLocationParents = "$location/getLocationParents.php"; static String getLocationParents = "$location/getLocationParents.php";
@@ -275,12 +261,12 @@ class AppLink {
"$location/getFemalDriver.php"; "$location/getFemalDriver.php";
static String getDriverCarsLocationToPassengerAfterApplied = static String getDriverCarsLocationToPassengerAfterApplied =
"$location/getDriverCarsLocationToPassengerAfterApplied.php"; "$location/getDriverCarsLocationToPassengerAfterApplied.php";
static String addCarsLocationByPassenger = "$location/add.php"; // static String addCarsLocationByPassenger = "$location/add.php";
static String deleteCarsLocationByPassenger = "$location/delete.php"; // static String deleteCarsLocationByPassenger = "$location/delete.php";
static String updateCarsLocationByPassenger = "$location/update.php"; // static String updateCarsLocationByPassenger = "$location/update.php";
static String getTotalDriverDuration = "$location/getTotalDriverDuration.php"; // static String getTotalDriverDuration = "$location/getTotalDriverDuration.php";
static String getTotalDriverDurationToday = // static String getTotalDriverDurationToday =
"$location/getTotalDriverDurationToday.php"; // "$location/getTotalDriverDurationToday.php";
//==================Blog============= //==================Blog=============
static String profile = '$server/ride/profile'; static String profile = '$server/ride/profile';
@@ -295,7 +281,7 @@ class AppLink {
static String auth = '$server/auth'; static String auth = '$server/auth';
static String login = "$auth/login.php"; static String login = "$auth/login.php";
static String loginJwtRider = "$server/login.php"; static String loginJwtRider = "$server/login.php";
static String loginJwtWalletRider = "$seferPaymentServer/loginWallet.php"; static String loginJwtWalletRider = "$server/loginWallet.php";
static String loginFirstTime = "$server/loginFirstTime.php"; static String loginFirstTime = "$server/loginFirstTime.php";
static String getTesterApp = "$auth/Tester/getTesterApp.php"; static String getTesterApp = "$auth/Tester/getTesterApp.php";
static String updateTesterApp = "$auth/Tester/updateTesterApp.php"; static String updateTesterApp = "$auth/Tester/updateTesterApp.php";

View File

@@ -87,34 +87,70 @@ class LoginController extends GetxController {
update(); update();
} }
getJwtWallet() async { Future<String> getJwtWallet() async {
final random = Random(); try {
final random = Random();
if (random.nextBool()) { // Perform security check randomly
await SecurityHelper.performSecurityChecks(); if (random.nextBool()) {
} else { await SecurityHelper.performSecurityChecks();
await SecurityChecks.isDeviceRootedFromNative(Get.context!); } else {
await SecurityChecks.isDeviceRootedFromNative(Get.context!);
}
String fingerPrint = await DeviceHelper.getDeviceFingerprint();
final dev = GetPlatform.isAndroid ? 'android' : 'ios';
var payload = {
'id': box.read(BoxName.passengerID),
'password': AK.passnpassenger,
'aud': '${AK.allowed}$dev',
'fingerPrint': fingerPrint,
};
var response = await http.post(
Uri.parse(AppLink.loginJwtWalletRider),
body: payload,
);
// Handle bad responses
if (response.statusCode != 200) {
_showJwtErrorDialog(
"حدث خطأ أثناء الاتصال بالخادم. يرجى المحاولة مرة أخرى.");
throw Exception("JWT request failed");
}
var data = jsonDecode(response.body);
// Validate JWT response structure
if (!data.containsKey('jwt') || !data.containsKey('hmac')) {
_showJwtErrorDialog("تعذّر التحقق من الأمان. يرجى إعادة المحاولة.");
throw Exception("Invalid JWT response format");
}
// Save HMAC locally
await box.write(BoxName.hmac, data['hmac']);
return data['jwt'].toString();
} catch (e) {
_showJwtErrorDialog("حدث خلل غير متوقع. يرجى المحاولة مرة أخرى.");
rethrow;
} }
}
String fingerPrint = await DeviceHelper.getDeviceFingerprint(); void _showJwtErrorDialog(String message) {
// print('fingerPrint: ${fingerPrint}'); if (Get.context == null) return;
dev = Platform.isAndroid ? 'android' : 'ios';
var payload = { Get.defaultDialog(
'id': box.read(BoxName.passengerID), title: "خطأ في الاتصال",
'password': AK.passnpassenger, middleText: message,
'aud': '${AK.allowed}$dev', textConfirm: "إعادة المحاولة",
'fingerPrint': fingerPrint confirmTextColor: Colors.white,
}; onConfirm: () {
var response1 = await http.post( Get.back();
Uri.parse(AppLink.loginJwtWalletRider), getJwtWallet();
body: payload, },
); );
await box.write(BoxName.hmac, jsonDecode(response1.body)['hmac']);
// Log.print('jsonDecoeded[hmac]: ${jsonDecoeded['hmac']}');
// Log.print('req: ${response1.request}');
// Log.print('response: ${response1.body}');
// Log.print('payload: ${payload}');
return jsonDecode(response1.body)['jwt'].toString();
} }
getJWT() async { getJWT() async {

View File

@@ -19,21 +19,78 @@ class PhoneAuthHelper {
static final String _verifyOtpUrl = '${_baseUrl}verifyOtp.php'; static final String _verifyOtpUrl = '${_baseUrl}verifyOtp.php';
static final String _registerUrl = '${_baseUrl}register_passenger.php'; static final String _registerUrl = '${_baseUrl}register_passenger.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');
}
// 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 {
// Log.print('_sendOtpUrl: ${_sendOtpUrl}'); // إصلاح الرقم قبل الإرسال
// Log.print('phoneNumber: ${phoneNumber}'); final fixedPhone = formatSyrianPhone(phoneNumber);
final response = await CRUD().post( final response = await CRUD().post(
link: _sendOtpUrl, link: _sendOtpUrl,
payload: {'receiver': phoneNumber}, payload: {'receiver': fixedPhone}, // ← ← استخدام الرقم المُعدّل
); );
// Log.print('response: ${response}');
if (response != 'failure') { if (response != 'failure') {
final data = (response); final data = response;
if (data['status'] == 'success') { if (data['status'] == 'success') {
mySnackbarSuccess('An OTP has been sent to your WhatsApp number.'.tr); mySnackbarSuccess('An OTP has been sent to your number.'.tr);
return true; return true;
} else { } else {
mySnackeBarError(data['message'] ?? 'Failed to send OTP.'); mySnackeBarError(data['message'] ?? 'Failed to send OTP.');
@@ -44,19 +101,20 @@ class PhoneAuthHelper {
return false; return false;
} }
} catch (e) { } catch (e) {
// Log.print('e: ${e}');
// mySnackeBarError('An error occurred: $e');
return false; return false;
} }
} }
/// Verifies the OTP and logs the user in. /// Verifies the OTP and logs the user in.
static Future<void> verifyOtp(String phoneNumber, String otp) async { static Future<void> verifyOtp(String phoneNumber) async {
try { try {
final fixedPhone = formatSyrianPhone(phoneNumber);
final response = await CRUD().post( final response = await CRUD().post(
link: _verifyOtpUrl, link: _verifyOtpUrl,
payload: {'phone_number': phoneNumber, 'otp': otp}, payload: {
'phone_number': fixedPhone,
},
); );
if (response != 'failure') { if (response != 'failure') {
@@ -96,13 +154,12 @@ class PhoneAuthHelper {
'passengerID': box.read(BoxName.passengerID).toString(), 'passengerID': box.read(BoxName.passengerID).toString(),
"fingerPrint": fingerPrint "fingerPrint": fingerPrint
}); });
await CRUD().post( await CRUD()
link: "${AppLink.seferPaymentServer}/ride/firebase/add.php", .post(link: "${AppLink.paymentServer}/ride/firebase/add.php", payload: {
payload: { 'token': (box.read(BoxName.tokenFCM.toString())),
'token': (box.read(BoxName.tokenFCM.toString())), 'passengerID': box.read(BoxName.passengerID).toString(),
'passengerID': box.read(BoxName.passengerID).toString(), "fingerPrint": fingerPrint
"fingerPrint": fingerPrint });
});
} }
static Future<void> registerUser({ static Future<void> registerUser({

View File

@@ -222,19 +222,19 @@ class RegisterController extends GetxController {
if (res1 != 'failure') { if (res1 != 'failure') {
//Multi-server signup (moved inside the successful registration check) //Multi-server signup (moved inside the successful registration check)
if (AppLink.IntaleqAlexandriaServer != AppLink.IntaleqSyriaServer) { // if (AppLink.IntaleqAlexandriaServer != AppLink.IntaleqSyriaServer) {
List<Future> signUp = [ // List<Future> signUp = [
CRUD().post( // CRUD().post(
link: '${AppLink.IntaleqAlexandriaServer}/auth/signup.php', // link: '${AppLink.IntaleqAlexandriaServer}/auth/signup.php',
payload: payload, // payload: payload,
), // ),
CRUD().post( // CRUD().post(
link: '${AppLink.IntaleqGizaServer}/auth/signup.php', // link: '${AppLink.IntaleqGizaServer}/auth/signup.php',
payload: payload, // payload: payload,
) // )
]; // ];
await Future.wait(signUp); // Wait for both sign-ups to complete. // await Future.wait(signUp); // Wait for both sign-ups to complete.
} // }
box.write(BoxName.isVerified, '1'); box.write(BoxName.isVerified, '1');
box.write( box.write(
@@ -297,19 +297,19 @@ class RegisterController extends GetxController {
); );
if (res1 != 'failure') { if (res1 != 'failure') {
if (AppLink.IntaleqAlexandriaServer != AppLink.IntaleqSyriaServer) { // if (AppLink.IntaleqAlexandriaServer != AppLink.IntaleqSyriaServer) {
List<Future> signUp = [ // List<Future> signUp = [
CRUD().post( // CRUD().post(
link: '${AppLink.IntaleqAlexandriaServer}/auth/signup.php', // link: '${AppLink.IntaleqAlexandriaServer}/auth/signup.php',
payload: payload, // payload: payload,
), // ),
CRUD().post( // CRUD().post(
link: '${AppLink.IntaleqGizaServer}/auth/signup.php', // link: '${AppLink.IntaleqGizaServer}/auth/signup.php',
payload: payload, // payload: payload,
) // )
]; // ];
await Future.wait(signUp); // await Future.wait(signUp);
} // }
box.write(BoxName.isVerified, '1'); box.write(BoxName.isVerified, '1');
box.write(BoxName.isFirstTime, '0'); box.write(BoxName.isFirstTime, '0');

View File

@@ -91,18 +91,8 @@ class OtpVerificationController extends GetxController {
); );
if (response != 'failure' && response['status'] == 'success') { if (response != 'failure' && response['status'] == 'success') {
final fcm = Get.isRegistered<FirebaseMessagesController>()
? Get.find<FirebaseMessagesController>()
: Get.put(FirebaseMessagesController());
// await fcm.sendNotificationToDriverMAP(
// 'token change',
// 'change device'.tr,
// ptoken.toString(),
// [],
// 'cancel',
// );
await NotificationService.sendNotification( await NotificationService.sendNotification(
category: 'token change',
target: ptoken.toString(), target: ptoken.toString(),
title: 'token change'.tr, title: 'token change'.tr,
body: 'change device'.tr, body: 'change device'.tr,
@@ -110,21 +100,7 @@ class OtpVerificationController extends GetxController {
tone: 'cancel', tone: 'cancel',
driverList: [], driverList: [],
); );
await CRUD().post(
link: "${AppLink.seferPaymentServer}/ride/firebase/add.php",
payload: {
'token': (box.read(BoxName.tokenFCM.toString())),
'passengerID': box.read(BoxName.passengerID).toString(),
"fingerPrint": fingerPrint.toString(),
});
// CRUD().post(
// link:
// '${AppLink.seferPaymentServer}/auth/token/update_passenger_token.php',
// payload: {
// 'token': box.read(BoxName.tokenFCM).toString(),
// 'fingerPrint': fingerPrint.toString(),
// 'passengerID': box.read(BoxName.passengerID).toString(),
// });
Get.offAll(() => const MapPagePassenger()); Get.offAll(() => const MapPagePassenger());
} else { } else {
Get.snackbar('Verification Failed', 'OTP is incorrect or expired'); Get.snackbar('Verification Failed', 'OTP is incorrect or expired');

View File

@@ -1,20 +1,15 @@
import 'dart:convert'; import 'dart:convert';
import 'dart:io'; import 'dart:io';
import 'package:Intaleq/controller/functions/encrypt_decrypt.dart'; import 'package:Intaleq/views/home/HomePage/trip_monitor/trip_link_monitor.dart';
import 'package:firebase_messaging/firebase_messaging.dart'; import 'package:firebase_messaging/firebase_messaging.dart';
import 'package:flutter/material.dart'; import 'package:flutter/material.dart';
import 'package:get/get.dart'; import 'package:get/get.dart';
import 'package:http/http.dart' as http;
import 'package:Intaleq/controller/functions/toast.dart'; import 'package:Intaleq/controller/functions/toast.dart';
import 'package:Intaleq/views/widgets/elevated_btn.dart'; import 'package:Intaleq/views/widgets/elevated_btn.dart';
import '../../constant/api_key.dart';
import '../../constant/box_name.dart'; import '../../constant/box_name.dart';
import '../../constant/colors.dart'; import '../../constant/colors.dart';
import '../../constant/links.dart';
import '../../constant/style.dart'; import '../../constant/style.dart';
import '../../constant/table_names.dart';
import '../../env/env.dart';
import '../../main.dart'; import '../../main.dart';
import '../../print.dart'; import '../../print.dart';
import '../../views/Rate/rate_captain.dart'; import '../../views/Rate/rate_captain.dart';
@@ -23,9 +18,7 @@ import '../../views/home/profile/promos_passenger_page.dart';
import '../auth/google_sign.dart'; import '../auth/google_sign.dart';
import '../functions/audio_record1.dart'; import '../functions/audio_record1.dart';
import '../home/map_passenger_controller.dart'; import '../home/map_passenger_controller.dart';
import 'access_token.dart';
import 'local_notification.dart'; import 'local_notification.dart';
import 'notification_service.dart';
class FirebaseMessagesController extends GetxController { class FirebaseMessagesController extends GetxController {
final fcmToken = FirebaseMessaging.instance; final fcmToken = FirebaseMessaging.instance;
@@ -76,9 +69,13 @@ class FirebaseMessagesController extends GetxController {
Future getToken() async { Future getToken() async {
fcmToken.getToken().then((token) { fcmToken.getToken().then((token) {
// Log.print('fcmToken: ${token}'); Log.print('fcmToken: ${token}');
box.write(BoxName.tokenFCM, (token.toString())); box.write(BoxName.tokenFCM, (token.toString()));
}); });
// 🔹 الاشتراك في topic
await fcmToken
.subscribeToTopic("passengers"); // أو "users" حسب نوع المستخدم
print("Subscribed to 'passengers' topic ✅");
FirebaseMessaging.onMessage.listen((RemoteMessage message) { FirebaseMessaging.onMessage.listen((RemoteMessage message) {
// If the app is in the background or terminated, show a system tray message // If the app is in the background or terminated, show a system tray message
@@ -104,115 +101,106 @@ class FirebaseMessagesController extends GetxController {
} }
Future<void> fireBaseTitles(RemoteMessage message) async { Future<void> fireBaseTitles(RemoteMessage message) async {
if (message.notification!.title! == 'Order'.tr) { // [!! تعديل !!]
// اقرأ "النوع" من حمولة البيانات، وليس من العنوان
String category = message.data['category'] ?? '';
// اقرأ العنوان (للعرض)
String title = message.notification?.title ?? '';
String body = message.notification?.body ?? '';
if (category == 'ORDER') {
// <-- مثال: كان 'Order'.tr
Log.print('message: ${message}'); Log.print('message: ${message}');
if (Platform.isAndroid) { if (Platform.isAndroid) {
notificationController.showNotification( notificationController.showNotification(title, body, 'Order');
'Order'.tr, message.notification!.body!, 'Order');
} }
} else if (message.notification!.title! == 'Accepted Ride') { }
if (Platform.isAndroid) {
notificationController.showNotification( // ... داخل معالج الإشعارات في تطبيق الراكب ...
'Accepted Ride'.tr, 'Driver Accepted the Ride for You'.tr, 'ding'); else if (category == 'Accepted Ride') {
// <-- كان 'Accepted Ride'
var driverListJson = message.data['driverList'];
if (driverListJson != null) {
var myList = jsonDecode(driverListJson) as List<dynamic>;
final controller = Get.find<MapPassengerController>();
// controller.currentRideState.value = RideState.driverApplied;
await controller.processRideAcceptance(
driverIdFromFCM: myList[0].toString(),
rideIdFromFCM: myList[3].toString());
} else {
Log.print('❌ خطأ: RIDE_ACCEPTED وصل بدون driverList');
} }
var passengerList = message.data['passengerList']; } else if (category == 'Promo') {
// <-- كان 'Promo'.tr
var myList = jsonDecode(passengerList) as List<dynamic>;
Log.print('myList: ${myList}');
final controller = Get.find<MapPassengerController>();
controller.driverId = myList[0].toString();
// assume rideId lives at index 2 in your list:
controller.rideId = myList[3].toString();
controller
..statusRide = 'Apply'
..isSearchingWindow = false
..update();
await controller.rideAppliedFromDriver(true);
// driverAppliedTripSnakBar();
} else if (message.notification!.title! == 'Promo'.tr) {
if (Platform.isAndroid) { if (Platform.isAndroid) {
notificationController.showNotification( notificationController.showNotification(title, body, 'promo');
'Promo', 'Show latest promo'.tr, 'promo');
} }
Get.to(const PromosPassengerPage()); Get.to(const PromosPassengerPage());
} else if (message.notification!.title! == 'Trip Monitoring'.tr) { } else if (category == 'Trip Monitoring') {
// <-- كان 'Trip Monitoring'.tr
if (Platform.isAndroid) { if (Platform.isAndroid) {
notificationController.showNotification( notificationController.showNotification(title, body, 'iphone_ringtone');
'Trip Monitoring'.tr, '', 'iphone_ringtone');
} }
var myListString = message.data['DriverList']; var myListString = message.data['DriverList'];
var myList = jsonDecode(myListString) as List<dynamic>; var myList = jsonDecode(myListString) as List<dynamic>;
Get.toNamed('/tripmonitor', arguments: { Get.to(() => TripMonitor(), arguments: {
'rideId': myList[0].toString(), 'rideId': myList[0].toString(),
'driverId': myList[1].toString(), 'driverId': myList[1].toString(),
}); });
} else if (message.notification!.title! == 'token change'.tr) { } else if (category == 'token change') {
// <-- كان 'token change'.tr
if (Platform.isAndroid) { if (Platform.isAndroid) {
notificationController.showNotification( notificationController.showNotification(title, body, 'cancel');
'token change'.tr, 'token change'.tr, 'cancel');
} }
GoogleSignInHelper.signOut(); GoogleSignInHelper.signOut();
} else if (message.notification!.title! == } else if (category == 'Driver Is Going To Passenger') {
'Driver Is Going To Passenger'.tr) { // <-- كان 'Driver Is Going To Passenger'
Get.find<MapPassengerController>().isDriverInPassengerWay = true; Get.find<MapPassengerController>().isDriverInPassengerWay = true;
Get.find<MapPassengerController>().update(); Get.find<MapPassengerController>().update();
if (Platform.isAndroid) { if (Platform.isAndroid) {
notificationController.showNotification('Driver is Going To You'.tr, notificationController.showNotification(title, body, 'tone1');
'Please stay on the picked point.'.tr, 'tone1');
} }
// Get.snackbar('Driver is Going To Passenger', '', } else if (category == 'message From passenger') {
// backgroundColor: AppColor.greenColor); // <-- كان 'message From passenger'
} else if (message.notification!.title! == 'message From passenger') {
if (Platform.isAndroid) { if (Platform.isAndroid) {
notificationController.showNotification( notificationController.showNotification(title, body, 'ding');
'message From passenger'.tr, ''.tr, 'ding');
} }
passengerDialog(message.notification!.body!); passengerDialog(body);
update(); update();
} else if (message.notification!.title! == 'message From Driver') { } else if (category == 'message From Driver') {
// <-- كان 'message From Driver'
if (Platform.isAndroid) { if (Platform.isAndroid) {
notificationController.showNotification( notificationController.showNotification(title, body, 'ding');
'message From Driver'.tr, ''.tr, 'ding');
} }
passengerDialog(message.notification!.body!); passengerDialog(body);
update(); update();
} else if (message.notification!.title! == 'Trip is Begin') { } else if (category == 'Trip is Begin') {
// <-- كان 'Trip is Begin'
Log.print('[FCM] استقبل إشعار "TRIP_BEGUN".');
final controller = Get.find<MapPassengerController>();
controller.processRideBegin();
} else if (category == 'Hi ,I will go now') {
// <-- كان 'Hi ,I will go now'.tr
if (Platform.isAndroid) { if (Platform.isAndroid) {
notificationController.showNotification( notificationController.showNotification(title, body, 'ding');
'Trip is Begin'.tr, ''.tr, 'start');
}
Get.find<MapPassengerController>().getBeginRideFromDriver();
// Get.snackbar('RideIsBegin', '', backgroundColor: AppColor.greenColor);
box.write(BoxName.passengerWalletTotal, '0');
update();
} else if (message.notification!.title! == 'Hi ,I will go now'.tr) {
// Get.snackbar('Hi ,I will go now', '',
// backgroundColor: AppColor.greenColor);
if (Platform.isAndroid) {
notificationController.showNotification(
'Passenger come to you'.tr, 'Hi ,I will go now'.tr, 'ding');
} }
update(); update();
} else if (message.notification!.title! == 'Hi ,I Arrive your site'.tr) { } else if (category == 'Hi ,I Arrive your site') {
if (Platform.isAndroid) { // <-- كان 'Hi ,I Arrive your site'.tr
notificationController.showNotification( final controller = Get.find<MapPassengerController>();
'Hi ,I Arrive your site'.tr, ''.tr, 'ding'); // if (controller.currentRideState.value == RideState.driverApplied) {
} Log.print('[FCM] السائق وصل. تغيير الحالة إلى driverArrived');
driverArrivePassengerDialoge(); controller.currentRideState.value = RideState.driverArrived;
// }
update(); } else if (category == 'Cancel Trip from driver') {
} else if (message.notification!.title! == "Cancel Trip from driver") { // <-- كان "Cancel Trip from driver"
Get.back(); Get.back();
if (Platform.isAndroid) { if (Platform.isAndroid) {
notificationController.showNotification("Cancel Trip from driver".tr, notificationController.showNotification(title, body, 'cancel');
"We will look for a new driver.\nPlease wait.".tr, 'cancel');
} }
Get.defaultDialog( Get.defaultDialog(
title: "The driver canceled your ride.".tr, title: "The driver canceled your ride.".tr, // العنوان المترجم للعرض
middleText: "We will look for a new driver.\nPlease wait.".tr, middleText: "We will look for a new driver.\nPlease wait.".tr,
confirm: MyElevatedButton( confirm: MyElevatedButton(
kolor: AppColor.greenColor, kolor: AppColor.greenColor,
@@ -229,66 +217,42 @@ class FirebaseMessagesController extends GetxController {
onPressed: () { onPressed: () {
Get.offAll(() => const MapPagePassenger()); Get.offAll(() => const MapPagePassenger());
}, },
) ));
// Get.find<MapPassengerController>() } else if (category == 'Driver Finish Trip') {
// .searchNewDriverAfterRejectingFromDriver(); // <-- كان 'Driver Finish Trip'.tr
);
} else if (message.notification!.title! == 'Driver Finish Trip'.tr) {
// الخطوة 1: استقبل البيانات وتحقق من وجودها
final rawData = message.data['DriverList']; final rawData = message.data['DriverList'];
List<dynamic> driverList = []; // ابدأ بقائمة فارغة كإجراء وقائي List<dynamic> driverList = [];
// الخطوة 2: قم بفك تشفير البيانات بأمان
if (rawData != null && rawData is String) { if (rawData != null && rawData is String) {
try { try {
driverList = jsonDecode(rawData); driverList = jsonDecode(rawData);
Log.print('Successfully decoded DriverList: $driverList');
} catch (e) { } catch (e) {
Log.print('Error decoding DriverList JSON: $e'); Log.print('Error decoding DriverList JSON: $e');
// اترك القائمة فارغة في حالة حدوث خطأ
} }
} else { } else {
Log.print('Error: DriverList data is null or not a String.'); Log.print('Error: DriverList data is null or not a String.');
} }
// الخطوة 3: استخدم البيانات فقط إذا كانت القائمة تحتوي على العناصر المطلوبة if (driverList.length >= 3) {
// هذا يمنع خطأ "RangeError" إذا كانت القائمة أقصر من المتوقع
if (driverList.length >= 4) {
if (Platform.isAndroid) { if (Platform.isAndroid) {
notificationController.showNotification( notificationController.showNotification(
"Driver Finish Trip".tr, title,
'${'you will pay to Driver'.tr} ${driverList[3].toString()} \$', // تم تحسين طريقة عرض النص '${'you will pay to Driver'.tr} ${driverList[3].toString()} \$',
'tone1'); 'tone1');
} }
Get.find<AudioRecorderController>().stopRecording(); Get.find<AudioRecorderController>().stopRecording();
// ... (باقي كود المحفظة) ...
if ((double.tryParse(
box.read(BoxName.passengerWalletTotal).toString()) ??
0) <
0) {
box.write(BoxName.passengerWalletTotal, 0);
}
Get.find<MapPassengerController>().tripFinishedFromDriver(); Get.find<MapPassengerController>().tripFinishedFromDriver();
// ... (إشعار "لا تنسى متعلقاتك") ...
NotificationController().showNotification(
'Dont forget your personal belongings.'.tr,
'Please make sure you have all your personal belongings and that any remaining fare, if applicable, has been added to your wallet before leaving. Thank you for choosing the Intaleq app'
.tr,
'ding');
Get.to(() => RateDriverFromPassenger(), arguments: { Get.to(() => RateDriverFromPassenger(), arguments: {
'driverId': driverList[0].toString(), 'driverId': driverList[0].toString(),
'rideId': driverList[1].toString(), 'rideId': driverList[1].toString(),
'price': driverList[3].toString() 'price': driverList[3].toString()
}); });
} else { } else {
Log.print( Log.print('Error: TRIP_FINISHED decoded list error.');
'Error: Decoded driverList does not have enough elements. Received: $driverList');
// هنا يمكنك عرض رسالة خطأ للمستخدم إذا لزم الأمر
} }
} else if (message.notification!.title! == "Finish Monitor".tr) { } else if (category == 'Finish Monitor') {
// <-- كان "Finish Monitor".tr
Get.defaultDialog( Get.defaultDialog(
titleStyle: AppStyle.title, titleStyle: AppStyle.title,
title: 'Trip finished '.tr, title: 'Trip finished '.tr,
@@ -298,69 +262,7 @@ class FirebaseMessagesController extends GetxController {
onPressed: () { onPressed: () {
Get.offAll(() => const MapPagePassenger()); Get.offAll(() => const MapPagePassenger());
})); }));
} } else if (category == 'Driver Cancelled Your Trip') {
// else if (message.notification!.title! == "Trip Monitoring".tr) {
// Get.to(() => const TripMonitor());
// }
else if (message.notification!.title! == 'Call Income') {
try {
var myListString = message.data['DriverList'];
var driverList = jsonDecode(myListString) as List<dynamic>;
// if (Platform.isAndroid) {
if (Platform.isAndroid) {
notificationController.showNotification(
'Call Income'.tr,
message.notification!.body!,
'iphone_ringtone',
);
}
// }
// Assuming GetMaterialApp is initialized and context is valid for navigation
// Get.to(() => PassengerCallPage(
// channelName: driverList[1].toString(),
// token: driverList[0].toString(),
// remoteID: driverList[2].toString(),
// ));
} catch (e) {}
} else if (message.notification!.title! == 'Call Income from Driver'.tr) {
try {
var myListString = message.data['DriverList'];
var driverList = jsonDecode(myListString) as List<dynamic>;
// if (Platform.isAndroid) {
if (Platform.isAndroid) {
notificationController.showNotification(
'Call Income'.tr,
message.notification!.body!,
'iphone_ringtone',
);
}
// Assuming GetMaterialApp is initialized and context is valid for navigation
// Get.to(() => PassengerCallPage(
// channelName: driverList[1].toString(),
// token: driverList[0].toString(),
// remoteID: driverList[2].toString(),
// ));
} catch (e) {}
} else if (message.notification!.title! == 'Call End'.tr) {
try {
var myListString = message.data['DriverList'];
var driverList = jsonDecode(myListString) as List<dynamic>;
if (Platform.isAndroid) {
notificationController.showNotification(
'Call End'.tr,
message.notification!.body!,
'ding',
);
}
// Assuming GetMaterialApp is initialized and context is valid for navigation
// Get.off(const CallPage());
} catch (e) {}
} else if (message.notification!.title! == 'Driver Cancelled Your Trip') {
// Get.snackbar(
// 'You will be pay the cost to driver or we will get it from you on next trip'
// .tr,
// 'message',
// backgroundColor: AppColor.redColor);
if (Platform.isAndroid) { if (Platform.isAndroid) {
notificationController.showNotification( notificationController.showNotification(
'Driver Cancelled Your Trip'.tr, 'Driver Cancelled Your Trip'.tr,
@@ -374,18 +276,10 @@ class FirebaseMessagesController extends GetxController {
Get.find<MapPassengerController>().restCounter(); Get.find<MapPassengerController>().restCounter();
Get.offAll(() => const MapPagePassenger()); Get.offAll(() => const MapPagePassenger());
} }
// else if (message.notification!.title! == 'Order Applied') { // ... (باقي الحالات مثل Call Income, Call End, إلخ) ...
// Get.snackbar( // ... بنفس الطريقة ...
// "The order has been accepted by another driver."
// .tr, // Corrected grammar
// "Be more mindful next time to avoid dropping orders."
// .tr, // Improved sentence structure
// backgroundColor: AppColor.yellowColor,
// snackPosition: SnackPosition.BOTTOM,
// );
// }
else if (message.notification!.title! == 'Order Applied'.tr) { else if (category == 'Order Applied') {
if (Platform.isAndroid) { if (Platform.isAndroid) {
notificationController.showNotification( notificationController.showNotification(
'The order Accepted by another Driver'.tr, 'The order Accepted by another Driver'.tr,
@@ -395,6 +289,308 @@ class FirebaseMessagesController extends GetxController {
} }
} }
} }
// Future<void> fireBaseTitles(RemoteMessage message) async {
// if (message.notification!.title! == 'Order'.tr) {
// Log.print('message: ${message}');
// if (Platform.isAndroid) {
// notificationController.showNotification(
// 'Order'.tr, message.notification!.body!, 'Order');
// }
// } else // ... داخل معالج الإشعارات في تطبيق الراكب ...
// if (message.notification!.title! == 'Accepted Ride') {
// // ...
// // انظر هنا: قمنا بتغيير "passengerList" إلى "driverList"
// var driverListJson = message.data['driverList'];
// // تأكد من أن البيانات ليست null قبل المتابعة
// if (driverListJson != null) {
// var myList = jsonDecode(driverListJson) as List<dynamic>;
// Log.print('myList: ${myList}');
// final controller = Get.find<MapPassengerController>();
// // استدعاء الدالة الموحدة الجديدة التي أنشأناها
// await controller.processRideAcceptance(
// driverIdFromFCM: myList[0].toString(),
// rideIdFromFCM: myList[3].toString());
// } else {
// Log.print(
// '❌ خطأ فادح: إشعار "Accepted Ride" وصل بدون بيانات (driverList is null)');
// }
// } else if (message.notification!.title! == 'Promo'.tr) {
// if (Platform.isAndroid) {
// notificationController.showNotification(
// 'Promo', 'Show latest promo'.tr, 'promo');
// }
// Get.to(const PromosPassengerPage());
// } else if (message.notification!.title! == 'Trip Monitoring'.tr) {
// if (Platform.isAndroid) {
// notificationController.showNotification(
// 'Trip Monitoring'.tr, '', 'iphone_ringtone');
// }
// var myListString = message.data['DriverList'];
// var myList = jsonDecode(myListString) as List<dynamic>;
// Get.toNamed('/tripmonitor', arguments: {
// 'rideId': myList[0].toString(),
// 'driverId': myList[1].toString(),
// });
// } else if (message.notification!.title! == 'token change'.tr) {
// if (Platform.isAndroid) {
// notificationController.showNotification(
// 'token change'.tr, 'token change'.tr, 'cancel');
// }
// GoogleSignInHelper.signOut();
// } else if (message.notification!.title! == 'Driver Is Going To Passenger') {
// Get.find<MapPassengerController>().isDriverInPassengerWay = true;
// Get.find<MapPassengerController>().update();
// if (Platform.isAndroid) {
// notificationController.showNotification('Driver is Going To You'.tr,
// 'Please stay on the picked point.'.tr, 'tone1');
// }
// // Get.snackbar('Driver is Going To Passenger', '',
// // backgroundColor: AppColor.greenColor);
// } else if (message.notification!.title! == 'message From passenger') {
// if (Platform.isAndroid) {
// notificationController.showNotification(
// 'message From passenger'.tr, ''.tr, 'ding');
// }
// passengerDialog(message.notification!.body!);
// update();
// } else if (message.notification!.title! == 'message From Driver') {
// if (Platform.isAndroid) {
// notificationController.showNotification(
// 'message From Driver'.tr, ''.tr, 'ding');
// }
// passengerDialog(message.notification!.body!);
// update();
// } else // (هذا الكود في معالج الإشعارات لديك)
// if (message.notification!.title! == 'Trip is Begin') {
// Log.print('[FCM] استقبل إشعار "Trip is Begin".');
// // (تم حذف الإشعار المحلي من هنا، نُقل إلى الدالة الموحدة)
// final controller = Get.find<MapPassengerController>();
// // استدعاء حارس البوابة الجديد والآمن
// controller.processRideBegin();
// // (تم حذف كل الأوامر التالية من هنا)
// // Get.find<MapPassengerController>().getBeginRideFromDriver();
// // box.write(BoxName.passengerWalletTotal, '0');
// // update();
// } else if (message.notification!.title! == 'Hi ,I will go now'.tr) {
// // Get.snackbar('Hi ,I will go now', '',
// // backgroundColor: AppColor.greenColor);
// if (Platform.isAndroid) {
// notificationController.showNotification(
// 'Passenger come to you'.tr, 'Hi ,I will go now'.tr, 'ding');
// }
// update();
// } // ... داخل معالج الإشعارات (FCM Handler) ...
// if (message.notification!.title! == 'Hi ,I Arrive your site'.tr) {
// final controller = Get.find<MapPassengerController>();
// // 1. التأكد أننا في الحالة الصحيحة (السائق كان في الطريق)
// if (controller.currentRideState.value == RideState.driverApplied) {
// Log.print('[FCM] السائق وصل. تغيير الحالة إلى driverArrived');
// // 2. تغيير الحالة فقط!
// controller.currentRideState.value = RideState.driverArrived;
// }
// } else if (message.notification!.title! == "Cancel Trip from driver") {
// Get.back();
// if (Platform.isAndroid) {
// notificationController.showNotification("Cancel Trip from driver".tr,
// "We will look for a new driver.\nPlease wait.".tr, 'cancel');
// }
// Get.defaultDialog(
// title: "The driver canceled your ride.".tr,
// middleText: "We will look for a new driver.\nPlease wait.".tr,
// confirm: MyElevatedButton(
// kolor: AppColor.greenColor,
// title: 'Ok'.tr,
// onPressed: () async {
// Get.back();
// await Get.find<MapPassengerController>()
// .reSearchAfterCanceledFromDriver();
// },
// ),
// cancel: MyElevatedButton(
// title: 'Cancel'.tr,
// kolor: AppColor.redColor,
// onPressed: () {
// Get.offAll(() => const MapPagePassenger());
// },
// )
// // Get.find<MapPassengerController>()
// // .searchNewDriverAfterRejectingFromDriver();
// );
// } else if (message.notification!.title! == 'Driver Finish Trip'.tr) {
// // الخطوة 1: استقبل البيانات وتحقق من وجودها
// final rawData = message.data['DriverList'];
// List<dynamic> driverList = []; // ابدأ بقائمة فارغة كإجراء وقائي
// // الخطوة 2: قم بفك تشفير البيانات بأمان
// if (rawData != null && rawData is String) {
// try {
// driverList = jsonDecode(rawData);
// Log.print('Successfully decoded DriverList: $driverList');
// } catch (e) {
// Log.print('Error decoding DriverList JSON: $e');
// // اترك القائمة فارغة في حالة حدوث خطأ
// }
// } else {
// Log.print('Error: DriverList data is null or not a String.');
// }
// // الخطوة 3: استخدم البيانات فقط إذا كانت القائمة تحتوي على العناصر المطلوبة
// // هذا يمنع خطأ "RangeError" إذا كانت القائمة أقصر من المتوقع
// if (driverList.length >= 4) {
// if (Platform.isAndroid) {
// notificationController.showNotification(
// "Driver Finish Trip".tr,
// '${'you will pay to Driver'.tr} ${driverList[3].toString()} \$', // تم تحسين طريقة عرض النص
// 'tone1');
// }
// Get.find<AudioRecorderController>().stopRecording();
// if ((double.tryParse(
// box.read(BoxName.passengerWalletTotal).toString()) ??
// 0) <
// 0) {
// box.write(BoxName.passengerWalletTotal, 0);
// }
// Get.find<MapPassengerController>().tripFinishedFromDriver();
// NotificationController().showNotification(
// 'Dont forget your personal belongings.'.tr,
// 'Please make sure you have all your personal belongings and that any remaining fare, if applicable, has been added to your wallet before leaving. Thank you for choosing the Intaleq app'
// .tr,
// 'ding');
// Get.to(() => RateDriverFromPassenger(), arguments: {
// 'driverId': driverList[0].toString(),
// 'rideId': driverList[1].toString(),
// 'price': driverList[3].toString()
// });
// } else {
// Log.print(
// 'Error: Decoded driverList does not have enough elements. Received: $driverList');
// // هنا يمكنك عرض رسالة خطأ للمستخدم إذا لزم الأمر
// }
// } else if (message.notification!.title! == "Finish Monitor".tr) {
// Get.defaultDialog(
// titleStyle: AppStyle.title,
// title: 'Trip finished '.tr,
// middleText: '',
// confirm: MyElevatedButton(
// title: 'Ok'.tr,
// onPressed: () {
// Get.offAll(() => const MapPagePassenger());
// }));
// }
// // else if (message.notification!.title! == "Trip Monitoring".tr) {
// // Get.to(() => const TripMonitor());
// // }
// else if (message.notification!.title! == 'Call Income') {
// try {
// var myListString = message.data['DriverList'];
// var driverList = jsonDecode(myListString) as List<dynamic>;
// // if (Platform.isAndroid) {
// if (Platform.isAndroid) {
// notificationController.showNotification(
// 'Call Income'.tr,
// message.notification!.body!,
// 'iphone_ringtone',
// );
// }
// // }
// // Assuming GetMaterialApp is initialized and context is valid for navigation
// // Get.to(() => PassengerCallPage(
// // channelName: driverList[1].toString(),
// // token: driverList[0].toString(),
// // remoteID: driverList[2].toString(),
// // ));
// } catch (e) {}
// } else if (message.notification!.title! == 'Call Income from Driver'.tr) {
// try {
// var myListString = message.data['DriverList'];
// var driverList = jsonDecode(myListString) as List<dynamic>;
// // if (Platform.isAndroid) {
// if (Platform.isAndroid) {
// notificationController.showNotification(
// 'Call Income'.tr,
// message.notification!.body!,
// 'iphone_ringtone',
// );
// }
// // Assuming GetMaterialApp is initialized and context is valid for navigation
// // Get.to(() => PassengerCallPage(
// // channelName: driverList[1].toString(),
// // token: driverList[0].toString(),
// // remoteID: driverList[2].toString(),
// // ));
// } catch (e) {}
// } else if (message.notification!.title! == 'Call End'.tr) {
// try {
// var myListString = message.data['DriverList'];
// var driverList = jsonDecode(myListString) as List<dynamic>;
// if (Platform.isAndroid) {
// notificationController.showNotification(
// 'Call End'.tr,
// message.notification!.body!,
// 'ding',
// );
// }
// // Assuming GetMaterialApp is initialized and context is valid for navigation
// // Get.off(const CallPage());
// } catch (e) {}
// } else if (message.notification!.title! == 'Driver Cancelled Your Trip') {
// // Get.snackbar(
// // 'You will be pay the cost to driver or we will get it from you on next trip'
// // .tr,
// // 'message',
// // backgroundColor: AppColor.redColor);
// if (Platform.isAndroid) {
// notificationController.showNotification(
// 'Driver Cancelled Your Trip'.tr,
// 'you will pay to Driver you will be pay the cost of driver time look to your Intaleq Wallet'
// .tr,
// 'cancel');
// }
// box.write(BoxName.parentTripSelected, false);
// box.remove(BoxName.tokenParent);
// Get.find<MapPassengerController>().restCounter();
// Get.offAll(() => const MapPagePassenger());
// }
// // else if (message.notification!.title! == 'Order Applied') {
// // Get.snackbar(
// // "The order has been accepted by another driver."
// // .tr, // Corrected grammar
// // "Be more mindful next time to avoid dropping orders."
// // .tr, // Improved sentence structure
// // backgroundColor: AppColor.yellowColor,
// // snackPosition: SnackPosition.BOTTOM,
// // );
// // }
// else if (message.notification!.title! == 'Order Applied'.tr) {
// if (Platform.isAndroid) {
// notificationController.showNotification(
// 'The order Accepted by another Driver'.tr,
// 'We regret to inform you that another driver has accepted this order.'
// .tr,
// 'order');
// }
// }
// }
SnackbarController driverAppliedTripSnakBar() { SnackbarController driverAppliedTripSnakBar() {
return Get.snackbar( return Get.snackbar(
@@ -418,40 +614,6 @@ class FirebaseMessagesController extends GetxController {
); );
} }
Future<dynamic> driverArrivePassengerDialoge() {
return Get.defaultDialog(
barrierDismissible: false,
title: 'Hi ,I Arrive your site'.tr,
titleStyle: AppStyle.title,
middleText: 'Please go to Car Driver'.tr,
middleTextStyle: AppStyle.title,
confirm: MyElevatedButton(
title: 'Ok I will go now.'.tr,
onPressed: () {
// sendNotificationToPassengerToken(
// 'Hi ,I will go now',
// 'I will go now'.tr,
// Get.find<MapPassengerController>().driverToken,
// [],
// 'ding');
NotificationService.sendNotification(
target:
Get.find<MapPassengerController>().driverToken.toString(),
title: 'Hi ,I will go now'.tr,
body: 'I will go now'.tr,
isTopic: false, // Important: this is a token
tone: 'ding',
driverList: [],
);
Get.find<MapPassengerController>()
.startTimerDriverWaitPassenger5Minute();
Get.back();
Get.find<MapPassengerController>().remainingTime = 0;
Get.find<MapPassengerController>().update();
}));
}
Future<dynamic> passengerDialog(String message) { Future<dynamic> passengerDialog(String message) {
return Get.defaultDialog( return Get.defaultDialog(
barrierDismissible: false, barrierDismissible: false,
@@ -500,246 +662,6 @@ class FirebaseMessagesController extends GetxController {
kolor: AppColor.redColor, kolor: AppColor.redColor,
)); ));
} }
// void sendNotificationAll(String title, body, tone) async {
// // Get the token you want to subtract.
// String token = box.read(BoxName.tokenFCM);
// tokens = box.read(BoxName.tokens);
// // Subtract the token from the list of tokens.
// tokens.remove(token);
// // Save the list of tokens back to the box.
// // box.write(BoxName.tokens, tokens);
// tokens = box.read(BoxName.tokens);
// for (var i = 0; i < tokens.length; i++) {
// http
// .post(
// Uri.parse('https://fcm.googleapis.com/fcm/send'),
// headers: <String, String>{
// 'Content-Type': 'application/json',
// 'Authorization': 'key=${AK.serverAPI}'
// },
// body: jsonEncode({
// 'message': {
// 'token': token,
// 'notification': {
// 'title': title,
// 'body': body,
// },
// // 'data': {
// // 'DriverList': jsonEncode(data),
// // },
// 'android': {
// 'priority': 'HIGH ', // Set priority to high
// 'notification': {
// 'sound': tone,
// },
// },
// 'apns': {
// 'headers': {
// 'apns-priority': '10', // Set APNs priority to 10
// },
// 'payload': {
// 'aps': {
// 'sound': tone,
// },
// },
// },
// },
// }),
// )
// .whenComplete(() {})
// .catchError((e) {});
// }
// }
// for (var i = 0; i < tokens.length; i++) {
// http
// .post(Uri.parse('https://fcm.googleapis.com/fcm/send'),
// headers: <String, String>{
// 'Content-Type': 'application/json',
// 'Authorization': 'key=${storage.read(key: BoxName.serverAPI}'
// },
// body: jsonEncode({
// 'notification': <String, dynamic>{
// 'title': title,
// 'body': body,
// 'sound': 'true'
// },
// 'priority': 'HIGH ',
// 'data': <String, dynamic>{
// 'click_action': 'FLUTTER_NOTIFICATION_CLICK',
// 'id': '1',
// 'status': 'done'
// },
// 'to': tokens[i],
// }))
// .whenComplete(() {})
// .catchError((e) {
// });
// }
// }
// late String serviceAccountKeyJson;
// '{"type": "service_account", "project_id": "intaleq-d48a7", "private_key_id": "d63a627dad96d0050c08a76c2920b1e48ddc4d38", "private_key": "-----BEGIN PRIVATE KEY-----\\nMIIEvQIBADANBgkqhkiG9w0BAQEFAASCBKcwggSjAgEAAoIBAQDHgHWUIlGskFWT\\nkjBvSiAYzXz51NbyMtqlvq1rZaiokd/yzqcEsjgxcAEGap93gRu72cuJ7QzDOpec\\nXSmhQwaGrdDyGyuS5x8nBa9ea3QEUGKjk975OhgIDoaIX2YHjah+jf/p3CPvwovC\\n+qypLsErv5DtcFfKtHkL+Z8gKJojU3p0gP2cVLHlhodGG4767w1f70fIv5LmQRHh\\nE0x5GgjO7MfA1CJewgHDWzj9GTuTd9o3G5nF6ojn8H1EOWminNDrsHAagsplY7iV\\nNmdvGoIAg2kRt66y5k4Li7EiH3e2ILvomGvUe3ahxBTcyFAt7UuAC5aPTmB0OCtN\\n39vMkJGtAgMBAAECggEAQ/FoWcBMX4AyXNUzQJuWjcvhzbXiVE7kbwEez44qH+q6\\nQdeGQw+tGo0iFDzYvVrPhqzYaEs+hvib7Kk/xcdtYA2vNNzy/I9Q6TnC7V2b/+Ie\\njcYM8IUL7SaBQ811kon4gc07hDowVPXFImy7w8yEBjGyGmMhywumk+D6A/o/8Fph\\n3lGRzgYZ7K7+mXxDpJVFp8DwX+uqP/3wOzcITXE12GZpvB+re7TQTs01qjsSTJ3/\\nCZMC6CvwYr3BvJzvgrn2TNZ6N6yowHE2iJo/HnoY/DutiB1V0B2EAMgcy05ZUouH\\nnTTOMAyV5LdcxgCtzlz+meCuhV5SUtfSz27bnUluMwKBgQDz+qJM38NhUpW7tmxZ\\nQsYwlo3Zp2a38UV8VC4mNDM9jjsft9QRHShos7potlIvmn9ryxP87SGNZrW9xy/k\\ngvTbDXu65/TwCUa3HYFCC+eJ5S4bBK/ctFwn1sr5AFjxavY2VV6YHUIzGezo8Bsj\\n1R5IGy3UHreTWngDapJYpA3JQwKBgQDRVNK7UP/Qt4qovrTVlNJ5mHjpwk7VoKBC\\nV0yrfbYVjYETFRFMrsKkcwCTQ3uk3lEl/UzAt2vV6o4Ql8KDzYJ/8ZHHXp9Z2eK9\\nTgR2fOIaEh2JJUjyVAUtuJo7RFl61K3a080+ZGWuZCY6K+prGneFqGuJ7XTtveGy\\njIsZTUhSTwKBgQCS0n5/Qp1iYP+IsjQr1zpLnR6KH+p5wXEua75F8V3wqjo8UTUG\\ng4SA1b/VKfr1eMU7ij9iExYA8RFnvom8u248sLWH+fT1yq9KnS/fHijdXBTN35kx\\neTyIIQOOqz3bMqIuelttsRXYiL6AQ5Yhjywk+m4u27lfrK7SZ3zgaQF+3wKBgEBy\\nfgKfmHLY3z6+oAwVqos3LxrA8OaCcnSaTgeKR5HxI+kNFmtmbpSUt3ufTiTfMVqh\\n1oyKrA+LDDv9jSxpDCF57SjVb/gIxe8EYwlbv3zJUQCVUxUQWxvNduaCT44qhnAV\\nv13TKR78xGwqcxyQZHXo+VrYmaRMTn1bGcQrb/WvAoGAIWUnnGQsvf6SwPQ/7gXC\\nVAq4i3E+coLStVyPK552HVorKa7J+TQnNBGHjCaQhxfCgp59/4qeT5AizzQaMhuS\\noGiUwGeo4RY4A1EEGoUpUk3zWZfC+bAjHVDyIjfN0YfxobL6Sh/97N68PMzb6ppq\\nybvddSGGsqZgucSxkEhIdTw=\\n-----END PRIVATE KEY-----\\n", "client_email": "firebase-adminsdk-fbsvc@intaleq-d48a7.iam.gserviceaccount.com", "client_id": "100558924056484926665", "auth_uri": "https://accounts.google.com/o/oauth2/auth", "token_uri": "https://oauth2.googleapis.com/token", "auth_provider_x509_cert_url": "https://www.googleapis.com/oauth2/v1/certs", "client_x509_cert_url": "https://www.googleapis.com/robot/v1/metadata/x509/firebase-adminsdk-fbsvc%40intaleq-d48a7.iam.gserviceaccount.com", "universe_domain": "googleapis.com"}';
@override
Future<void> onInit() async {
super.onInit();
// try {
// // var encryptedKey = Env.privateKeyFCM;
// // // Log.print('encryptedKey: ${encryptedKey}');
// // serviceAccountKeyJson =
// // EncryptionHelper.instance.decryptData(encryptedKey);
// // Log.print('serviceAccountKeyJson: ${serviceAccountKeyJson}');
// } catch (e) {
// print('🔴 Error decrypting FCM key: $e');
// }
}
// Future<void> sendNotificationToDriverMAP(
// String title, String body, String token, List<String> data, String tone,
// {int retryCount = 1}) async {
// try {
// if (serviceAccountKeyJson.isEmpty) {
// print("🔴 Error: Service Account Key is empty");
// return;
// }
// // Initialize AccessTokenManager
// final accessTokenManager = AccessTokenManager(serviceAccountKeyJson);
// // Log.print(
// // 'accessTokenManager: ${accessTokenManager.serviceAccountJsonKey}');
// // Obtain an OAuth 2.0 access token
// final accessToken = await accessTokenManager.getAccessToken();
// // Log.print('accessToken: ${accessToken}');
// // Send the notification
// final response = await http.post(
// Uri.parse(
// 'https://fcm.googleapis.com/v1/projects/intaleq-d48a7/messages:send'),
// headers: <String, String>{
// 'Content-Type': 'application/json',
// 'Authorization': 'Bearer $accessToken',
// },
// body: jsonEncode({
// 'message': {
// 'token': token,
// 'notification': {
// 'title': title,
// 'body': body,
// },
// 'data': {
// 'DriverList': jsonEncode(data),
// },
// 'android': {
// 'priority': 'HIGH ', // Set priority to high
// 'notification': {
// 'sound': tone,
// },
// },
// 'apns': {
// 'headers': {
// 'apns-priority': '10', // Set APNs priority to 10
// },
// 'payload': {
// 'aps': {
// 'sound': tone,
// },
// },
// },
// },
// }),
// );
// if (response.statusCode == 200) {
// print(
// 'Notification sent successfully. Status code: ${response.statusCode}');
// // print('Response token: ${token}');
// } else {
// print(
// 'Failed to send notification. Status code: ${response.statusCode}');
// print('Response body: ${response.body}');
// if (retryCount > 0) {
// print('Retrying... Attempts remaining: $retryCount');
// await Future.delayed(
// Duration(seconds: 2)); // Optional delay before retrying
// return sendNotificationToDriverMAP(title, body, token, data, tone,
// retryCount: retryCount - 1);
// }
// }
// } catch (e) {
// print('Error sending notification: $e');
// if (retryCount > 0) {
// print('Retrying... Attempts remaining: $retryCount');
// await Future.delayed(
// Duration(seconds: 2)); // Optional delay before retrying
// return sendNotificationToDriverMAP(title, body, token, data, tone,
// retryCount: retryCount - 1);
// }
// }
// }
// void sendNotificationToPassengerToken(
// String title, body, token, List<String> map, String tone) async {
// try {
// if (serviceAccountKeyJson.isEmpty) {
// print("🔴 Error: Service Account Key is empty");
// return;
// }
// // Initialize AccessTokenManager
// final accessTokenManager = AccessTokenManager(serviceAccountKeyJson);
// // Obtain an OAuth 2.0 access token
// final accessToken = await accessTokenManager.getAccessToken();
// // Log.print('accessToken: ${accessToken}');
// // Send the notification
// final response = await http.post(
// Uri.parse(
// 'https://fcm.googleapis.com/v1/projects/intaleq-d48a7/messages:send'),
// headers: <String, String>{
// 'Content-Type': 'application/json',
// 'Authorization': 'Bearer $accessToken',
// },
// body: jsonEncode({
// 'message': {
// 'token': token,
// 'notification': {
// 'title': title,
// 'body': body,
// },
// 'android': {
// 'priority': 'HIGH ', // Set priority to high
// 'notification': {
// 'sound': tone,
// },
// },
// 'apns': {
// 'headers': {
// 'apns-priority': '10', // Set APNs priority to 10
// },
// 'payload': {
// 'aps': {
// 'sound': tone,
// },
// },
// },
// },
// }),
// );
// if (response.statusCode == 200) {
// print('✅ Notification sent successfully!');
// } else {
// print(
// '🔴 Failed to send notification. Status code: ${response.statusCode}');
// print('Response body: ${response.body}');
// }
// } catch (e) {
// print('🔴 Error sending notification: $e');
// }
// }
} }
class DriverTipWidget extends StatelessWidget { class DriverTipWidget extends StatelessWidget {

View File

@@ -1,3 +1,4 @@
import 'package:Intaleq/print.dart';
import 'package:http/http.dart' as http; import 'package:http/http.dart' as http;
import 'dart:convert'; import 'dart:convert';
@@ -5,11 +6,13 @@ class NotificationService {
// استبدل هذا الرابط بالرابط الصحيح لملف PHP على السيرفر الخاص بك // استبدل هذا الرابط بالرابط الصحيح لملف PHP على السيرفر الخاص بك
static const String _serverUrl = static const String _serverUrl =
'https://syria.intaleq.xyz/intaleq/fcm/send_fcm.php'; 'https://syria.intaleq.xyz/intaleq/fcm/send_fcm.php';
static const String _batchServerUrl =
'https://syria.intaleq.xyz/intaleq/fcm/send_fcm_batch.php';
static Future<void> sendNotification({ static Future<void> sendNotification({
required String target, required String target,
required String title, required String title,
required String body, required String body,
required String? category, // <-- [الإضافة الأولى]
String? tone, String? tone,
List<String>? driverList, // <-- [تعديل 1] : إضافة المتغير الجديد List<String>? driverList, // <-- [تعديل 1] : إضافة المتغير الجديد
bool isTopic = false, bool isTopic = false,
@@ -21,7 +24,10 @@ class NotificationService {
'body': body, 'body': body,
'isTopic': isTopic, 'isTopic': isTopic,
}; };
if (category != null) {
payload['category'] =
category; // <-- [الإضافة الثانية] (النص الثابت للتحكم)
}
// نضيف النغمة فقط إذا لم تكن فارغة // نضيف النغمة فقط إذا لم تكن فارغة
if (tone != null) { if (tone != null) {
payload['tone'] = tone; payload['tone'] = tone;
@@ -52,4 +58,56 @@ class NotificationService {
print('❌ An error occurred while sending notification: $e'); print('❌ An error occurred while sending notification: $e');
} }
} }
/// [4] !! دالة جديدة مضافة !!
/// ترسل إشعاراً "مجمعاً" إلى قائمة من السائقين
static Future<void> sendBatchNotification({
required List<String> targets, // <-- قائمة التوكينز
required String title,
required String body,
String? tone,
List<String>? driverList, // <-- بيانات الرحلة (نفسها للجميع)
}) async {
// لا ترسل شيئاً إذا كانت القائمة فارغة
if (targets.isEmpty) {
Log.print('⚠️ [Batch] No targets to send to. Skipped.');
return;
}
try {
final Map<String, dynamic> payload = {
// "targets" بدلاً من "target"
'targets': jsonEncode(targets), // تشفير قائمة التوكينز
'title': title,
'body': body,
};
if (tone != null) {
payload['tone'] = tone;
}
// بيانات الرحلة (DriverList)
if (driverList != null) {
payload['driverList'] = jsonEncode(driverList);
}
final response = await http.post(
Uri.parse(_batchServerUrl), // <-- !! تستخدم الرابط الجديد
headers: {
'Content-Type': 'application/json; charset=UTF-8',
},
body: jsonEncode(payload),
);
if (response.statusCode == 200) {
Log.print('✅ [Batch] Notifications sent successfully.');
Log.print('Server Response: ${response.body}');
} else {
Log.print('❌ [Batch] Failed to send. Status: ${response.statusCode}');
Log.print('Server Error: ${response.body}');
}
} catch (e) {
Log.print('❌ [Batch] An error occurred: $e');
}
}
} }

View File

@@ -17,9 +17,6 @@ import 'encrypt_decrypt.dart';
import 'upload_image.dart'; import 'upload_image.dart';
import 'dart:io'; import 'dart:io';
import 'package:jwt_decoder/jwt_decoder.dart';
import 'network/connection_check.dart';
import 'network/net_guard.dart'; import 'network/net_guard.dart';
class CRUD { class CRUD {
@@ -364,29 +361,29 @@ class CRUD {
} }
} }
Future<dynamic> getTokenParent({ // Future<dynamic> getTokenParent({
required String link, // required String link,
Map<String, dynamic>? payload, // Map<String, dynamic>? payload,
}) async { // }) async {
// Uses Basic Auth, so it's a separate implementation. // // Uses Basic Auth, so it's a separate implementation.
var url = Uri.parse( // var url = Uri.parse(
link, // link,
); // );
var response = await http.post( // var response = await http.post(
url, // url,
body: payload, // body: payload,
headers: { // headers: {
"Content-Type": "application/x-www-form-urlencoded", // "Content-Type": "application/x-www-form-urlencoded",
'Authorization': // 'Authorization':
'Basic ${base64Encode(utf8.encode(AK.basicAuthCredentials.toString()))}', // 'Basic ${base64Encode(utf8.encode(AK.basicAuthCredentials.toString()))}',
}, // },
); // );
if (response.statusCode == 200) { // if (response.statusCode == 200) {
return jsonDecode(response.body); // return jsonDecode(response.body);
} // }
// Consider adding error handling here. // // Consider adding error handling here.
return null; // return null;
} // }
Future sendWhatsAppAuth(String to, String token) async { Future sendWhatsAppAuth(String to, String token) async {
var res = await CRUD() var res = await CRUD()

View File

@@ -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(

View File

@@ -1,148 +1,148 @@
import 'dart:async'; // import 'dart:async';
import 'package:get/get.dart'; // import 'package:get/get.dart';
import 'package:google_maps_flutter/google_maps_flutter.dart'; // import 'package:google_maps_flutter/google_maps_flutter.dart';
import 'package:location/location.dart'; // import 'package:location/location.dart';
import 'package:Intaleq/constant/box_name.dart'; // import 'package:Intaleq/constant/box_name.dart';
import 'package:Intaleq/constant/links.dart'; // import 'package:Intaleq/constant/links.dart';
import 'package:Intaleq/controller/functions/crud.dart'; // import 'package:Intaleq/controller/functions/crud.dart';
import 'package:Intaleq/controller/home/payment/captain_wallet_controller.dart'; // import 'package:Intaleq/controller/home/payment/captain_wallet_controller.dart';
import 'package:Intaleq/main.dart'; // import 'package:Intaleq/main.dart';
// LocationController.dart // // LocationController.dart
class LocationController extends GetxController { // class LocationController extends GetxController {
LocationData? _currentLocation; // LocationData? _currentLocation;
late Location location; // late Location location;
bool isLoading = false; // bool isLoading = false;
late double heading = 0; // late double heading = 0;
late double accuracy = 0; // late double accuracy = 0;
late double previousTime = 0; // late double previousTime = 0;
late double latitude; // late double latitude;
late double totalDistance = 0; // late double totalDistance = 0;
late double longitude; // late double longitude;
late DateTime time; // late DateTime time;
late double speed = 0; // late double speed = 0;
late double speedAccuracy = 0; // late double speedAccuracy = 0;
late double headingAccuracy = 0; // late double headingAccuracy = 0;
bool isActive = false; // bool isActive = false;
late LatLng myLocation; // late LatLng myLocation;
String totalPoints = '0'; // String totalPoints = '0';
LocationData? get currentLocation => _currentLocation; // LocationData? get currentLocation => _currentLocation;
Timer? _locationTimer; // Timer? _locationTimer;
@override // @override
void onInit() async { // void onInit() async {
super.onInit(); // super.onInit();
location = Location(); // location = Location();
getLocation(); // getLocation();
// startLocationUpdates(); // // startLocationUpdates();
totalPoints = Get.put(CaptainWalletController()).totalPoints; // totalPoints = Get.put(CaptainWalletController()).totalPoints;
} // }
Future<void> startLocationUpdates() async { // Future<void> startLocationUpdates() async {
if (box.read(BoxName.driverID) != null) { // if (box.read(BoxName.driverID) != null) {
_locationTimer = // _locationTimer =
Timer.periodic(const Duration(seconds: 5), (timer) async { // Timer.periodic(const Duration(seconds: 5), (timer) async {
try { // try {
totalPoints = Get.find<CaptainWalletController>().totalPoints; // totalPoints = Get.find<CaptainWalletController>().totalPoints;
// if (isActive) { // // if (isActive) {
if (double.parse(totalPoints) > -300) { // if (double.parse(totalPoints) > -300) {
await getLocation(); // await getLocation();
// if (box.read(BoxName.driverID) != null) { // // if (box.read(BoxName.driverID) != null) {
await CRUD() // await CRUD()
.post(link: AppLink.addCarsLocationByPassenger, payload: { // .post(link: AppLink.addCarsLocationByPassenger, payload: {
'driver_id': box.read(BoxName.driverID).toString(), // 'driver_id': box.read(BoxName.driverID).toString(),
'latitude': myLocation.latitude.toString(), // 'latitude': myLocation.latitude.toString(),
'longitude': myLocation.longitude.toString(), // 'longitude': myLocation.longitude.toString(),
'heading': heading.toString(), // 'heading': heading.toString(),
'speed': (speed * 3.6).toStringAsFixed(1), // 'speed': (speed * 3.6).toStringAsFixed(1),
'distance': totalDistance == 0 // 'distance': totalDistance == 0
? '0' // ? '0'
: totalDistance < 1 // : totalDistance < 1
? totalDistance.toStringAsFixed(3) // ? totalDistance.toStringAsFixed(3)
: totalDistance.toStringAsFixed(1), // : totalDistance.toStringAsFixed(1),
'status': box.read(BoxName.statusDriverLocation).toString() // 'status': box.read(BoxName.statusDriverLocation).toString()
}); // });
} // }
// } // // }
} catch (e) { // } catch (e) {
// Handle the error gracefully // // Handle the error gracefully
} // }
}); // });
} // }
} // }
void stopLocationUpdates() { // void stopLocationUpdates() {
_locationTimer?.cancel(); // _locationTimer?.cancel();
} // }
Future<void> getLocation() async { // Future<void> getLocation() async {
// isLoading = true; // // isLoading = true;
// update(); // // update();
bool serviceEnabled; // bool serviceEnabled;
PermissionStatus permissionGranted; // PermissionStatus permissionGranted;
// Check if location services are enabled // // Check if location services are enabled
serviceEnabled = await location.serviceEnabled(); // serviceEnabled = await location.serviceEnabled();
if (!serviceEnabled) { // if (!serviceEnabled) {
serviceEnabled = await location.requestService(); // serviceEnabled = await location.requestService();
if (!serviceEnabled) { // if (!serviceEnabled) {
// Location services are still not enabled, handle the error // // Location services are still not enabled, handle the error
return; // return;
} // }
} // }
// Check if the app has permission to access location // // Check if the app has permission to access location
permissionGranted = await location.hasPermission(); // permissionGranted = await location.hasPermission();
if (permissionGranted == PermissionStatus.denied) { // if (permissionGranted == PermissionStatus.denied) {
permissionGranted = await location.requestPermission(); // permissionGranted = await location.requestPermission();
if (permissionGranted != PermissionStatus.granted) { // if (permissionGranted != PermissionStatus.granted) {
// Location permission is still not granted, handle the error // // Location permission is still not granted, handle the error
return; // return;
} // }
} // }
// Configure location accuracy // // Configure location accuracy
// LocationAccuracy desiredAccuracy = LocationAccuracy.high; // // LocationAccuracy desiredAccuracy = LocationAccuracy.high;
// Get the current location // // Get the current location
LocationData _locationData = await location.getLocation(); // LocationData _locationData = await location.getLocation();
myLocation = // myLocation =
(_locationData.latitude != null && _locationData.longitude != null // (_locationData.latitude != null && _locationData.longitude != null
? LatLng(_locationData.latitude!, _locationData.longitude!) // ? LatLng(_locationData.latitude!, _locationData.longitude!)
: null)!; // : null)!;
speed = _locationData.speed!; // speed = _locationData.speed!;
heading = _locationData.heading!; // heading = _locationData.heading!;
// Calculate the distance between the current location and the previous location // // Calculate the distance between the current location and the previous location
// if (Get.find<HomeCaptainController>().rideId == 'rideId') { // // if (Get.find<HomeCaptainController>().rideId == 'rideId') {
// if (previousTime > 0) { // // if (previousTime > 0) {
// double distance = calculateDistanceInKmPerHour( // // double distance = calculateDistanceInKmPerHour(
// previousTime, _locationData.time, speed); // // previousTime, _locationData.time, speed);
// totalDistance += distance; // // totalDistance += distance;
// } // // }
// previousTime = _locationData.time!; // // previousTime = _locationData.time!;
// } // // }
// Print location details // // Print location details
// isLoading = false; // // isLoading = false;
update(); // update();
} // }
double calculateDistanceInKmPerHour( // double calculateDistanceInKmPerHour(
double? startTime, double? endTime, double speedInMetersPerSecond) { // double? startTime, double? endTime, double speedInMetersPerSecond) {
// Calculate the time difference in hours // // Calculate the time difference in hours
double timeDifferenceInHours = (endTime! - startTime!) / 1000 / 3600; // double timeDifferenceInHours = (endTime! - startTime!) / 1000 / 3600;
// Convert speed to kilometers per hour // // Convert speed to kilometers per hour
double speedInKmPerHour = speedInMetersPerSecond * 3.6; // double speedInKmPerHour = speedInMetersPerSecond * 3.6;
// Calculate the distance in kilometers // // Calculate the distance in kilometers
double distanceInKilometers = speedInKmPerHour * timeDifferenceInHours; // double distanceInKilometers = speedInKmPerHour * timeDifferenceInHours;
return distanceInKilometers; // return distanceInKilometers;
} // }
} // }

View File

@@ -1,3 +1,4 @@
import 'dart:math';
import 'package:flutter/cupertino.dart'; import 'package:flutter/cupertino.dart';
import 'package:flutter/material.dart'; import 'package:flutter/material.dart';
import 'package:flutter_font_icons/flutter_font_icons.dart'; import 'package:flutter_font_icons/flutter_font_icons.dart';
@@ -7,10 +8,9 @@ import '../../../constant/colors.dart';
import '../functions/launch.dart'; import '../functions/launch.dart';
class ContactUsController extends GetxController { class ContactUsController extends GetxController {
final String phone1 = '+201018805430'; /// WORKING HOURS (10:00 → 16:00)
final String phone2 = '+201080182934'; final TimeOfDay workStartTime = const TimeOfDay(hour: 10, minute: 0);
final TimeOfDay workStartTime = const TimeOfDay(hour: 12, minute: 0); final TimeOfDay workEndTime = const TimeOfDay(hour: 16, minute: 0);
final TimeOfDay workEndTime = const TimeOfDay(hour: 19, minute: 0);
bool _isWithinWorkTime(TimeOfDay now) { bool _isWithinWorkTime(TimeOfDay now) {
return (now.hour > workStartTime.hour || return (now.hour > workStartTime.hour ||
@@ -20,8 +20,23 @@ class ContactUsController extends GetxController {
(now.hour == workEndTime.hour && now.minute <= workEndTime.minute)); (now.hour == workEndTime.hour && now.minute <= workEndTime.minute));
} }
/// PHONE LIST (USED FOR CALLS + WHATSAPP)
final List<String> phoneNumbers = [
'+963952475734',
'+963952475740',
'+963952475742'
];
/// RANDOM PHONE SELECTOR
String getRandomPhone() {
final random = Random();
return phoneNumbers[random.nextInt(phoneNumbers.length)];
}
/// SHOW DIALOG
void showContactDialog(BuildContext context) { void showContactDialog(BuildContext context) {
TimeOfDay now = TimeOfDay.now(); TimeOfDay now = TimeOfDay.now();
bool withinHours = _isWithinWorkTime(now);
showCupertinoModalPopup( showCupertinoModalPopup(
context: context, context: context,
@@ -29,25 +44,34 @@ class ContactUsController extends GetxController {
title: Text('Contact Us'.tr), title: Text('Contact Us'.tr),
message: Text('Choose a contact option'.tr), message: Text('Choose a contact option'.tr),
actions: <Widget>[ actions: <Widget>[
if (_isWithinWorkTime(now)) /// 📞 CALL (RANDOM) — ONLY DURING WORK HOURS
if (withinHours)
CupertinoActionSheetAction( CupertinoActionSheetAction(
child: Text(phone1), child: Row(
onPressed: () => makePhoneCall( mainAxisAlignment: MainAxisAlignment.spaceBetween,
phone1, children: [
const Icon(CupertinoIcons.phone),
Text('Call Support'.tr),
],
), ),
onPressed: () {
final phone = getRandomPhone();
makePhoneCall(phone);
},
), ),
if (_isWithinWorkTime(now))
CupertinoActionSheetAction( /// ⛔ OUTSIDE WORK HOURS — SHOW INFO
child: Text(phone2), if (!withinHours)
onPressed: () => makePhoneCall(phone2),
),
if (!_isWithinWorkTime(now))
CupertinoActionSheetAction( CupertinoActionSheetAction(
child: Text( child: Text(
'Work time is from 12:00 - 19:00.\nYou can send a WhatsApp message or email.' 'Work time is from 10:00 AM to 16:00 PM.\nYou can send a WhatsApp message or email.'
.tr), .tr,
textAlign: TextAlign.center,
),
onPressed: () => Navigator.pop(context), onPressed: () => Navigator.pop(context),
), ),
/// 💬 WHATSAPP (RANDOM)
CupertinoActionSheetAction( CupertinoActionSheetAction(
child: Row( child: Row(
mainAxisAlignment: MainAxisAlignment.spaceAround, mainAxisAlignment: MainAxisAlignment.spaceAround,
@@ -59,15 +83,21 @@ class ContactUsController extends GetxController {
Text('Send WhatsApp Message'.tr), Text('Send WhatsApp Message'.tr),
], ],
), ),
onPressed: () => onPressed: () {
launchCommunication('whatsapp', phone1, 'Hello'.tr), final phone = getRandomPhone();
launchCommunication('whatsapp', phone, 'Hello'.tr);
},
), ),
/// 📧 EMAIL
CupertinoActionSheetAction( CupertinoActionSheetAction(
child: Text('Send Email'.tr), child: Text('Send Email'.tr),
onPressed: () => onPressed: () => launchCommunication(
launchCommunication('email', 'support@sefer.live', 'Hello'.tr), 'email', 'support@intaleqapp.com', 'Hello'.tr),
), ),
], ],
/// ❌ CANCEL BUTTON
cancelButton: CupertinoActionSheetAction( cancelButton: CupertinoActionSheetAction(
child: Text('Cancel'.tr), child: Text('Cancel'.tr),
onPressed: () => Navigator.pop(context), onPressed: () => Navigator.pop(context),

File diff suppressed because it is too large Load Diff

View File

@@ -112,7 +112,7 @@ class WayPointController extends GetxController {
@override @override
void onInit() { void onInit() {
Get.put(LocationController()); // Get.put(LocationController());
addWayPoints(); addWayPoints();
myLocation = Get.find<MapPassengerController>().passengerLocation; myLocation = Get.find<MapPassengerController>().passengerLocation;
super.onInit(); super.onInit();

View File

@@ -162,7 +162,7 @@ ${'Download the Intaleq app now and enjoy your ride!'.tr}
// 2. If it already starts with the country code, we assume it's correct. // 2. If it already starts with the country code, we assume it's correct.
if (digitsOnly.startsWith('963')) { if (digitsOnly.startsWith('963')) {
return '+$digitsOnly'; return '$digitsOnly';
} }
// 3. If it starts with '09' (common local format), remove the leading '0'. // 3. If it starts with '09' (common local format), remove the leading '0'.
@@ -171,7 +171,7 @@ ${'Download the Intaleq app now and enjoy your ride!'.tr}
} }
// 4. Prepend the Syrian country code. // 4. Prepend the Syrian country code.
return '+963$digitsOnly'; return '963$digitsOnly';
} }
/// **IMPROVEMENT**: This method now uses the new phone formatting logic and /// **IMPROVEMENT**: This method now uses the new phone formatting logic and

View File

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

View File

@@ -201,7 +201,16 @@ class MyTranslation extends Translations {
"Top up Wallet": "تعبئة المحفظة", "Top up Wallet": "تعبئة المحفظة",
"Add funds using our secure methods": "Add funds using our secure methods":
"أضف رصيداً باستخدام طرقنا الآمنة", "أضف رصيداً باستخدام طرقنا الآمنة",
"Speed": "سرعة", 'Driver is waiting': 'الكابتن في انتظارك',
'Type your message...': 'اكتب رسالتك...',
'Driver Accepted Request': 'الكابتن قبل الطلب',
'Message': 'رسالة',
'Call': 'اتصال',
"You can call or record audio during this trip.":
"يمكنك الاتصال أو تسجيل صوت أثناء هذه الرحلة.",
"Warning: Speeding detected!": "تحذير: تم الكشف عن تجاوز السرعة!",
'Fixed Price': 'سعر ثابت',
'Report': 'إبلاغ',
"Comfort": "‏مريحة", "Comfort": "‏مريحة",
"Intaleq Balance": "رصيد انطلق", "Intaleq Balance": "رصيد انطلق",
'Search for a starting point': 'ابحث عن نقطة انطلاق', 'Search for a starting point': 'ابحث عن نقطة انطلاق',
@@ -316,6 +325,8 @@ class MyTranslation extends Translations {
"Van for familly": "سيارة فان للعائلة", "Van for familly": "سيارة فان للعائلة",
"Are you sure to delete this location?": "Are you sure to delete this location?":
"هل أنت متأكد من حذف هذا الموقع؟", "هل أنت متأكد من حذف هذا الموقع؟",
'Change Work location?': 'تغيير موقع العمل؟',
'Change Home location?': 'تغيير موقع البيت؟',
"Submit a Complaint": "تقديم شكوى", "Submit a Complaint": "تقديم شكوى",
"Submit Complaint": "إرسال الشكوى", "Submit Complaint": "إرسال الشكوى",
"No trip history found": "لا يوجد سجل للرحلات", "No trip history found": "لا يوجد سجل للرحلات",
@@ -395,6 +406,41 @@ class MyTranslation extends Translations {
"OK": "موافق", "OK": "موافق",
"Confirm Pick-up Location": "تأكيد موقع الانطلاق", "Confirm Pick-up Location": "تأكيد موقع الانطلاق",
"Set Location on Map": "حدد الموقع على الخريطة", "Set Location on Map": "حدد الموقع على الخريطة",
'Leave a detailed comment (Optional)':
'اترك تعليقاً مفصلاً (اختياري)',
'Share your experience to help us improve...':
'شارك تجربتك لمساعدتنا على التحسن...',
'Your valuable feedback helps us improve our service quality.':
'ملاحظاتك القيمة تساعدنا في تحسين جودة خدماتنا.',
'witout zero': 'بدون صفر',
'Top up Balance': 'اشحن الرصيد',
'An error occurred': 'حدث خطأ',
'Send WhatsApp Message': 'إرسال رسالة واتساب',
'How was your trip with': 'كيف كانت رحلتك مع',
'Drawing route on map...': 'جارٍ رسم المسار على الخريطة...',
'Please wait while we prepare your trip.':
'يرجى الانتظار بينما نحضر رحلتك.',
'Submit Rating': 'إرسال التقييم',
'Call Support': 'الاتصال بالدعم',
"You can contact us during working hours from 10:00 - 16:00.":
"يمكنك التواصل معنا خلال ساعات العمل من 10:00 إلى 16:00.",
"Intaleq is the safest and most reliable ride-sharing app designed especially for passengers in Syria. We provide a comfortable, respectful, and affordable riding experience with features that prioritize your safety and convenience. Our trusted captains are verified, insured, and supported by regular car maintenance carried out by top engineers. We also offer on-road support services to make sure every trip is smooth and worry-free. With Intaleq, you enjoy quality, safety, and peace of mind—every time you ride.":
"""إنطلِق هو التطبيق الأكثر أماناً وموثوقية لمشاركة الركوب والمصمم خصيصاً للركّاب في سوريا. نوفر لك تجربة تنقّل مريحة، محترمة، وبأسعار مناسبة، مع ميزات تضع سلامتك وراحتك في المقام الأول.
جميع الكباتن لدينا موثوقون، مُؤمَّنون، وتخضع سياراتهم لصيانة دورية يقوم بها مهندسون مختصون لضمان أفضل جودة. كما نقدّم خدمات دعم على الطريق لضمان أن تكون كل رحلة سلسة وخالية من القلق.
مع إنطلِق، ستستمتع دائماً بالأمان، والجودة، وراحة البال في كل رحلة تقوم بها.""",
'Work time is from 10:00 AM to 16:00 PM.\nYou can send a WhatsApp message or email.':
'وقت العمل من 10:00 صباحاً إلى 16:00 مساءً.\nيمكنك إرسال رسالة واتساب أو بريد إلكتروني.',
'Sorry': 'عذراً',
"Customer MSISDN doesnt have customer wallet":
"رقم هاتف العميل لا يحتوي على محفظة عميل",
'Please enter the number without the leading 0':
'يرجى إدخال الرقم بدون الصفر الأولي',
'Please enter your phone number': 'يرجى إدخال رقم هاتفك',
'Phone number seems too short': 'يبدو أن رقم الهاتف قصير جدًا',
'No cars are available at the moment. Please try again later.':
'لا توجد سيارات متاحة حالياً. الرجاء المحاولة مرة أخرى لاحقاً.',
"Nearest Car: ~": "أقرب سيارة: ~", "Nearest Car: ~": "أقرب سيارة: ~",
"Nearest Car": "أقرب سيارة", "Nearest Car": "أقرب سيارة",
"No cars nearby": "لا توجد سيارات قريبة", "No cars nearby": "لا توجد سيارات قريبة",

View File

@@ -56,6 +56,7 @@ class PassengerNotificationController extends GetxController {
// 'iphone_ringtone', // 'iphone_ringtone',
// ); // );
await NotificationService.sendNotification( await NotificationService.sendNotification(
category: title,
target: 'token'.toString(), target: 'token'.toString(),
title: title, title: title,
body: body.tr, body: body.tr,

View File

@@ -31,9 +31,9 @@ class PassengerWalletHistoryController extends GetxController {
}); });
} }
} catch (e) { } catch (e) {
MyDialog().getDialog('An error occurred'.tr, e.toString(), () { // MyDialog().getDialog('An error occurred'.tr, '', () {
Get.back(); // Get.back();
}); // });
} finally { } finally {
isLoading = false; isLoading = false;
update(); update();

View File

@@ -144,8 +144,9 @@ class PaymentController extends GetxController {
// 'cancel', // 'cancel',
// ); // );
await NotificationService.sendNotification( await NotificationService.sendNotification(
category: 'Cancel',
target: Get.find<MapPassengerController>().driverToken.toString(), target: Get.find<MapPassengerController>().driverToken.toString(),
title: 'Cancel', title: 'Cancel'.tr,
body: body:
'Trip Cancelled. The cost of the trip will be added to your wallet.' 'Trip Cancelled. The cost of the trip will be added to your wallet.'
.tr, .tr,

View File

@@ -9,7 +9,6 @@ import 'package:Intaleq/main.dart';
import 'package:Intaleq/views/home/map_page_passenger.dart'; import 'package:Intaleq/views/home/map_page_passenger.dart';
import 'package:Intaleq/views/widgets/elevated_btn.dart'; import 'package:Intaleq/views/widgets/elevated_btn.dart';
import '../firebase/firbase_messge.dart';
import '../firebase/notification_service.dart'; import '../firebase/notification_service.dart';
import '../payment/payment_controller.dart'; import '../payment/payment_controller.dart';
@@ -18,7 +17,7 @@ import '../payment/payment_controller.dart';
class RateController extends GetxController { class RateController extends GetxController {
double selectedRateItemId = -1; double selectedRateItemId = -1;
TextEditingController comment = TextEditingController(); TextEditingController comment = TextEditingController();
String? rideId, passengerId, driverId, price; String? rideId, passengerId, driverId, driverName, price;
late GlobalKey<FormState> formKey; late GlobalKey<FormState> formKey;
@override @override
void onInit() { void onInit() {
@@ -26,6 +25,7 @@ class RateController extends GetxController {
passengerId = Get.arguments['passengerId']; passengerId = Get.arguments['passengerId'];
rideId = Get.arguments['rideId']; rideId = Get.arguments['rideId'];
driverId = Get.arguments['driverId']; driverId = Get.arguments['driverId'];
driverName = Get.arguments['driverName'];
price = Get.arguments['price']; price = Get.arguments['price'];
box.write(BoxName.tipPercentage, '0'); box.write(BoxName.tipPercentage, '0');
super.onInit(); super.onInit();
@@ -61,7 +61,7 @@ class RateController extends GetxController {
box.read(BoxName.countryCode) == 'Egypt' box.read(BoxName.countryCode) == 'Egypt'
? tip.toStringAsFixed(0) ? tip.toStringAsFixed(0)
: (tip * 100).toString()); : (tip * 100).toString());
await CRUD().post(link: AppLink.addDriversWalletPoints, payload: { await CRUD().postWallet(link: AppLink.addDriversWalletPoints, payload: {
'driverID': Get.find<MapPassengerController>().driverId.toString(), 'driverID': Get.find<MapPassengerController>().driverId.toString(),
'paymentID': '${Get.find<MapPassengerController>().rideId}tip', 'paymentID': '${Get.find<MapPassengerController>().rideId}tip',
'amount': box.read(BoxName.countryCode) == 'Egypt' 'amount': box.read(BoxName.countryCode) == 'Egypt'
@@ -71,16 +71,10 @@ class RateController extends GetxController {
'token': token1, 'token': token1,
}); });
if (res != 'failure') { if (res != 'failure') {
// Get.find<FirebaseMessagesController>().sendNotificationToDriverMAP(
// 'You Have Tips'.tr,
// '${'${tip.toString()}\$${' tips\nTotal is'.tr}'} ${tip + (Get.find<MapPassengerController>().totalPassenger)}',
// Get.find<MapPassengerController>().driverToken.toString(),
// [],
// 'ding',
// );
await NotificationService.sendNotification( await NotificationService.sendNotification(
category: 'You Have Tips',
target: Get.find<MapPassengerController>().driverToken.toString(), target: Get.find<MapPassengerController>().driverToken.toString(),
title: 'You Have Tips', title: 'You Have Tips'.tr,
body: body:
'${'${tip.toString()}\$${' tips\nTotal is'.tr}'} ${tip + (Get.find<MapPassengerController>().totalPassenger)}', '${'${tip.toString()}\$${' tips\nTotal is'.tr}'} ${tip + (Get.find<MapPassengerController>().totalPassenger)}',
isTopic: false, // Important: this is a token isTopic: false, // Important: this is a token
@@ -91,26 +85,15 @@ class RateController extends GetxController {
} }
} }
await CRUD().post( await CRUD().post(
link: "${AppLink.IntaleqSyriaServer}/ride/rate/addRateToDriver.php", link: "${AppLink.server}/ride/rate/addRateToDriver.php",
payload: { payload: {
'passenger_id': box.read(BoxName.passengerID).toString(), 'passenger_id': box.read(BoxName.passengerID).toString(),
'driver_id': driverId.toString(), 'driver_id': driverId.toString(),
'ride_id': rideId.toString(), 'ride_id': rideId.toString(),
'rating': selectedRateItemId.toString(), 'rating': selectedRateItemId.toString(),
'comment': comment.text, 'comment': comment.text,
}); },
);
if (AppLink.endPoint != AppLink.IntaleqSyriaServer) {
CRUD().post(
link: "${AppLink.endPoint}/ride/rate/addRateToDriver.php",
payload: {
'passenger_id': box.read(BoxName.passengerID).toString(),
'driver_id': driverId.toString(),
'ride_id': rideId.toString(),
'rating': selectedRateItemId.toString(),
'comment': comment.text,
});
}
Get.find<MapPassengerController>().restCounter(); Get.find<MapPassengerController>().restCounter();
Get.offAll(const MapPagePassenger()); Get.offAll(const MapPagePassenger());

3
lib/env/env.dart vendored
View File

@@ -10,6 +10,9 @@ abstract class Env {
@EnviedField(varName: 'basicCompareFaces', obfuscate: true) @EnviedField(varName: 'basicCompareFaces', obfuscate: true)
static final String basicCompareFaces = _Env.basicCompareFaces; static final String basicCompareFaces = _Env.basicCompareFaces;
@EnviedField(varName: 'mapKeyOsm', obfuscate: true)
static final String mapKeyOsm = _Env.mapKeyOsm;
@EnviedField(varName: 'sss_encryptionSalt', obfuscate: true) @EnviedField(varName: 'sss_encryptionSalt', obfuscate: true)
static final String sss_encryptionSalt = _Env.sss_encryptionSalt; static final String sss_encryptionSalt = _Env.sss_encryptionSalt;

26160
lib/env/env.g.dart vendored

File diff suppressed because it is too large Load Diff

View File

@@ -0,0 +1,164 @@
import 'package:flutter/material.dart';
import 'package:flutter_rating_bar/flutter_rating_bar.dart';
import 'package:get/get.dart';
import '../../constant/colors.dart';
import '../../constant/links.dart';
import '../../constant/style.dart';
import '../../controller/home/map_passenger_controller.dart';
import '../../controller/rate/rate_conroller.dart';
import '../widgets/elevated_btn.dart';
import '../widgets/my_scafold.dart';
// ملاحظة: تم حذف جميع الأكواد المتعلقة بالسعر والإكرامية كما طُلِب.
// التركيز الآن على التقييم والملاحظات فقط.
class RatingDriverBottomSheet extends StatelessWidget {
RatingDriverBottomSheet({super.key});
final RateController controller = Get.put(RateController());
@override
Widget build(BuildContext context) {
// تم تبسيط استخدام MyScafolld لإزالة الـ Positioned واستخدام تصميم مركزي ونظيف.
return MyScafolld(
title: 'Rate Driver'.tr,
body: [
Center(
child: Container(
width: Get.width * .85,
padding: const EdgeInsets.all(24),
// يفترض أن AppStyle.boxDecoration1 يوفر تصميمًا جميلاً مع حواف مدورة وظل.
decoration: AppStyle.boxDecoration1,
child: Column(
mainAxisSize: MainAxisSize.min,
crossAxisAlignment: CrossAxisAlignment.center,
children: [
// 1. قسم معلومات السائق والترحيب
CircleAvatar(
radius: 30,
backgroundImage: NetworkImage(
'${AppLink.server}/portrate_captain_image/${controller.driverId}.jpg'),
onBackgroundImageError: (exception, stackTrace) => const Icon(
Icons.person,
size: 30,
color: AppColor.blueColor),
),
const SizedBox(height: 16),
// رسالة ترحيب مركزة على تجربة الرحلة
Text(
'${'How was your trip with'.tr} ${controller.driverName}?',
style: AppStyle.title
.copyWith(fontSize: 20, fontWeight: FontWeight.bold),
textAlign: TextAlign.center,
),
const SizedBox(height: 12),
// نص إرشادي يؤكد على أهمية التقييم لتحسين الجودة
Text(
'Your valuable feedback helps us improve our service quality.'
.tr,
style: AppStyle.title
.copyWith(color: Colors.grey.shade600, fontSize: 14),
textAlign: TextAlign.center,
),
const SizedBox(height: 32),
// 2. شريط التقييم (النجمي)
Center(
child: RatingBar.builder(
initialRating: 0,
itemCount: 5,
itemSize: 50, // حجم كبير لجعله النقطة البؤرية
glow: true,
glowColor: Colors.amber.shade200,
itemPadding: const EdgeInsets.symmetric(horizontal: 4),
itemBuilder: (context, index) {
// استخدام أيقونات المشاعر مع أيقونة النجمة للحصول على مظهر أكثر جاذبية
switch (index) {
case 0:
return const Icon(Icons.sentiment_very_dissatisfied,
color: Colors.red);
case 1:
return const Icon(Icons.sentiment_dissatisfied,
color: Colors.redAccent);
case 2:
return const Icon(Icons.sentiment_neutral,
color: Colors.amber);
case 3:
return const Icon(Icons.sentiment_satisfied,
color: Colors.lightGreen);
case 4:
return const Icon(Icons.sentiment_very_satisfied,
color: Colors.green);
default:
return const Icon(Icons.star_rounded,
color: Colors.amber);
}
},
onRatingUpdate: (rating) {
controller.selectRateItem(rating);
},
),
),
const SizedBox(height: 32),
// 3. قسم التعليقات (الملاحظات)
SizedBox(
width: Get.width * .75,
child: TextFormField(
maxLines: 4,
minLines: 1,
keyboardType: TextInputType.multiline,
controller: controller.comment,
decoration: InputDecoration(
labelText: 'Leave a detailed comment (Optional)'.tr,
hintText:
'Share your experience to help us improve...'.tr,
prefixIcon:
const Icon(Icons.rate_review, color: Colors.blueGrey),
suffixIcon: IconButton(
icon: const Icon(Icons.clear, color: AppColor.redColor),
onPressed: () {
controller.comment.clear();
},
),
// تحسين شكل الحدود لتكون أكثر جمالية
border: OutlineInputBorder(
borderRadius: BorderRadius.circular(12)),
enabledBorder: OutlineInputBorder(
borderRadius: BorderRadius.circular(12),
borderSide:
const BorderSide(color: Colors.blueGrey, width: 1),
),
focusedBorder: OutlineInputBorder(
borderRadius: BorderRadius.circular(12),
borderSide: BorderSide(
color: AppColor.greenColor,
width: 2), // لون جذاب عند التركيز
),
),
),
),
const SizedBox(height: 32),
// 4. زر الإرسال
MyElevatedButton(
title: 'Submit Rating'.tr,
onPressed: () {
controller.addRateToDriver();
Get.find<MapPassengerController>()
.getRideStatusFromStartApp();
})
],
),
),
),
],
isleading: false,
);
}
}

View File

@@ -310,6 +310,61 @@ class _PhoneNumberScreenState extends State<PhoneNumberScreen> {
final _phoneController = TextEditingController(); final _phoneController = TextEditingController();
final _formKey = GlobalKey<FormState>(); final _formKey = GlobalKey<FormState>();
bool _isLoading = false; bool _isLoading = false;
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');
}
// 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;
}
void _submit() async { void _submit() async {
if (_formKey.currentState!.validate()) { if (_formKey.currentState!.validate()) {
@@ -318,7 +373,10 @@ class _PhoneNumberScreenState extends State<PhoneNumberScreen> {
final rawPhone = _phoneController.text.trim().replaceFirst('+', ''); final rawPhone = _phoneController.text.trim().replaceFirst('+', '');
final success = await PhoneAuthHelper.sendOtp(rawPhone); final success = await PhoneAuthHelper.sendOtp(rawPhone);
if (success && mounted) { if (success && mounted) {
Get.to(() => OtpVerificationScreen(phoneNumber: rawPhone)); // Get.to(() => OtpVerificationScreen(phoneNumber: rawPhone));
await PhoneAuthHelper.verifyOtp(
rawPhone,
); // For testing purposes, auto-verify with a dummy OTP
} }
if (mounted) setState(() => _isLoading = false); if (mounted) setState(() => _isLoading = false);
} }
@@ -349,6 +407,7 @@ class _PhoneNumberScreenState extends State<PhoneNumberScreen> {
dropdownTextStyle: const TextStyle(color: Colors.black87), dropdownTextStyle: const TextStyle(color: Colors.black87),
decoration: InputDecoration( decoration: InputDecoration(
labelText: 'Phone Number'.tr, labelText: 'Phone Number'.tr,
hintText: 'witout zero'.tr,
labelStyle: TextStyle(color: Colors.white.withOpacity(0.7)), labelStyle: TextStyle(color: Colors.white.withOpacity(0.7)),
enabledBorder: OutlineInputBorder( enabledBorder: OutlineInputBorder(
borderRadius: BorderRadius.circular(12), borderRadius: BorderRadius.circular(12),
@@ -365,11 +424,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'; return 'Please enter your phone number'.tr;
}
if (phone.number.startsWith('0')) {
return 'Please enter the number without the leading 0'.tr;
} }
if (phone.completeNumber.length < 10) { if (phone.completeNumber.length < 10) {
// Example validation // Example validation
return 'Phone number seems too short'; return 'Phone number seems too short'.tr;
} }
return null; return null;
}, },
@@ -419,7 +481,7 @@ class _OtpVerificationScreenState extends State<OtpVerificationScreen> {
if (_formKey.currentState!.validate()) { if (_formKey.currentState!.validate()) {
setState(() => _isLoading = true); setState(() => _isLoading = true);
// PRODUCTION READY: Using the actual PhoneAuthHelper // PRODUCTION READY: Using the actual PhoneAuthHelper
await PhoneAuthHelper.verifyOtp(widget.phoneNumber, _otpController.text); // await PhoneAuthHelper.verifyOtp(widget.phoneNumber, _otpController.text);
if (mounted) setState(() => _isLoading = false); if (mounted) setState(() => _isLoading = false);
} }
} }

View File

@@ -37,7 +37,7 @@ class ContactUsPage extends StatelessWidget {
IconButton( IconButton(
onPressed: () async { onPressed: () async {
Get.put(TextToSpeechController()).speakText( Get.put(TextToSpeechController()).speakText(
'Intaleq is the safest ride-sharing app that introduces many features for both captains and passengers. We offer the lowest commission rate of just 8%, ensuring you get the best value for your rides. Our app includes insurance for the best captains, regular maintenance of cars with top engineers, and on-road services to ensure a respectful and high-quality experience for all users.' 'Intaleq is the safest and most reliable ride-sharing app designed especially for passengers in Syria. We provide a comfortable, respectful, and affordable riding experience with features that prioritize your safety and convenience. Our trusted captains are verified, insured, and supported by regular car maintenance carried out by top engineers. We also offer on-road support services to make sure every trip is smooth and worry-free. With Intaleq, you enjoy quality, safety, and peace of mind—every time you ride.'
.tr); .tr);
}, },
icon: const Icon(Icons.headphones), icon: const Icon(Icons.headphones),
@@ -45,7 +45,7 @@ class ContactUsPage extends StatelessWidget {
Padding( Padding(
padding: const EdgeInsets.all(8.0), padding: const EdgeInsets.all(8.0),
child: Text( child: Text(
'Intaleq is the safest ride-sharing app that introduces many features for both captains and passengers. We offer the lowest commission rate of just 8%, ensuring you get the best value for your rides. Our app includes insurance for the best captains, regular maintenance of cars with top engineers, and on-road services to ensure a respectful and high-quality experience for all users.' 'Intaleq is the safest and most reliable ride-sharing app designed especially for passengers in Syria. We provide a comfortable, respectful, and affordable riding experience with features that prioritize your safety and convenience. Our trusted captains are verified, insured, and supported by regular car maintenance carried out by top engineers. We also offer on-road support services to make sure every trip is smooth and worry-free. With Intaleq, you enjoy quality, safety, and peace of mind—every time you ride.'
.tr, .tr,
style: AppStyle.title, style: AppStyle.title,
textAlign: TextAlign.center, textAlign: TextAlign.center,
@@ -62,7 +62,7 @@ class ContactUsPage extends StatelessWidget {
child: Padding( child: Padding(
padding: const EdgeInsets.all(8.0), padding: const EdgeInsets.all(8.0),
child: Text( child: Text(
"You can contact us during working hours from 12:00 - 19:00." "You can contact us during working hours from 10:00 - 16:00."
.tr, .tr,
style: AppStyle.title, style: AppStyle.title,
textAlign: TextAlign.center, textAlign: TextAlign.center,

View File

@@ -43,6 +43,7 @@ class MapPagePassenger extends StatelessWidget {
child: Stack( child: Stack(
children: [ children: [
GoogleMapPassengerWidget(), GoogleMapPassengerWidget(),
// OsmMapPassengerWidget(),
leftMainMenuIcons(), leftMainMenuIcons(),
// PaymobPackage(), // PaymobPackage(),
const PickerIconOnMap(), const PickerIconOnMap(),
@@ -88,33 +89,33 @@ class CancelRidePageShow extends StatelessWidget {
@override @override
Widget build(BuildContext context) { Widget build(BuildContext context) {
return GetBuilder<MapPassengerController>( return GetBuilder<MapPassengerController>(
builder: (controller) => builder: (controller) => (controller.polyLines.isNotEmpty &&
(controller.data.isNotEmpty && controller.statusRide != 'Begin') controller.statusRide != 'Begin')
// || // ||
// controller.timeToPassengerFromDriverAfterApplied == 0 // controller.timeToPassengerFromDriverAfterApplied == 0
? Positioned( ? Positioned(
right: box.read(BoxName.lang) != 'ar' ? 10 : null, right: box.read(BoxName.lang) != 'ar' ? 10 : null,
left: box.read(BoxName.lang) == 'ar' ? 10 : null, left: box.read(BoxName.lang) == 'ar' ? 10 : null,
top: Get.height * .013, top: Get.height * .013,
child: GestureDetector( child: GestureDetector(
onTap: () { onTap: () {
controller.changeCancelRidePageShow(); controller.changeCancelRidePageShow();
}, },
child: Container( child: Container(
decoration: BoxDecoration( decoration: BoxDecoration(
color: AppColor.redColor, color: AppColor.redColor,
borderRadius: BorderRadius.circular(15)), borderRadius: BorderRadius.circular(15)),
child: const Padding( child: const Padding(
padding: EdgeInsets.all(3), padding: EdgeInsets.all(3),
child: Icon( child: Icon(
Icons.clear, Icons.clear,
size: 40, size: 40,
color: AppColor.secondaryColor, color: AppColor.secondaryColor,
),
),
), ),
)) ),
: const SizedBox()); ),
))
: const SizedBox());
} }
} }

View File

@@ -1,286 +1,412 @@
import 'package:Intaleq/constant/colors.dart'; import 'package:Intaleq/constant/colors.dart';
import 'package:Intaleq/constant/links.dart'; import 'package:Intaleq/constant/links.dart';
import 'package:Intaleq/constant/style.dart'; import 'package:Intaleq/constant/style.dart';
import 'package:Intaleq/controller/firebase/firbase_messge.dart';
import 'package:Intaleq/controller/home/map_passenger_controller.dart'; import 'package:Intaleq/controller/home/map_passenger_controller.dart';
import 'package:Intaleq/main.dart';
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 '../../../constant/box_name.dart'; import '../../../constant/box_name.dart';
import '../../../controller/firebase/notification_service.dart'; import '../../../controller/firebase/notification_service.dart';
import '../../../controller/functions/launch.dart'; import '../../../controller/functions/launch.dart';
import '../../../main.dart';
import '../../widgets/my_textField.dart'; import '../../widgets/my_textField.dart';
class ApplyOrderWidget extends StatelessWidget { class ApplyOrderWidget extends StatelessWidget {
ApplyOrderWidget({super.key}); const ApplyOrderWidget({super.key});
final firebaseMessagesController =
Get.isRegistered<FirebaseMessagesController>()
? Get.find<FirebaseMessagesController>()
: Get.put(FirebaseMessagesController());
@override @override
Widget build(BuildContext context) { Widget build(BuildContext context) {
Color _parseColor(String colorHex) { // دالة لتحويل كود اللون الهيكس إلى لون
Color parseColor(String colorHex) {
if (colorHex.isEmpty) return Colors.grey; if (colorHex.isEmpty) return Colors.grey;
String processedHex = colorHex.replaceFirst('#', '0xff').trim(); try {
return Color(int.parse(processedHex.startsWith('0xff') String processedHex = colorHex.replaceFirst('#', '').trim();
? processedHex if (processedHex.length == 6) processedHex = 'FF$processedHex';
: '0xff$processedHex')); return Color(int.parse('0x$processedHex'));
} catch (e) {
return Colors.grey;
}
} }
return GetBuilder<MapPassengerController>(builder: (controller) { return Obx(() {
Get.put( final controller = Get.find<MapPassengerController>();
FirebaseMessagesController()); // Ensure FirebaseMessagesController is initialized
if (controller.statusRide == 'Apply' && !controller.isSearchingWindow) { final bool isVisible =
return Positioned( controller.currentRideState.value == RideState.driverApplied ||
bottom: 0, controller.currentRideState.value == RideState.driverArrived;
left: 0,
right: 0, return AnimatedPositioned(
child: Container( duration: const Duration(milliseconds: 500),
decoration: BoxDecoration( curve: Curves.elasticOut, // تأثير حركي أجمل
// More modern BoxDecoration bottom: isVisible ? 0 : -Get.height * 0.6,
color: Theme.of(context).cardColor, left: 0,
borderRadius: right: 0,
const BorderRadius.vertical(top: Radius.circular(20)), child: Container(
boxShadow: [BoxShadow(blurRadius: 10, color: Colors.black12)], // height: Get.height * 0.38, // زيادة الارتفاع قليلاً للتصميم الجديد
), decoration: BoxDecoration(
padding: const EdgeInsets.all(16), color: Theme.of(context).cardColor,
child: Column( borderRadius: const BorderRadius.vertical(top: Radius.circular(30)),
mainAxisSize: MainAxisSize.min, boxShadow: [
children: [ BoxShadow(
_buildPriceInfo(context, controller), blurRadius: 20,
const SizedBox(height: 16), spreadRadius: 2,
_buildDriverInfoCard(context, controller, _parseColor), color: Colors.black.withOpacity(0.15),
], offset: const Offset(0, -2),
), )
],
), ),
); padding: const EdgeInsets.fromLTRB(20, 10, 20, 20),
} else { child: GetBuilder<MapPassengerController>(
return const SizedBox(); builder: (c) {
} return Column(
mainAxisSize: MainAxisSize.min,
children: [
// مقبض صغير في الأعلى
Container(
width: 40,
height: 5,
decoration: BoxDecoration(
color: Colors.grey.withOpacity(0.3),
borderRadius: BorderRadius.circular(10),
),
),
const SizedBox(height: 15),
// السعر والعنوان
_buildPriceHeader(context, c),
const SizedBox(height: 15),
// كرت المعلومات الرئيسي (سائق + سيارة)
_buildMainInfoCard(context, c, parseColor),
const SizedBox(height: 15),
// أزرار الاتصال
_buildContactButtonsRow(context, c),
const SizedBox(height: 15),
// شريط الوقت
c.currentRideState.value == RideState.driverArrived
? const DriverArrivePassengerAndWaitMinute()
: const TimeDriverToPassenger(),
],
);
},
),
),
);
}); });
} }
Widget _buildPriceInfo( // ---------------------------------------------------------------------------
// 1. قسم السعر (مع التنسيق الجديد)
// ---------------------------------------------------------------------------
Widget _buildPriceHeader(
BuildContext context, MapPassengerController controller) { BuildContext context, MapPassengerController controller) {
return InkWell( // تنسيق الرقم (مثلاً: 60,000)
onTap: () { final formatter = NumberFormat("#,###");
String message; String formattedPrice = formatter.format(controller.totalPassenger);
if (box.read(BoxName.carType) == 'Speed' ||
box.read(BoxName.carType) == 'Awfar Car' || return Column(
box.read(BoxName.carType) == 'Delivery') { children: [
message = Text(
'This ride type does not allow changes to the destination or additional stops' 'Driver Accepted Request'.tr,
.tr; style: AppStyle.subtitle.copyWith(color: Colors.grey[600]),
} else { ),
message = const SizedBox(height: 5),
'This ride type allows changes, but the price may increase'.tr; Row(
} mainAxisAlignment: MainAxisAlignment.center,
Get.snackbar( children: [
'This price is'.tr + Text(
' ${controller.totalPassenger.toStringAsFixed(2)}'.tr, formattedPrice,
message, style: AppStyle.title.copyWith(
snackPosition: SnackPosition.BOTTOM, fontSize: 28,
duration: const Duration(seconds: 2), fontWeight: FontWeight.w900,
backgroundColor: color: AppColor.primaryColor,
AppColor.yellowColor.withOpacity(0.8), // More subtle background
);
},
child: Center(
child: Text.rich(
TextSpan(
children: [
TextSpan(
text: '${'The driver accepted your order for'.tr} ',
style: AppStyle.title),
TextSpan(
text: controller.totalPassenger.toStringAsFixed(2),
style: AppStyle.title.copyWith(
fontWeight: FontWeight.bold, color: AppColor.redColor),
), ),
TextSpan(text: ' ${'LE'.tr}', style: AppStyle.title), ),
], const SizedBox(width: 5),
Padding(
padding: const EdgeInsets.only(top: 8.0),
child: Text(
'SYP'.tr,
style: AppStyle.subtitle.copyWith(
fontSize: 14,
fontWeight: FontWeight.bold,
color: Colors.grey[700],
),
),
),
],
),
],
);
}
// ---------------------------------------------------------------------------
// 2. كرت المعلومات الرئيسي (السائق + السيارة 3D)
// ---------------------------------------------------------------------------
Widget _buildMainInfoCard(BuildContext context,
MapPassengerController controller, Color Function(String) parseColor) {
return Container(
padding: const EdgeInsets.all(12),
decoration: BoxDecoration(
color: Theme.of(context).scaffoldBackgroundColor, // لون خلفية فاتح
borderRadius: BorderRadius.circular(20),
border: Border.all(color: Colors.grey.withOpacity(0.1)),
),
child: Row(
mainAxisAlignment: MainAxisAlignment.spaceBetween,
children: [
// الجزء الأيسر: معلومات السائق
Expanded(
child: Row(
children: [
// صورة السائق
Container(
padding: const EdgeInsets.all(3),
decoration: BoxDecoration(
shape: BoxShape.circle,
border: Border.all(color: AppColor.primaryColor, width: 2),
),
child: CircleAvatar(
radius: 26,
backgroundImage: NetworkImage(
'${AppLink.server}/portrate_captain_image/${controller.driverId}.jpg'),
onBackgroundImageError: (exception, stackTrace) =>
const Icon(Icons.person, size: 26, color: Colors.grey),
),
),
const SizedBox(width: 12),
// الاسم والتقييم والسيارة نص
Expanded(
child: Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
Text(
controller.driverName,
style: AppStyle.title.copyWith(
fontSize: 16, fontWeight: FontWeight.bold),
maxLines: 1,
overflow: TextOverflow.ellipsis,
),
const SizedBox(height: 4),
Row(
children: [
const Icon(Icons.star, color: Colors.amber, size: 16),
const SizedBox(width: 4),
Text(
controller.driverRate,
style: const TextStyle(
fontWeight: FontWeight.bold, fontSize: 13),
),
],
),
const SizedBox(height: 4),
Text(
'${controller.model}${controller.licensePlate}',
style: TextStyle(color: Colors.grey[600], fontSize: 12),
maxLines: 1,
overflow: TextOverflow.ellipsis,
),
],
),
),
],
),
),
// الجزء الأيمن: أيقونة السيارة الـ 3D
_build3DCarIcon(controller, parseColor),
],
),
);
}
// ---------------------------------------------------------------------------
// 3. أيقونة السيارة الـ 3D (الدائرة والظلال والخلفية الذكية)
// ---------------------------------------------------------------------------
Widget _build3DCarIcon(
MapPassengerController controller, Color Function(String) parseColor) {
Color carColor = parseColor(controller.colorHex);
// تحديد سطوع لون السيارة لتحديد لون الخلفية
// إذا كانت السيارة فاتحة (أكثر من 0.6)، الخلفية تكون غامقة، والعكس
bool isCarLight = carColor.computeLuminance() > 0.6;
// ألوان الخلفية للدائرة
Color bgGradientStart =
isCarLight ? Colors.blueGrey.shade700 : Colors.grey.shade100;
Color bgGradientEnd =
isCarLight ? Colors.blueGrey.shade900 : Colors.grey.shade300;
Color borderColor = isCarLight ? Colors.blueGrey.shade600 : Colors.white;
return Container(
width: 75,
height: 75,
padding: const EdgeInsets.all(12),
decoration: BoxDecoration(
shape: BoxShape.circle,
// تدرج لوني للخلفية لتبدو 3D
gradient: LinearGradient(
begin: Alignment.topLeft,
end: Alignment.bottomRight,
colors: [bgGradientStart, bgGradientEnd],
),
border: Border.all(color: borderColor, width: 2),
// ظلال لرفع الدائرة عن السطح
boxShadow: [
BoxShadow(
color: Colors.black.withOpacity(0.2),
blurRadius: 10,
offset: const Offset(4, 4),
),
BoxShadow(
color: Colors.white.withOpacity(isCarLight ? 0.1 : 0.8),
blurRadius: 10,
offset: const Offset(-4, -4),
),
],
),
child: Center(
child: ColorFiltered(
colorFilter: ColorFilter.mode(carColor, BlendMode.srcIn),
child: Image.asset(
box.read(BoxName.carType) == 'Scooter' ||
box.read(BoxName.carType) == 'Pink Bike'
? 'assets/images/moto.png'
: 'assets/images/car3.png',
fit: BoxFit.contain,
), ),
textAlign: TextAlign.center,
), ),
), ),
); );
} }
Widget _buildDriverInfoCard(BuildContext context, // ---------------------------------------------------------------------------
MapPassengerController controller, Color Function(String) parseColor) { // 4. أزرار الاتصال (بتصميم جديد)
return Container( // ---------------------------------------------------------------------------
decoration: BoxDecoration( Widget _buildContactButtonsRow(
color: Theme.of(context).canvasColor, BuildContext context, MapPassengerController controller) {
borderRadius: BorderRadius.circular(10), return Row(
border: Border.all(color: Colors.grey.shade200), children: [
Expanded(
child: _buildActionButton(
label: 'Message'.tr,
icon: Icons.chat_bubble_outline_rounded,
color: AppColor.blueColor,
onTap: () => _showContactOptionsDialog(context, controller),
),
),
const SizedBox(width: 15),
Expanded(
child: _buildActionButton(
label: 'Call'.tr,
icon: Icons.phone_rounded,
color: AppColor.greenColor,
onTap: () {
HapticFeedback.heavyImpact();
makePhoneCall(controller.driverPhone);
},
),
),
],
);
}
Widget _buildActionButton({
required String label,
required IconData icon,
required Color color,
required VoidCallback onTap,
}) {
return ElevatedButton(
onPressed: onTap,
style: ElevatedButton.styleFrom(
backgroundColor: color.withOpacity(0.1),
foregroundColor: color,
elevation: 0,
padding: const EdgeInsets.symmetric(vertical: 12),
shape: RoundedRectangleBorder(borderRadius: BorderRadius.circular(12)),
), ),
child: Column( child: Row(
mainAxisAlignment: MainAxisAlignment.center,
children: [ children: [
Padding( Icon(icon, size: 20),
padding: const EdgeInsets.all(12.0), const SizedBox(width: 8),
child: Row( Text(
mainAxisAlignment: MainAxisAlignment.spaceBetween, label,
children: [ style: const TextStyle(fontWeight: FontWeight.bold),
Text(box.read(BoxName.carType.toString()),
style:
AppStyle.title.copyWith(fontWeight: FontWeight.w500)),
Row(
children: [
_buildCarDetails(context, controller),
const SizedBox(width: 10),
_buildCarImage(controller, parseColor),
],
),
],
),
),
const Divider(height: 1, thickness: 1, color: Colors.grey),
Padding(
padding: const EdgeInsets.all(12.0),
child: Row(
mainAxisAlignment: MainAxisAlignment.spaceBetween,
children: [
_buildDriverAvatarAndInfo(controller),
_buildContactButtons(context, controller),
],
),
),
Padding(
padding:
const EdgeInsets.only(left: 12.0, right: 12.0, bottom: 12.0),
child: controller.isDriverArrivePassenger
? const DriverArrivePassengerAndWaitMinute()
: const TimeDriverToPassenger(),
), ),
], ],
), ),
); );
} }
Widget _buildCarDetails( // --- النوافذ المنبثقة للرسائل (نفس المنطق القديم) ---
BuildContext context, MapPassengerController controller) {
return Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
Text(controller.model.toString(), style: AppStyle.title),
Text(controller.licensePlate.toString(),
style: Theme.of(context).textTheme.bodyMedium),
Text(controller.carColor.toString(),
style: Theme.of(context).textTheme.bodyMedium),
],
);
}
Widget _buildCarImage(
MapPassengerController controller, Color Function(String) parseColor) {
return ColorFiltered(
colorFilter:
ColorFilter.mode(parseColor(controller.colorHex), BlendMode.srcIn),
child: Image.asset(
box.read(BoxName.carType) == 'Scooter' ||
box.read(BoxName.carType) == 'Pink Bike'
? 'assets/images/moto.png'
: 'assets/images/car3.png',
height: 60,
),
);
}
Widget _buildDriverAvatarAndInfo(MapPassengerController controller) {
return Row(
children: [
CircleAvatar(
radius: 30,
backgroundImage: NetworkImage(
'${AppLink.server}/portrate_captain_image/${controller.driverId}.jpg'),
onBackgroundImageError: (exception, stackTrace) =>
const Icon(Icons.person, size: 30, color: AppColor.blueColor),
),
const SizedBox(width: 8),
Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
Text(controller.driverName,
style: AppStyle.title.copyWith(fontWeight: FontWeight.w500)),
Text('${controller.driverRate}',
style: const TextStyle(fontSize: 16, color: Colors.grey)),
],
),
],
);
}
Widget _buildContactButtons(
BuildContext context, MapPassengerController controller) {
return Row(
children: [
IconButton(
onPressed: () => _showContactOptionsDialog(context, controller),
icon: const Icon(Icons.message, color: AppColor.blueColor, size: 28),
),
IconButton(
onPressed: () {
HapticFeedback.heavyImpact();
makePhoneCall(controller.driverPhone);
},
icon: const Icon(Icons.call, color: AppColor.greenColor, size: 28),
),
],
);
}
void _showContactOptionsDialog( void _showContactOptionsDialog(
BuildContext context, MapPassengerController controller) { BuildContext context, MapPassengerController controller) {
Get.defaultDialog( Get.bottomSheet(
title: 'Contact Options'.tr, Container(
content: SizedBox( padding: const EdgeInsets.all(20),
width: 300, decoration: BoxDecoration(
height: Get.height * .4, color: Theme.of(context).cardColor,
child: ListView( borderRadius: const BorderRadius.vertical(top: Radius.circular(20)),
// shrinkWrap: true, ),
child: Column(
mainAxisSize: MainAxisSize.min,
crossAxisAlignment: CrossAxisAlignment.start,
children: [ children: [
Text('Quick Message'.tr, style: AppStyle.title),
const SizedBox(height: 15),
..._buildPredefinedMessages(controller), ..._buildPredefinedMessages(controller),
const SizedBox(height: 8), const Divider(height: 30),
_buildCustomMessageInput(controller, context), _buildCustomMessageInput(controller, context),
SizedBox(
height: MediaQuery.of(context).viewInsets.bottom), // للكيبورد
], ],
), ),
), ),
isScrollControlled: true,
); );
} }
List<Widget> _buildPredefinedMessages(MapPassengerController controller) { List<Widget> _buildPredefinedMessages(MapPassengerController controller) {
const messages = [ const messages = [
'Hello, I\'m at the agreed-upon location', 'Hello, I\'m at the agreed-upon location',
'My location is correct. You can search for me using the navigation app',
'I\'m waiting for you', 'I\'m waiting for you',
"How much longer will you be?", "How much longer will you be?",
]; ];
return messages return messages
.map((message) => Padding( .map((message) => Padding(
padding: const EdgeInsets.only(bottom: 8.0), padding: const EdgeInsets.only(bottom: 10.0),
child: ElevatedButton( child: InkWell(
onPressed: () { onTap: () {
// firebaseMessagesController.sendNotificationToDriverMAP( _sendMessage(controller, message.tr);
// 'message From passenger',
// message.tr,
// controller.driverToken.toString(),
// [],
// 'ding',
// );
NotificationService.sendNotification(
target: controller.driverToken.toString(),
title: 'message From passenger',
body: message.tr, // Make sure to translate the message
isTopic: false, // Important: this is a token
tone: 'ding',
driverList: [],
);
Get.back(); Get.back();
}, },
child: Text(message.tr), child: Container(
padding:
const EdgeInsets.symmetric(vertical: 12, horizontal: 15),
decoration: BoxDecoration(
color: Colors.grey.withOpacity(0.1),
borderRadius: BorderRadius.circular(10),
),
child: Row(
children: [
const Icon(Icons.quickreply_rounded,
size: 18, color: Colors.grey),
const SizedBox(width: 10),
Expanded(
child: Text(message.tr, style: AppStyle.subtitle)),
],
),
),
), ),
)) ))
.toList(); .toList();
@@ -291,45 +417,59 @@ class ApplyOrderWidget extends StatelessWidget {
return Row( return Row(
children: [ children: [
Expanded( Expanded(
child: Form( child: Container(
key: controller.messagesFormKey, padding: const EdgeInsets.symmetric(horizontal: 15),
child: MyTextForm( decoration: BoxDecoration(
controller: controller.messageToDriver, color: Colors.grey.withOpacity(0.1),
label: 'Send a custom message'.tr, borderRadius: BorderRadius.circular(25),
hint: 'Type your message'.tr, ),
type: TextInputType.text, child: Form(
key: controller.messagesFormKey,
child: TextFormField(
controller: controller.messageToDriver,
decoration: InputDecoration(
hintText: 'Type your message...'.tr,
border: InputBorder.none,
),
),
), ),
), ),
), ),
IconButton( const SizedBox(width: 10),
onPressed: () { CircleAvatar(
if (controller.messagesFormKey.currentState!.validate()) { backgroundColor: AppColor.primaryColor,
// firebaseMessagesController.sendNotificationToDriverMAP( child: IconButton(
// 'message From passenger', onPressed: () {
// controller.messageToDriver.text, if (controller.messagesFormKey.currentState!.validate()) {
// controller.driverToken, _sendMessage(controller, controller.messageToDriver.text);
// [], controller.messageToDriver.clear();
// 'ding', Get.back();
// ); }
NotificationService.sendNotification( },
target: controller.driverToken.toString(), icon: const Icon(Icons.send_rounded, color: Colors.white, size: 20),
title: 'message From passenger', ),
body: controller.messageToDriver.text,
isTopic: false, // Important: this is a token
tone: 'ding',
driverList: [],
);
controller.messageToDriver.clear();
Get.back();
}
},
icon: const Icon(Icons.send),
), ),
], ],
); );
} }
void _sendMessage(MapPassengerController controller, String text) {
NotificationService.sendNotification(
category: 'message From passenger',
target: controller.driverToken.toString(),
title: 'Message From passenger'.tr,
body: text,
isTopic: false,
tone: 'ding',
driverList: [],
);
}
} }
// -----------------------------------------------------------------------------
// مؤشرات الانتظار والوقت (نفس المنطق مع تحسين بسيط في التصميم)
// -----------------------------------------------------------------------------
class DriverArrivePassengerAndWaitMinute extends StatelessWidget { class DriverArrivePassengerAndWaitMinute extends StatelessWidget {
const DriverArrivePassengerAndWaitMinute({Key? key}) : super(key: key); const DriverArrivePassengerAndWaitMinute({Key? key}) : super(key: key);
@@ -338,35 +478,31 @@ class DriverArrivePassengerAndWaitMinute extends StatelessWidget {
return GetBuilder<MapPassengerController>(builder: (controller) { return GetBuilder<MapPassengerController>(builder: (controller) {
return Column( return Column(
children: [ children: [
Row(
mainAxisAlignment: MainAxisAlignment.spaceBetween,
children: [
Text('Driver is waiting'.tr,
style: const TextStyle(fontWeight: FontWeight.bold)),
Text(
controller.stringRemainingTimeDriverWaitPassenger5Minute,
style: const TextStyle(
fontWeight: FontWeight.bold, color: AppColor.redColor),
),
],
),
const SizedBox(height: 6),
ClipRRect( ClipRRect(
borderRadius: BorderRadius.circular(15), borderRadius: BorderRadius.circular(10),
child: LinearProgressIndicator( child: LinearProgressIndicator(
backgroundColor: AppColor.accentColor.withOpacity(0.3), backgroundColor: Colors.grey[200],
color: controller.remainingTimeDriverWaitPassenger5Minute < 60 color: controller.remainingTimeDriverWaitPassenger5Minute < 60
? AppColor.redColor ? AppColor.redColor
: AppColor.greenColor, : AppColor.greenColor,
minHeight: 20, minHeight: 8,
value: value:
controller.progressTimerDriverWaitPassenger5Minute.toDouble(), controller.progressTimerDriverWaitPassenger5Minute.toDouble(),
), ),
), ),
const SizedBox(height: 4),
Center(
child: Text.rich(
TextSpan(
children: [
TextSpan(
text: '${'Driver is waiting at pickup.'.tr} ',
style: AppStyle.subtitle),
TextSpan(
text: controller
.stringRemainingTimeDriverWaitPassenger5Minute,
style: AppStyle.title),
],
),
textAlign: TextAlign.center,
),
),
], ],
); );
}); });
@@ -379,47 +515,37 @@ class TimeDriverToPassenger extends StatelessWidget {
@override @override
Widget build(BuildContext context) { Widget build(BuildContext context) {
return GetBuilder<MapPassengerController>(builder: (controller) { return GetBuilder<MapPassengerController>(builder: (controller) {
return controller.isDriverInPassengerWay == false || if (controller.timeToPassengerFromDriverAfterApplied <= 0) {
controller.timeToPassengerFromDriverAfterApplied > 0 return const SizedBox();
? Column( }
children: [ return Column(
ClipRRect( children: [
borderRadius: BorderRadius.circular(15), Row(
child: LinearProgressIndicator( mainAxisAlignment: MainAxisAlignment.spaceBetween,
backgroundColor: AppColor.accentColor.withOpacity(0.3), children: [
color: controller Text('Driver arriving in'.tr,
.remainingTimeToPassengerFromDriverAfterApplied < style: const TextStyle(fontWeight: FontWeight.bold)),
60 Text(
? AppColor.redColor controller.stringRemainingTimeToPassenger,
: AppColor.greenColor, style: const TextStyle(
minHeight: 20, fontWeight: FontWeight.bold, color: AppColor.primaryColor),
value: controller ),
.progressTimerToPassengerFromDriverAfterApplied ],
.toDouble() ),
.clamp(0.0, 1.0), const SizedBox(height: 6),
), ClipRRect(
), borderRadius: BorderRadius.circular(10),
const SizedBox(height: 4), child: LinearProgressIndicator(
Center( backgroundColor: Colors.grey[200],
child: Text.rich( color: AppColor.primaryColor,
TextSpan( minHeight: 8,
children: [ value: controller.progressTimerToPassengerFromDriverAfterApplied
TextSpan( .toDouble()
text: '${'Driver is on the way'.tr} ', .clamp(0.0, 1.0),
style: AppStyle.subtitle, ),
), ),
TextSpan( ],
text: controller.stringRemainingTimeToPassenger, );
style: AppStyle.title,
),
],
),
textAlign: TextAlign.center,
),
),
],
)
: const SizedBox();
}); });
} }
} }

View File

@@ -515,7 +515,7 @@ class Details extends StatelessWidget {
mainAxisAlignment: MainAxisAlignment.spaceAround, mainAxisAlignment: MainAxisAlignment.spaceAround,
children: [ children: [
Text( Text(
'${'Distance is'.tr} ${controller.data[0]['distance']['text']}', '${'Distance is'.tr} ${controller.distance.toStringAsFixed(2)} KM',
style: AppStyle.title, style: AppStyle.title,
), ),
Text( Text(

View File

@@ -7,6 +7,7 @@ import 'package:Intaleq/main.dart';
import 'package:Intaleq/views/home/profile/passenger_profile_page.dart'; import 'package:Intaleq/views/home/profile/passenger_profile_page.dart';
import 'package:Intaleq/views/widgets/elevated_btn.dart'; import 'package:Intaleq/views/widgets/elevated_btn.dart';
import 'package:Intaleq/views/widgets/my_textField.dart'; import 'package:Intaleq/views/widgets/my_textField.dart';
import 'package:intl/intl.dart';
import 'dart:ui'; import 'dart:ui';
import '../../../constant/info.dart'; import '../../../constant/info.dart';
@@ -79,9 +80,7 @@ class CarDetailsTypeToChoose extends StatelessWidget {
return GetBuilder<MapPassengerController>(builder: (controller) { return GetBuilder<MapPassengerController>(builder: (controller) {
_prepareCarTypes(controller); _prepareCarTypes(controller);
if (!(controller.data.isNotEmpty && if (!(controller.isBottomSheetShown) && controller.rideConfirm == false) {
controller.isBottomSheetShown &&
controller.rideConfirm == false)) {
return const SizedBox.shrink(); return const SizedBox.shrink();
} }
// Added a BackdropFilter for a modern glassmorphism effect // Added a BackdropFilter for a modern glassmorphism effect
@@ -351,7 +350,7 @@ class CarDetailsTypeToChoose extends StatelessWidget {
color: AppColor.primaryColor, size: 16), color: AppColor.primaryColor, size: 16),
const SizedBox(width: 4), const SizedBox(width: 4),
Text( Text(
'${controller.distance} ${'KM'.tr}', '${controller.distance.toStringAsFixed(1)} ${'KM'.tr}',
style: AppStyle.subtitle.copyWith( style: AppStyle.subtitle.copyWith(
color: AppColor.primaryColor, color: AppColor.primaryColor,
fontWeight: FontWeight.w600, fontWeight: FontWeight.w600,
@@ -418,30 +417,54 @@ class CarDetailsTypeToChoose extends StatelessWidget {
// --- LOGIC METHODS (UNCHANGED) --- // --- LOGIC METHODS (UNCHANGED) ---
// 1. قم بإضافة هذا السطر في أعلى الملف
String _getPassengerPriceText( String _getPassengerPriceText(
CarType carType, MapPassengerController mapPassengerController) { CarType carType, MapPassengerController mapPassengerController) {
// الخطوة 1: احصل على السعر كـ double أولاً
double rawPrice;
switch (carType.carType) { switch (carType.carType) {
case 'Comfort': case 'Comfort':
return mapPassengerController.totalPassengerComfort.toStringAsFixed(1); rawPrice = mapPassengerController.totalPassengerComfort;
break;
case 'Speed': case 'Speed':
return mapPassengerController.totalPassengerSpeed.toStringAsFixed(1); rawPrice = mapPassengerController.totalPassengerSpeed;
break;
case 'Electric': case 'Electric':
return mapPassengerController.totalPassengerElectric.toStringAsFixed(1); rawPrice = mapPassengerController.totalPassengerElectric;
break;
case 'Awfar Car': case 'Awfar Car':
return mapPassengerController.totalPassengerBalash.toStringAsFixed(1); rawPrice = mapPassengerController.totalPassengerBalash;
break;
case 'Scooter': case 'Scooter':
return mapPassengerController.totalPassengerScooter.toStringAsFixed(1); case 'Pink Bike': // دمج الحالات المتشابهة
rawPrice = mapPassengerController.totalPassengerScooter;
break;
case 'Van': case 'Van':
return mapPassengerController.totalPassengerVan.toStringAsFixed(1); rawPrice = mapPassengerController.totalPassengerVan;
break;
case 'Lady': case 'Lady':
return mapPassengerController.totalPassengerLady.toStringAsFixed(1); rawPrice = mapPassengerController.totalPassengerLady;
case 'Pink Bike': break;
return mapPassengerController.totalPassengerScooter.toStringAsFixed(1);
case 'Rayeh Gai': case 'Rayeh Gai':
return mapPassengerController.totalPassengerRayehGai.toStringAsFixed(1); rawPrice = mapPassengerController.totalPassengerRayehGai;
break;
default: default:
return '...'; return '...'; // إذا كان نوع السيارة غير معروف
} }
// الخطوة 2: قم بإزالة الكسور العشرية
// .round() ستحول 65000.00 إلى 65000
final int roundedPrice = rawPrice.round();
// الخطوة 3: أنشئ "مُنسّق" ليضيف فواصل الآلاف
// NumberFormat.decimalPattern() يستخدم إعدادات اللغة الافتراضية للجهاز
// لوضع الفاصلة (,) أو النقطة (.) حسب الدولة
final formatter = NumberFormat.decimalPattern();
// الخطوة 4: قم بتنسيق الرقم الصحيح
// سيحول 65000 إلى "65,000"
return formatter.format(roundedPrice);
} }
void _showCarDetailsDialog( void _showCarDetailsDialog(

View File

@@ -137,9 +137,9 @@ class CashConfirmPageShown extends StatelessWidget {
onPressed: () { onPressed: () {
// --- نفس منطقك القديم بالضبط --- // --- نفس منطقك القديم بالضبط ---
controller.changeCashConfirmPageShown(); controller.changeCashConfirmPageShown();
controller.isSearchingWindow = true; // controller.isSearchingWindow = true;
controller.confirmRideForAllDriverAvailable(); controller.startSearchingForDriver();
controller.update(); // controller.update();
}, },
); );
} }

View File

@@ -168,8 +168,8 @@ class _SearchFieldState extends State<_SearchField> {
icon: Icon(Icons.location_on_outlined, icon: Icon(Icons.location_on_outlined,
color: AppColor.accentColor, size: 30), color: AppColor.accentColor, size: 30),
tooltip: widget.controller.isAnotherOreder tooltip: widget.controller.isAnotherOreder
? 'Pick destination on map' ? 'Pick destination on map'.tr
: 'Pick on map', : 'Pick on map'.tr,
), ),
], ],
), ),

View File

@@ -7,8 +7,8 @@ import 'package:Intaleq/controller/home/points_for_rider_controller.dart';
import '../../../constant/colors.dart'; import '../../../constant/colors.dart';
import '../../../constant/style.dart'; import '../../../constant/style.dart';
import '../../../controller/functions/location_controller.dart'; // import '../../../controller/functions/location_controller.dart'; // Un-comment if needed
import '../../../controller/home/device_tier.dart'; // import '../../../controller/home/device_tier.dart'; // Removed to rely on Controller logic
import '../../../controller/home/map_passenger_controller.dart'; import '../../../controller/home/map_passenger_controller.dart';
import '../../widgets/mycircular.dart'; import '../../widgets/mycircular.dart';
import '../../widgets/mydialoug.dart'; import '../../widgets/mydialoug.dart';
@@ -17,7 +17,6 @@ class GoogleMapPassengerWidget extends StatelessWidget {
GoogleMapPassengerWidget({super.key}); GoogleMapPassengerWidget({super.key});
final WayPointController wayPointController = Get.put(WayPointController()); final WayPointController wayPointController = Get.put(WayPointController());
final LocationController locationController = Get.find<LocationController>();
@override @override
Widget build(BuildContext context) { Widget build(BuildContext context) {
@@ -32,24 +31,26 @@ class GoogleMapPassengerWidget extends StatelessWidget {
child: GoogleMap( child: GoogleMap(
onMapCreated: controller.onMapCreated, onMapCreated: controller.onMapCreated,
// ✅ حدود الكاميرا كما هي // ✅ Camera Bounds
cameraTargetBounds: CameraTargetBounds(controller.boundsdata), cameraTargetBounds: CameraTargetBounds(controller.boundsdata),
// ✅ Zoom أهدأ للأجهزة الضعيفة // ✅ Performance: Smoother zoom limits for low-end devices
minMaxZoomPreference: controller.lowPerf minMaxZoomPreference: controller.lowPerf
? const MinMaxZoomPreference(6, 17) ? const MinMaxZoomPreference(6, 17)
: const MinMaxZoomPreference(6, 18), : const MinMaxZoomPreference(6, 18),
onLongPress: (argument) { // ✅ Destination Selection on Long Press
onLongPress: (LatLng argument) {
MyDialog().getDialog('Are you want to go to this site'.tr, '', MyDialog().getDialog('Are you want to go to this site'.tr, '',
() async { () async {
controller.clearPolyline(); controller.clearPolyline();
// Ensure we have car data available before routing
if (controller.dataCarsLocationByPassenger != null) { if (controller.dataCarsLocationByPassenger != null) {
await controller.getDirectionMap( await controller.getDirectionMap(
'${controller.passengerLocation.latitude},${controller.passengerLocation.longitude}', '${controller.passengerLocation.latitude},${controller.passengerLocation.longitude}',
'${argument.latitude},${argument.longitude}', '${argument.latitude},${argument.longitude}',
); );
Get.back(); Get.back(); // Close Dialog
await controller.bottomSheet(); await controller.bottomSheet();
controller.showBottomSheet1(); controller.showBottomSheet1();
} else { } else {
@@ -59,52 +60,21 @@ class GoogleMapPassengerWidget extends StatelessWidget {
.tr, .tr,
'', '',
colorText: AppColor.redColor, colorText: AppColor.redColor,
duration: const Duration(seconds: 11), duration: const Duration(seconds: 5),
instantInit: true, backgroundColor: AppColor.secondaryColor,
snackPosition: SnackPosition.TOP, icon: const Icon(Icons.error, color: AppColor.redColor),
titleText: Text('Error'.tr, titleText: Text('Error'.tr,
style: const TextStyle(color: AppColor.redColor)), style: const TextStyle(color: AppColor.redColor)),
messageText: Text( messageText: Text(
'We Are Sorry That we dont have cars in your Location!' 'We Are Sorry That we dont have cars in your Location!'
.tr, .tr,
style: AppStyle.title), style: AppStyle.title),
icon: const Icon(Icons.error),
shouldIconPulse: true,
maxWidth: double.infinity,
margin: const EdgeInsets.all(16),
padding: const EdgeInsets.all(16),
borderRadius: 8,
borderColor: AppColor.redColor,
borderWidth: 2,
backgroundColor: AppColor.secondaryColor,
leftBarIndicatorColor: AppColor.redColor,
boxShadows: [
BoxShadow(
color: Colors.black.withOpacity(0.25),
blurRadius: 4,
spreadRadius: 2,
offset: const Offset(0, 4),
),
],
backgroundGradient: const LinearGradient(
colors: [AppColor.redColor, AppColor.accentColor],
begin: Alignment.topLeft,
end: Alignment.bottomRight,
),
isDismissible: true,
showProgressIndicator: false,
dismissDirection: DismissDirection.up,
snackStyle: SnackStyle.GROUNDED,
forwardAnimationCurve: Curves.easeInToLinear,
reverseAnimationCurve: Curves.easeInOut,
animationDuration: const Duration(milliseconds: 4000),
barBlur: 8,
overlayColor: AppColor.primaryColor.withOpacity(0.5),
); );
} }
}); });
}, },
// ✅ Hide UI elements on tap
onTap: (argument) { onTap: (argument) {
controller.hidePlaces(); controller.hidePlaces();
}, },
@@ -114,71 +84,66 @@ class GoogleMapPassengerWidget extends StatelessWidget {
zoom: controller.lowPerf ? 14.5 : 15, zoom: controller.lowPerf ? 14.5 : 15,
), ),
// ✅ ماركرز (احرص أن الأيقونات محجّمة ومخزّنة Cache في الكنترولر) // ✅ Markers
markers: controller.markers.toSet(), markers: controller.markers.toSet(),
// ✅ بوليغونز كما هي // ✅ Polygons (e.g., University/Country borders)
polygons: controller.polygons, polygons: controller.polygons,
// ✅ Polyline مُبسّطة للأجهزة الضعيفة (الكنترولر يجهّز مجموعة مبسطة عند lowPerf) // ✅ Polylines: Switch to lighter version if lowPerf is detected
polylines: controller.lowPerf polylines: controller.lowPerf
? controller.polyLinesLight ? controller.polyLinesLight.toSet()
.toSet() // <- استخدم مجموعة خفيفة
: controller.polyLines.toSet(), : controller.polyLines.toSet(),
// ✅ دوائر خفيفة على الأجهزة الضعيفة // ✅ Map Type: Switch to Normal map on low-end devices to save RAM
// circles: {
// Circle(
// circleId: const CircleId('circle_id'),
// center: controller.passengerLocation,
// radius: controller.lowPerf ? 80 : 100,
// fillColor:
// Colors.blue.withOpacity(controller.lowPerf ? 0.2 : 0.3),
// strokeColor: Colors.blue,
// strokeWidth: controller.lowPerf ? 1 : 2,
// ),
// },
// ✅ الوضع الخفيف: liteMode + تعطيل الطبقات المكلفة + خريطة Normal
mapType: controller.lowPerf mapType: controller.lowPerf
? MapType.normal ? MapType.normal
: (controller.mapType : (controller.mapType
? MapType.satellite ? MapType.satellite
: MapType.terrain), : MapType
.normal), // Changed terrain default to normal for better performance
// ✅ UI Settings for Performance
myLocationButtonEnabled: false, myLocationButtonEnabled: false,
// ⚠️ liteMode (Android فقط): فعّله على الأجهزة الضعيفة
// liteModeEnabled: controller.lowPerf,
liteModeEnabled: Platform.isAndroid ? isLowEnd() : false,
trafficEnabled: controller.mapTrafficON && !isLowEnd(),
buildingsEnabled: !isLowEnd(),
// ✅ تقليل الكلفة الرسومية
mapToolbarEnabled: false, mapToolbarEnabled: false,
rotateGesturesEnabled: isLowEnd() ? false : true, tiltGesturesEnabled:
tiltGesturesEnabled: false, // تعطيل الميلان لتقليل الحمل false, // Disable tilt to save GPU resources
// ✅ Throttle لحركة الكاميرا على الأجهزة الضعيفة // Lite Mode (Static image) only on very low-end Androids if needed,
onCameraMove: (position) { // but usually handled by lowPerf logic in mapType/Traffic
liteModeEnabled: Platform.isAndroid && controller.lowPerf,
trafficEnabled: controller.mapTrafficON && !controller.lowPerf,
buildingsEnabled: !controller.lowPerf,
rotateGesturesEnabled:
!controller.lowPerf, // Disable rotation on low-end
// ✅ Camera Movement Logic
onCameraMove: (CameraPosition position) {
// 1. Always update current view target (for pickers)
controller.newMyLocation = position.target;
// 2. Handle Drag-to-Select for specific states
if (controller.startLocationFromMap == true) {
controller.newStartPointLocation = position.target;
} else if (controller.passengerStartLocationFromMap == true) {
controller.newStartPointLocation = position.target;
}
// 3. Handle Waypoints Dragging
int waypointsLength =
Get.find<WayPointController>().wayPoints.length;
if (waypointsLength > 0 &&
controller.wayPointIndex >= 0 &&
controller.wayPointIndex <
controller.placesCoordinate.length) {
controller.placesCoordinate[controller.wayPointIndex] =
'${position.target.latitude},${position.target.longitude}';
}
// 4. Throttle heavy calculations (Reverse Geocoding / API calls)
if (controller.lowPerf) { if (controller.lowPerf) {
controller.onCameraMoveThrottled(position); controller.onCameraMoveThrottled(position);
} else {
// منطقك الحالي
int waypointsLength =
Get.find<WayPointController>().wayPoints.length;
int index = controller.wayPointIndex;
if (waypointsLength > 0) {
controller.placesCoordinate[index] =
'${position.target.latitude},${position.target.longitude}';
}
if (controller.startLocationFromMap == true) {
controller.newStartPointLocation = position.target;
} else if (controller.passengerStartLocationFromMap ==
true) {
controller.newStartPointLocation = position.target;
}
controller.newMyLocation = position.target;
} }
}, },

View File

@@ -76,11 +76,11 @@ GetBuilder<MapPassengerController> leftMainMenuIcons() {
tooltip: 'VIP Waiting Page', tooltip: 'VIP Waiting Page',
onPressed: () => Get.to(() => VipWaittingPage()), onPressed: () => Get.to(() => VipWaittingPage()),
), ),
_buildMapActionButton( // _buildMapActionButton(
icon: Octicons.ellipsis, // icon: Octicons.ellipsis,
tooltip: 'test', // tooltip: 'test',
onPressed: () => Get.to(() => TestPage()), // onPressed: () => Get.to(() => TestPage()),
), // ),
], ],
), ),
), ),
@@ -129,18 +129,22 @@ class TestPage extends StatelessWidget {
body: Center( body: Center(
child: TextButton( child: TextButton(
onPressed: () async { onPressed: () async {
var token = (box.read(BoxName.tokenFCM).toString()); // var token = (box.read(BoxName.tokenFCM).toString());
Log.print( // Log.print(
'box.read(BoxName.tokenFCM).toString(): ${box.read(BoxName.tokenFCM).toString()}'); // 'box.read(BoxName.tokenFCM).toString(): ${box.read(BoxName.tokenFCM).toString()}');
// 'e-EE5Z5Fn0x5s6EYbtgT6f:APA91bHBTxkbdljuvDF0iPhso58r7fCwGh-WcYh3CYfUJEShUKFcQf496Xc5E6LHqRFKfOQBxYrWSdLO8d9gLbL-IdgyDuZ7jNUjzvrcV_YmagDtgz7-UNw'; // // 'e-EE5Z5Fn0x5s6EYbtgT6f:APA91bHBTxkbdljuvDF0iPhso58r7fCwGh-WcYh3CYfUJEShUKFcQf496Xc5E6LHqRFKfOQBxYrWSdLO8d9gLbL-IdgyDuZ7jNUjzvrcV_YmagDtgz7-UNw';
// 'fdN1o8akwURHj47wvShC4T:APA91bFm-mFfFjdCbHsDReN0MzPE1hiaHKtPJnzayMec6LiInjzk6YCX41SeF0T1FE7Z6d4Hjy1AkZhLIeebSgX4RrodzwSwZSH0kboTQEfqkrjrk4xw9aM'; // // 'fdN1o8akwURHj47wvShC4T:APA91bFm-mFfFjdCbHsDReN0MzPE1hiaHKtPJnzayMec6LiInjzk6YCX41SeF0T1FE7Z6d4Hjy1AkZhLIeebSgX4RrodzwSwZSH0kboTQEfqkrjrk4xw9aM';
NotificationService.sendNotification( // NotificationService.sendNotification(
target: token, // target:
title: 'Hi ,I will go now'.tr, // 'eznj5vRWRnqwKNtKJBaYNg:APA91bHhJ2DJ1KQa3KRx6wQtX8BkFHq6I_-dXGxT16p6pnV5AwI0bWOeiTJOI35VfTBaK4YSCKmAB4SsRnpARK0MTJ96xtpPmwAKfkvsZFga8OoGMeb3PmA',
body: 'A passenger is waiting for you.', // title: 'Order',
isTopic: false, // Important: this is a token // body: 'endNameAddress',
tone: 'ding', // isTopic: false,
); // tone: 'tone1',
// category: 'Order', // استخدام الفئة الثابتة
// driverList: []);
// RideState.driverApplied;
// Get.find<MapPassengerController>().Ride
}, },
child: Text( child: Text(
"Text Button", "Text Button",

View File

@@ -480,7 +480,7 @@ class MainBottomMenuMap extends StatelessWidget {
controller.placeDestinationController.clear(); controller.placeDestinationController.clear();
controller.showBottomSheet1(); // controller.showBottomSheet1();
controller.changeMainBottomMenuMap(); controller.changeMainBottomMenuMap();
}, },

View File

@@ -1,3 +1,288 @@
// import 'package:flutter/material.dart';
// import 'package:flutter_font_icons/flutter_font_icons.dart';
// import 'package:get/get.dart';
// import 'package:Intaleq/constant/box_name.dart';
// import 'package:Intaleq/controller/profile/profile_controller.dart';
// import 'package:Intaleq/main.dart';
// import 'package:Intaleq/views/home/profile/complaint_page.dart';
// import '../../../constant/colors.dart';
// import '../../../constant/links.dart';
// import '../../../constant/style.dart';
// import '../../../controller/functions/audio_record1.dart';
// import '../../../controller/functions/launch.dart';
// import '../../../controller/functions/toast.dart';
// import '../../../controller/home/map_passenger_controller.dart';
// // --- الويدجت الرئيسية بالتصميم الجديد ---
// class RideBeginPassenger extends StatelessWidget {
// const RideBeginPassenger({super.key});
// @override
// Widget build(BuildContext context) {
// // --- نفس منطق استدعاء الكنترولرز ---
// final ProfileController profileController = Get.put(ProfileController());
// final AudioRecorderController audioController =
// Get.put(AudioRecorderController());
// return GetBuilder<MapPassengerController>(builder: (controller) {
// // --- نفس شرط الإظهار الخاص بك ---
// if (controller.statusRide != 'Begin') {
// return const SizedBox.shrink();
// }
// return Positioned(
// left: 0,
// right: 0,
// bottom: 0,
// child: Container(
// decoration: BoxDecoration(
// color: AppColor.secondaryColor,
// borderRadius: const BorderRadius.only(
// topLeft: Radius.circular(24),
// topRight: Radius.circular(24),
// ),
// boxShadow: [
// BoxShadow(
// color: Colors.black.withOpacity(0.2),
// blurRadius: 20,
// offset: const Offset(0, -5),
// ),
// ],
// ),
// child: Padding(
// padding: const EdgeInsets.fromLTRB(16, 12, 16, 16),
// child: Column(
// mainAxisSize: MainAxisSize.min,
// children: [
// // مقبض السحب (Handle)
// Container(
// width: 40,
// height: 5,
// decoration: BoxDecoration(
// color: AppColor.writeColor.withOpacity(0.3),
// borderRadius: BorderRadius.circular(12),
// ),
// ),
// const SizedBox(height: 12),
// // --- 1. قسم معلومات السائق ---
// _buildDriverInfo(controller),
// const Divider(height: 24, thickness: 0.5),
// // --- 2. قسم تقدم الرحلة ---
// _buildTripProgress(controller),
// const SizedBox(height: 16),
// // --- 3. قسم الإجراءات والأمان ---
// _buildActionButtons(
// context, controller, profileController, audioController),
// ],
// ),
// ),
// ),
// );
// });
// }
// // --- ويدجت مساعدة لعرض معلومات السائق بشكل منظم ---
// Widget _buildDriverInfo(MapPassengerController controller) {
// return Row(
// children: [
// CircleAvatar(
// radius: 28,
// backgroundImage: NetworkImage(
// '${AppLink.server}/portrate_captain_image/${controller.driverId}.jpg'),
// ),
// const SizedBox(width: 12),
// Expanded(
// child: Column(
// crossAxisAlignment: CrossAxisAlignment.start,
// children: [
// Text(controller.driverName,
// style: AppStyle.title.copyWith(fontWeight: FontWeight.bold)),
// const SizedBox(height: 2),
// Text(
// '${controller.make} ${controller.model} • ${box.read(BoxName.carType)}',
// style: AppStyle.subtitle
// .copyWith(color: AppColor.writeColor.withOpacity(0.7)),
// ),
// ],
// ),
// ),
// const SizedBox(width: 12),
// Column(
// crossAxisAlignment: CrossAxisAlignment.end,
// children: [
// Container(
// padding: const EdgeInsets.symmetric(horizontal: 8, vertical: 4),
// decoration: BoxDecoration(
// color: AppColor.writeColor.withOpacity(0.1),
// borderRadius: BorderRadius.circular(6),
// ),
// child: Text(
// controller.licensePlate,
// style: AppStyle.subtitle
// .copyWith(fontWeight: FontWeight.bold, letterSpacing: 1.5),
// ),
// ),
// const SizedBox(height: 4),
// Row(
// children: [
// Text(controller.driverRate,
// style: AppStyle.subtitle
// .copyWith(fontWeight: FontWeight.bold)),
// const SizedBox(width: 2),
// const Icon(Icons.star_rounded,
// color: AppColor.yellowColor, size: 16),
// ],
// ),
// ],
// )
// ],
// );
// }
// // --- ويدجت مساعدة لعرض شريط التقدم ---
// Widget _buildTripProgress(MapPassengerController controller) {
// return Column(
// children: [
// Row(
// mainAxisAlignment: MainAxisAlignment.spaceBetween,
// children: [
// Text('Time to Destination'.tr, style: AppStyle.subtitle),
// Text(controller.stringRemainingTimeRideBegin,
// style: AppStyle.subtitle.copyWith(
// fontWeight: FontWeight.bold, color: AppColor.primaryColor)),
// ],
// ),
// const SizedBox(height: 8),
// ClipRRect(
// borderRadius: BorderRadius.circular(10),
// child: LinearProgressIndicator(
// backgroundColor: AppColor.primaryColor.withOpacity(0.2),
// color: controller.remainingTimeTimerRideBegin < 60
// ? AppColor.redColor
// : AppColor.greenColor,
// minHeight: 10,
// value: controller.progressTimerRideBegin.toDouble(),
// ),
// ),
// ],
// );
// }
// // --- ويدجت مساعدة لعرض أزرار الإجراءات ---
// Widget _buildActionButtons(
// BuildContext context,
// MapPassengerController controller,
// ProfileController profileController,
// AudioRecorderController audioController) {
// return Row(
// mainAxisAlignment: MainAxisAlignment.spaceAround,
// children: [
// _buildActionButton(
// icon: Icons.sos_rounded,
// label: 'SOS'.tr,
// color: AppColor.redColor,
// onTap: () async {
// // --- نفس منطقك القديم ---
// if (box.read(BoxName.sosPhonePassenger) == null) {
// await profileController.updatField(
// 'sosPhone', TextInputType.phone);
// box.write(BoxName.sosPhonePassenger,
// profileController.prfoileData['sosPhone']);
// } else {
// makePhoneCall('112');
// }
// }),
// _buildActionButton(
// icon: FontAwesome.whatsapp,
// label: 'WhatsApp'.tr,
// color: AppColor.greenColor,
// onTap: () async {
// // --- نفس منطقك القديم ---
// if (box.read(BoxName.sosPhonePassenger) == null ||
// box.read(BoxName.sosPhonePassenger) == 'sos') {
// await profileController.updatField(
// 'sosPhone', TextInputType.phone);
// box.write(BoxName.sosPhonePassenger,
// profileController.prfoileData['sosPhone']);
// } else {
// final phoneNumber =
// box.read(BoxName.sosPhonePassenger).toString();
// final phone = controller.formatSyrianPhoneNumber(phoneNumber);
// controller.sendWhatsapp(phone); //
// }
// }),
// _buildActionButton(
// icon: Icons.share_location_outlined, // أيقونة جديدة ومناسبة
// label: 'Share'.tr, // اسم جديد وواضح
// color: AppColor.blueColor,
// onTap: () async {
// // نفس الوظيفة السابقة التي كانت تحت اسم "Video Call"
// await controller.getTokenForParent();
// }),
// _buildActionButton(
// icon: audioController.isRecording
// ? Icons.mic_off_rounded
// : Icons.mic_none_rounded,
// label: audioController.isRecording ? 'Stop'.tr : 'Record'.tr,
// color: AppColor.primaryColor,
// onTap: () async {
// // --- نفس منطقك القديم ---
// if (audioController.isRecording == false) {
// await audioController.startRecording();
// Toast.show(context, 'Start Record'.tr, AppColor.greenColor);
// } else {
// await audioController.stopRecording();
// Toast.show(context, 'Record saved'.tr, AppColor.greenColor);
// }
// },
// ),
// _buildActionButton(
// icon: Icons.note_add_outlined,
// label: 'Complaint'.tr,
// color: AppColor.yellowColor,
// onTap: () {
// // --- نفس منطقك القديم ---
// Get.to(() => ComplaintPage(), transition: Transition.downToUp);
// }),
// ],
// );
// }
// // --- ويدجت مساعدة لبناء زر إجراء فردي ---
// Widget _buildActionButton(
// {required IconData icon,
// required String label,
// required Color color,
// required VoidCallback onTap}) {
// return InkWell(
// onTap: onTap,
// borderRadius: BorderRadius.circular(12),
// child: Padding(
// padding: const EdgeInsets.all(4.0),
// child: Column(
// mainAxisSize: MainAxisSize.min,
// children: [
// Container(
// padding: const EdgeInsets.all(12),
// decoration: BoxDecoration(
// color: color.withOpacity(0.1),
// shape: BoxShape.circle,
// ),
// child: Icon(icon, color: color, size: 26),
// ),
// const SizedBox(height: 6),
// Text(label, style: AppStyle.subtitle.copyWith(fontSize: 12)),
// ],
// ),
// ),
// );
// }
// }
import 'package:flutter/material.dart'; import 'package:flutter/material.dart';
import 'package:flutter_font_icons/flutter_font_icons.dart'; import 'package:flutter_font_icons/flutter_font_icons.dart';
import 'package:get/get.dart'; import 'package:get/get.dart';
@@ -5,6 +290,7 @@ import 'package:Intaleq/constant/box_name.dart';
import 'package:Intaleq/controller/profile/profile_controller.dart'; import 'package:Intaleq/controller/profile/profile_controller.dart';
import 'package:Intaleq/main.dart'; import 'package:Intaleq/main.dart';
import 'package:Intaleq/views/home/profile/complaint_page.dart'; import 'package:Intaleq/views/home/profile/complaint_page.dart';
import 'package:intl/intl.dart';
import '../../../constant/colors.dart'; import '../../../constant/colors.dart';
import '../../../constant/links.dart'; import '../../../constant/links.dart';
@@ -14,67 +300,80 @@ import '../../../controller/functions/launch.dart';
import '../../../controller/functions/toast.dart'; import '../../../controller/functions/toast.dart';
import '../../../controller/home/map_passenger_controller.dart'; import '../../../controller/home/map_passenger_controller.dart';
// --- الويدجت الرئيسية بالتصميم الجديد ---
class RideBeginPassenger extends StatelessWidget { class RideBeginPassenger extends StatelessWidget {
const RideBeginPassenger({super.key}); const RideBeginPassenger({super.key});
@override @override
Widget build(BuildContext context) { Widget build(BuildContext context) {
// --- نفس منطق استدعاء الكنترولرز ---
final ProfileController profileController = Get.put(ProfileController()); final ProfileController profileController = Get.put(ProfileController());
final AudioRecorderController audioController = final AudioRecorderController audioController =
Get.put(AudioRecorderController()); Get.put(AudioRecorderController());
return GetBuilder<MapPassengerController>(builder: (controller) { return Obx(() {
// --- نفس شرط الإظهار الخاص بك --- final controller = Get.find<MapPassengerController>();
if (controller.statusRide != 'Begin') {
return const SizedBox.shrink();
}
return Positioned( // شرط الإظهار: تظهر فقط عندما تكون الرحلة جارية
final bool isVisible =
controller.currentRideState.value == RideState.inProgress &&
controller.isStartAppHasRide == false;
;
return AnimatedPositioned(
duration: const Duration(milliseconds: 500),
curve: Curves.easeOutBack, // حركة أكثر سلاسة
bottom: isVisible ? 0 : -Get.height * 0.6,
left: 0, left: 0,
right: 0, right: 0,
bottom: 0,
child: Container( child: Container(
decoration: BoxDecoration( decoration: BoxDecoration(
color: AppColor.secondaryColor, color: Colors.white, // خلفية بيضاء لنظافة التصميم
borderRadius: const BorderRadius.only( borderRadius: const BorderRadius.only(
topLeft: Radius.circular(24), topLeft: Radius.circular(30),
topRight: Radius.circular(24), topRight: Radius.circular(30),
), ),
boxShadow: [ boxShadow: [
BoxShadow( BoxShadow(
color: Colors.black.withOpacity(0.2), color: Colors.black.withOpacity(0.15),
blurRadius: 20, blurRadius: 25,
spreadRadius: 5,
offset: const Offset(0, -5), offset: const Offset(0, -5),
), ),
], ],
), ),
child: Padding( child: Padding(
padding: const EdgeInsets.fromLTRB(16, 12, 16, 16), padding: const EdgeInsets.fromLTRB(20, 12, 20, 20),
child: Column( child: Column(
mainAxisSize: MainAxisSize.min, mainAxisSize: MainAxisSize.min,
children: [ children: [
// مقبض السحب (Handle) // مقبض السحب
Container( Container(
width: 40, width: 50,
height: 5, height: 5,
decoration: BoxDecoration( decoration: BoxDecoration(
color: AppColor.writeColor.withOpacity(0.3), color: Colors.grey[300],
borderRadius: BorderRadius.circular(12), borderRadius: BorderRadius.circular(10),
), ),
), ),
const SizedBox(height: 12), const SizedBox(height: 20),
// --- 1. قسم معلومات السائق --- // الصف العلوي: معلومات السائق + السعر المثبت
_buildDriverInfo(controller), _buildDriverAndPriceSection(controller),
const Divider(height: 24, thickness: 0.5),
// --- 2. قسم تقدم الرحلة --- const SizedBox(height: 20),
// الصف الأوسط: لوحة السيارة الواقعية + نوع السيارة
_buildCarInfoSection(controller),
const SizedBox(height: 20),
// شريط التقدم والوقت
_buildTripProgress(controller), _buildTripProgress(controller),
const SizedBox(height: 16),
// --- 3. قسم الإجراءات والأمان --- const SizedBox(height: 20),
const Divider(thickness: 1, color: Color(0xFFEEEEEE)),
const SizedBox(height: 10),
// الأزرار
_buildActionButtons( _buildActionButtons(
context, controller, profileController, audioController), context, controller, profileController, audioController),
], ],
@@ -85,86 +384,227 @@ class RideBeginPassenger extends StatelessWidget {
}); });
} }
// --- ويدجت مساعدة لعرض معلومات السائق بشكل منظم --- // ويدجت معلومات السائق والسعر
Widget _buildDriverInfo(MapPassengerController controller) { Widget _buildDriverAndPriceSection(MapPassengerController controller) {
return Row( return Row(
children: [ children: [
CircleAvatar( // صورة السائق
radius: 28, Container(
backgroundImage: NetworkImage( decoration: BoxDecoration(
'${AppLink.server}/portrate_captain_image/${controller.driverId}.jpg'), shape: BoxShape.circle,
border: Border.all(color: AppColor.primaryColor, width: 2),
boxShadow: [
BoxShadow(
color: Colors.black.withOpacity(0.1),
blurRadius: 10,
offset: const Offset(0, 5),
),
],
),
child: CircleAvatar(
radius: 30,
backgroundImage: NetworkImage(
'${AppLink.server}/portrate_captain_image/${controller.driverId}.jpg'),
),
), ),
const SizedBox(width: 12), const SizedBox(width: 15),
// الاسم والتقييم
Expanded( Expanded(
child: Column( child: Column(
crossAxisAlignment: CrossAxisAlignment.start, crossAxisAlignment: CrossAxisAlignment.start,
children: [ children: [
Text(controller.driverName,
style: AppStyle.title.copyWith(fontWeight: FontWeight.bold)),
const SizedBox(height: 2),
Text( Text(
'${controller.make} ${controller.model}${box.read(BoxName.carType)}', controller.driverName,
style: AppStyle.subtitle style: AppStyle.title.copyWith(
.copyWith(color: AppColor.writeColor.withOpacity(0.7)), fontWeight: FontWeight.w800,
fontSize: 18,
color: Colors.black87,
),
maxLines: 1,
overflow: TextOverflow.ellipsis,
),
const SizedBox(height: 4),
Row(
children: [
const Icon(Icons.star_rounded,
color: AppColor.yellowColor, size: 18),
const SizedBox(width: 4),
Text(
controller.driverRate,
style: AppStyle.subtitle.copyWith(
fontWeight: FontWeight.bold, color: Colors.grey[600]),
),
],
), ),
], ],
), ),
), ),
const SizedBox(width: 12),
Column( // السعر المثبت (تصميم كارد للسعر)
crossAxisAlignment: CrossAxisAlignment.end, Container(
children: [ padding: const EdgeInsets.symmetric(horizontal: 16, vertical: 8),
Container( decoration: BoxDecoration(
padding: const EdgeInsets.symmetric(horizontal: 8, vertical: 4), color: AppColor.primaryColor.withOpacity(0.1),
decoration: BoxDecoration( borderRadius: BorderRadius.circular(12),
color: AppColor.writeColor.withOpacity(0.1), border: Border.all(color: AppColor.primaryColor.withOpacity(0.2)),
borderRadius: BorderRadius.circular(6), ),
child: Column(
children: [
Text('Total'.tr,
style: TextStyle(fontSize: 10, color: Colors.grey[600])),
Text(
'${NumberFormat('#,###').format(controller.totalPassenger)} 💰',
style: const TextStyle(
fontFamily: 'Roboto',
fontWeight: FontWeight.w900,
fontSize: 18,
color: AppColor.primaryColor,
),
), ),
child: Text( ],
controller.licensePlate, ),
style: AppStyle.subtitle ),
.copyWith(fontWeight: FontWeight.bold, letterSpacing: 1.5),
),
),
const SizedBox(height: 4),
Row(
children: [
Text(controller.driverRate,
style: AppStyle.subtitle
.copyWith(fontWeight: FontWeight.bold)),
const SizedBox(width: 2),
const Icon(Icons.star_rounded,
color: AppColor.yellowColor, size: 16),
],
),
],
)
], ],
); );
} }
// --- ويدجت مساعدة لعرض شريط التقدم --- // ويدجت معلومات السيارة ولوحة الأرقام
Widget _buildCarInfoSection(MapPassengerController controller) {
return Container(
padding: const EdgeInsets.all(12),
decoration: BoxDecoration(
color: const Color(0xFFF9F9F9),
borderRadius: BorderRadius.circular(16),
border: Border.all(color: Colors.grey[200]!),
),
child: Row(
mainAxisAlignment: MainAxisAlignment.spaceBetween,
children: [
// نوع السيارة وموديلها
Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
Text(
'${controller.make} ${controller.model}',
style: const TextStyle(
fontWeight: FontWeight.bold,
fontSize: 16,
color: Colors.black87),
),
const SizedBox(height: 4),
Container(
padding: const EdgeInsets.symmetric(horizontal: 8, vertical: 2),
decoration: BoxDecoration(
color: Colors.grey[300],
borderRadius: BorderRadius.circular(4),
),
child: Text(
box.read(BoxName.carType) == 'Speed'
? 'Fixed Price'
.tr // سيظهر "سعر ثابت" (تأكد من إضافتها لملف الترجمة)
: box.read(BoxName.carType) ?? 'Car',
style: const TextStyle(fontSize: 12, color: Colors.black54),
),
),
],
),
// -------------------------------------------
// تصميم لوحة السيارة الواقعي (Realistic Plate)
// -------------------------------------------
Container(
padding: const EdgeInsets.symmetric(horizontal: 2, vertical: 2),
decoration: BoxDecoration(
color: Colors.white,
borderRadius: BorderRadius.circular(6),
border: Border.all(color: Colors.black, width: 2), // إطار أسود
boxShadow: [
BoxShadow(
color: Colors.black.withOpacity(0.1),
blurRadius: 4,
offset: const Offset(2, 2)),
],
),
child: Row(
mainAxisSize: MainAxisSize.min,
children: [
// الشريط الأزرق الجانبي (مثل اللوحات الدولية)
Container(
width: 15,
height: 35, // ارتفاع اللوحة
decoration: const BoxDecoration(
color: Color(0xFF003399), // أزرق غامق
borderRadius: BorderRadius.only(
topLeft: Radius.circular(2),
bottomLeft: Radius.circular(2),
),
),
child: const Center(
child: Text(
"SY", // رمز الدولة (مثال)
style: TextStyle(
color: Colors.white,
fontSize: 8,
fontWeight: FontWeight.bold),
),
),
),
const SizedBox(width: 8),
// رقم اللوحة
Text(
controller.licensePlate, // رقم اللوحة من الكونترولر
style: const TextStyle(
fontSize: 20,
fontWeight: FontWeight.w900,
letterSpacing: 2.0, // تباعد الأحرف لتبدو كأرقام محفورة
fontFamily: 'monospace', // خط ثابت العرض ليشبه اللوحات
color: Colors.black,
),
),
const SizedBox(width: 8),
],
),
),
],
),
);
}
Widget _buildTripProgress(MapPassengerController controller) { Widget _buildTripProgress(MapPassengerController controller) {
return Column( return Column(
children: [ children: [
Row( Row(
mainAxisAlignment: MainAxisAlignment.spaceBetween, mainAxisAlignment: MainAxisAlignment.spaceBetween,
children: [ children: [
Text('Time to Destination'.tr, style: AppStyle.subtitle), Row(
Text(controller.stringRemainingTimeRideBegin, children: [
style: AppStyle.subtitle.copyWith( const Icon(Icons.access_time_filled,
fontWeight: FontWeight.bold, color: AppColor.primaryColor)), size: 16, color: Colors.grey),
const SizedBox(width: 5),
Text('Arriving in'.tr,
style: TextStyle(color: Colors.grey[600], fontSize: 13)),
],
),
Text(
controller.stringRemainingTimeRideBegin,
style: const TextStyle(
fontWeight: FontWeight.bold,
fontSize: 16,
color: AppColor.primaryColor,
),
),
], ],
), ),
const SizedBox(height: 8), const SizedBox(height: 10),
ClipRRect( ClipRRect(
borderRadius: BorderRadius.circular(10), borderRadius: BorderRadius.circular(10),
child: LinearProgressIndicator( child: LinearProgressIndicator(
backgroundColor: AppColor.primaryColor.withOpacity(0.2), backgroundColor: Colors.grey[200],
color: controller.remainingTimeTimerRideBegin < 60 color: controller.remainingTimeTimerRideBegin < 60
? AppColor.redColor ? AppColor.redColor
: AppColor.greenColor, : AppColor.primaryColor,
minHeight: 10, minHeight: 8,
value: controller.progressTimerRideBegin.toDouble(), value: controller.progressTimerRideBegin.toDouble(),
), ),
), ),
@@ -172,21 +612,21 @@ class RideBeginPassenger extends StatelessWidget {
); );
} }
// --- ويدجت مساعدة لعرض أزرار الإجراءات ---
Widget _buildActionButtons( Widget _buildActionButtons(
BuildContext context, BuildContext context,
MapPassengerController controller, MapPassengerController controller,
ProfileController profileController, ProfileController profileController,
AudioRecorderController audioController) { AudioRecorderController audioController) {
return Row( return Row(
mainAxisAlignment: MainAxisAlignment.spaceAround, mainAxisAlignment: MainAxisAlignment.spaceEvenly,
children: [ children: [
// زر SOS بتصميم تحذيري
_buildActionButton( _buildActionButton(
icon: Icons.sos_rounded, icon: Icons.sos_rounded,
label: 'SOS'.tr, label: 'SOS',
color: AppColor.redColor, iconColor: Colors.white,
bgColor: AppColor.redColor,
onTap: () async { onTap: () async {
// --- نفس منطقك القديم ---
if (box.read(BoxName.sosPhonePassenger) == null) { if (box.read(BoxName.sosPhonePassenger) == null) {
await profileController.updatField( await profileController.updatField(
'sosPhone', TextInputType.phone); 'sosPhone', TextInputType.phone);
@@ -196,12 +636,14 @@ class RideBeginPassenger extends StatelessWidget {
makePhoneCall('112'); makePhoneCall('112');
} }
}), }),
// زر واتساب
_buildActionButton( _buildActionButton(
icon: FontAwesome.whatsapp, icon: FontAwesome.whatsapp,
label: 'WhatsApp'.tr, label: 'WhatsApp',
color: AppColor.greenColor, iconColor: Colors.white,
bgColor: const Color(0xFF25D366),
onTap: () async { onTap: () async {
// --- نفس منطقك القديم ---
if (box.read(BoxName.sosPhonePassenger) == null || if (box.read(BoxName.sosPhonePassenger) == null ||
box.read(BoxName.sosPhonePassenger) == 'sos') { box.read(BoxName.sosPhonePassenger) == 'sos') {
await profileController.updatField( await profileController.updatField(
@@ -211,75 +653,105 @@ class RideBeginPassenger extends StatelessWidget {
} else { } else {
final phoneNumber = final phoneNumber =
box.read(BoxName.sosPhonePassenger).toString(); box.read(BoxName.sosPhonePassenger).toString();
final phone = controller.formatSyrianPhoneNumber(phoneNumber); final phone = controller.formatSyrianPhoneNumber(phoneNumber);
controller.sendWhatsapp(phone); // controller.sendWhatsapp(phone);
} }
}), }),
// زر المشاركة
_buildActionButton( _buildActionButton(
icon: Icons.share_location_outlined, // أيقونة جديدة ومناسبة icon: Icons.share_location_rounded,
label: 'Share'.tr, // اسم جديد وواضح label: 'Share',
color: AppColor.blueColor, iconColor: AppColor.primaryColor,
bgColor: AppColor.primaryColor.withOpacity(0.1),
onTap: () async { onTap: () async {
// نفس الوظيفة السابقة التي كانت تحت اسم "Video Call" await controller.shareTripWithFamily();
await controller.getTokenForParent();
}), }),
_buildActionButton(
icon: audioController.isRecording // زر التسجيل
? Icons.mic_off_rounded GetBuilder<AudioRecorderController>(
: Icons.mic_none_rounded, init: audioController,
label: audioController.isRecording ? 'Stop'.tr : 'Record'.tr, builder: (audioCtx) {
color: AppColor.primaryColor, return _buildActionButton(
onTap: () async { icon: audioCtx.isRecording ? Icons.stop : Icons.mic,
// --- نفس منطقك القديم --- label: audioCtx.isRecording ? 'Stop' : 'Record',
if (audioController.isRecording == false) { iconColor:
await audioController.startRecording(); audioCtx.isRecording ? Colors.white : AppColor.primaryColor,
Toast.show(context, 'Start Record'.tr, AppColor.greenColor); bgColor: audioCtx.isRecording
} else { ? AppColor.redColor
await audioController.stopRecording(); : AppColor.primaryColor.withOpacity(0.1),
Toast.show(context, 'Record saved'.tr, AppColor.greenColor); isRecordingAnimation: audioCtx.isRecording,
} onTap: () async {
if (audioCtx.isRecording == false) {
await audioCtx.startRecording();
Toast.show(context, 'Start Record'.tr, AppColor.greenColor);
} else {
await audioCtx.stopRecording();
Toast.show(context, 'Record saved'.tr, AppColor.greenColor);
}
},
);
}, },
), ),
// زر الشكوى
_buildActionButton( _buildActionButton(
icon: Icons.note_add_outlined, icon: Icons.report_gmailerrorred_rounded,
label: 'Complaint'.tr, label: 'Report'.tr,
color: AppColor.yellowColor, iconColor: Colors.grey[700]!,
bgColor: Colors.grey[200]!,
onTap: () { onTap: () {
// --- نفس منطقك القديم ---
Get.to(() => ComplaintPage(), transition: Transition.downToUp); Get.to(() => ComplaintPage(), transition: Transition.downToUp);
}), }),
], ],
); );
} }
// --- ويدجت مساعدة لبناء زر إجراء فردي --- Widget _buildActionButton({
Widget _buildActionButton( required IconData icon,
{required IconData icon, required String label,
required String label, required Color iconColor,
required Color color, required Color bgColor,
required VoidCallback onTap}) { required VoidCallback onTap,
return InkWell( bool isRecordingAnimation = false,
onTap: onTap, }) {
borderRadius: BorderRadius.circular(12), return Column(
child: Padding( mainAxisSize: MainAxisSize.min,
padding: const EdgeInsets.all(4.0), children: [
child: Column( InkWell(
mainAxisSize: MainAxisSize.min, onTap: onTap,
children: [ borderRadius: BorderRadius.circular(15),
Container( child: AnimatedContainer(
padding: const EdgeInsets.all(12), duration: const Duration(milliseconds: 300),
decoration: BoxDecoration( width: 50,
color: color.withOpacity(0.1), height: 50,
shape: BoxShape.circle, decoration: BoxDecoration(
), color: bgColor,
child: Icon(icon, color: color, size: 26), borderRadius: BorderRadius.circular(15),
border: isRecordingAnimation
? Border.all(color: AppColor.redColor, width: 2)
: null,
boxShadow: [
BoxShadow(
color: bgColor.withOpacity(0.3),
blurRadius: 8,
offset: const Offset(0, 3),
),
],
), ),
const SizedBox(height: 6), child: Icon(icon, color: iconColor, size: 24),
Text(label, style: AppStyle.subtitle.copyWith(fontSize: 12)), ),
],
), ),
), const SizedBox(height: 6),
Text(
label.tr,
style: const TextStyle(
fontSize: 11,
fontWeight: FontWeight.w600,
color: Colors.black54,
),
),
],
); );
} }
} }

View File

@@ -1,9 +1,12 @@
import 'package:Intaleq/controller/functions/launch.dart';
import 'package:flutter/material.dart'; import 'package:flutter/material.dart';
import 'package:flutter_font_icons/flutter_font_icons.dart'; import 'package:flutter_font_icons/flutter_font_icons.dart';
import 'package:get/get.dart'; import 'package:get/get.dart';
// ... استيراد ملفاتك الأخرى ...
import '../../../constant/box_name.dart'; import '../../../constant/box_name.dart';
import '../../../constant/colors.dart'; import '../../../constant/colors.dart';
import '../../../constant/links.dart';
import '../../../constant/style.dart'; import '../../../constant/style.dart';
import '../../../controller/home/map_passenger_controller.dart'; import '../../../controller/home/map_passenger_controller.dart';
import '../../../controller/profile/profile_controller.dart'; import '../../../controller/profile/profile_controller.dart';
@@ -14,161 +17,299 @@ class RideFromStartApp extends StatelessWidget {
@override @override
Widget build(BuildContext context) { Widget build(BuildContext context) {
ProfileController profileController = Get.put(ProfileController()); final profileController = Get.put(ProfileController());
return GetBuilder<MapPassengerController>(builder: (controller) { final MapPassengerController controller =
return (controller.statusRideFromStart Get.find<MapPassengerController>();
// || controller.statusRide == 'Begin'
) return Obx(() {
? Positioned( final bool isRideActive =
left: 10, controller.currentRideState.value == RideState.inProgress &&
right: 10, controller.isStartAppHasRide == true;
bottom: 4,
child: Container( if (!isRideActive) return const SizedBox();
decoration: AppStyle.boxDecoration1,
height: Get.height * .3, // قراءة البيانات
child: Column( final rideData = controller.rideStatusFromStartApp['data'] ?? {};
mainAxisAlignment: MainAxisAlignment.spaceEvenly, final driverId = rideData['driver_id'];
crossAxisAlignment: CrossAxisAlignment.start, final driverName = rideData['driverName'] ?? 'Captain'.tr;
children: <Widget>[ final driverRate = controller.driverRate;
Row( final carType = rideData['carType'] ?? 'Car'.tr;
mainAxisAlignment: MainAxisAlignment.spaceEvenly, final carModel = controller.model ?? '';
children: [ final arrivalTime = box.read(BoxName.arrivalTime) ?? '--:--';
Container(
width: Get.width * .15, // تحديد البيانات للعرض
decoration: AppStyle.boxDecoration1, final displayTime = controller.stringRemainingTimeRideBegin.isNotEmpty
child: Column( ? controller.stringRemainingTimeRideBegin
children: [ : arrivalTime;
Text( final displayDistance = rideData['distance']?.toStringAsFixed(1) ?? 'N/A';
'⏱️', final displayPrice = rideData['price']?.toString() ?? 'N/A';
style: AppStyle.title,
), return Positioned(
Text( left: 0,
box.read(BoxName.arrivalTime), right: 0,
style: AppStyle.title, bottom: 0, // ملتصق بالأسفل تماماً
), child: Container(
], padding: const EdgeInsets.symmetric(horizontal: 20, vertical: 15),
), decoration: const BoxDecoration(
), color: Colors.white, // خلفية بيضاء نظيفة
Container( borderRadius: BorderRadius.only(
width: Get.width * .15, topLeft: Radius.circular(25),
decoration: AppStyle.boxDecoration1, topRight: Radius.circular(25),
child: Column( ),
children: [ boxShadow: [
Text( BoxShadow(
'📍', color: Colors.black12,
style: AppStyle.title, blurRadius: 15.0,
), spreadRadius: 5.0,
Text( offset: Offset(0, -5),
controller.rideStatusFromStartApp['data'] ),
['distance'] ],
.toString(), ),
style: AppStyle.title, child: Column(
), mainAxisSize: MainAxisSize.min,
], crossAxisAlignment: CrossAxisAlignment.start,
), children: <Widget>[
), // 1. مقبض صغير للدلالة على السحب (تصميم جمالي)
Container( Center(
width: Get.width * .15, child: Container(
decoration: AppStyle.boxDecoration1, width: 40,
child: Column( height: 4,
children: [ margin: const EdgeInsets.only(bottom: 15),
Text( decoration: BoxDecoration(
'💵 ', color: Colors.grey[300],
style: AppStyle.title, borderRadius: BorderRadius.circular(10),
), ),
Text( ),
controller.rideStatusFromStartApp['data'] ),
['price'],
style: AppStyle.title, // 2. حالة الرحلة + معلومات السائق
), Row(
], crossAxisAlignment: CrossAxisAlignment.start,
), children: [
), // صورة السائق
], Container(
padding: const EdgeInsets.all(2),
decoration: BoxDecoration(
shape: BoxShape.circle,
border:
Border.all(color: AppColor.primaryColor, width: 2),
), ),
Row( child: CircleAvatar(
mainAxisAlignment: MainAxisAlignment.spaceEvenly, radius: 28,
backgroundImage: NetworkImage(
'${AppLink.server}/portrate_captain_image/$driverId.jpg'),
),
),
const SizedBox(width: 12),
// الاسم والسيارة
Expanded(
child: Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: [ children: [
CircleAvatar(
radius: 30,
backgroundImage: NetworkImage(
// '',
// ),
'https://ride.mobile-app.store/portrate_captain_image/${controller.rideStatusFromStartApp['data']['driver_id']}.jpg'),
),
Text( Text(
'${controller.rideStatusFromStartApp['data']['driverName']}', driverName,
style: AppStyle.title, style: AppStyle.title.copyWith(
fontSize: 16,
fontWeight: FontWeight.bold,
color: Colors.black87,
),
maxLines: 1,
overflow: TextOverflow.ellipsis,
), ),
Column( const SizedBox(height: 4),
Row(
children: [ children: [
Icon(Icons.star,
color: AppColor.yellowColor, size: 16),
const SizedBox(width: 4),
Text( Text(
'${controller.rideStatusFromStartApp['data']['rateDriver']} 📈', driverRate,
style: AppStyle.title, style: AppStyle.title.copyWith(
fontSize: 13, fontWeight: FontWeight.bold),
), ),
const SizedBox(width: 8),
Container(width: 1, height: 12, color: Colors.grey),
const SizedBox(width: 8),
Text( Text(
'${controller.rideStatusFromStartApp['data']['carType']}', "$carType - $carModel",
style: AppStyle.title, style: AppStyle.title.copyWith(
fontSize: 13, color: Colors.grey[600]),
), ),
], ],
), ),
], ],
), ),
Row( ),
mainAxisAlignment: MainAxisAlignment.spaceAround,
children: [ // حالة الرحلة (نص ملون)
IconButton( _buildStatusBadge(controller.currentRideState.value),
onPressed: () async { ],
if (box.read(BoxName.sosPhonePassenger) == null) { ),
{
await profileController.updatField( const SizedBox(height: 20),
'sosPhone', TextInputType.phone);
box.write(BoxName.sosPhonePassenger, // 3. شريط المعلومات (وقت، مسافة، سعر)
profileController.prfoileData['sosPhone']); Container(
} padding:
} else { const EdgeInsets.symmetric(vertical: 12, horizontal: 10),
controller decoration: BoxDecoration(
.sendSMS(box.read(BoxName.sosPhonePassenger)); color: AppColor.grayColor
} .withOpacity(0.1), // خلفية رمادية خفيفة جداً
}, borderRadius: BorderRadius.circular(15),
icon: const Icon( ),
Icons.sos_rounded, child: Row(
color: AppColor.redColor, mainAxisAlignment: MainAxisAlignment.spaceAround,
), children: [
), _buildInfoColumn(
IconButton( Icons.access_time_filled, displayTime, "Time".tr),
onPressed: () async { _buildVerticalDivider(),
if (box.read(BoxName.sosPhonePassenger) == null || _buildInfoColumn(Icons.location_on, "$displayDistance KM",
box.read(BoxName.sosPhonePassenger) == 'sos') { "Distance".tr),
{ _buildVerticalDivider(),
await profileController.updatField( _buildInfoColumn(
'sosPhone', TextInputType.phone); Icons.attach_money, "$displayPrice SYP", "Price".tr),
box.write(BoxName.sosPhonePassenger,
profileController.prfoileData['sosPhone']);
}
} else {
String phoneNumber = box
.read(BoxName.sosPhonePassenger)
.toString();
// phoneNumber = phoneNumber.replaceAll('0', '');
var phone =
// '+${box.read(BoxName.countryCode)}${box.read(BoxName.sosPhonePassenger)}';
'${box.read(BoxName.sosPhonePassenger)}';
controller.sendWhatsapp(phone);
}
},
icon: const Icon(
FontAwesome.whatsapp,
color: AppColor.greenColor,
),
),
],
)
], ],
), ),
), ),
)
: const SizedBox(); const SizedBox(height: 20),
// 4. أزرار التحكم (SOS & Share)
Row(
children: [
// زر المشاركة (يأخذ مساحة أكبر)
Expanded(
flex: 2,
child: ElevatedButton.icon(
onPressed: () => _checkAndCall(
controller.sendWhatsapp, profileController),
icon:
const Icon(FontAwesome.whatsapp, color: Colors.white),
label: Text("Share Trip".tr,
style: const TextStyle(color: Colors.white)),
style: ElevatedButton.styleFrom(
backgroundColor: AppColor.greenColor,
padding: const EdgeInsets.symmetric(vertical: 12),
shape: RoundedRectangleBorder(
borderRadius: BorderRadius.circular(12),
),
elevation: 0,
),
),
),
const SizedBox(width: 10),
// زر الاستغاثة SOS
Expanded(
flex: 1,
child: ElevatedButton.icon(
onPressed: () =>
makePhoneCall(box.read(BoxName.sosPhonePassenger)),
icon: const Icon(Icons.sos, color: Colors.white),
label: Text("SOS".tr,
style: const TextStyle(color: Colors.white)),
style: ElevatedButton.styleFrom(
backgroundColor: AppColor.redColor,
padding: const EdgeInsets.symmetric(vertical: 12),
shape: RoundedRectangleBorder(
borderRadius: BorderRadius.circular(12),
),
elevation: 0,
),
),
),
],
),
],
),
),
);
}); });
} }
// ------------------------- Widgets Helper Methods -------------------------
Widget _buildStatusBadge(RideState status) {
String text;
Color color;
if (status == RideState.inProgress) {
text = 'On Trip'.tr;
color = AppColor.primaryColor;
} else if (status == RideState.driverArrived) {
text = 'Arrived'.tr;
color = AppColor.greenColor;
} else {
text = 'Coming'.tr;
color = AppColor.yellowColor;
}
return Container(
padding: const EdgeInsets.symmetric(horizontal: 10, vertical: 5),
decoration: BoxDecoration(
color: color.withOpacity(0.1),
borderRadius: BorderRadius.circular(8),
border: Border.all(color: color.withOpacity(0.5)),
),
child: Text(
text,
style: TextStyle(
color: color,
fontWeight: FontWeight.bold,
fontSize: 12,
),
),
);
}
Widget _buildInfoColumn(IconData icon, String value, String label) {
return Column(
children: [
Icon(icon,
color: AppColor.secondaryColor,
size: 22), // افترضت أن السكندري لون داكن، أو استخدم Primary
const SizedBox(height: 4),
Text(
value,
style: const TextStyle(
fontWeight: FontWeight.w800,
fontSize: 15,
color: Colors.black87,
),
),
Text(
label,
style: TextStyle(
fontSize: 11,
color: Colors.grey[600],
),
),
],
);
}
Widget _buildVerticalDivider() {
return Container(
height: 30,
width: 1,
color: Colors.grey[300],
);
}
// دالة المساعدة للمنطق (بقيت كما هي ولكن تم تمرير البروفايل كونترولر)
Future<void> _checkAndCall(
Function(String) action, ProfileController profileController) async {
String? sosPhone = box.read(BoxName.sosPhonePassenger);
if (sosPhone == null || sosPhone == 'sos') {
await profileController.updatField('sosPhone', TextInputType.phone);
sosPhone = profileController.prfoileData['sosPhone'];
}
if (sosPhone != null && sosPhone != 'sos') {
action(sosPhone);
} else {
Get.snackbar('Warning'.tr, 'Please set a valid SOS phone number.'.tr,
backgroundColor: AppColor.redColor, colorText: Colors.white);
}
}
} }

View File

@@ -1,11 +1,8 @@
import 'dart:async';
import 'package:flutter/material.dart'; import 'package:flutter/material.dart';
import 'package:get/get.dart'; import 'package:get/get.dart';
import 'package:Intaleq/constant/colors.dart'; import 'package:Intaleq/constant/colors.dart';
import 'package:Intaleq/constant/style.dart'; import 'package:Intaleq/constant/style.dart';
import 'package:Intaleq/controller/home/map_passenger_controller.dart'; import 'package:Intaleq/controller/home/map_passenger_controller.dart';
import 'package:Intaleq/views/widgets/elevated_btn.dart';
import 'package:Intaleq/views/widgets/my_textField.dart';
// --- الويدجت الرئيسية بالتصميم الجديد --- // --- الويدجت الرئيسية بالتصميم الجديد ---
class SearchingCaptainWindow extends StatefulWidget { class SearchingCaptainWindow extends StatefulWidget {
@@ -36,62 +33,68 @@ class _SearchingCaptainWindowState extends State<SearchingCaptainWindow>
@override @override
Widget build(BuildContext context) { Widget build(BuildContext context) {
return GetBuilder<MapPassengerController>( // [تعديل 1] نستخدم Obx للاستماع إلى التغييرات في حالة الرحلة
builder: (controller) { return Obx(() {
return AnimatedPositioned( // ابحث عن الكنترولر مرة واحدة
duration: const Duration(milliseconds: 300), final controller = Get.find<MapPassengerController>();
curve: Curves.easeInOut,
bottom: controller.isSearchingWindow ? 0 : -Get.height * 0.4,
left: 0,
right: 0,
child: Container(
padding: const EdgeInsets.fromLTRB(20, 20, 20, 16),
decoration: BoxDecoration(
color: AppColor.secondaryColor,
borderRadius: const BorderRadius.only(
topLeft: Radius.circular(24),
topRight: Radius.circular(24),
),
boxShadow: [
BoxShadow(
color: Colors.black.withOpacity(0.2),
blurRadius: 20,
offset: const Offset(0, -5),
),
],
),
child: Column(
mainAxisSize: MainAxisSize.min,
children: [
// --- 1. أنيميشن الرادار ---
_buildRadarAnimation(controller),
const SizedBox(height: 20),
// --- 2. زر الإلغاء --- // [تعديل 2] شرط الإظهار يعتمد الآن على حالة الرحلة مباشرة
SizedBox( final bool isVisible =
width: double.infinity, controller.currentRideState.value == RideState.searching;
child: OutlinedButton(
onPressed: () { return AnimatedPositioned(
// --- نفس منطقك للإلغاء --- duration: const Duration(milliseconds: 300),
controller.cancelRide(); curve: Curves.easeInOut,
}, bottom: isVisible ? 0 : -Get.height * 0.45, // زيادة الارتفاع قليلاً
style: OutlinedButton.styleFrom( left: 0,
foregroundColor: AppColor.writeColor, right: 0,
side: BorderSide( child: Container(
color: AppColor.writeColor.withOpacity(0.3)), padding: const EdgeInsets.fromLTRB(20, 20, 20, 16),
shape: RoundedRectangleBorder( decoration: BoxDecoration(
borderRadius: BorderRadius.circular(12)), color: AppColor.secondaryColor,
padding: const EdgeInsets.symmetric(vertical: 12), borderRadius: const BorderRadius.only(
), topLeft: Radius.circular(24),
child: Text('Cancel Search'.tr), topRight: Radius.circular(24),
),
),
],
), ),
boxShadow: [
BoxShadow(
color: Colors.black.withOpacity(0.2),
blurRadius: 20,
offset: const Offset(0, -5),
),
],
), ),
); child: Column(
}, mainAxisSize: MainAxisSize.min,
); children: [
// --- 1. أنيميشن الرادار ---
_buildRadarAnimation(controller),
const SizedBox(height: 20),
// --- 2. زر الإلغاء ---
SizedBox(
width: double.infinity,
child: OutlinedButton(
onPressed: () {
// [تعديل 3] استدعاء دالة الإلغاء الموحدة
controller.cancelRide();
},
style: OutlinedButton.styleFrom(
foregroundColor: AppColor.writeColor,
side:
BorderSide(color: AppColor.writeColor.withOpacity(0.3)),
shape: RoundedRectangleBorder(
borderRadius: BorderRadius.circular(12)),
padding: const EdgeInsets.symmetric(vertical: 12),
),
child: Text('Cancel Search'.tr),
),
),
],
),
),
);
});
} }
// --- ويدجت بناء أنيميشن الرادار --- // --- ويدجت بناء أنيميشن الرادار ---
@@ -101,7 +104,7 @@ class _SearchingCaptainWindowState extends State<SearchingCaptainWindow>
child: Stack( child: Stack(
alignment: Alignment.center, alignment: Alignment.center,
children: [ children: [
// --- دوائر الرادار المتحركة --- // --- دوائر الرادار المتحركة (تبقى كما هي) ---
...List.generate(3, (index) { ...List.generate(3, (index) {
return FadeTransition( return FadeTransition(
opacity: Tween<double>(begin: 1.0, end: 0.0).animate( opacity: Tween<double>(begin: 1.0, end: 0.0).animate(
@@ -134,6 +137,7 @@ class _SearchingCaptainWindowState extends State<SearchingCaptainWindow>
mainAxisAlignment: MainAxisAlignment.center, mainAxisAlignment: MainAxisAlignment.center,
children: [ children: [
Text( Text(
// [تعديل 4] النص يأتي مباشرة من الكنترولر
controller.driversStatusForSearchWindow, controller.driversStatusForSearchWindow,
style: AppStyle.headTitle.copyWith(fontSize: 20), style: AppStyle.headTitle.copyWith(fontSize: 20),
textAlign: TextAlign.center, textAlign: TextAlign.center,
@@ -146,8 +150,32 @@ class _SearchingCaptainWindowState extends State<SearchingCaptainWindow>
textAlign: TextAlign.center, textAlign: TextAlign.center,
), ),
const SizedBox(height: 16), const SizedBox(height: 16),
// --- استدعاء نفس دالة المؤقت الخاصة بك ---
buildTimerForIncrease(controller), // --- [!! تعديل جوهري !!] ---
// لم نعد بحاجة لـ buildTimerForIncrease
// المؤقت الرئيسي في الكنترولر هو من يقرر متى يعرض الحوار
// وهذا الجزء من الواجهة أصبح "غبياً" (لا يحتوي على منطق)
// الكنترولر سيستدعي _showIncreaseFeeDialog مباشرة
SizedBox(
height: 40,
width: 40,
child: Stack(
fit: StackFit.expand,
children: [
CircularProgressIndicator(
strokeWidth: 3,
color: AppColor.primaryColor,
backgroundColor: AppColor.primaryColor.withOpacity(0.2),
),
Center(
child: Icon(
Icons.search,
color: AppColor.writeColor.withOpacity(0.8),
),
),
],
),
),
], ],
), ),
], ],
@@ -156,128 +184,285 @@ class _SearchingCaptainWindowState extends State<SearchingCaptainWindow>
} }
} }
// --- نفس دالة المؤقت الخاصة بك مع تعديلات شكلية بسيطة --- // --- [!! تعديل جوهري !!] ---
Widget buildTimerForIncrease(MapPassengerController mapPassengerController) { // تم حذف دالة `buildTimerForIncrease` بالكامل.
return StreamBuilder<int>( // تم حذف دالة `_showIncreaseFeeDialog` من هذا الملف.
stream: Stream.periodic(const Duration(seconds: 1)) // لماذا؟ لأن الكنترولر الآن هو المسؤول الوحيد عن إظهار الحوار.
.map((_) => ++mapPassengerController.currentTimeSearchingCaptainWindow), // دالة `_showIncreaseFeeDialog` موجودة بالفعل داخل `map_passenger_controller.dart`
initialData: 0, // وسيتم استدعاؤها من `_handleRideState` عند انتهاء مهلة الـ 90 ثانية.
builder: (context, snapshot) {
if (snapshot.hasData && snapshot.data! > 30) {
// --- عرض زر زيادة الأجرة بنفس منطقك القديم ---
return TextButton(
onPressed: () =>
_showIncreaseFeeDialog(context, mapPassengerController),
child: Text(
"No one accepted? Try increasing the fare.".tr,
style: AppStyle.title.copyWith(
color: AppColor.primaryColor,
decoration: TextDecoration.underline),
textAlign: TextAlign.center,
),
);
}
final double progress = (snapshot.data ?? 0).toDouble() / 30.0; // // --- الويدجت الرئيسية بالتصميم الجديد ---
// class SearchingCaptainWindow extends StatefulWidget {
// const SearchingCaptainWindow({super.key});
return SizedBox( // @override
height: 40, // State<SearchingCaptainWindow> createState() => _SearchingCaptainWindowState();
width: 40, // }
child: Stack(
fit: StackFit.expand,
children: [
CircularProgressIndicator(
value: progress,
strokeWidth: 3,
color: AppColor.primaryColor,
backgroundColor: AppColor.primaryColor.withOpacity(0.2),
),
Center(
child: Text(
'${snapshot.data ?? 0}',
style: AppStyle.title.copyWith(
color: AppColor.writeColor, fontWeight: FontWeight.bold),
),
),
],
),
);
},
);
}
// --- دالة لعرض نافذة زيادة الأجرة (مأخوذة من منطقك القديم) --- // class _SearchingCaptainWindowState extends State<SearchingCaptainWindow>
void _showIncreaseFeeDialog( // with SingleTickerProviderStateMixin {
BuildContext context, MapPassengerController mapPassengerController) { // late AnimationController _animationController;
Get.defaultDialog(
barrierDismissible: false, // @override
title: "Increase Your Trip Fee (Optional)".tr, // void initState() {
titleStyle: AppStyle.title, // super.initState();
content: Column( // _animationController = AnimationController(
children: [ // vsync: this,
Text( // duration: const Duration(seconds: 2),
"We haven't found any drivers yet. Consider increasing your trip fee to make your offer more attractive to drivers." // )..repeat();
.tr, // }
style: AppStyle.subtitle,
textAlign: TextAlign.center, // @override
), // void dispose() {
const SizedBox(height: 16), // _animationController.dispose();
Row( // super.dispose();
mainAxisAlignment: MainAxisAlignment.center, // }
children: [
IconButton( // @override
onPressed: () { // Widget build(BuildContext context) {
mapPassengerController.increasFeeFromPassenger.text = // return GetBuilder<MapPassengerController>(
(mapPassengerController.totalPassenger + 3) // builder: (controller) {
.toStringAsFixed(1); // return AnimatedPositioned(
mapPassengerController.update(); // duration: const Duration(milliseconds: 300),
}, // curve: Curves.easeInOut,
icon: const Icon(Icons.add_circle, // bottom: controller.isSearchingWindow ? 0 : -Get.height * 0.4,
size: 40, color: AppColor.greenColor), // left: 0,
), // right: 0,
SizedBox( // child: Container(
width: 100, // padding: const EdgeInsets.fromLTRB(20, 20, 20, 16),
child: Form( // decoration: BoxDecoration(
key: mapPassengerController.increaseFeeFormKey, // color: AppColor.secondaryColor,
child: MyTextForm( // borderRadius: const BorderRadius.only(
controller: mapPassengerController.increasFeeFromPassenger, // topLeft: Radius.circular(24),
label: // topRight: Radius.circular(24),
mapPassengerController.totalPassenger.toStringAsFixed(2), // ),
hint: // boxShadow: [
mapPassengerController.totalPassenger.toStringAsFixed(2), // BoxShadow(
type: TextInputType.number, // color: Colors.black.withOpacity(0.2),
), // blurRadius: 20,
), // offset: const Offset(0, -5),
), // ),
IconButton( // ],
onPressed: () { // ),
mapPassengerController.increasFeeFromPassenger.text = // child: Column(
(mapPassengerController.totalPassenger - 3) // mainAxisSize: MainAxisSize.min,
.toStringAsFixed(1); // children: [
mapPassengerController.update(); // // --- 1. أنيميشن الرادار ---
}, // _buildRadarAnimation(controller),
icon: const Icon(Icons.remove_circle, // const SizedBox(height: 20),
size: 40, color: AppColor.redColor),
), // // --- 2. زر الإلغاء ---
], // SizedBox(
), // width: double.infinity,
], // child: OutlinedButton(
), // onPressed: () {
actions: [ // // --- نفس منطقك للإلغاء ---
TextButton( // controller.changeCancelRidePageShow();
child: Text("No, thanks".tr, // },
style: const TextStyle(color: AppColor.redColor)), // style: OutlinedButton.styleFrom(
onPressed: () { // foregroundColor: AppColor.writeColor,
Get.back(); // side: BorderSide(
mapPassengerController.cancelRide(); // color: AppColor.writeColor.withOpacity(0.3)),
}, // shape: RoundedRectangleBorder(
), // borderRadius: BorderRadius.circular(12)),
ElevatedButton( // padding: const EdgeInsets.symmetric(vertical: 12),
style: ElevatedButton.styleFrom(backgroundColor: AppColor.greenColor), // ),
child: Text("Increase Fee".tr), // child: Text('Cancel Search'.tr),
onPressed: () => // ),
mapPassengerController.increaseFeeByPassengerAndReOrder(), // ),
), // ],
], // ),
); // ),
} // );
// },
// );
// }
// // --- ويدجت بناء أنيميشن الرادار ---
// Widget _buildRadarAnimation(MapPassengerController controller) {
// return SizedBox(
// height: 180, // ارتفاع ثابت لمنطقة الأنيميشن
// child: Stack(
// alignment: Alignment.center,
// children: [
// // --- دوائر الرادار المتحركة ---
// ...List.generate(3, (index) {
// return FadeTransition(
// opacity: Tween<double>(begin: 1.0, end: 0.0).animate(
// CurvedAnimation(
// parent: _animationController,
// curve: Interval((index) / 3, 1.0, curve: Curves.easeInOut),
// ),
// ),
// child: ScaleTransition(
// scale: Tween<double>(begin: 0.3, end: 1.0).animate(
// CurvedAnimation(
// parent: _animationController,
// curve: Interval((index) / 3, 1.0, curve: Curves.easeInOut),
// ),
// ),
// child: Container(
// decoration: BoxDecoration(
// shape: BoxShape.circle,
// border: Border.all(
// color: AppColor.primaryColor.withOpacity(0.7),
// width: 2,
// ),
// ),
// ),
// ),
// );
// }),
// // --- المحتوى في المنتصف ---
// Column(
// mainAxisAlignment: MainAxisAlignment.center,
// children: [
// Text(
// controller.driversStatusForSearchWindow,
// style: AppStyle.headTitle.copyWith(fontSize: 20),
// textAlign: TextAlign.center,
// ),
// const SizedBox(height: 8),
// Text(
// 'Searching for the nearest captain...'.tr,
// style: AppStyle.subtitle
// .copyWith(color: AppColor.writeColor.withOpacity(0.7)),
// textAlign: TextAlign.center,
// ),
// const SizedBox(height: 16),
// // --- استدعاء نفس دالة المؤقت الخاصة بك ---
// buildTimerForIncrease(controller),
// ],
// ),
// ],
// ),
// );
// }
// }
// // --- نفس دالة المؤقت الخاصة بك مع تعديلات شكلية بسيطة ---
// Widget buildTimerForIncrease(MapPassengerController mapPassengerController) {
// return StreamBuilder<int>(
// stream: Stream.periodic(const Duration(seconds: 1))
// .map((_) => ++mapPassengerController.currentTimeSearchingCaptainWindow),
// initialData: 0,
// builder: (context, snapshot) {
// if (snapshot.hasData && snapshot.data! > 45) {
// // --- عرض زر زيادة الأجرة بنفس منطقك القديم ---
// return TextButton(
// onPressed: () =>
// _showIncreaseFeeDialog(context, mapPassengerController),
// child: Text(
// "No one accepted? Try increasing the fare.".tr,
// style: AppStyle.title.copyWith(
// color: AppColor.primaryColor,
// decoration: TextDecoration.underline),
// textAlign: TextAlign.center,
// ),
// );
// }
// final double progress = (snapshot.data ?? 0).toDouble() / 30.0;
// return SizedBox(
// height: 40,
// width: 40,
// child: Stack(
// fit: StackFit.expand,
// children: [
// CircularProgressIndicator(
// value: progress,
// strokeWidth: 3,
// color: AppColor.primaryColor,
// backgroundColor: AppColor.primaryColor.withOpacity(0.2),
// ),
// Center(
// child: Text(
// '${snapshot.data ?? 0}',
// style: AppStyle.title.copyWith(
// color: AppColor.writeColor, fontWeight: FontWeight.bold),
// ),
// ),
// ],
// ),
// );
// },
// );
// }
// // --- دالة لعرض نافذة زيادة الأجرة (مأخوذة من منطقك القديم) ---
// void _showIncreaseFeeDialog(
// BuildContext context, MapPassengerController mapPassengerController) {
// Get.defaultDialog(
// barrierDismissible: false,
// title: "Increase Your Trip Fee (Optional)".tr,
// titleStyle: AppStyle.title,
// content: Column(
// children: [
// Text(
// "We haven't found any drivers yet. Consider increasing your trip fee to make your offer more attractive to drivers."
// .tr,
// style: AppStyle.subtitle,
// textAlign: TextAlign.center,
// ),
// const SizedBox(height: 16),
// Row(
// mainAxisAlignment: MainAxisAlignment.center,
// children: [
// IconButton(
// onPressed: () {
// mapPassengerController.increasFeeFromPassenger.text =
// (mapPassengerController.totalPassenger + 3)
// .toStringAsFixed(1);
// mapPassengerController.update();
// },
// icon: const Icon(Icons.add_circle,
// size: 40, color: AppColor.greenColor),
// ),
// SizedBox(
// width: 100,
// child: Form(
// key: mapPassengerController.increaseFeeFormKey,
// child: MyTextForm(
// controller: mapPassengerController.increasFeeFromPassenger,
// label:
// mapPassengerController.totalPassenger.toStringAsFixed(2),
// hint:
// mapPassengerController.totalPassenger.toStringAsFixed(2),
// type: TextInputType.number,
// ),
// ),
// ),
// IconButton(
// onPressed: () {
// mapPassengerController.increasFeeFromPassenger.text =
// (mapPassengerController.totalPassenger - 3)
// .toStringAsFixed(1);
// mapPassengerController.update();
// },
// icon: const Icon(Icons.remove_circle,
// size: 40, color: AppColor.redColor),
// ),
// ],
// ),
// ],
// ),
// actions: [
// TextButton(
// child: Text("No, thanks".tr,
// style: const TextStyle(color: AppColor.redColor)),
// onPressed: () {
// Get.back();
// // mapPassengerController.cancelRide();
// mapPassengerController.changeCancelRidePageShow();
// },
// ),
// ElevatedButton(
// style: ElevatedButton.styleFrom(backgroundColor: AppColor.greenColor),
// child: Text("Increase Fee".tr),
// onPressed: () =>
// mapPassengerController.increaseFeeByPassengerAndReOrder(),
// ),
// ],
// );
// }

View File

@@ -237,7 +237,7 @@ class VipRideBeginPassenger extends StatelessWidget {
width: Get.width * .15, width: Get.width * .15,
child: IconButton( child: IconButton(
onPressed: () async { onPressed: () async {
await controller.getTokenForParent(); await controller.shareTripWithFamily();
}, },
icon: const Icon( icon: const Icon(
AntDesign.Safety, AntDesign.Safety,

View File

@@ -447,64 +447,37 @@ void showPaymentOptions(BuildContext context, PaymentController controller) {
)), )),
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: controller.formKey, bool didAuthenticate = await LocalAuthentication().authenticate(
child: MyTextForm( localizedReason: 'استخدم بصمة الإصبع أو الوجه لتأكيد الدفع',
controller: controller.walletphoneController, );
label: 'Insert Wallet phone number'.tr,
hint: '963941234567', if (!didAuthenticate) {
type: TextInputType.phone)), print("❌ User did not authenticate with biometrics");
confirm: MyElevatedButton( return;
title: 'OK'.tr, }
onPressed: () async { }
Get.back();
if (controller.formKey.currentState!.validate()) { // الانتقال مباشرة لإنشاء الفاتورة بعد النجاح بالبصمة
box.write(BoxName.phoneWallet, Get.to(() => PaymentScreenSmsProvider(
controller.walletphoneController.text); amount: double.parse(controller.selectedAmount.toString())));
// 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: controller.selectedAmount!.toDouble()));
}
}));
}, },
child: Padding( child: Row(
padding: const EdgeInsets.all(8.0), mainAxisAlignment: MainAxisAlignment.spaceBetween,
child: Row( children: [
mainAxisAlignment: MainAxisAlignment.spaceBetween, Text('Pay by Sham Cash'.tr),
children: [ const SizedBox(width: 10),
Text( Image.asset(
'Pay by Sham Cash'.tr, 'assets/images/shamCash.png',
style: AppStyle.title, width: 70,
), height: 70,
const SizedBox(width: 10), fit: BoxFit.fill,
Image.asset( ),
'assets/images/shamCash.png', ],
width: 70,
height: 70,
fit: BoxFit.fill,
),
],
),
)), )),
], ],
cancelButton: CupertinoActionSheetAction( cancelButton: CupertinoActionSheetAction(

View File

@@ -1,71 +1,56 @@
import 'dart:async'; import 'dart:async';
import 'package:Intaleq/views/widgets/mydialoug.dart';
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:get/get_core/src/get_main.dart';
import 'package:intl/intl.dart'; import 'package:intl/intl.dart';
import '../../../constant/box_name.dart'; import '../../../constant/box_name.dart';
import '../../../constant/links.dart'; import '../../../constant/links.dart';
import '../../../controller/functions/crud.dart'; import '../../../controller/functions/crud.dart';
import '../../../main.dart'; import '../../../main.dart';
// خدمة الدفع للراكب (تم تحديث المسارات)
class PaymentService { class PaymentService {
final String _baseUrl = "${AppLink.seferPaymentServer}/sms_webhook"; // المسار الجديد لمجلد الركاب
final String _baseUrl = "${AppLink.paymentServer}/ride/shamcash/passenger";
Future<String?> createInvoice({ Future<String?> createInvoice({required double amount}) async {
required String userPhone, final url = "$_baseUrl/create_invoice.php";
required double amount,
}) async {
final url = "$_baseUrl/create_invoice_passenger.php";
try { try {
final response = await CRUD().postWallet( final response = await CRUD().postWallet(
link: url, link: url,
payload: { payload: {
'user_phone': userPhone.toString(), 'passengerID': box.read(BoxName.passengerID), // استخدام passengerID
'passengerID': box.read(BoxName.passengerID),
'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"); debugPrint("Create Invoice Error: $e");
return null; return null;
} }
} }
/// دالة للتحقق من حالة فاتورة واحدة
Future<bool> checkInvoiceStatus(String invoiceNumber) async { Future<bool> checkInvoiceStatus(String invoiceNumber) async {
final url = "$_baseUrl/check_invoice_status_passenger.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;
} }
} }
@@ -83,14 +68,14 @@ 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',
}); });
@override @override
@@ -103,7 +88,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() {
@@ -113,17 +97,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(() {
@@ -137,7 +118,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 {
@@ -147,72 +128,55 @@ 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: تحديث رصيد المستخدم أو تنفيذ الإجراءات اللازمة
MyDialog().getDialog(
'Payment Successful!'.tr,
''.tr,
() {
Get.back();
Get.back();
},
);
} }
}); });
} }
/// دالة جديدة لمعالجة محاولة الرجوع للخلف 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>( final shouldPop = await showDialog<bool>(
context: context, context: context,
builder: (context) => AlertDialog( builder: (context) => AlertDialog(
title: const Text('هل أنت متأكد؟'), title: const Text('إلغاء العملية؟', textAlign: TextAlign.right),
content: const Text('إذا خرجت الآن، سيتم إلغاء عملية الدفع الحالية.'), content: const Text('الخروج الآن سيؤدي لإلغاء متابعة عملية الدفع.',
textAlign: TextAlign.right),
actions: <Widget>[ actions: <Widget>[
TextButton( TextButton(
onPressed: () => Navigator.of(context).pop(false), onPressed: () => Navigator.of(context).pop(false),
child: const Text('البقاء'), child: const Text('البقاء')),
),
TextButton( TextButton(
onPressed: () => Navigator.of(context).pop(true), onPressed: () => Navigator.of(context).pop(true),
child: const Text('الخروج'), child: const Text('خروج', style: TextStyle(color: Colors.red))),
),
], ],
), ),
); );
return shouldPop ?? false;
// إذا وافق المستخدم على الخروج، قم بإغلاق الشاشة
if (shouldPop ?? false) {
Navigator.of(context).pop();
}
} }
return true;
} }
@override @override
Widget build(BuildContext context) { Widget build(BuildContext context) {
// استخدام PopScope بدلاً من WillPopScope return WillPopScope(
return PopScope( onWillPop: _onPopInvoked,
// منع الرجوع التلقائي فقط في حالة انتظار الدفع
canPop: _status != PaymentStatus.waitingForPayment,
// استدعاء دالة التحقق عند محاولة الرجوع
onPopInvoked: _onPopInvoked,
child: Scaffold( child: Scaffold(
appBar: AppBar(title: Text("الدفع عبر ${widget.providerName}")), backgroundColor: Colors.grey[50],
body: Padding( appBar: AppBar(
padding: const EdgeInsets.all(16.0), title: Text("دفع عبر ${widget.providerName}"),
child: Center( centerTitle: true,
child: _buildContentByStatus(), elevation: 0,
backgroundColor: Colors.white,
foregroundColor: Colors.black,
),
body: SafeArea(
child: Padding(
padding: const EdgeInsets.all(20.0),
child: Center(child: _buildContentByStatus()),
), ),
), ),
), ),
@@ -227,7 +191,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:
@@ -242,94 +206,162 @@ 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));
},
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)),
const SizedBox(height: 15),
GestureDetector(
onTap: () {
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)),
child: Column( child: Column(
children: [ children: [
_StepTile(number: 1, text: "افتح تطبيق محفظتك الإلكترونية."), Image.asset(widget.qrImagePath,
_StepTile(number: 2, text: "اختر خدمة تحويل الأموال."), width: 180,
_StepTile( height: 180,
number: 3, fit: BoxFit.contain,
text: errorBuilder: (c, o, s) => const Icon(Icons.qr_code_2,
"أدخل المبلغ المطلوب: ${currencyFormat.format(widget.amount)} ل.س"), size: 100, color: Colors.grey)),
_StepTile(number: 4, text: "حوّل إلى الرقم التالي:"),
// --- التعديل هنا ---
ListTile(
contentPadding: EdgeInsets.zero,
title: Text(
widget.paymentPhoneNumber,
style: const TextStyle(
fontSize: 20,
fontWeight: FontWeight.bold,
letterSpacing: 1.2),
),
trailing: OutlinedButton.icon(
onPressed: () async {
await Clipboard.setData(
ClipboardData(text: widget.paymentPhoneNumber));
if (mounted) {
ScaffoldMessenger.of(context).showSnackBar(
const SnackBar(
content: Text("تم نسخ رقم الهاتف")));
}
},
icon: const Icon(Icons.copy, size: 18),
label: const Text("نسخ"),
),
),
// --- نهاية التعديل ---
const SizedBox(height: 8), const SizedBox(height: 8),
_StepTile( 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)),
], ],
), ),
); );
@@ -339,14 +371,21 @@ 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: 40),
ElevatedButton( SizedBox(
onPressed: () => Navigator.of(context).pop(), width: double.infinity,
child: const Text("العودة"), child: ElevatedButton(
style: ElevatedButton.styleFrom(
backgroundColor: Colors.green,
foregroundColor: Colors.white,
padding: const EdgeInsets.symmetric(vertical: 16)),
onPressed: () => Navigator.of(context).pop(),
child: const Text("متابعة", style: TextStyle(fontSize: 18)),
),
), ),
], ],
); );
@@ -356,47 +395,35 @@ 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: 15),
), const Padding(
const SizedBox(height: 8), padding: EdgeInsets.symmetric(horizontal: 30),
const Text("يرجى المحاولة مرة أخرى.", style: TextStyle(fontSize: 16)), child: Text("لم يصلنا إشعار الدفع.",
const SizedBox(height: 20), textAlign: TextAlign.center,
ElevatedButton( style: TextStyle(color: Colors.grey))),
onPressed: _createAndPollInvoice, const SizedBox(height: 40),
child: const Text("المحاولة مرة أخرى"), SizedBox(
width: double.infinity,
child: ElevatedButton.icon(
style: ElevatedButton.styleFrom(
padding: const EdgeInsets.symmetric(vertical: 15)),
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),
);
}
}

View File

@@ -78,6 +78,8 @@ dependencies:
internet_connection_checker: ^3.0.1 internet_connection_checker: ^3.0.1
connectivity_plus: ^6.1.5 connectivity_plus: ^6.1.5
app_links: ^6.4.1 app_links: ^6.4.1
# flutter_map: ^8.2.2
# latlong2: ^0.9.1
# home_widget: ^0.7.0+1 # home_widget: ^0.7.0+1
dev_dependencies: dev_dependencies: