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
# mapAPIKEY=AIzaSyCFsWBqvkXzk1Gb-bCGxwqTwJQKIeHjH64
mapAPIKEY=AIzaSyAPFR_XbRN0XZ5Iz3AYDjNYHGJG2s2QWwM
mapKeyOsm=maldev@route-dollars
mapAPIKEYIOS=AIzaSyDdqkLMCrqjVrn7XmadIqynyoBa7P27OeM
twilloRecoveryCode=CAU79DHPH1BjE9PUH4ETXTSXZXrXlBl
apiKeyHere=g_WNUb5L-tripz7-F8omHpUmgIzH7ETeH9xZ8RwGG9_G8zX9A

View File

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

View File

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

View File

@@ -1,6 +1,8 @@
<resources>
<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="device_secure">الجهاز آمن. الاستمرار بشكل طبيعي.</string>
<string name="label">انطلق</string>
</resources>

View File

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

View File

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

View File

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

View File

@@ -87,9 +87,11 @@ class LoginController extends GetxController {
update();
}
getJwtWallet() async {
Future<String> getJwtWallet() async {
try {
final random = Random();
// Perform security check randomly
if (random.nextBool()) {
await SecurityHelper.performSecurityChecks();
} else {
@@ -97,24 +99,58 @@ class LoginController extends GetxController {
}
String fingerPrint = await DeviceHelper.getDeviceFingerprint();
// print('fingerPrint: ${fingerPrint}');
dev = Platform.isAndroid ? 'android' : 'ios';
final dev = GetPlatform.isAndroid ? 'android' : 'ios';
var payload = {
'id': box.read(BoxName.passengerID),
'password': AK.passnpassenger,
'aud': '${AK.allowed}$dev',
'fingerPrint': fingerPrint
'fingerPrint': fingerPrint,
};
var response1 = await http.post(
var response = await http.post(
Uri.parse(AppLink.loginJwtWalletRider),
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();
// 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;
}
}
void _showJwtErrorDialog(String message) {
if (Get.context == null) return;
Get.defaultDialog(
title: "خطأ في الاتصال",
middleText: message,
textConfirm: "إعادة المحاولة",
confirmTextColor: Colors.white,
onConfirm: () {
Get.back();
getJwtWallet();
},
);
}
getJWT() async {

View File

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

View File

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

View File

@@ -91,18 +91,8 @@ class OtpVerificationController extends GetxController {
);
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(
category: 'token change',
target: ptoken.toString(),
title: 'token change'.tr,
body: 'change device'.tr,
@@ -110,21 +100,7 @@ class OtpVerificationController extends GetxController {
tone: 'cancel',
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());
} else {
Get.snackbar('Verification Failed', 'OTP is incorrect or expired');

View File

@@ -1,20 +1,15 @@
import 'dart:convert';
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:flutter/material.dart';
import 'package:get/get.dart';
import 'package:http/http.dart' as http;
import 'package:Intaleq/controller/functions/toast.dart';
import 'package:Intaleq/views/widgets/elevated_btn.dart';
import '../../constant/api_key.dart';
import '../../constant/box_name.dart';
import '../../constant/colors.dart';
import '../../constant/links.dart';
import '../../constant/style.dart';
import '../../constant/table_names.dart';
import '../../env/env.dart';
import '../../main.dart';
import '../../print.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 '../functions/audio_record1.dart';
import '../home/map_passenger_controller.dart';
import 'access_token.dart';
import 'local_notification.dart';
import 'notification_service.dart';
class FirebaseMessagesController extends GetxController {
final fcmToken = FirebaseMessaging.instance;
@@ -76,9 +69,13 @@ class FirebaseMessagesController extends GetxController {
Future getToken() async {
fcmToken.getToken().then((token) {
// Log.print('fcmToken: ${token}');
Log.print('fcmToken: ${token}');
box.write(BoxName.tokenFCM, (token.toString()));
});
// 🔹 الاشتراك في topic
await fcmToken
.subscribeToTopic("passengers"); // أو "users" حسب نوع المستخدم
print("Subscribed to 'passengers' topic ✅");
FirebaseMessaging.onMessage.listen((RemoteMessage 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 {
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}');
if (Platform.isAndroid) {
notificationController.showNotification(
'Order'.tr, message.notification!.body!, 'Order');
notificationController.showNotification(title, 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');
}
var passengerList = message.data['passengerList'];
var myList = jsonDecode(passengerList) as List<dynamic>;
Log.print('myList: ${myList}');
// ... داخل معالج الإشعارات في تطبيق الراكب ...
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.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) {
// controller.currentRideState.value = RideState.driverApplied;
await controller.processRideAcceptance(
driverIdFromFCM: myList[0].toString(),
rideIdFromFCM: myList[3].toString());
} else {
Log.print('❌ خطأ: RIDE_ACCEPTED وصل بدون driverList');
}
} else if (category == 'Promo') {
// <-- كان 'Promo'.tr
if (Platform.isAndroid) {
notificationController.showNotification(
'Promo', 'Show latest promo'.tr, 'promo');
notificationController.showNotification(title, body, 'promo');
}
Get.to(const PromosPassengerPage());
} else if (message.notification!.title! == 'Trip Monitoring'.tr) {
} else if (category == 'Trip Monitoring') {
// <-- كان 'Trip Monitoring'.tr
if (Platform.isAndroid) {
notificationController.showNotification(
'Trip Monitoring'.tr, '', 'iphone_ringtone');
notificationController.showNotification(title, body, 'iphone_ringtone');
}
var myListString = message.data['DriverList'];
var myList = jsonDecode(myListString) as List<dynamic>;
Get.toNamed('/tripmonitor', arguments: {
Get.to(() => TripMonitor(), arguments: {
'rideId': myList[0].toString(),
'driverId': myList[1].toString(),
});
} else if (message.notification!.title! == 'token change'.tr) {
} else if (category == 'token change') {
// <-- كان 'token change'.tr
if (Platform.isAndroid) {
notificationController.showNotification(
'token change'.tr, 'token change'.tr, 'cancel');
notificationController.showNotification(title, body, 'cancel');
}
GoogleSignInHelper.signOut();
} else if (message.notification!.title! ==
'Driver Is Going To Passenger'.tr) {
} else if (category == 'Driver Is Going To Passenger') {
// <-- كان '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');
notificationController.showNotification(title, body, 'tone1');
}
// Get.snackbar('Driver is Going To Passenger', '',
// backgroundColor: AppColor.greenColor);
} else if (message.notification!.title! == 'message From passenger') {
} else if (category == 'message From passenger') {
// <-- كان 'message From passenger'
if (Platform.isAndroid) {
notificationController.showNotification(
'message From passenger'.tr, ''.tr, 'ding');
notificationController.showNotification(title, body, 'ding');
}
passengerDialog(message.notification!.body!);
passengerDialog(body);
update();
} else if (message.notification!.title! == 'message From Driver') {
} else if (category == 'message From Driver') {
// <-- كان 'message From Driver'
if (Platform.isAndroid) {
notificationController.showNotification(
'message From Driver'.tr, ''.tr, 'ding');
notificationController.showNotification(title, body, 'ding');
}
passengerDialog(message.notification!.body!);
passengerDialog(body);
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) {
notificationController.showNotification(
'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');
notificationController.showNotification(title, body, 'ding');
}
update();
} else if (message.notification!.title! == 'Hi ,I Arrive your site'.tr) {
if (Platform.isAndroid) {
notificationController.showNotification(
'Hi ,I Arrive your site'.tr, ''.tr, 'ding');
}
driverArrivePassengerDialoge();
update();
} else if (message.notification!.title! == "Cancel Trip from driver") {
} else if (category == 'Hi ,I Arrive your site') {
// <-- كان 'Hi ,I Arrive your site'.tr
final controller = Get.find<MapPassengerController>();
// if (controller.currentRideState.value == RideState.driverApplied) {
Log.print('[FCM] السائق وصل. تغيير الحالة إلى driverArrived');
controller.currentRideState.value = RideState.driverArrived;
// }
} else if (category == 'Cancel Trip from driver') {
// <-- كان "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');
notificationController.showNotification(title, body, 'cancel');
}
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,
confirm: MyElevatedButton(
kolor: AppColor.greenColor,
@@ -229,66 +217,42 @@ class FirebaseMessagesController extends GetxController {
onPressed: () {
Get.offAll(() => const MapPagePassenger());
},
)
// Get.find<MapPassengerController>()
// .searchNewDriverAfterRejectingFromDriver();
);
} else if (message.notification!.title! == 'Driver Finish Trip'.tr) {
// الخطوة 1: استقبل البيانات وتحقق من وجودها
));
} else if (category == 'Driver Finish Trip') {
// <-- كان 'Driver Finish Trip'.tr
final rawData = message.data['DriverList'];
List<dynamic> driverList = []; // ابدأ بقائمة فارغة كإجراء وقائي
// الخطوة 2: قم بفك تشفير البيانات بأمان
List<dynamic> driverList = [];
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 (driverList.length >= 3) {
if (Platform.isAndroid) {
notificationController.showNotification(
"Driver Finish Trip".tr,
'${'you will pay to Driver'.tr} ${driverList[3].toString()} \$', // تم تحسين طريقة عرض النص
title,
'${'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');
// هنا يمكنك عرض رسالة خطأ للمستخدم إذا لزم الأمر
Log.print('Error: TRIP_FINISHED decoded list error.');
}
} else if (message.notification!.title! == "Finish Monitor".tr) {
} else if (category == 'Finish Monitor') {
// <-- كان "Finish Monitor".tr
Get.defaultDialog(
titleStyle: AppStyle.title,
title: 'Trip finished '.tr,
@@ -298,69 +262,7 @@ class FirebaseMessagesController extends GetxController {
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);
} else if (category == 'Driver Cancelled Your Trip') {
if (Platform.isAndroid) {
notificationController.showNotification(
'Driver Cancelled Your Trip'.tr,
@@ -374,18 +276,10 @@ class FirebaseMessagesController extends GetxController {
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,
// );
// }
// ... (باقي الحالات مثل Call Income, Call End, إلخ) ...
// ... بنفس الطريقة ...
else if (message.notification!.title! == 'Order Applied'.tr) {
else if (category == 'Order Applied') {
if (Platform.isAndroid) {
notificationController.showNotification(
'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() {
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) {
return Get.defaultDialog(
barrierDismissible: false,
@@ -500,246 +662,6 @@ class FirebaseMessagesController extends GetxController {
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 {

View File

@@ -1,3 +1,4 @@
import 'package:Intaleq/print.dart';
import 'package:http/http.dart' as http;
import 'dart:convert';
@@ -5,11 +6,13 @@ class NotificationService {
// استبدل هذا الرابط بالرابط الصحيح لملف PHP على السيرفر الخاص بك
static const String _serverUrl =
'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({
required String target,
required String title,
required String body,
required String? category, // <-- [الإضافة الأولى]
String? tone,
List<String>? driverList, // <-- [تعديل 1] : إضافة المتغير الجديد
bool isTopic = false,
@@ -21,7 +24,10 @@ class NotificationService {
'body': body,
'isTopic': isTopic,
};
if (category != null) {
payload['category'] =
category; // <-- [الإضافة الثانية] (النص الثابت للتحكم)
}
// نضيف النغمة فقط إذا لم تكن فارغة
if (tone != null) {
payload['tone'] = tone;
@@ -52,4 +58,56 @@ class NotificationService {
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 'dart:io';
import 'package:jwt_decoder/jwt_decoder.dart';
import 'network/connection_check.dart';
import 'network/net_guard.dart';
class CRUD {
@@ -364,29 +361,29 @@ class CRUD {
}
}
Future<dynamic> getTokenParent({
required String link,
Map<String, dynamic>? payload,
}) async {
// Uses Basic Auth, so it's a separate implementation.
var url = Uri.parse(
link,
);
var response = await http.post(
url,
body: payload,
headers: {
"Content-Type": "application/x-www-form-urlencoded",
'Authorization':
'Basic ${base64Encode(utf8.encode(AK.basicAuthCredentials.toString()))}',
},
);
if (response.statusCode == 200) {
return jsonDecode(response.body);
}
// Consider adding error handling here.
return null;
}
// Future<dynamic> getTokenParent({
// required String link,
// Map<String, dynamic>? payload,
// }) async {
// // Uses Basic Auth, so it's a separate implementation.
// var url = Uri.parse(
// link,
// );
// var response = await http.post(
// url,
// body: payload,
// headers: {
// "Content-Type": "application/x-www-form-urlencoded",
// 'Authorization':
// 'Basic ${base64Encode(utf8.encode(AK.basicAuthCredentials.toString()))}',
// },
// );
// if (response.statusCode == 200) {
// return jsonDecode(response.body);
// }
// // Consider adding error handling here.
// return null;
// }
Future sendWhatsAppAuth(String to, String token) async {
var res = await CRUD()

View File

@@ -8,12 +8,33 @@ void showInBrowser(String url) 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(
scheme: 'tel',
path: phoneNumber,
path: formattedNumber,
);
if (await canLaunchUrl(launchUri)) {
await launchUrl(launchUri);
}
}
void launchCommunication(
String method, String contactInfo, String message) async {

View File

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

View File

@@ -1,3 +1,4 @@
import 'dart:math';
import 'package:flutter/cupertino.dart';
import 'package:flutter/material.dart';
import 'package:flutter_font_icons/flutter_font_icons.dart';
@@ -7,10 +8,9 @@ import '../../../constant/colors.dart';
import '../functions/launch.dart';
class ContactUsController extends GetxController {
final String phone1 = '+201018805430';
final String phone2 = '+201080182934';
final TimeOfDay workStartTime = const TimeOfDay(hour: 12, minute: 0);
final TimeOfDay workEndTime = const TimeOfDay(hour: 19, minute: 0);
/// WORKING HOURS (10:00 → 16:00)
final TimeOfDay workStartTime = const TimeOfDay(hour: 10, minute: 0);
final TimeOfDay workEndTime = const TimeOfDay(hour: 16, minute: 0);
bool _isWithinWorkTime(TimeOfDay now) {
return (now.hour > workStartTime.hour ||
@@ -20,8 +20,23 @@ class ContactUsController extends GetxController {
(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) {
TimeOfDay now = TimeOfDay.now();
bool withinHours = _isWithinWorkTime(now);
showCupertinoModalPopup(
context: context,
@@ -29,25 +44,34 @@ class ContactUsController extends GetxController {
title: Text('Contact Us'.tr),
message: Text('Choose a contact option'.tr),
actions: <Widget>[
if (_isWithinWorkTime(now))
/// 📞 CALL (RANDOM) — ONLY DURING WORK HOURS
if (withinHours)
CupertinoActionSheetAction(
child: Text(phone1),
onPressed: () => makePhoneCall(
phone1,
child: Row(
mainAxisAlignment: MainAxisAlignment.spaceBetween,
children: [
const Icon(CupertinoIcons.phone),
Text('Call Support'.tr),
],
),
onPressed: () {
final phone = getRandomPhone();
makePhoneCall(phone);
},
),
if (_isWithinWorkTime(now))
CupertinoActionSheetAction(
child: Text(phone2),
onPressed: () => makePhoneCall(phone2),
),
if (!_isWithinWorkTime(now))
/// ⛔ OUTSIDE WORK HOURS — SHOW INFO
if (!withinHours)
CupertinoActionSheetAction(
child: Text(
'Work time is from 12:00 - 19:00.\nYou can send a WhatsApp message or email.'
.tr),
'Work time is from 10:00 AM to 16:00 PM.\nYou can send a WhatsApp message or email.'
.tr,
textAlign: TextAlign.center,
),
onPressed: () => Navigator.pop(context),
),
/// 💬 WHATSAPP (RANDOM)
CupertinoActionSheetAction(
child: Row(
mainAxisAlignment: MainAxisAlignment.spaceAround,
@@ -59,15 +83,21 @@ class ContactUsController extends GetxController {
Text('Send WhatsApp Message'.tr),
],
),
onPressed: () =>
launchCommunication('whatsapp', phone1, 'Hello'.tr),
onPressed: () {
final phone = getRandomPhone();
launchCommunication('whatsapp', phone, 'Hello'.tr);
},
),
/// 📧 EMAIL
CupertinoActionSheetAction(
child: Text('Send Email'.tr),
onPressed: () =>
launchCommunication('email', 'support@sefer.live', 'Hello'.tr),
onPressed: () => launchCommunication(
'email', 'support@intaleqapp.com', 'Hello'.tr),
),
],
/// ❌ CANCEL BUTTON
cancelButton: CupertinoActionSheetAction(
child: Text('Cancel'.tr),
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
void onInit() {
Get.put(LocationController());
// Get.put(LocationController());
addWayPoints();
myLocation = Get.find<MapPassengerController>().passengerLocation;
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.
if (digitsOnly.startsWith('963')) {
return '+$digitsOnly';
return '$digitsOnly';
}
// 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.
return '+963$digitsOnly';
return '963$digitsOnly';
}
/// **IMPROVEMENT**: This method now uses the new phone formatting logic and

View File

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

View File

@@ -201,7 +201,16 @@ class MyTranslation extends Translations {
"Top up Wallet": "تعبئة المحفظة",
"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": "‏مريحة",
"Intaleq Balance": "رصيد انطلق",
'Search for a starting point': 'ابحث عن نقطة انطلاق',
@@ -316,6 +325,8 @@ class MyTranslation extends Translations {
"Van for familly": "سيارة فان للعائلة",
"Are you sure to delete this location?":
"هل أنت متأكد من حذف هذا الموقع؟",
'Change Work location?': 'تغيير موقع العمل؟',
'Change Home location?': 'تغيير موقع البيت؟',
"Submit a Complaint": "تقديم شكوى",
"Submit Complaint": "إرسال الشكوى",
"No trip history found": "لا يوجد سجل للرحلات",
@@ -395,6 +406,41 @@ class MyTranslation extends Translations {
"OK": "موافق",
"Confirm Pick-up Location": "تأكيد موقع الانطلاق",
"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": "أقرب سيارة",
"No cars nearby": "لا توجد سيارات قريبة",

View File

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

View File

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

View File

@@ -144,8 +144,9 @@ class PaymentController extends GetxController {
// 'cancel',
// );
await NotificationService.sendNotification(
category: 'Cancel',
target: Get.find<MapPassengerController>().driverToken.toString(),
title: 'Cancel',
title: 'Cancel'.tr,
body:
'Trip Cancelled. The cost of the trip will be added to your wallet.'
.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/widgets/elevated_btn.dart';
import '../firebase/firbase_messge.dart';
import '../firebase/notification_service.dart';
import '../payment/payment_controller.dart';
@@ -18,7 +17,7 @@ import '../payment/payment_controller.dart';
class RateController extends GetxController {
double selectedRateItemId = -1;
TextEditingController comment = TextEditingController();
String? rideId, passengerId, driverId, price;
String? rideId, passengerId, driverId, driverName, price;
late GlobalKey<FormState> formKey;
@override
void onInit() {
@@ -26,6 +25,7 @@ class RateController extends GetxController {
passengerId = Get.arguments['passengerId'];
rideId = Get.arguments['rideId'];
driverId = Get.arguments['driverId'];
driverName = Get.arguments['driverName'];
price = Get.arguments['price'];
box.write(BoxName.tipPercentage, '0');
super.onInit();
@@ -61,7 +61,7 @@ class RateController extends GetxController {
box.read(BoxName.countryCode) == 'Egypt'
? tip.toStringAsFixed(0)
: (tip * 100).toString());
await CRUD().post(link: AppLink.addDriversWalletPoints, payload: {
await CRUD().postWallet(link: AppLink.addDriversWalletPoints, payload: {
'driverID': Get.find<MapPassengerController>().driverId.toString(),
'paymentID': '${Get.find<MapPassengerController>().rideId}tip',
'amount': box.read(BoxName.countryCode) == 'Egypt'
@@ -71,16 +71,10 @@ class RateController extends GetxController {
'token': token1,
});
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(
category: 'You Have Tips',
target: Get.find<MapPassengerController>().driverToken.toString(),
title: 'You Have Tips',
title: 'You Have Tips'.tr,
body:
'${'${tip.toString()}\$${' tips\nTotal is'.tr}'} ${tip + (Get.find<MapPassengerController>().totalPassenger)}',
isTopic: false, // Important: this is a token
@@ -91,26 +85,15 @@ class RateController extends GetxController {
}
}
await CRUD().post(
link: "${AppLink.IntaleqSyriaServer}/ride/rate/addRateToDriver.php",
link: "${AppLink.server}/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,
});
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.offAll(const MapPagePassenger());

3
lib/env/env.dart vendored
View File

@@ -10,6 +10,9 @@ abstract class Env {
@EnviedField(varName: 'basicCompareFaces', obfuscate: true)
static final String basicCompareFaces = _Env.basicCompareFaces;
@EnviedField(varName: 'mapKeyOsm', obfuscate: true)
static final String mapKeyOsm = _Env.mapKeyOsm;
@EnviedField(varName: 'sss_encryptionSalt', obfuscate: true)
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 _formKey = GlobalKey<FormState>();
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 {
if (_formKey.currentState!.validate()) {
@@ -318,7 +373,10 @@ class _PhoneNumberScreenState extends State<PhoneNumberScreen> {
final rawPhone = _phoneController.text.trim().replaceFirst('+', '');
final success = await PhoneAuthHelper.sendOtp(rawPhone);
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);
}
@@ -349,6 +407,7 @@ class _PhoneNumberScreenState extends State<PhoneNumberScreen> {
dropdownTextStyle: const TextStyle(color: Colors.black87),
decoration: InputDecoration(
labelText: 'Phone Number'.tr,
hintText: 'witout zero'.tr,
labelStyle: TextStyle(color: Colors.white.withOpacity(0.7)),
enabledBorder: OutlineInputBorder(
borderRadius: BorderRadius.circular(12),
@@ -365,11 +424,14 @@ class _PhoneNumberScreenState extends State<PhoneNumberScreen> {
},
validator: (phone) {
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) {
// Example validation
return 'Phone number seems too short';
return 'Phone number seems too short'.tr;
}
return null;
},
@@ -419,7 +481,7 @@ class _OtpVerificationScreenState extends State<OtpVerificationScreen> {
if (_formKey.currentState!.validate()) {
setState(() => _isLoading = true);
// 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);
}
}

View File

@@ -37,7 +37,7 @@ class ContactUsPage extends StatelessWidget {
IconButton(
onPressed: () async {
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);
},
icon: const Icon(Icons.headphones),
@@ -45,7 +45,7 @@ class ContactUsPage extends StatelessWidget {
Padding(
padding: const EdgeInsets.all(8.0),
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,
style: AppStyle.title,
textAlign: TextAlign.center,
@@ -62,7 +62,7 @@ class ContactUsPage extends StatelessWidget {
child: Padding(
padding: const EdgeInsets.all(8.0),
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,
style: AppStyle.title,
textAlign: TextAlign.center,

View File

@@ -43,6 +43,7 @@ class MapPagePassenger extends StatelessWidget {
child: Stack(
children: [
GoogleMapPassengerWidget(),
// OsmMapPassengerWidget(),
leftMainMenuIcons(),
// PaymobPackage(),
const PickerIconOnMap(),
@@ -88,8 +89,8 @@ class CancelRidePageShow extends StatelessWidget {
@override
Widget build(BuildContext context) {
return GetBuilder<MapPassengerController>(
builder: (controller) =>
(controller.data.isNotEmpty && controller.statusRide != 'Begin')
builder: (controller) => (controller.polyLines.isNotEmpty &&
controller.statusRide != 'Begin')
// ||
// controller.timeToPassengerFromDriverAfterApplied == 0
? Positioned(

View File

@@ -1,286 +1,412 @@
import 'package:Intaleq/constant/colors.dart';
import 'package:Intaleq/constant/links.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/main.dart';
import 'package:flutter/material.dart';
import 'package:flutter/services.dart';
import 'package:get/get.dart';
import 'package:intl/intl.dart'; // لتنسيق الأرقام
import '../../../constant/box_name.dart';
import '../../../controller/firebase/notification_service.dart';
import '../../../controller/functions/launch.dart';
import '../../../main.dart';
import '../../widgets/my_textField.dart';
class ApplyOrderWidget extends StatelessWidget {
ApplyOrderWidget({super.key});
final firebaseMessagesController =
Get.isRegistered<FirebaseMessagesController>()
? Get.find<FirebaseMessagesController>()
: Get.put(FirebaseMessagesController());
const ApplyOrderWidget({super.key});
@override
Widget build(BuildContext context) {
Color _parseColor(String colorHex) {
// دالة لتحويل كود اللون الهيكس إلى لون
Color parseColor(String colorHex) {
if (colorHex.isEmpty) return Colors.grey;
String processedHex = colorHex.replaceFirst('#', '0xff').trim();
return Color(int.parse(processedHex.startsWith('0xff')
? processedHex
: '0xff$processedHex'));
try {
String processedHex = colorHex.replaceFirst('#', '').trim();
if (processedHex.length == 6) processedHex = 'FF$processedHex';
return Color(int.parse('0x$processedHex'));
} catch (e) {
return Colors.grey;
}
}
return GetBuilder<MapPassengerController>(builder: (controller) {
Get.put(
FirebaseMessagesController()); // Ensure FirebaseMessagesController is initialized
if (controller.statusRide == 'Apply' && !controller.isSearchingWindow) {
return Positioned(
bottom: 0,
return Obx(() {
final controller = Get.find<MapPassengerController>();
final bool isVisible =
controller.currentRideState.value == RideState.driverApplied ||
controller.currentRideState.value == RideState.driverArrived;
return AnimatedPositioned(
duration: const Duration(milliseconds: 500),
curve: Curves.elasticOut, // تأثير حركي أجمل
bottom: isVisible ? 0 : -Get.height * 0.6,
left: 0,
right: 0,
child: Container(
// height: Get.height * 0.38, // زيادة الارتفاع قليلاً للتصميم الجديد
decoration: BoxDecoration(
// More modern BoxDecoration
color: Theme.of(context).cardColor,
borderRadius:
const BorderRadius.vertical(top: Radius.circular(20)),
boxShadow: [BoxShadow(blurRadius: 10, color: Colors.black12)],
borderRadius: const BorderRadius.vertical(top: Radius.circular(30)),
boxShadow: [
BoxShadow(
blurRadius: 20,
spreadRadius: 2,
color: Colors.black.withOpacity(0.15),
offset: const Offset(0, -2),
)
],
),
padding: const EdgeInsets.all(16),
child: Column(
padding: const EdgeInsets.fromLTRB(20, 10, 20, 20),
child: GetBuilder<MapPassengerController>(
builder: (c) {
return Column(
mainAxisSize: MainAxisSize.min,
children: [
_buildPriceInfo(context, controller),
const SizedBox(height: 16),
_buildDriverInfoCard(context, controller, _parseColor),
// مقبض صغير في الأعلى
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(),
],
);
},
),
),
);
} else {
return const SizedBox();
}
});
}
Widget _buildPriceInfo(
// ---------------------------------------------------------------------------
// 1. قسم السعر (مع التنسيق الجديد)
// ---------------------------------------------------------------------------
Widget _buildPriceHeader(
BuildContext context, MapPassengerController controller) {
return InkWell(
onTap: () {
String message;
if (box.read(BoxName.carType) == 'Speed' ||
box.read(BoxName.carType) == 'Awfar Car' ||
box.read(BoxName.carType) == 'Delivery') {
message =
'This ride type does not allow changes to the destination or additional stops'
.tr;
} else {
message =
'This ride type allows changes, but the price may increase'.tr;
}
Get.snackbar(
'This price is'.tr +
' ${controller.totalPassenger.toStringAsFixed(2)}'.tr,
message,
snackPosition: SnackPosition.BOTTOM,
duration: const Duration(seconds: 2),
backgroundColor:
AppColor.yellowColor.withOpacity(0.8), // More subtle background
);
},
child: Center(
child: Text.rich(
TextSpan(
// تنسيق الرقم (مثلاً: 60,000)
final formatter = NumberFormat("#,###");
String formattedPrice = formatter.format(controller.totalPassenger);
return Column(
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),
Text(
'Driver Accepted Request'.tr,
style: AppStyle.subtitle.copyWith(color: Colors.grey[600]),
),
const SizedBox(height: 5),
Row(
mainAxisAlignment: MainAxisAlignment.center,
children: [
Text(
formattedPrice,
style: AppStyle.title.copyWith(
fontSize: 28,
fontWeight: FontWeight.w900,
color: AppColor.primaryColor,
),
),
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],
),
),
),
TextSpan(text: ' ${'LE'.tr}', style: AppStyle.title),
],
),
textAlign: TextAlign.center,
),
),
],
);
}
Widget _buildDriverInfoCard(BuildContext context,
// ---------------------------------------------------------------------------
// 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).canvasColor,
borderRadius: BorderRadius.circular(10),
border: Border.all(color: Colors.grey.shade200),
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(
children: [
Padding(
padding: const EdgeInsets.all(12.0),
child: Row(
mainAxisAlignment: MainAxisAlignment.spaceBetween,
children: [
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),
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),
],
),
);
}
Widget _buildCarImage(
// ---------------------------------------------------------------------------
// 3. أيقونة السيارة الـ 3D (الدائرة والظلال والخلفية الذكية)
// ---------------------------------------------------------------------------
Widget _build3DCarIcon(
MapPassengerController controller, Color Function(String) parseColor) {
return ColorFiltered(
colorFilter:
ColorFilter.mode(parseColor(controller.colorHex), BlendMode.srcIn),
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',
height: 60,
fit: BoxFit.contain,
),
),
),
);
}
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(
// ---------------------------------------------------------------------------
// 4. أزرار الاتصال (بتصميم جديد)
// ---------------------------------------------------------------------------
Widget _buildContactButtonsRow(
BuildContext context, MapPassengerController controller) {
return Row(
children: [
IconButton(
onPressed: () => _showContactOptionsDialog(context, controller),
icon: const Icon(Icons.message, color: AppColor.blueColor, size: 28),
Expanded(
child: _buildActionButton(
label: 'Message'.tr,
icon: Icons.chat_bubble_outline_rounded,
color: AppColor.blueColor,
onTap: () => _showContactOptionsDialog(context, controller),
),
IconButton(
onPressed: () {
),
const SizedBox(width: 15),
Expanded(
child: _buildActionButton(
label: 'Call'.tr,
icon: Icons.phone_rounded,
color: AppColor.greenColor,
onTap: () {
HapticFeedback.heavyImpact();
makePhoneCall(controller.driverPhone);
},
icon: const Icon(Icons.call, color: AppColor.greenColor, size: 28),
),
),
],
);
}
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: Row(
mainAxisAlignment: MainAxisAlignment.center,
children: [
Icon(icon, size: 20),
const SizedBox(width: 8),
Text(
label,
style: const TextStyle(fontWeight: FontWeight.bold),
),
],
),
);
}
// --- النوافذ المنبثقة للرسائل (نفس المنطق القديم) ---
void _showContactOptionsDialog(
BuildContext context, MapPassengerController controller) {
Get.defaultDialog(
title: 'Contact Options'.tr,
content: SizedBox(
width: 300,
height: Get.height * .4,
child: ListView(
// shrinkWrap: true,
Get.bottomSheet(
Container(
padding: const EdgeInsets.all(20),
decoration: BoxDecoration(
color: Theme.of(context).cardColor,
borderRadius: const BorderRadius.vertical(top: Radius.circular(20)),
),
child: Column(
mainAxisSize: MainAxisSize.min,
crossAxisAlignment: CrossAxisAlignment.start,
children: [
Text('Quick Message'.tr, style: AppStyle.title),
const SizedBox(height: 15),
..._buildPredefinedMessages(controller),
const SizedBox(height: 8),
const Divider(height: 30),
_buildCustomMessageInput(controller, context),
SizedBox(
height: MediaQuery.of(context).viewInsets.bottom), // للكيبورد
],
),
),
isScrollControlled: true,
);
}
List<Widget> _buildPredefinedMessages(MapPassengerController controller) {
const messages = [
'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',
"How much longer will you be?",
];
return messages
.map((message) => Padding(
padding: const EdgeInsets.only(bottom: 8.0),
child: ElevatedButton(
onPressed: () {
// firebaseMessagesController.sendNotificationToDriverMAP(
// '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: [],
);
padding: const EdgeInsets.only(bottom: 10.0),
child: InkWell(
onTap: () {
_sendMessage(controller, message.tr);
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();
@@ -291,44 +417,58 @@ class ApplyOrderWidget extends StatelessWidget {
return Row(
children: [
Expanded(
child: Container(
padding: const EdgeInsets.symmetric(horizontal: 15),
decoration: BoxDecoration(
color: Colors.grey.withOpacity(0.1),
borderRadius: BorderRadius.circular(25),
),
child: Form(
key: controller.messagesFormKey,
child: MyTextForm(
child: TextFormField(
controller: controller.messageToDriver,
label: 'Send a custom message'.tr,
hint: 'Type your message'.tr,
type: TextInputType.text,
decoration: InputDecoration(
hintText: 'Type your message...'.tr,
border: InputBorder.none,
),
),
),
IconButton(
),
),
const SizedBox(width: 10),
CircleAvatar(
backgroundColor: AppColor.primaryColor,
child: IconButton(
onPressed: () {
if (controller.messagesFormKey.currentState!.validate()) {
// firebaseMessagesController.sendNotificationToDriverMAP(
// 'message From passenger',
// controller.messageToDriver.text,
// controller.driverToken,
// [],
// 'ding',
// );
NotificationService.sendNotification(
target: controller.driverToken.toString(),
title: 'message From passenger',
body: controller.messageToDriver.text,
isTopic: false, // Important: this is a token
tone: 'ding',
driverList: [],
);
_sendMessage(controller, controller.messageToDriver.text);
controller.messageToDriver.clear();
Get.back();
}
},
icon: const Icon(Icons.send),
icon: const Icon(Icons.send_rounded, color: Colors.white, size: 20),
),
),
],
);
}
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 {
const DriverArrivePassengerAndWaitMinute({Key? key}) : super(key: key);
@@ -338,35 +478,31 @@ class DriverArrivePassengerAndWaitMinute extends StatelessWidget {
return GetBuilder<MapPassengerController>(builder: (controller) {
return Column(
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(
borderRadius: BorderRadius.circular(15),
borderRadius: BorderRadius.circular(10),
child: LinearProgressIndicator(
backgroundColor: AppColor.accentColor.withOpacity(0.3),
backgroundColor: Colors.grey[200],
color: controller.remainingTimeDriverWaitPassenger5Minute < 60
? AppColor.redColor
: AppColor.greenColor,
minHeight: 20,
minHeight: 8,
value:
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
Widget build(BuildContext context) {
return GetBuilder<MapPassengerController>(builder: (controller) {
return controller.isDriverInPassengerWay == false ||
controller.timeToPassengerFromDriverAfterApplied > 0
? Column(
if (controller.timeToPassengerFromDriverAfterApplied <= 0) {
return const SizedBox();
}
return Column(
children: [
Row(
mainAxisAlignment: MainAxisAlignment.spaceBetween,
children: [
Text('Driver arriving in'.tr,
style: const TextStyle(fontWeight: FontWeight.bold)),
Text(
controller.stringRemainingTimeToPassenger,
style: const TextStyle(
fontWeight: FontWeight.bold, color: AppColor.primaryColor),
),
],
),
const SizedBox(height: 6),
ClipRRect(
borderRadius: BorderRadius.circular(15),
borderRadius: BorderRadius.circular(10),
child: LinearProgressIndicator(
backgroundColor: AppColor.accentColor.withOpacity(0.3),
color: controller
.remainingTimeToPassengerFromDriverAfterApplied <
60
? AppColor.redColor
: AppColor.greenColor,
minHeight: 20,
value: controller
.progressTimerToPassengerFromDriverAfterApplied
backgroundColor: Colors.grey[200],
color: AppColor.primaryColor,
minHeight: 8,
value: controller.progressTimerToPassengerFromDriverAfterApplied
.toDouble()
.clamp(0.0, 1.0),
),
),
const SizedBox(height: 4),
Center(
child: Text.rich(
TextSpan(
children: [
TextSpan(
text: '${'Driver is on the way'.tr} ',
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,
children: [
Text(
'${'Distance is'.tr} ${controller.data[0]['distance']['text']}',
'${'Distance is'.tr} ${controller.distance.toStringAsFixed(2)} KM',
style: AppStyle.title,
),
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/widgets/elevated_btn.dart';
import 'package:Intaleq/views/widgets/my_textField.dart';
import 'package:intl/intl.dart';
import 'dart:ui';
import '../../../constant/info.dart';
@@ -79,9 +80,7 @@ class CarDetailsTypeToChoose extends StatelessWidget {
return GetBuilder<MapPassengerController>(builder: (controller) {
_prepareCarTypes(controller);
if (!(controller.data.isNotEmpty &&
controller.isBottomSheetShown &&
controller.rideConfirm == false)) {
if (!(controller.isBottomSheetShown) && controller.rideConfirm == false) {
return const SizedBox.shrink();
}
// Added a BackdropFilter for a modern glassmorphism effect
@@ -351,7 +350,7 @@ class CarDetailsTypeToChoose extends StatelessWidget {
color: AppColor.primaryColor, size: 16),
const SizedBox(width: 4),
Text(
'${controller.distance} ${'KM'.tr}',
'${controller.distance.toStringAsFixed(1)} ${'KM'.tr}',
style: AppStyle.subtitle.copyWith(
color: AppColor.primaryColor,
fontWeight: FontWeight.w600,
@@ -418,30 +417,54 @@ class CarDetailsTypeToChoose extends StatelessWidget {
// --- LOGIC METHODS (UNCHANGED) ---
// 1. قم بإضافة هذا السطر في أعلى الملف
String _getPassengerPriceText(
CarType carType, MapPassengerController mapPassengerController) {
// الخطوة 1: احصل على السعر كـ double أولاً
double rawPrice;
switch (carType.carType) {
case 'Comfort':
return mapPassengerController.totalPassengerComfort.toStringAsFixed(1);
rawPrice = mapPassengerController.totalPassengerComfort;
break;
case 'Speed':
return mapPassengerController.totalPassengerSpeed.toStringAsFixed(1);
rawPrice = mapPassengerController.totalPassengerSpeed;
break;
case 'Electric':
return mapPassengerController.totalPassengerElectric.toStringAsFixed(1);
rawPrice = mapPassengerController.totalPassengerElectric;
break;
case 'Awfar Car':
return mapPassengerController.totalPassengerBalash.toStringAsFixed(1);
rawPrice = mapPassengerController.totalPassengerBalash;
break;
case 'Scooter':
return mapPassengerController.totalPassengerScooter.toStringAsFixed(1);
case 'Pink Bike': // دمج الحالات المتشابهة
rawPrice = mapPassengerController.totalPassengerScooter;
break;
case 'Van':
return mapPassengerController.totalPassengerVan.toStringAsFixed(1);
rawPrice = mapPassengerController.totalPassengerVan;
break;
case 'Lady':
return mapPassengerController.totalPassengerLady.toStringAsFixed(1);
case 'Pink Bike':
return mapPassengerController.totalPassengerScooter.toStringAsFixed(1);
rawPrice = mapPassengerController.totalPassengerLady;
break;
case 'Rayeh Gai':
return mapPassengerController.totalPassengerRayehGai.toStringAsFixed(1);
rawPrice = mapPassengerController.totalPassengerRayehGai;
break;
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(

View File

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

View File

@@ -168,8 +168,8 @@ class _SearchFieldState extends State<_SearchField> {
icon: Icon(Icons.location_on_outlined,
color: AppColor.accentColor, size: 30),
tooltip: widget.controller.isAnotherOreder
? 'Pick destination on map'
: 'Pick on map',
? 'Pick destination on map'.tr
: '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/style.dart';
import '../../../controller/functions/location_controller.dart';
import '../../../controller/home/device_tier.dart';
// import '../../../controller/functions/location_controller.dart'; // Un-comment if needed
// import '../../../controller/home/device_tier.dart'; // Removed to rely on Controller logic
import '../../../controller/home/map_passenger_controller.dart';
import '../../widgets/mycircular.dart';
import '../../widgets/mydialoug.dart';
@@ -17,7 +17,6 @@ class GoogleMapPassengerWidget extends StatelessWidget {
GoogleMapPassengerWidget({super.key});
final WayPointController wayPointController = Get.put(WayPointController());
final LocationController locationController = Get.find<LocationController>();
@override
Widget build(BuildContext context) {
@@ -32,24 +31,26 @@ class GoogleMapPassengerWidget extends StatelessWidget {
child: GoogleMap(
onMapCreated: controller.onMapCreated,
// ✅ حدود الكاميرا كما هي
// ✅ Camera Bounds
cameraTargetBounds: CameraTargetBounds(controller.boundsdata),
// ✅ Zoom أهدأ للأجهزة الضعيفة
// ✅ Performance: Smoother zoom limits for low-end devices
minMaxZoomPreference: controller.lowPerf
? const MinMaxZoomPreference(6, 17)
: 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, '',
() async {
controller.clearPolyline();
// Ensure we have car data available before routing
if (controller.dataCarsLocationByPassenger != null) {
await controller.getDirectionMap(
'${controller.passengerLocation.latitude},${controller.passengerLocation.longitude}',
'${argument.latitude},${argument.longitude}',
);
Get.back();
Get.back(); // Close Dialog
await controller.bottomSheet();
controller.showBottomSheet1();
} else {
@@ -59,52 +60,21 @@ class GoogleMapPassengerWidget extends StatelessWidget {
.tr,
'',
colorText: AppColor.redColor,
duration: const Duration(seconds: 11),
instantInit: true,
snackPosition: SnackPosition.TOP,
duration: const Duration(seconds: 5),
backgroundColor: AppColor.secondaryColor,
icon: const Icon(Icons.error, color: AppColor.redColor),
titleText: Text('Error'.tr,
style: const TextStyle(color: AppColor.redColor)),
messageText: Text(
'We Are Sorry That we dont have cars in your Location!'
.tr,
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) {
controller.hidePlaces();
},
@@ -114,71 +84,66 @@ class GoogleMapPassengerWidget extends StatelessWidget {
zoom: controller.lowPerf ? 14.5 : 15,
),
// ✅ ماركرز (احرص أن الأيقونات محجّمة ومخزّنة Cache في الكنترولر)
// ✅ Markers
markers: controller.markers.toSet(),
// ✅ بوليغونز كما هي
// ✅ Polygons (e.g., University/Country borders)
polygons: controller.polygons,
// ✅ Polyline مُبسّطة للأجهزة الضعيفة (الكنترولر يجهّز مجموعة مبسطة عند lowPerf)
// ✅ Polylines: Switch to lighter version if lowPerf is detected
polylines: controller.lowPerf
? controller.polyLinesLight
.toSet() // <- استخدم مجموعة خفيفة
? controller.polyLinesLight.toSet()
: controller.polyLines.toSet(),
// ✅ دوائر خفيفة على الأجهزة الضعيفة
// 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
// ✅ Map Type: Switch to Normal map on low-end devices to save RAM
mapType: controller.lowPerf
? MapType.normal
: (controller.mapType
? MapType.satellite
: MapType.terrain),
: MapType
.normal), // Changed terrain default to normal for better performance
// ✅ UI Settings for Performance
myLocationButtonEnabled: false,
// ⚠️ liteMode (Android فقط): فعّله على الأجهزة الضعيفة
// liteModeEnabled: controller.lowPerf,
liteModeEnabled: Platform.isAndroid ? isLowEnd() : false,
trafficEnabled: controller.mapTrafficON && !isLowEnd(),
buildingsEnabled: !isLowEnd(),
// ✅ تقليل الكلفة الرسومية
mapToolbarEnabled: false,
rotateGesturesEnabled: isLowEnd() ? false : true,
tiltGesturesEnabled: false, // تعطيل الميلان لتقليل الحمل
tiltGesturesEnabled:
false, // Disable tilt to save GPU resources
// ✅ Throttle لحركة الكاميرا على الأجهزة الضعيفة
onCameraMove: (position) {
if (controller.lowPerf) {
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}';
}
// Lite Mode (Static image) only on very low-end Androids if needed,
// 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) {
} else if (controller.passengerStartLocationFromMap == true) {
controller.newStartPointLocation = position.target;
}
controller.newMyLocation = 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) {
controller.onCameraMoveThrottled(position);
}
},

View File

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

View File

@@ -480,7 +480,7 @@ class MainBottomMenuMap extends StatelessWidget {
controller.placeDestinationController.clear();
controller.showBottomSheet1();
// controller.showBottomSheet1();
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_font_icons/flutter_font_icons.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/main.dart';
import 'package:Intaleq/views/home/profile/complaint_page.dart';
import 'package:intl/intl.dart';
import '../../../constant/colors.dart';
import '../../../constant/links.dart';
@@ -14,67 +300,80 @@ 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 Obx(() {
final controller = Get.find<MapPassengerController>();
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,
right: 0,
bottom: 0,
child: Container(
decoration: BoxDecoration(
color: AppColor.secondaryColor,
color: Colors.white, // خلفية بيضاء لنظافة التصميم
borderRadius: const BorderRadius.only(
topLeft: Radius.circular(24),
topRight: Radius.circular(24),
topLeft: Radius.circular(30),
topRight: Radius.circular(30),
),
boxShadow: [
BoxShadow(
color: Colors.black.withOpacity(0.2),
blurRadius: 20,
color: Colors.black.withOpacity(0.15),
blurRadius: 25,
spreadRadius: 5,
offset: const Offset(0, -5),
),
],
),
child: Padding(
padding: const EdgeInsets.fromLTRB(16, 12, 16, 16),
padding: const EdgeInsets.fromLTRB(20, 12, 20, 20),
child: Column(
mainAxisSize: MainAxisSize.min,
children: [
// مقبض السحب (Handle)
// مقبض السحب
Container(
width: 40,
width: 50,
height: 5,
decoration: BoxDecoration(
color: AppColor.writeColor.withOpacity(0.3),
borderRadius: BorderRadius.circular(12),
color: Colors.grey[300],
borderRadius: BorderRadius.circular(10),
),
),
const SizedBox(height: 12),
const SizedBox(height: 20),
// --- 1. قسم معلومات السائق ---
_buildDriverInfo(controller),
const Divider(height: 24, thickness: 0.5),
// الصف العلوي: معلومات السائق + السعر المثبت
_buildDriverAndPriceSection(controller),
// --- 2. قسم تقدم الرحلة ---
const SizedBox(height: 20),
// الصف الأوسط: لوحة السيارة الواقعية + نوع السيارة
_buildCarInfoSection(controller),
const SizedBox(height: 20),
// شريط التقدم والوقت
_buildTripProgress(controller),
const SizedBox(height: 16),
// --- 3. قسم الإجراءات والأمان ---
const SizedBox(height: 20),
const Divider(thickness: 1, color: Color(0xFFEEEEEE)),
const SizedBox(height: 10),
// الأزرار
_buildActionButtons(
context, controller, profileController, audioController),
],
@@ -85,86 +384,227 @@ class RideBeginPassenger extends StatelessWidget {
});
}
// --- ويدجت مساعدة لعرض معلومات السائق بشكل منظم ---
Widget _buildDriverInfo(MapPassengerController controller) {
// ويدجت معلومات السائق والسعر
Widget _buildDriverAndPriceSection(MapPassengerController controller) {
return Row(
children: [
CircleAvatar(
radius: 28,
// صورة السائق
Container(
decoration: BoxDecoration(
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(
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),
controller.driverName,
style: AppStyle.title.copyWith(
fontWeight: FontWeight.w800,
fontSize: 18,
color: Colors.black87,
),
maxLines: 1,
overflow: TextOverflow.ellipsis,
),
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),
color: AppColor.yellowColor, size: 18),
const SizedBox(width: 4),
Text(
controller.driverRate,
style: AppStyle.subtitle.copyWith(
fontWeight: FontWeight.bold, color: Colors.grey[600]),
),
],
),
],
)
),
),
// السعر المثبت (تصميم كارد للسعر)
Container(
padding: const EdgeInsets.symmetric(horizontal: 16, vertical: 8),
decoration: BoxDecoration(
color: AppColor.primaryColor.withOpacity(0.1),
borderRadius: BorderRadius.circular(12),
border: Border.all(color: AppColor.primaryColor.withOpacity(0.2)),
),
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,
),
),
],
),
),
],
);
}
// --- ويدجت مساعدة لعرض شريط التقدم ---
// ويدجت معلومات السيارة ولوحة الأرقام
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) {
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)),
Row(
children: [
const Icon(Icons.access_time_filled,
size: 16, color: Colors.grey),
const SizedBox(width: 5),
Text('Arriving in'.tr,
style: TextStyle(color: Colors.grey[600], fontSize: 13)),
],
),
const SizedBox(height: 8),
Text(
controller.stringRemainingTimeRideBegin,
style: const TextStyle(
fontWeight: FontWeight.bold,
fontSize: 16,
color: AppColor.primaryColor,
),
),
],
),
const SizedBox(height: 10),
ClipRRect(
borderRadius: BorderRadius.circular(10),
child: LinearProgressIndicator(
backgroundColor: AppColor.primaryColor.withOpacity(0.2),
backgroundColor: Colors.grey[200],
color: controller.remainingTimeTimerRideBegin < 60
? AppColor.redColor
: AppColor.greenColor,
minHeight: 10,
: AppColor.primaryColor,
minHeight: 8,
value: controller.progressTimerRideBegin.toDouble(),
),
),
@@ -172,21 +612,21 @@ class RideBeginPassenger extends StatelessWidget {
);
}
// --- ويدجت مساعدة لعرض أزرار الإجراءات ---
Widget _buildActionButtons(
BuildContext context,
MapPassengerController controller,
ProfileController profileController,
AudioRecorderController audioController) {
return Row(
mainAxisAlignment: MainAxisAlignment.spaceAround,
mainAxisAlignment: MainAxisAlignment.spaceEvenly,
children: [
// زر SOS بتصميم تحذيري
_buildActionButton(
icon: Icons.sos_rounded,
label: 'SOS'.tr,
color: AppColor.redColor,
label: 'SOS',
iconColor: Colors.white,
bgColor: AppColor.redColor,
onTap: () async {
// --- نفس منطقك القديم ---
if (box.read(BoxName.sosPhonePassenger) == null) {
await profileController.updatField(
'sosPhone', TextInputType.phone);
@@ -196,12 +636,14 @@ class RideBeginPassenger extends StatelessWidget {
makePhoneCall('112');
}
}),
// زر واتساب
_buildActionButton(
icon: FontAwesome.whatsapp,
label: 'WhatsApp'.tr,
color: AppColor.greenColor,
label: 'WhatsApp',
iconColor: Colors.white,
bgColor: const Color(0xFF25D366),
onTap: () async {
// --- نفس منطقك القديم ---
if (box.read(BoxName.sosPhonePassenger) == null ||
box.read(BoxName.sosPhonePassenger) == 'sos') {
await profileController.updatField(
@@ -211,75 +653,105 @@ class RideBeginPassenger extends StatelessWidget {
} else {
final phoneNumber =
box.read(BoxName.sosPhonePassenger).toString();
final phone = controller.formatSyrianPhoneNumber(phoneNumber);
controller.sendWhatsapp(phone); //
controller.sendWhatsapp(phone);
}
}),
// زر المشاركة
_buildActionButton(
icon: Icons.share_location_outlined, // أيقونة جديدة ومناسبة
label: 'Share'.tr, // اسم جديد وواضح
color: AppColor.blueColor,
icon: Icons.share_location_rounded,
label: 'Share',
iconColor: AppColor.primaryColor,
bgColor: AppColor.primaryColor.withOpacity(0.1),
onTap: () async {
// نفس الوظيفة السابقة التي كانت تحت اسم "Video Call"
await controller.getTokenForParent();
await controller.shareTripWithFamily();
}),
_buildActionButton(
icon: audioController.isRecording
? Icons.mic_off_rounded
: Icons.mic_none_rounded,
label: audioController.isRecording ? 'Stop'.tr : 'Record'.tr,
color: AppColor.primaryColor,
// زر التسجيل
GetBuilder<AudioRecorderController>(
init: audioController,
builder: (audioCtx) {
return _buildActionButton(
icon: audioCtx.isRecording ? Icons.stop : Icons.mic,
label: audioCtx.isRecording ? 'Stop' : 'Record',
iconColor:
audioCtx.isRecording ? Colors.white : AppColor.primaryColor,
bgColor: audioCtx.isRecording
? AppColor.redColor
: AppColor.primaryColor.withOpacity(0.1),
isRecordingAnimation: audioCtx.isRecording,
onTap: () async {
// --- نفس منطقك القديم ---
if (audioController.isRecording == false) {
await audioController.startRecording();
if (audioCtx.isRecording == false) {
await audioCtx.startRecording();
Toast.show(context, 'Start Record'.tr, AppColor.greenColor);
} else {
await audioController.stopRecording();
await audioCtx.stopRecording();
Toast.show(context, 'Record saved'.tr, AppColor.greenColor);
}
},
);
},
),
// زر الشكوى
_buildActionButton(
icon: Icons.note_add_outlined,
label: 'Complaint'.tr,
color: AppColor.yellowColor,
icon: Icons.report_gmailerrorred_rounded,
label: 'Report'.tr,
iconColor: Colors.grey[700]!,
bgColor: Colors.grey[200]!,
onTap: () {
// --- نفس منطقك القديم ---
Get.to(() => ComplaintPage(), transition: Transition.downToUp);
}),
],
);
}
// --- ويدجت مساعدة لبناء زر إجراء فردي ---
Widget _buildActionButton(
{required IconData icon,
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(
required Color iconColor,
required Color bgColor,
required VoidCallback onTap,
bool isRecordingAnimation = false,
}) {
return Column(
mainAxisSize: MainAxisSize.min,
children: [
Container(
padding: const EdgeInsets.all(12),
InkWell(
onTap: onTap,
borderRadius: BorderRadius.circular(15),
child: AnimatedContainer(
duration: const Duration(milliseconds: 300),
width: 50,
height: 50,
decoration: BoxDecoration(
color: color.withOpacity(0.1),
shape: BoxShape.circle,
color: bgColor,
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),
),
child: Icon(icon, color: color, size: 26),
),
const SizedBox(height: 6),
Text(label, style: AppStyle.subtitle.copyWith(fontSize: 12)),
],
),
child: Icon(icon, color: iconColor, size: 24),
),
),
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_font_icons/flutter_font_icons.dart';
import 'package:get/get.dart';
// ... استيراد ملفاتك الأخرى ...
import '../../../constant/box_name.dart';
import '../../../constant/colors.dart';
import '../../../constant/links.dart';
import '../../../constant/style.dart';
import '../../../controller/home/map_passenger_controller.dart';
import '../../../controller/profile/profile_controller.dart';
@@ -14,161 +17,299 @@ class RideFromStartApp extends StatelessWidget {
@override
Widget build(BuildContext context) {
ProfileController profileController = Get.put(ProfileController());
return GetBuilder<MapPassengerController>(builder: (controller) {
return (controller.statusRideFromStart
// || controller.statusRide == 'Begin'
)
? Positioned(
left: 10,
right: 10,
bottom: 4,
final profileController = Get.put(ProfileController());
final MapPassengerController controller =
Get.find<MapPassengerController>();
return Obx(() {
final bool isRideActive =
controller.currentRideState.value == RideState.inProgress &&
controller.isStartAppHasRide == true;
if (!isRideActive) return const SizedBox();
// قراءة البيانات
final rideData = controller.rideStatusFromStartApp['data'] ?? {};
final driverId = rideData['driver_id'];
final driverName = rideData['driverName'] ?? 'Captain'.tr;
final driverRate = controller.driverRate;
final carType = rideData['carType'] ?? 'Car'.tr;
final carModel = controller.model ?? '';
final arrivalTime = box.read(BoxName.arrivalTime) ?? '--:--';
// تحديد البيانات للعرض
final displayTime = controller.stringRemainingTimeRideBegin.isNotEmpty
? controller.stringRemainingTimeRideBegin
: arrivalTime;
final displayDistance = rideData['distance']?.toStringAsFixed(1) ?? 'N/A';
final displayPrice = rideData['price']?.toString() ?? 'N/A';
return Positioned(
left: 0,
right: 0,
bottom: 0, // ملتصق بالأسفل تماماً
child: Container(
decoration: AppStyle.boxDecoration1,
height: Get.height * .3,
padding: const EdgeInsets.symmetric(horizontal: 20, vertical: 15),
decoration: const BoxDecoration(
color: Colors.white, // خلفية بيضاء نظيفة
borderRadius: BorderRadius.only(
topLeft: Radius.circular(25),
topRight: Radius.circular(25),
),
boxShadow: [
BoxShadow(
color: Colors.black12,
blurRadius: 15.0,
spreadRadius: 5.0,
offset: Offset(0, -5),
),
],
),
child: Column(
mainAxisAlignment: MainAxisAlignment.spaceEvenly,
mainAxisSize: MainAxisSize.min,
crossAxisAlignment: CrossAxisAlignment.start,
children: <Widget>[
// 1. مقبض صغير للدلالة على السحب (تصميم جمالي)
Center(
child: Container(
width: 40,
height: 4,
margin: const EdgeInsets.only(bottom: 15),
decoration: BoxDecoration(
color: Colors.grey[300],
borderRadius: BorderRadius.circular(10),
),
),
),
// 2. حالة الرحلة + معلومات السائق
Row(
mainAxisAlignment: MainAxisAlignment.spaceEvenly,
crossAxisAlignment: CrossAxisAlignment.start,
children: [
// صورة السائق
Container(
width: Get.width * .15,
decoration: AppStyle.boxDecoration1,
child: Column(
children: [
Text(
'⏱️',
style: AppStyle.title,
padding: const EdgeInsets.all(2),
decoration: BoxDecoration(
shape: BoxShape.circle,
border:
Border.all(color: AppColor.primaryColor, width: 2),
),
Text(
box.read(BoxName.arrivalTime),
style: AppStyle.title,
),
],
),
),
Container(
width: Get.width * .15,
decoration: AppStyle.boxDecoration1,
child: Column(
children: [
Text(
'📍',
style: AppStyle.title,
),
Text(
controller.rideStatusFromStartApp['data']
['distance']
.toString(),
style: AppStyle.title,
),
],
),
),
Container(
width: Get.width * .15,
decoration: AppStyle.boxDecoration1,
child: Column(
children: [
Text(
'💵 ',
style: AppStyle.title,
),
Text(
controller.rideStatusFromStartApp['data']
['price'],
style: AppStyle.title,
),
],
),
),
],
),
Row(
mainAxisAlignment: MainAxisAlignment.spaceEvenly,
children: [
CircleAvatar(
radius: 30,
child: CircleAvatar(
radius: 28,
backgroundImage: NetworkImage(
// '',
// ),
'https://ride.mobile-app.store/portrate_captain_image/${controller.rideStatusFromStartApp['data']['driver_id']}.jpg'),
'${AppLink.server}/portrate_captain_image/$driverId.jpg'),
),
Text(
'${controller.rideStatusFromStartApp['data']['driverName']}',
style: AppStyle.title,
),
Column(
const SizedBox(width: 12),
// الاسم والسيارة
Expanded(
child: Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
Text(
'${controller.rideStatusFromStartApp['data']['rateDriver']} 📈',
style: AppStyle.title,
driverName,
style: AppStyle.title.copyWith(
fontSize: 16,
fontWeight: FontWeight.bold,
color: Colors.black87,
),
Text(
'${controller.rideStatusFromStartApp['data']['carType']}',
style: AppStyle.title,
),
],
),
],
maxLines: 1,
overflow: TextOverflow.ellipsis,
),
const SizedBox(height: 4),
Row(
children: [
Icon(Icons.star,
color: AppColor.yellowColor, size: 16),
const SizedBox(width: 4),
Text(
driverRate,
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(
"$carType - $carModel",
style: AppStyle.title.copyWith(
fontSize: 13, color: Colors.grey[600]),
),
],
),
],
),
),
// حالة الرحلة (نص ملون)
_buildStatusBadge(controller.currentRideState.value),
],
),
const SizedBox(height: 20),
// 3. شريط المعلومات (وقت، مسافة، سعر)
Container(
padding:
const EdgeInsets.symmetric(vertical: 12, horizontal: 10),
decoration: BoxDecoration(
color: AppColor.grayColor
.withOpacity(0.1), // خلفية رمادية خفيفة جداً
borderRadius: BorderRadius.circular(15),
),
child: Row(
mainAxisAlignment: MainAxisAlignment.spaceAround,
children: [
IconButton(
onPressed: () async {
if (box.read(BoxName.sosPhonePassenger) == null) {
{
await profileController.updatField(
'sosPhone', TextInputType.phone);
box.write(BoxName.sosPhonePassenger,
profileController.prfoileData['sosPhone']);
}
} else {
controller
.sendSMS(box.read(BoxName.sosPhonePassenger));
}
},
icon: const Icon(
Icons.sos_rounded,
color: AppColor.redColor,
),
),
IconButton(
onPressed: () 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 {
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,
),
),
],
)
_buildInfoColumn(
Icons.access_time_filled, displayTime, "Time".tr),
_buildVerticalDivider(),
_buildInfoColumn(Icons.location_on, "$displayDistance KM",
"Distance".tr),
_buildVerticalDivider(),
_buildInfoColumn(
Icons.attach_money, "$displayPrice SYP", "Price".tr),
],
),
),
)
: 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:get/get.dart';
import 'package:Intaleq/constant/colors.dart';
import 'package:Intaleq/constant/style.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 {
@@ -36,12 +33,19 @@ class _SearchingCaptainWindowState extends State<SearchingCaptainWindow>
@override
Widget build(BuildContext context) {
return GetBuilder<MapPassengerController>(
builder: (controller) {
// [تعديل 1] نستخدم Obx للاستماع إلى التغييرات في حالة الرحلة
return Obx(() {
// ابحث عن الكنترولر مرة واحدة
final controller = Get.find<MapPassengerController>();
// [تعديل 2] شرط الإظهار يعتمد الآن على حالة الرحلة مباشرة
final bool isVisible =
controller.currentRideState.value == RideState.searching;
return AnimatedPositioned(
duration: const Duration(milliseconds: 300),
curve: Curves.easeInOut,
bottom: controller.isSearchingWindow ? 0 : -Get.height * 0.4,
bottom: isVisible ? 0 : -Get.height * 0.45, // زيادة الارتفاع قليلاً
left: 0,
right: 0,
child: Container(
@@ -72,13 +76,13 @@ class _SearchingCaptainWindowState extends State<SearchingCaptainWindow>
width: double.infinity,
child: OutlinedButton(
onPressed: () {
// --- نفس منطقك للإلغاء ---
// [تعديل 3] استدعاء دالة الإلغاء الموحدة
controller.cancelRide();
},
style: OutlinedButton.styleFrom(
foregroundColor: AppColor.writeColor,
side: BorderSide(
color: AppColor.writeColor.withOpacity(0.3)),
side:
BorderSide(color: AppColor.writeColor.withOpacity(0.3)),
shape: RoundedRectangleBorder(
borderRadius: BorderRadius.circular(12)),
padding: const EdgeInsets.symmetric(vertical: 12),
@@ -90,8 +94,7 @@ class _SearchingCaptainWindowState extends State<SearchingCaptainWindow>
),
),
);
},
);
});
}
// --- ويدجت بناء أنيميشن الرادار ---
@@ -101,7 +104,7 @@ class _SearchingCaptainWindowState extends State<SearchingCaptainWindow>
child: Stack(
alignment: Alignment.center,
children: [
// --- دوائر الرادار المتحركة ---
// --- دوائر الرادار المتحركة (تبقى كما هي) ---
...List.generate(3, (index) {
return FadeTransition(
opacity: Tween<double>(begin: 1.0, end: 0.0).animate(
@@ -134,6 +137,7 @@ class _SearchingCaptainWindowState extends State<SearchingCaptainWindow>
mainAxisAlignment: MainAxisAlignment.center,
children: [
Text(
// [تعديل 4] النص يأتي مباشرة من الكنترولر
controller.driversStatusForSearchWindow,
style: AppStyle.headTitle.copyWith(fontSize: 20),
textAlign: TextAlign.center,
@@ -146,138 +150,319 @@ class _SearchingCaptainWindowState extends State<SearchingCaptainWindow>
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! > 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;
return SizedBox(
// --- [!! تعديل جوهري !!] ---
// لم نعد بحاجة لـ buildTimerForIncrease
// المؤقت الرئيسي في الكنترولر هو من يقرر متى يعرض الحوار
// وهذا الجزء من الواجهة أصبح "غبياً" (لا يحتوي على منطق)
// الكنترولر سيستدعي _showIncreaseFeeDialog مباشرة
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),
child: Icon(
Icons.search,
color: AppColor.writeColor.withOpacity(0.8),
),
),
],
),
),
],
),
],
),
);
},
);
}
}
// --- دالة لعرض نافذة زيادة الأجرة (مأخوذة من منطقك القديم) ---
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();
},
),
ElevatedButton(
style: ElevatedButton.styleFrom(backgroundColor: AppColor.greenColor),
child: Text("Increase Fee".tr),
onPressed: () =>
mapPassengerController.increaseFeeByPassengerAndReOrder(),
),
],
);
}
// --- [!! تعديل جوهري !!] ---
// تم حذف دالة `buildTimerForIncrease` بالكامل.
// تم حذف دالة `_showIncreaseFeeDialog` من هذا الملف.
// لماذا؟ لأن الكنترولر الآن هو المسؤول الوحيد عن إظهار الحوار.
// دالة `_showIncreaseFeeDialog` موجودة بالفعل داخل `map_passenger_controller.dart`
// وسيتم استدعاؤها من `_handleRideState` عند انتهاء مهلة الـ 90 ثانية.
// // --- الويدجت الرئيسية بالتصميم الجديد ---
// class SearchingCaptainWindow extends StatefulWidget {
// const SearchingCaptainWindow({super.key});
// @override
// State<SearchingCaptainWindow> createState() => _SearchingCaptainWindowState();
// }
// class _SearchingCaptainWindowState extends State<SearchingCaptainWindow>
// with SingleTickerProviderStateMixin {
// late AnimationController _animationController;
// @override
// void initState() {
// super.initState();
// _animationController = AnimationController(
// vsync: this,
// duration: const Duration(seconds: 2),
// )..repeat();
// }
// @override
// void dispose() {
// _animationController.dispose();
// super.dispose();
// }
// @override
// Widget build(BuildContext context) {
// return GetBuilder<MapPassengerController>(
// builder: (controller) {
// return AnimatedPositioned(
// duration: const Duration(milliseconds: 300),
// 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. زر الإلغاء ---
// SizedBox(
// width: double.infinity,
// child: OutlinedButton(
// onPressed: () {
// // --- نفس منطقك للإلغاء ---
// controller.changeCancelRidePageShow();
// },
// 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),
// ),
// ),
// ],
// ),
// ),
// );
// },
// );
// }
// // --- ويدجت بناء أنيميشن الرادار ---
// 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,
child: IconButton(
onPressed: () async {
await controller.getTokenForParent();
await controller.shareTripWithFamily();
},
icon: const Icon(
AntDesign.Safety,

View File

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

View File

@@ -1,71 +1,56 @@
import 'dart:async';
import 'package:Intaleq/views/widgets/mydialoug.dart';
import 'package:flutter/material.dart';
import 'package:flutter/services.dart';
import 'package:get/get.dart';
import 'package:get/get_core/src/get_main.dart';
import 'package:intl/intl.dart';
import '../../../constant/box_name.dart';
import '../../../constant/links.dart';
import '../../../controller/functions/crud.dart';
import '../../../main.dart';
// خدمة الدفع للراكب (تم تحديث المسارات)
class PaymentService {
final String _baseUrl = "${AppLink.seferPaymentServer}/sms_webhook";
// المسار الجديد لمجلد الركاب
final String _baseUrl = "${AppLink.paymentServer}/ride/shamcash/passenger";
Future<String?> createInvoice({
required String userPhone,
required double amount,
}) async {
final url = "$_baseUrl/create_invoice_passenger.php";
Future<String?> createInvoice({required double amount}) async {
final url = "$_baseUrl/create_invoice.php";
try {
final response = await CRUD().postWallet(
link: url,
payload: {
'user_phone': userPhone.toString(),
'passengerID': box.read(BoxName.passengerID),
'passengerID': box.read(BoxName.passengerID), // استخدام passengerID
'amount': amount.toString(),
},
).timeout(const Duration(seconds: 15)); // إضافة مهلة للطلب
).timeout(const Duration(seconds: 15));
if (response != 'failure') {
final data = (response);
final data = response;
if (data['status'] == 'success' && data['invoice_number'] != null) {
debugPrint(
"تم إنشاء الفاتورة بنجاح. الرقم: ${data['invoice_number']}");
return data['invoice_number'].toString();
} else {
debugPrint("فشل في إنشاء الفاتورة من السيرفر: ${data['message']}");
return null;
}
} else {
debugPrint("خطأ في السيرفر عند إنشاء الفاتورة: ${response.statusCode}");
return null;
}
return null;
} catch (e) {
debugPrint("حدث استثناء عند إنشاء الفاتورة: $e");
debugPrint("Create Invoice Error: $e");
return null;
}
}
/// دالة للتحقق من حالة فاتورة واحدة
Future<bool> checkInvoiceStatus(String invoiceNumber) async {
final url = "$_baseUrl/check_invoice_status_passenger.php";
final url = "$_baseUrl/check_status.php";
try {
final response = await CRUD().postWallet(link: url, payload: {
'invoice_number': invoiceNumber,
}).timeout(const Duration(seconds: 10)); // مهلة للشبكة
}).timeout(const Duration(seconds: 10));
if (response != 'failure') {
final data = (response);
final data = response;
return data['status'] == 'success' &&
data['invoice_status'] == 'completed';
}
return false;
} catch (e) {
debugPrint("خطأ أثناء التحقق من الفاتورة: $e");
return false;
}
}
@@ -83,14 +68,14 @@ class PaymentScreenSmsProvider extends StatefulWidget {
final double amount;
final String providerName;
final String providerLogo;
final String paymentPhoneNumber;
final String qrImagePath;
const PaymentScreenSmsProvider({
super.key,
required this.amount,
this.providerName = 'شام كاش',
this.providerLogo = 'assets/images/shamCash.png',
this.paymentPhoneNumber = '963942542053',
this.qrImagePath = 'assets/images/shamcashsend.png',
});
@override
@@ -103,7 +88,6 @@ class _PaymentScreenSmsProviderState extends State<PaymentScreenSmsProvider> {
Timer? _pollingTimer;
PaymentStatus _status = PaymentStatus.creatingInvoice;
String? _invoiceNumber;
final String phone = box.read(BoxName.phoneWallet);
@override
void initState() {
@@ -113,17 +97,14 @@ class _PaymentScreenSmsProviderState extends State<PaymentScreenSmsProvider> {
@override
void dispose() {
_pollingTimer?.cancel(); // مهم جداً: إلغاء المؤقت عند الخروج من الشاشة
_pollingTimer?.cancel();
super.dispose();
}
void _createAndPollInvoice() async {
setState(() => _status = PaymentStatus.creatingInvoice);
final invoiceNumber = await _paymentService.createInvoice(
userPhone: phone,
amount: widget.amount,
);
final invoiceNumber =
await _paymentService.createInvoice(amount: widget.amount);
if (invoiceNumber != null && mounted) {
setState(() {
@@ -137,7 +118,7 @@ class _PaymentScreenSmsProviderState extends State<PaymentScreenSmsProvider> {
}
void _startPolling(String invoiceNumber) {
const timeoutDuration = Duration(minutes: 3);
const timeoutDuration = Duration(minutes: 5);
var elapsed = Duration.zero;
_pollingTimer = Timer.periodic(const Duration(seconds: 5), (timer) async {
@@ -147,72 +128,55 @@ class _PaymentScreenSmsProviderState extends State<PaymentScreenSmsProvider> {
if (mounted) setState(() => _status = PaymentStatus.paymentTimeout);
return;
}
debugPrint("Polling... Checking invoice status for: $invoiceNumber");
final isCompleted =
await _paymentService.checkInvoiceStatus(invoiceNumber);
if (isCompleted && mounted) {
timer.cancel();
setState(() => _status = PaymentStatus.paymentSuccess);
// TODO: تحديث رصيد المستخدم أو تنفيذ الإجراءات اللازمة
MyDialog().getDialog(
'Payment Successful!'.tr,
''.tr,
() {
Get.back();
Get.back();
},
);
}
});
}
/// دالة جديدة لمعالجة محاولة الرجوع للخلف
void _onPopInvoked(bool didPop) async {
// إذا كان الرجوع قد تم بالفعل (مثلاً من خلال Navigator.pop)، لا تفعل شيئاً
if (didPop) return;
// إذا كان المستخدم ينتظر الدفع، أظهر له حوار التأكيد
Future<bool> _onPopInvoked() async {
if (_status == PaymentStatus.waitingForPayment) {
final shouldPop = await showDialog<bool>(
context: context,
builder: (context) => AlertDialog(
title: const Text('هل أنت متأكد؟'),
content: const Text('إذا خرجت الآن، سيتم إلغاء عملية الدفع الحالية.'),
title: const Text('إلغاء العملية؟', textAlign: TextAlign.right),
content: const Text('الخروج الآن سيؤدي لإلغاء متابعة عملية الدفع.',
textAlign: TextAlign.right),
actions: <Widget>[
TextButton(
onPressed: () => Navigator.of(context).pop(false),
child: const Text('البقاء'),
),
child: const Text('البقاء')),
TextButton(
onPressed: () => Navigator.of(context).pop(true),
child: const Text('الخروج'),
),
child: const Text('خروج', style: TextStyle(color: Colors.red))),
],
),
);
// إذا وافق المستخدم على الخروج، قم بإغلاق الشاشة
if (shouldPop ?? false) {
Navigator.of(context).pop();
}
return shouldPop ?? false;
}
return true;
}
@override
Widget build(BuildContext context) {
// استخدام PopScope بدلاً من WillPopScope
return PopScope(
// منع الرجوع التلقائي فقط في حالة انتظار الدفع
canPop: _status != PaymentStatus.waitingForPayment,
// استدعاء دالة التحقق عند محاولة الرجوع
onPopInvoked: _onPopInvoked,
return WillPopScope(
onWillPop: _onPopInvoked,
child: Scaffold(
appBar: AppBar(title: Text("الدفع عبر ${widget.providerName}")),
body: Padding(
padding: const EdgeInsets.all(16.0),
child: Center(
child: _buildContentByStatus(),
backgroundColor: Colors.grey[50],
appBar: AppBar(
title: Text("دفع عبر ${widget.providerName}"),
centerTitle: true,
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: [
CircularProgressIndicator(),
SizedBox(height: 20),
Text("جاري إنشاء فاتورة الدفع...", style: TextStyle(fontSize: 16)),
Text("جاري إنشاء رقم البيان...", style: TextStyle(fontSize: 16)),
],
);
case PaymentStatus.waitingForPayment:
@@ -242,94 +206,162 @@ class _PaymentScreenSmsProviderState extends State<PaymentScreenSmsProvider> {
Widget _buildWaitingForPaymentUI() {
final currencyFormat = NumberFormat.decimalPattern('ar_SY');
final invoiceText = _invoiceNumber ?? '------';
final invoiceText = _invoiceNumber ?? '---';
return SingleChildScrollView(
physics: const BouncingScrollPhysics(),
child: Column(
crossAxisAlignment: CrossAxisAlignment.center,
children: [
// 1. المبلغ
Container(
width: double.infinity,
padding: const EdgeInsets.symmetric(vertical: 25, horizontal: 15),
decoration: BoxDecoration(
gradient: LinearGradient(
colors: [Colors.blue.shade800, Colors.blue.shade600]),
borderRadius: BorderRadius.circular(16),
boxShadow: [
BoxShadow(
color: Colors.blue.withOpacity(0.25),
blurRadius: 15,
offset: const Offset(0, 8))
],
),
child: Column(
children: [
Image.asset(widget.providerLogo, width: 96),
const SizedBox(height: 16),
Text("تعليمات الدفع", style: Theme.of(context).textTheme.titleLarge),
const SizedBox(height: 12),
Card(
elevation: 1.5,
shape:
RoundedRectangleBorder(borderRadius: BorderRadius.circular(12)),
child: Padding(
padding: const EdgeInsets.all(16),
child: Column(
children: [
_StepTile(number: 1, text: "افتح تطبيق محفظتك الإلكترونية."),
_StepTile(number: 2, text: "اختر خدمة تحويل الأموال."),
_StepTile(
number: 3,
text:
"أدخل المبلغ المطلوب: ${currencyFormat.format(widget.amount)} ل.س"),
_StepTile(number: 4, text: "حوّل إلى الرقم التالي:"),
// --- التعديل هنا ---
ListTile(
contentPadding: EdgeInsets.zero,
title: Text(
widget.paymentPhoneNumber,
style: const TextStyle(
fontSize: 20,
fontWeight: FontWeight.bold,
letterSpacing: 1.2),
),
trailing: OutlinedButton.icon(
onPressed: () async {
await Clipboard.setData(
ClipboardData(text: widget.paymentPhoneNumber));
if (mounted) {
ScaffoldMessenger.of(context).showSnackBar(
const SnackBar(
content: Text("تم نسخ رقم الهاتف")));
}
},
icon: const Icon(Icons.copy, size: 18),
label: const Text("نسخ"),
),
),
// --- نهاية التعديل ---
const Text("المبلغ المطلوب",
style: TextStyle(color: Colors.white70, fontSize: 14)),
const SizedBox(height: 8),
_StepTile(
number: 5,
text: "هام: انسخ رقم القسيمة والصقه في خانة \"البيان\"."),
ListTile(
contentPadding: EdgeInsets.zero,
title: Text(invoiceText,
Text("${currencyFormat.format(widget.amount)} ل.س",
style: const TextStyle(
fontSize: 20,
color: Colors.white,
fontSize: 32,
fontWeight: FontWeight.bold)),
],
),
),
const SizedBox(height: 30),
// 2. التعليمات والنسخ (للراكب)
Container(
padding: const EdgeInsets.all(20),
decoration: BoxDecoration(
color: Colors.white,
borderRadius: BorderRadius.circular(16),
border: Border.all(color: Colors.grey.shade200),
boxShadow: [
BoxShadow(
color: Colors.grey.shade100,
blurRadius: 10,
offset: const Offset(0, 4))
],
),
child: Column(
children: [
Row(
children: [
Container(
padding: const EdgeInsets.all(8),
decoration: BoxDecoration(
color: Colors.orange.shade50, shape: BoxShape.circle),
child: Icon(Icons.priority_high_rounded,
color: Colors.orange.shade800, size: 20),
),
const SizedBox(width: 12),
const Expanded(
child: Text(
"انسخ الرقم أدناه وضعه في خانة (الملاحظات) عند الدفع.",
style: TextStyle(
fontSize: 14, fontWeight: FontWeight.w600)),
),
],
),
const SizedBox(height: 20),
InkWell(
onTap: () {
Clipboard.setData(ClipboardData(text: invoiceText));
ScaffoldMessenger.of(context).showSnackBar(SnackBar(
content: const Text("تم نسخ رقم البيان ✅",
textAlign: TextAlign.center),
backgroundColor: Colors.green.shade600));
},
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)),
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 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(
children: [
Image.asset(widget.qrImagePath,
width: 180,
height: 180,
fit: BoxFit.contain,
errorBuilder: (c, o, s) => const Icon(Icons.qr_code_2,
size: 100, color: Colors.grey)),
const SizedBox(height: 8),
const Text("اضغط للتكبير",
style: TextStyle(fontSize: 10, color: Colors.grey)),
],
),
),
),
const SizedBox(height: 40),
const LinearProgressIndicator(backgroundColor: Colors.white),
const SizedBox(height: 10),
const Text("ننتظر إشعار الدفع تلقائياً...",
style: TextStyle(color: Colors.grey, fontSize: 12)),
const SizedBox(height: 20),
const 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(
mainAxisAlignment: MainAxisAlignment.center,
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 Text("تم الدفع بنجاح!",
style: TextStyle(fontSize: 22, fontWeight: FontWeight.bold)),
const SizedBox(height: 20),
ElevatedButton(
style: TextStyle(fontSize: 24, fontWeight: FontWeight.bold)),
const SizedBox(height: 40),
SizedBox(
width: double.infinity,
child: ElevatedButton(
style: ElevatedButton.styleFrom(
backgroundColor: Colors.green,
foregroundColor: Colors.white,
padding: const EdgeInsets.symmetric(vertical: 16)),
onPressed: () => Navigator.of(context).pop(),
child: const Text("العودة"),
child: const Text("متابعة", style: TextStyle(fontSize: 18)),
),
),
],
);
@@ -356,47 +395,35 @@ class _PaymentScreenSmsProviderState extends State<PaymentScreenSmsProvider> {
return Column(
mainAxisAlignment: MainAxisAlignment.center,
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),
Text(
_status == PaymentStatus.paymentTimeout
? "انتهى الوقت المحدد للدفع"
: "حدث خطأ ما",
style: const TextStyle(fontSize: 22, fontWeight: FontWeight.bold),
? "انتهى الوقت"
: "لم يتم التحقق",
style: const TextStyle(fontSize: 22, fontWeight: FontWeight.bold)),
const SizedBox(height: 15),
const Padding(
padding: EdgeInsets.symmetric(horizontal: 30),
child: Text("لم يصلنا إشعار الدفع.",
textAlign: TextAlign.center,
),
const SizedBox(height: 8),
const Text("يرجى المحاولة مرة أخرى.", style: TextStyle(fontSize: 16)),
const SizedBox(height: 20),
ElevatedButton(
style: TextStyle(color: Colors.grey))),
const SizedBox(height: 40),
SizedBox(
width: double.infinity,
child: ElevatedButton.icon(
style: ElevatedButton.styleFrom(
padding: const EdgeInsets.symmetric(vertical: 15)),
onPressed: _createAndPollInvoice,
child: const Text("المحاولة مرة أخرى"),
icon: const Icon(Icons.refresh),
label: const Text("حاول مرة أخرى"),
),
),
const SizedBox(height: 15),
TextButton(
onPressed: () => Navigator.of(context).pop(),
child: const Text("إلغاء", style: TextStyle(color: Colors.grey)))
],
);
}
}
// ويدجت مساعد لعرض خطوات التعليمات بشكل أنيق
class _StepTile extends StatelessWidget {
final int number;
final String text;
const _StepTile({required this.number, required this.text});
@override
Widget build(BuildContext context) {
return ListTile(
contentPadding: EdgeInsets.zero,
leading: CircleAvatar(
radius: 12,
backgroundColor: Theme.of(context).primaryColor,
child: Text("$number",
style: const TextStyle(
fontSize: 12,
color: Colors.white,
fontWeight: FontWeight.bold)),
),
title: Text(text),
);
}
}

View File

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