25-10-5/1
This commit is contained in:
@@ -44,8 +44,8 @@ android {
|
|||||||
applicationId = "com.intaleq_driver"
|
applicationId = "com.intaleq_driver"
|
||||||
minSdk = 29
|
minSdk = 29
|
||||||
targetSdk = 36
|
targetSdk = 36
|
||||||
versionCode = 16 // I've used the higher version number from your first file
|
versionCode = 17 // I've used the higher version number from your first file
|
||||||
versionName = '1.0.16' // I've used the higher version name
|
versionName = '1.0.17' // I've used the higher version name
|
||||||
multiDexEnabled = true
|
multiDexEnabled = true
|
||||||
|
|
||||||
ndk {
|
ndk {
|
||||||
|
|||||||
@@ -49,12 +49,12 @@
|
|||||||
android:value="2" />
|
android:value="2" />
|
||||||
|
|
||||||
<!-- تحديد نقطة دخول خلفية (للـ overlay / background executor) -->
|
<!-- تحديد نقطة دخول خلفية (للـ overlay / background executor) -->
|
||||||
<meta-data
|
<!-- <meta-data
|
||||||
android:name="io.flutter.embedding.android.BackgroundExecutor.DART_ENTRYPOINT"
|
android:name="io.flutter.embedding.android.BackgroundExecutor.DART_ENTRYPOINT"
|
||||||
android:value="overlayMain" />
|
android:value="overlayMain" />
|
||||||
<meta-data
|
<meta-data
|
||||||
android:name="io.flutter.embedding.android.BackgroundExecutor.DART_LIBRARY_URI"
|
android:name="io.flutter.embedding.android.BackgroundExecutor.DART_LIBRARY_URI"
|
||||||
android:value="main.dart" />
|
android:value="main.dart" /> -->
|
||||||
|
|
||||||
<!-- خرائط + إشعارات فFirebase (قناة افتراضية) -->
|
<!-- خرائط + إشعارات فFirebase (قناة افتراضية) -->
|
||||||
<meta-data
|
<meta-data
|
||||||
@@ -63,7 +63,9 @@
|
|||||||
<meta-data
|
<meta-data
|
||||||
android:name="com.google.firebase.messaging.default_notification_channel_id"
|
android:name="com.google.firebase.messaging.default_notification_channel_id"
|
||||||
android:value="@string/default_notification_channel_id" />
|
android:value="@string/default_notification_channel_id" />
|
||||||
|
<meta-data
|
||||||
|
android:name="io.flutter.embedding.android.EnableImpeller"
|
||||||
|
android:value="false" />
|
||||||
<!-- Main Activity -->
|
<!-- Main Activity -->
|
||||||
<activity
|
<activity
|
||||||
android:name=".MainActivity"
|
android:name=".MainActivity"
|
||||||
@@ -119,10 +121,13 @@
|
|||||||
</service>
|
</service>
|
||||||
|
|
||||||
<!-- خدمة overlay للمكتبة الأولى (إن كنت تستخدمها) -->
|
<!-- خدمة overlay للمكتبة الأولى (إن كنت تستخدمها) -->
|
||||||
<service
|
<!-- <service
|
||||||
android:name="com.phan_tech.flutter_overlay_apps.OverlayService"
|
android:name="com.phan_tech.flutter_overlay_apps.OverlayService"
|
||||||
android:exported="false" />
|
android:exported="false" /> -->
|
||||||
|
<service
|
||||||
|
android:name="flutter.overlay.window.flutter_overlay_window.OverlayService"
|
||||||
|
android:exported="false"
|
||||||
|
android:foregroundServiceType="specialUse" />
|
||||||
<!-- خدمة overlay الخاصة بمكتبة flutter_overlay_window -->
|
<!-- خدمة overlay الخاصة بمكتبة flutter_overlay_window -->
|
||||||
|
|
||||||
|
|
||||||
|
|||||||
@@ -21,7 +21,7 @@ class Bubble {
|
|||||||
/// puts app in background and shows floaty-bubble head
|
/// puts app in background and shows floaty-bubble head
|
||||||
Future<void> startBubbleHead({bool sendAppToBackground = true}) async {
|
Future<void> startBubbleHead({bool sendAppToBackground = true}) async {
|
||||||
ByteData bytes = await rootBundle.load(
|
ByteData bytes = await rootBundle.load(
|
||||||
'assets/images/s.png',
|
'assets/images/logo.png',
|
||||||
);
|
);
|
||||||
var buffer = bytes.buffer;
|
var buffer = bytes.buffer;
|
||||||
var encodedImage = base64.encode(Uint8List.view(buffer));
|
var encodedImage = base64.encode(Uint8List.view(buffer));
|
||||||
|
|||||||
@@ -8,8 +8,7 @@ import 'box_name.dart';
|
|||||||
class AppLink {
|
class AppLink {
|
||||||
static String serverPHP = box.read('serverPHP');
|
static String serverPHP = box.read('serverPHP');
|
||||||
|
|
||||||
static String seferPaymentServer =
|
static String paymentServer = 'https://walletintaleq.intaleq.xyz/v1/main';
|
||||||
'https://walletintaleq.intaleq.xyz/v1/main';
|
|
||||||
static String seferPaymentServer0 =
|
static String seferPaymentServer0 =
|
||||||
'https://walletintaleq.intaleq.xyz/v1/main';
|
'https://walletintaleq.intaleq.xyz/v1/main';
|
||||||
|
|
||||||
@@ -26,7 +25,7 @@ class AppLink {
|
|||||||
|
|
||||||
static String loginJwtDriver = "$server/loginJwtDriver.php";
|
static String loginJwtDriver = "$server/loginJwtDriver.php";
|
||||||
static String loginJwtWalletDriver =
|
static String loginJwtWalletDriver =
|
||||||
"$seferPaymentServer/loginJwtWalletDriver.php";
|
"$paymentServer/loginJwtWalletDriver.php";
|
||||||
static String loginFirstTimeDriver = "$server/loginFirstTimeDriver.php";
|
static String loginFirstTimeDriver = "$server/loginFirstTimeDriver.php";
|
||||||
|
|
||||||
static String googleMapsLink = 'https://maps.googleapis.com/maps/api/';
|
static String googleMapsLink = 'https://maps.googleapis.com/maps/api/';
|
||||||
@@ -48,25 +47,25 @@ class AppLink {
|
|||||||
static String addTokens = "$ride/firebase/add.php";
|
static String addTokens = "$ride/firebase/add.php";
|
||||||
static String addTokensDriver = "$ride/firebase/addDriver.php";
|
static String addTokensDriver = "$ride/firebase/addDriver.php";
|
||||||
static String addTokensDriverWallet =
|
static String addTokensDriverWallet =
|
||||||
"$seferPaymentServer/ride/firebase/addDriver.php";
|
"$paymentServer/ride/firebase/addDriver.php";
|
||||||
|
|
||||||
//=======================Wallet===================
|
//=======================Wallet===================
|
||||||
static String wallet = '$seferPaymentServer/ride/passengerWallet';
|
static String wallet = '$paymentServer/ride/passengerWallet';
|
||||||
static String walletDriver = '$seferPaymentServer/ride/driverWallet';
|
static String walletDriver = '$paymentServer/ride/driverWallet';
|
||||||
static String getAllPassengerTransaction =
|
static String getAllPassengerTransaction =
|
||||||
"$wallet/getAllPassengerTransaction.php";
|
"$wallet/getAllPassengerTransaction.php";
|
||||||
static String payWithMTNConfirm =
|
static String payWithMTNConfirm =
|
||||||
"$seferPaymentServer/ride/mtn/driver/confirm_payment.php";
|
"$paymentServer/ride/mtn/driver/confirm_payment.php";
|
||||||
static String payWithMTNStart =
|
static String payWithMTNStart =
|
||||||
"$seferPaymentServer/ride/mtn/driver/mtn_start.php";
|
"$paymentServer/ride/mtn/driver/mtn_start.php";
|
||||||
static String payWithSyriatelConfirm =
|
static String payWithSyriatelConfirm =
|
||||||
"$seferPaymentServer/ride/syriatel/driver/confirm_payment.php";
|
"$paymentServer/ride/syriatel/driver/confirm_payment.php";
|
||||||
static String payWithSyriatelStart =
|
static String payWithSyriatelStart =
|
||||||
"$seferPaymentServer/ride/syriatel/driver/start_payment.php";
|
"$paymentServer/ride/syriatel/driver/start_payment.php";
|
||||||
static String payWithEcashDriver =
|
static String payWithEcashDriver =
|
||||||
"$seferPaymentServer/ride/ecash/driver/payWithEcash.php";
|
"$paymentServer/ride/ecash/driver/payWithEcash.php";
|
||||||
static String payWithEcashPassenger =
|
static String payWithEcashPassenger =
|
||||||
"$seferPaymentServer/ride/ecash/passenger/payWithEcash.php";
|
"$paymentServer/ride/ecash/passenger/payWithEcash.php";
|
||||||
// wl.tripz-egypt.com/v1/main/ride/ecash/driver
|
// wl.tripz-egypt.com/v1/main/ride/ecash/driver
|
||||||
static String getWalletByPassenger = "$wallet/getWalletByPassenger.php";
|
static String getWalletByPassenger = "$wallet/getWalletByPassenger.php";
|
||||||
static String getPassengersWallet = "$wallet/get.php";
|
static String getPassengersWallet = "$wallet/get.php";
|
||||||
@@ -136,35 +135,34 @@ class AppLink {
|
|||||||
static String addKazanPercent = "$ride/kazan/add.php";
|
static String addKazanPercent = "$ride/kazan/add.php";
|
||||||
|
|
||||||
////-----------------DriverPayment------------------
|
////-----------------DriverPayment------------------
|
||||||
static String addDrivePayment = "$seferPaymentServer/ride/payment/add.php";
|
static String addDrivePayment = "$paymentServer/ride/payment/add.php";
|
||||||
static String payWithPayMobCardDriver =
|
static String payWithPayMobCardDriver =
|
||||||
"$seferPaymentServer/ride/payMob/paymob_driver/payWithCard.php";
|
"$paymentServer/ride/payMob/paymob_driver/payWithCard.php";
|
||||||
static String payWithWallet =
|
static String payWithWallet =
|
||||||
"$seferPaymentServer/ride/payMob/paymob_driver/payWithWallet.php";
|
"$paymentServer/ride/payMob/paymob_driver/payWithWallet.php";
|
||||||
static String paymetVerifyDriver =
|
static String paymetVerifyDriver =
|
||||||
"$seferPaymentServer/ride/payMob/paymob_driver/paymet_verfy.php";
|
"$paymentServer/ride/payMob/paymob_driver/paymet_verfy.php";
|
||||||
static String updatePaymetToPaid =
|
static String updatePaymetToPaid =
|
||||||
"$seferPaymentServer/ride/payment/updatePaymetToPaid.php";
|
"$paymentServer/ride/payment/updatePaymetToPaid.php";
|
||||||
static String paymobPayoutDriverWallet =
|
static String paymobPayoutDriverWallet =
|
||||||
"$seferPaymentServer/ride/payMob/paymob_driver/paymob_payout.php'";
|
"$paymentServer/ride/payMob/paymob_driver/paymob_payout.php'";
|
||||||
|
|
||||||
static String addSeferWallet = "$seferPaymentServer/ride/seferWallet/add.php";
|
static String addSeferWallet = "$paymentServer/ride/seferWallet/add.php";
|
||||||
static String getSeferWallet = "$seferPaymentServer/ride/seferWallet/get.php";
|
static String getSeferWallet = "$paymentServer/ride/seferWallet/get.php";
|
||||||
static String addDriverPaymentPoints =
|
static String addDriverPaymentPoints =
|
||||||
"$seferPaymentServer/ride/driverPayment/add.php";
|
"$paymentServer/ride/driverPayment/add.php";
|
||||||
static String addPaymentTokenDriver =
|
static String addPaymentTokenDriver =
|
||||||
"$seferPaymentServer/ride/driverWallet/addPaymentToken.php"; //driverWallet/addPaymentToken.php
|
"$paymentServer/ride/driverWallet/addPaymentToken.php"; //driverWallet/addPaymentToken.php
|
||||||
static String addPaymentTokenPassenger =
|
static String addPaymentTokenPassenger =
|
||||||
"$seferPaymentServer/ride/passengerWallet/addPaymentTokenPassenger.php";
|
"$paymentServer/ride/passengerWallet/addPaymentTokenPassenger.php";
|
||||||
static String getDriverPaymentPoints =
|
static String getDriverPaymentPoints =
|
||||||
"$seferPaymentServer/ride/driverWallet/get.php";
|
"$paymentServer/ride/driverWallet/get.php";
|
||||||
static String getDriverPaymentToday =
|
static String getDriverPaymentToday = "$paymentServer/ride/payment/get.php";
|
||||||
"$seferPaymentServer/ride/payment/get.php";
|
|
||||||
static String getCountRide = "$ride/payment/getCountRide.php";
|
static String getCountRide = "$ride/payment/getCountRide.php";
|
||||||
static String getAllPaymentFromRide =
|
static String getAllPaymentFromRide =
|
||||||
"$seferPaymentServer/ride/payment/getAllPayment.php";
|
"$paymentServer/ride/payment/getAllPayment.php";
|
||||||
static String getAllPaymentVisa =
|
static String getAllPaymentVisa =
|
||||||
"$seferPaymentServer/ride/payment/getAllPaymentVisa.php";
|
"$paymentServer/ride/payment/getAllPaymentVisa.php";
|
||||||
|
|
||||||
//-----------------Passenger NotificationCaptain------------------
|
//-----------------Passenger NotificationCaptain------------------
|
||||||
static String addNotificationPassenger =
|
static String addNotificationPassenger =
|
||||||
|
|||||||
@@ -325,10 +325,8 @@ class LoginDriverController extends GetxController {
|
|||||||
key: BoxName.fingerPrint, value: fingerPrint.toString());
|
key: BoxName.fingerPrint, value: fingerPrint.toString());
|
||||||
// print(jsonDecode(token)['data'][0]['token'].toString());
|
// print(jsonDecode(token)['data'][0]['token'].toString());
|
||||||
// print(box.read(BoxName.tokenDriver).toString());
|
// print(box.read(BoxName.tokenDriver).toString());
|
||||||
if (box.read(BoxName.emailDriver).toString() ==
|
// if (box.read(BoxName.emailDriver).toString() !=
|
||||||
'963992952235@intaleqapp.com') {
|
// '963992952235@intaleqapp.com') {
|
||||||
Get.offAll(() => HomeCaptain());
|
|
||||||
}
|
|
||||||
if (token != 'failure') {
|
if (token != 'failure') {
|
||||||
if ((jsonDecode(token)['data'][0]['token'].toString()) !=
|
if ((jsonDecode(token)['data'][0]['token'].toString()) !=
|
||||||
box.read(BoxName.tokenDriver).toString()) {
|
box.read(BoxName.tokenDriver).toString()) {
|
||||||
@@ -353,6 +351,7 @@ class LoginDriverController extends GetxController {
|
|||||||
},
|
},
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
// }
|
||||||
}
|
}
|
||||||
|
|
||||||
Get.offAll(() => HomeCaptain());
|
Get.offAll(() => HomeCaptain());
|
||||||
@@ -379,7 +378,7 @@ class LoginDriverController extends GetxController {
|
|||||||
logintest(String driverID, email) async {
|
logintest(String driverID, email) async {
|
||||||
isloading = true;
|
isloading = true;
|
||||||
update();
|
update();
|
||||||
await SecurityHelper.performSecurityChecks();
|
// await SecurityHelper.performSecurityChecks();
|
||||||
// Log.print('(BoxName.emailDriver): ${box.read(BoxName.emailDriver)}');
|
// Log.print('(BoxName.emailDriver): ${box.read(BoxName.emailDriver)}');
|
||||||
|
|
||||||
var res = await CRUD().get(link: AppLink.loginFromGoogleCaptin, payload: {
|
var res = await CRUD().get(link: AppLink.loginFromGoogleCaptin, payload: {
|
||||||
@@ -388,75 +387,77 @@ class LoginDriverController extends GetxController {
|
|||||||
});
|
});
|
||||||
|
|
||||||
// print('res is $res');
|
// print('res is $res');
|
||||||
if (res == 'failure') {
|
// if (res == 'failure') {
|
||||||
await isPhoneVerified();
|
// await isPhoneVerified();
|
||||||
// Get.snackbar('Failure', '', backgroundColor: Colors.red);
|
// // Get.snackbar('Failure', '', backgroundColor: Colors.red);
|
||||||
} else {
|
// } else
|
||||||
var jsonDecoeded = jsonDecode(res);
|
// {
|
||||||
var d = jsonDecoeded['data'][0];
|
var jsonDecoeded = jsonDecode(res);
|
||||||
if (jsonDecoeded.isNotEmpty) {
|
var d = jsonDecoeded['data'][0];
|
||||||
if (jsonDecoeded['status'] == 'success' &&
|
if (jsonDecoeded.isNotEmpty) {
|
||||||
d['is_verified'].toString() == '1') {
|
if (jsonDecoeded['status'] == 'success' &&
|
||||||
box.write(BoxName.emailDriver, d['email']);
|
d['is_verified'].toString() == '1') {
|
||||||
box.write(BoxName.firstTimeLoadKey, 'false');
|
box.write(BoxName.emailDriver, d['email']);
|
||||||
box.write(BoxName.driverID, (d['id']));
|
box.write(BoxName.firstTimeLoadKey, 'false');
|
||||||
box.write(BoxName.isTest, '1');
|
box.write(BoxName.driverID, (d['id']));
|
||||||
box.write(BoxName.gender, (d['gender']));
|
box.write(BoxName.isTest, '1');
|
||||||
box.write(BoxName.phoneVerified, d['is_verified'].toString());
|
box.write(BoxName.gender, (d['gender']));
|
||||||
box.write(BoxName.phoneDriver, (d['phone']));
|
box.write(BoxName.phoneVerified, d['is_verified'].toString());
|
||||||
box.write(BoxName.is_claimed, d['is_claimed']);
|
box.write(BoxName.phoneDriver, (d['phone']));
|
||||||
box.write(BoxName.isInstall, d['isInstall']);
|
box.write(BoxName.is_claimed, d['is_claimed']);
|
||||||
// box.write(
|
box.write(BoxName.isInstall, d['isInstall']);
|
||||||
// BoxName.isGiftToken, d['isGiftToken']);
|
// box.write(
|
||||||
box.write(BoxName.nameArabic, (d['name_arabic']));
|
// BoxName.isGiftToken, d['isGiftToken']);
|
||||||
box.write(BoxName.carYear, d['year']);
|
box.write(BoxName.nameArabic, (d['name_arabic']));
|
||||||
box.write(BoxName.bankCodeDriver, (d['bankCode']));
|
box.write(BoxName.carYear, d['year']);
|
||||||
box.write(BoxName.accountBankNumberDriver, (d['accountBank']));
|
box.write(BoxName.bankCodeDriver, (d['bankCode']));
|
||||||
box.write(
|
box.write(BoxName.accountBankNumberDriver, (d['accountBank']));
|
||||||
BoxName.nameDriver,
|
box.write(
|
||||||
'${(d['first_name'])}'
|
BoxName.nameDriver,
|
||||||
' ${(d['last_name'])}');
|
'${(d['first_name'])}'
|
||||||
if (((d['model']).toString().contains('دراجه') ||
|
' ${(d['last_name'])}');
|
||||||
d['make'].toString().contains('دراجه '))) {
|
if (((d['model']).toString().contains('دراجه') ||
|
||||||
if ((d['gender']).toString() == 'Male') {
|
d['make'].toString().contains('دراجه '))) {
|
||||||
box.write(BoxName.carTypeOfDriver, 'Scooter');
|
if ((d['gender']).toString() == 'Male') {
|
||||||
} else {
|
box.write(BoxName.carTypeOfDriver, 'Scooter');
|
||||||
box.write(BoxName.carTypeOfDriver, 'Pink Bike');
|
} else {
|
||||||
}
|
box.write(BoxName.carTypeOfDriver, 'Pink Bike');
|
||||||
} else if (int.parse(d['year'].toString()) > 2016) {
|
|
||||||
if (d['gender'].toString() != 'Male') {
|
|
||||||
box.write(BoxName.carTypeOfDriver, 'Lady');
|
|
||||||
} else {
|
|
||||||
box.write(BoxName.carTypeOfDriver, 'Comfort');
|
|
||||||
}
|
|
||||||
} else if (int.parse(d['year'].toString()) > 2002 &&
|
|
||||||
int.parse(d['year'].toString()) < 2016) {
|
|
||||||
box.write(BoxName.carTypeOfDriver, 'Speed');
|
|
||||||
} else if (int.parse(d['year'].toString()) < 2002) {
|
|
||||||
box.write(BoxName.carTypeOfDriver, 'Awfar Car');
|
|
||||||
}
|
}
|
||||||
updateAppTester(AppInformation.appName);
|
} else if (int.parse(d['year'].toString()) > 2016) {
|
||||||
|
if (d['gender'].toString() != 'Male') {
|
||||||
var token = await CRUD().get(
|
box.write(BoxName.carTypeOfDriver, 'Lady');
|
||||||
link: AppLink.getDriverToken,
|
} else {
|
||||||
payload: {'captain_id': (box.read(BoxName.driverID)).toString()});
|
box.write(BoxName.carTypeOfDriver, 'Comfort');
|
||||||
|
}
|
||||||
String fingerPrint = await DeviceHelper.getDeviceFingerprint();
|
} else if (int.parse(d['year'].toString()) > 2002 &&
|
||||||
await storage.write(
|
int.parse(d['year'].toString()) < 2016) {
|
||||||
key: BoxName.fingerPrint, value: fingerPrint.toString());
|
box.write(BoxName.carTypeOfDriver, 'Speed');
|
||||||
|
} else if (int.parse(d['year'].toString()) < 2002) {
|
||||||
Get.off(() => HomeCaptain());
|
box.write(BoxName.carTypeOfDriver, 'Awfar Car');
|
||||||
} else {
|
|
||||||
Get.offAll(() => PhoneNumberScreen());
|
|
||||||
|
|
||||||
isloading = false;
|
|
||||||
update();
|
|
||||||
}
|
}
|
||||||
} else {
|
// updateAppTester(AppInformation.appName);
|
||||||
mySnackbarSuccess('');
|
|
||||||
|
|
||||||
isloading = false;
|
// var token = await CRUD().get(
|
||||||
update();
|
// link: AppLink.getDriverToken,
|
||||||
|
// payload: {'captain_id': (box.read(BoxName.driverID)).toString()});
|
||||||
|
|
||||||
|
// String fingerPrint = await DeviceHelper.getDeviceFingerprint();
|
||||||
|
// await storage.write(
|
||||||
|
// key: BoxName.fingerPrint, value: fingerPrint.toString());
|
||||||
|
|
||||||
|
Get.off(() => HomeCaptain());
|
||||||
|
// } else {
|
||||||
|
// Get.offAll(() => PhoneNumberScreen());
|
||||||
|
|
||||||
|
// isloading = false;
|
||||||
|
// update();
|
||||||
|
// }
|
||||||
|
// }
|
||||||
|
// else {
|
||||||
|
// mySnackbarSuccess('');
|
||||||
|
|
||||||
|
// isloading = false;
|
||||||
|
// update();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,6 +1,7 @@
|
|||||||
import 'dart:async';
|
import 'dart:async';
|
||||||
|
|
||||||
import 'package:get/get.dart';
|
import 'package:get/get.dart';
|
||||||
|
import 'package:sefer_driver/print.dart';
|
||||||
import 'package:sefer_driver/views/home/Captin/home_captain/home_captin.dart';
|
import 'package:sefer_driver/views/home/Captin/home_captain/home_captin.dart';
|
||||||
|
|
||||||
import '../../../constant/box_name.dart';
|
import '../../../constant/box_name.dart';
|
||||||
@@ -87,10 +88,10 @@ class OtpVerificationController extends GetxController {
|
|||||||
);
|
);
|
||||||
|
|
||||||
if (response != 'failure') {
|
if (response != 'failure') {
|
||||||
|
Log.print('response: ${response}');
|
||||||
Get.back(); // توجه إلى الصفحة التالية
|
Get.back(); // توجه إلى الصفحة التالية
|
||||||
await CRUD().post(
|
await CRUD().post(
|
||||||
link:
|
link: '${AppLink.paymentServer}/auth/token/update_driver_auth.php',
|
||||||
'${AppLink.seferPaymentServer}/auth/token/update_driver_auth.php',
|
|
||||||
payload: {
|
payload: {
|
||||||
'token': box.read(BoxName.tokenDriver).toString(),
|
'token': box.read(BoxName.tokenDriver).toString(),
|
||||||
'fingerPrint': finger.toString(),
|
'fingerPrint': finger.toString(),
|
||||||
|
|||||||
@@ -30,7 +30,7 @@ class PhoneAuthHelper {
|
|||||||
final data = (response);
|
final data = (response);
|
||||||
Log.print('data: ${data}');
|
Log.print('data: ${data}');
|
||||||
// if (data['status'] == 'success') {
|
// if (data['status'] == 'success') {
|
||||||
mySnackbarSuccess('An OTP has been sent to your WhatsApp number.'.tr);
|
mySnackbarSuccess('An OTP has been sent to your number.'.tr);
|
||||||
return true;
|
return true;
|
||||||
// } else {
|
// } else {
|
||||||
// mySnackeBarError(data['message'] ?? 'Failed to send OTP.');
|
// mySnackeBarError(data['message'] ?? 'Failed to send OTP.');
|
||||||
|
|||||||
@@ -85,9 +85,9 @@ class FirebaseMessagesController extends GetxController {
|
|||||||
if (message.data.isNotEmpty && message.notification != null) {
|
if (message.data.isNotEmpty && message.notification != null) {
|
||||||
fireBaseTitles(message);
|
fireBaseTitles(message);
|
||||||
}
|
}
|
||||||
if (message.data.isNotEmpty && message.notification != null) {
|
// if (message.data.isNotEmpty && message.notification != null) {
|
||||||
fireBaseTitles(message);
|
// fireBaseTitles(message);
|
||||||
}
|
// }
|
||||||
});
|
});
|
||||||
FirebaseMessaging.onBackgroundMessage((RemoteMessage message) async {});
|
FirebaseMessaging.onBackgroundMessage((RemoteMessage message) async {});
|
||||||
|
|
||||||
@@ -387,33 +387,12 @@ class FirebaseMessagesController extends GetxController {
|
|||||||
// }));
|
// }));
|
||||||
// }
|
// }
|
||||||
|
|
||||||
Future<dynamic> passengerDialog(String message) {
|
|
||||||
return Get.defaultDialog(
|
|
||||||
barrierDismissible: false,
|
|
||||||
title: 'message From passenger'.tr,
|
|
||||||
titleStyle: AppStyle.title,
|
|
||||||
middleTextStyle: AppStyle.title,
|
|
||||||
middleText: message.tr,
|
|
||||||
confirm: MyElevatedButton(
|
|
||||||
title: 'Ok'.tr,
|
|
||||||
onPressed: () {
|
|
||||||
// FirebaseMessagesController().sendNotificationToPassengerToken(
|
|
||||||
// 'Hi ,I will go now'.tr,
|
|
||||||
// 'I will go now'.tr,
|
|
||||||
// Get.find<MapPassengerController>().driverToken, []);
|
|
||||||
// Get.find<MapPassengerController>()
|
|
||||||
// .startTimerDriverWaitPassenger5Minute();
|
|
||||||
|
|
||||||
Get.back();
|
|
||||||
}));
|
|
||||||
}
|
|
||||||
|
|
||||||
late String serviceAccountKeyJson;
|
late String serviceAccountKeyJson;
|
||||||
@override
|
@override
|
||||||
Future<void> onInit() async {
|
Future<void> onInit() async {
|
||||||
super.onInit();
|
super.onInit();
|
||||||
try {
|
try {
|
||||||
getToken();
|
// getToken();
|
||||||
var encryptedKey = Env.privateKeyFCM;
|
var encryptedKey = Env.privateKeyFCM;
|
||||||
// Log.print('encryptedKey: ${encryptedKey}');
|
// Log.print('encryptedKey: ${encryptedKey}');
|
||||||
serviceAccountKeyJson =
|
serviceAccountKeyJson =
|
||||||
@@ -425,67 +404,84 @@ class FirebaseMessagesController extends GetxController {
|
|||||||
}
|
}
|
||||||
|
|
||||||
void sendNotificationAll(String title, body, tone) async {
|
void sendNotificationAll(String title, body, tone) async {
|
||||||
// Get the token you want to subtract.
|
// توكني الحالي (لا أرسل لنفسي)
|
||||||
String token = box.read(BoxName.tokenFCM);
|
final String myToken = box.read(BoxName.tokenFCM) ?? '';
|
||||||
tokens = box.read(BoxName.tokens);
|
// اقرأ قائمة كل التوكنات
|
||||||
// Subtract the token from the list of tokens.
|
final List<String> all =
|
||||||
tokens.remove(token);
|
List<String>.from(box.read(BoxName.tokens) ?? const []);
|
||||||
|
|
||||||
// Save the list of tokens back to the box.
|
// استبعد توكنك واحذف الفارغ
|
||||||
// box.write(BoxName.tokens, tokens);
|
final targets = all.where((t) => t.isNotEmpty && t != myToken).toList();
|
||||||
tokens = box.read(BoxName.tokens);
|
|
||||||
for (var i = 0; i < tokens.length; i++) {
|
|
||||||
if (serviceAccountKeyJson.isEmpty) {
|
|
||||||
print("🔴 Error: Service Account Key is empty");
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
// Initialize AccessTokenManager
|
|
||||||
final accessTokenManager = AccessTokenManager(serviceAccountKeyJson);
|
|
||||||
|
|
||||||
// Obtain an OAuth 2.0 access token
|
if (serviceAccountKeyJson.isEmpty) {
|
||||||
final accessToken = await accessTokenManager.getAccessToken();
|
print("🔴 Error: Service Account Key is empty");
|
||||||
// Log.print('accessToken: ${accessToken}');
|
return;
|
||||||
|
}
|
||||||
|
final accessTokenManager = AccessTokenManager(serviceAccountKeyJson);
|
||||||
|
final accessToken = await accessTokenManager.getAccessToken();
|
||||||
|
|
||||||
// Send the notification
|
for (final t in targets) {
|
||||||
final response = await http
|
// ⚠️ المهم: استخدم t (توكن الهدف)، وليس المتغير myToken
|
||||||
.post(
|
final response = await http.post(
|
||||||
Uri.parse(
|
Uri.parse(
|
||||||
'https://fcm.googleapis.com/v1/projects/ride-b1bd8/messages:send'),
|
'https://fcm.googleapis.com/v1/projects/ride-b1bd8/messages:send'),
|
||||||
headers: <String, String>{
|
headers: {
|
||||||
'Content-Type': 'application/json',
|
'Content-Type': 'application/json',
|
||||||
'Authorization': 'Bearer $accessToken',
|
'Authorization': 'Bearer $accessToken',
|
||||||
|
},
|
||||||
|
body: jsonEncode({
|
||||||
|
'message': {
|
||||||
|
'token': t,
|
||||||
|
'notification': {'title': title, 'body': body},
|
||||||
|
'android': {
|
||||||
|
'priority': 'HIGH', // القيم الصحيحة: HIGH/NORMAL
|
||||||
|
'notification': {'sound': tone},
|
||||||
|
// (اختياري) TTL لتجنّب رسائل قديمة
|
||||||
|
'ttl': '30s',
|
||||||
},
|
},
|
||||||
body: jsonEncode({
|
'apns': {
|
||||||
'message': {
|
'headers': {
|
||||||
'token': token,
|
'apns-priority': '10',
|
||||||
'notification': {
|
// لو iOS: حدد نوع الدفع
|
||||||
'title': title,
|
'apns-push-type': 'alert',
|
||||||
'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,
|
|
||||||
},
|
|
||||||
},
|
|
||||||
},
|
|
||||||
},
|
},
|
||||||
}),
|
'payload': {
|
||||||
)
|
'aps': {'sound': tone}
|
||||||
.whenComplete(() {})
|
},
|
||||||
.catchError((e) {});
|
},
|
||||||
|
},
|
||||||
|
}),
|
||||||
|
);
|
||||||
|
|
||||||
|
if (response.statusCode != 200) {
|
||||||
|
// حاول تقرأ الخطأ وتشيل التوكنات التالفة
|
||||||
|
_handleV1Error(response, badToken: t);
|
||||||
|
await Future.delayed(const Duration(milliseconds: 50)); // تخفيف ضغط
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
void _handleV1Error(http.Response res, {required String badToken}) {
|
||||||
|
try {
|
||||||
|
final body = jsonDecode(res.body);
|
||||||
|
final err = body['error']?['status']?.toString() ?? '';
|
||||||
|
// أمثلة شائعة:
|
||||||
|
if (err.contains('UNREGISTERED') || err.contains('NOT_FOUND')) {
|
||||||
|
removeInvalidToken(badToken);
|
||||||
|
} else if (err.contains('INVALID_ARGUMENT')) {
|
||||||
|
// payload غير صحيح
|
||||||
|
print(
|
||||||
|
'⚠️ INVALID_ARGUMENT for $badToken: ${body['error']?['message']}');
|
||||||
|
} else if (err.contains('RESOURCE_EXHAUSTED') ||
|
||||||
|
err.contains('QUOTA_EXCEEDED')) {
|
||||||
|
// تجاوزت الحصة—خفّف السرعة/قسّم الإرسال (FCM v1 له حصة/دقيقة)
|
||||||
|
// https docs: 600k req/min per project (token bucket)
|
||||||
|
print('⏳ Throttled by FCM: slow down sending rate.');
|
||||||
|
} else {
|
||||||
|
print('FCM v1 error: ${res.statusCode} ${res.body}');
|
||||||
|
}
|
||||||
|
} catch (_) {
|
||||||
|
print('FCM v1 error: ${res.statusCode} ${res.body}');
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -523,7 +519,7 @@ class FirebaseMessagesController extends GetxController {
|
|||||||
'passengerList': jsonEncode(map),
|
'passengerList': jsonEncode(map),
|
||||||
},
|
},
|
||||||
'android': {
|
'android': {
|
||||||
'priority': 'high', // Set priority to high
|
'priority': 'HIGH ', // Set priority to high
|
||||||
'notification': {
|
'notification': {
|
||||||
'sound': tone,
|
'sound': tone,
|
||||||
},
|
},
|
||||||
@@ -605,7 +601,7 @@ class FirebaseMessagesController extends GetxController {
|
|||||||
'passengerList': jsonEncode(map),
|
'passengerList': jsonEncode(map),
|
||||||
},
|
},
|
||||||
'android': {
|
'android': {
|
||||||
'priority': 'high', // Set priority to high
|
'priority': 'HIGH ', // Set priority to high
|
||||||
'notification': {
|
'notification': {
|
||||||
'sound': tone,
|
'sound': tone,
|
||||||
},
|
},
|
||||||
@@ -692,7 +688,7 @@ class FirebaseMessagesController extends GetxController {
|
|||||||
'DriverList': jsonEncode(data),
|
'DriverList': jsonEncode(data),
|
||||||
},
|
},
|
||||||
'android': {
|
'android': {
|
||||||
'priority': 'high', // Set priority to high
|
'priority': 'HIGH ', // Set priority to high
|
||||||
'notification': {
|
'notification': {
|
||||||
'sound': tone,
|
'sound': tone,
|
||||||
},
|
},
|
||||||
|
|||||||
71
lib/controller/firebase/notification_service.dart
Normal file
71
lib/controller/firebase/notification_service.dart
Normal file
@@ -0,0 +1,71 @@
|
|||||||
|
import 'package:http/http.dart' as http;
|
||||||
|
import 'dart:convert';
|
||||||
|
|
||||||
|
class NotificationService {
|
||||||
|
// استبدل هذا الرابط بالرابط الصحيح لملف PHP على السيرفر الخاص بك
|
||||||
|
static const String _serverUrl =
|
||||||
|
'https://syria.intaleq.xyz/intaleq/fcm/send_fcm.php';
|
||||||
|
|
||||||
|
/// Sends a notification via your backend server.
|
||||||
|
///
|
||||||
|
/// [target]: The device token or the topic name.
|
||||||
|
/// [title]: The notification title.
|
||||||
|
/// [body]: The notification body.
|
||||||
|
/// [isTopic]: Set to true if the target is a topic, false if it's a device token.
|
||||||
|
static Future<void> sendNotification({
|
||||||
|
required String target,
|
||||||
|
required String title,
|
||||||
|
required String body,
|
||||||
|
bool isTopic = false,
|
||||||
|
}) async {
|
||||||
|
try {
|
||||||
|
final response = await http.post(
|
||||||
|
Uri.parse(_serverUrl),
|
||||||
|
headers: {
|
||||||
|
'Content-Type': 'application/json; charset=UTF-8',
|
||||||
|
},
|
||||||
|
body: jsonEncode({
|
||||||
|
'target': target,
|
||||||
|
'title': title,
|
||||||
|
'body': body,
|
||||||
|
'isTopic': isTopic,
|
||||||
|
}),
|
||||||
|
);
|
||||||
|
|
||||||
|
if (response.statusCode == 200) {
|
||||||
|
print('Notification sent successfully.');
|
||||||
|
print('Server Response: ${response.body}');
|
||||||
|
} else {
|
||||||
|
print(
|
||||||
|
'Failed to send notification. Status code: ${response.statusCode}');
|
||||||
|
print('Server Error: ${response.body}');
|
||||||
|
}
|
||||||
|
} catch (e) {
|
||||||
|
print('An error occurred while sending notification: $e');
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// --- Example of how to use it ---
|
||||||
|
|
||||||
|
// To send to a specific driver (using their token)
|
||||||
|
void sendToSpecificDriver() {
|
||||||
|
String driverToken =
|
||||||
|
'bk3RNwTe3H0:CI2k_HHwgIpoDKCI5oT...'; // The driver's FCM token
|
||||||
|
NotificationService.sendNotification(
|
||||||
|
target: driverToken,
|
||||||
|
title: 'New Trip Request!',
|
||||||
|
body: 'A passenger is waiting for you.',
|
||||||
|
isTopic: false, // Important: this is a token
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
// To send to all drivers (using a topic)
|
||||||
|
void sendToAllDrivers() {
|
||||||
|
NotificationService.sendNotification(
|
||||||
|
target: 'drivers', // The name of the topic
|
||||||
|
title: 'Important Announcement',
|
||||||
|
body: 'Please update your app to the latest version.',
|
||||||
|
isTopic: true, // Important: this is a topic
|
||||||
|
);
|
||||||
|
}
|
||||||
@@ -108,8 +108,6 @@ class CRUD {
|
|||||||
|
|
||||||
final sc = response.statusCode;
|
final sc = response.statusCode;
|
||||||
final body = response.body;
|
final body = response.body;
|
||||||
Log.print('body: ${body}');
|
|
||||||
Log.print('body: ${body}');
|
|
||||||
|
|
||||||
// 2xx
|
// 2xx
|
||||||
if (sc >= 200 && sc < 300) {
|
if (sc >= 200 && sc < 300) {
|
||||||
@@ -193,6 +191,9 @@ class CRUD {
|
|||||||
'Bearer ${r(box.read(BoxName.jwt)).toString().split(Env.addd)[0]}'
|
'Bearer ${r(box.read(BoxName.jwt)).toString().split(Env.addd)[0]}'
|
||||||
},
|
},
|
||||||
);
|
);
|
||||||
|
// Log.print('response: ${response.body}');
|
||||||
|
// Log.print('req: ${response.request}');
|
||||||
|
// Log.print('payload: ${payload}');
|
||||||
|
|
||||||
if (response.statusCode == 200) {
|
if (response.statusCode == 200) {
|
||||||
var jsonData = jsonDecode(response.body);
|
var jsonData = jsonDecode(response.body);
|
||||||
@@ -263,9 +264,6 @@ class CRUD {
|
|||||||
'X-HMAC-Auth': hmac.toString(),
|
'X-HMAC-Auth': hmac.toString(),
|
||||||
},
|
},
|
||||||
);
|
);
|
||||||
// Log.print('response.request: ${response.request}');
|
|
||||||
// Log.print('response.body: ${response.body}');
|
|
||||||
// Log.print('response.payload: ${payload}');
|
|
||||||
|
|
||||||
if (response.statusCode == 200) {
|
if (response.statusCode == 200) {
|
||||||
var jsonData = jsonDecode(response.body);
|
var jsonData = jsonDecode(response.body);
|
||||||
|
|||||||
@@ -336,7 +336,7 @@ class HomeCaptainController extends GetxController {
|
|||||||
CRUD().post(link: AppLink.addTokensDriver, payload: payload);
|
CRUD().post(link: AppLink.addTokensDriver, payload: payload);
|
||||||
|
|
||||||
await CRUD().post(
|
await CRUD().post(
|
||||||
link: "${AppLink.seferPaymentServer}/ride/firebase/addDriver.php",
|
link: "${AppLink.paymentServer}/ride/firebase/addDriver.php",
|
||||||
payload: payload);
|
payload: payload);
|
||||||
// MapDriverController().driverCallPassenger();
|
// MapDriverController().driverCallPassenger();
|
||||||
// box.write(BoxName.statusDriverLocation, 'off');
|
// box.write(BoxName.statusDriverLocation, 'off');
|
||||||
|
|||||||
@@ -335,7 +335,7 @@ class MapDriverController extends GetxController {
|
|||||||
// Get.find<HomeCaptainController>().changeToAppliedRide('Applied');
|
// Get.find<HomeCaptainController>().changeToAppliedRide('Applied');
|
||||||
|
|
||||||
Get.find<FirebaseMessagesController>().sendNotificationToDriverMAP(
|
Get.find<FirebaseMessagesController>().sendNotificationToDriverMAP(
|
||||||
'Driver Is Going To Passenger'.tr,
|
'Driver Is Going To Passenger',
|
||||||
box.read(BoxName.nameDriver).toString(), //todo name driver
|
box.read(BoxName.nameDriver).toString(), //todo name driver
|
||||||
tokenPassenger,
|
tokenPassenger,
|
||||||
[],
|
[],
|
||||||
@@ -431,7 +431,7 @@ class MapDriverController extends GetxController {
|
|||||||
? Get.find<FirebaseMessagesController>()
|
? Get.find<FirebaseMessagesController>()
|
||||||
: Get.put(FirebaseMessagesController());
|
: Get.put(FirebaseMessagesController());
|
||||||
fcm.sendNotificationToDriverMAP(
|
fcm.sendNotificationToDriverMAP(
|
||||||
'Trip is Begin'.tr,
|
'Trip is Begin',
|
||||||
box.read(BoxName.nameDriver).toString(),
|
box.read(BoxName.nameDriver).toString(),
|
||||||
tokenPassenger,
|
tokenPassenger,
|
||||||
[],
|
[],
|
||||||
@@ -732,8 +732,7 @@ class MapDriverController extends GetxController {
|
|||||||
));
|
));
|
||||||
|
|
||||||
apiCalls.add(CRUD().postWallet(
|
apiCalls.add(CRUD().postWallet(
|
||||||
link:
|
link: "${AppLink.paymentServer}/ride/payment/process_ride_payments.php",
|
||||||
"${AppLink.seferPaymentServer}/ride/payment/process_ride_payments.php",
|
|
||||||
payload: paymentProcessingPayload,
|
payload: paymentProcessingPayload,
|
||||||
));
|
));
|
||||||
|
|
||||||
@@ -754,7 +753,7 @@ class MapDriverController extends GetxController {
|
|||||||
.sendSummaryToServer(driverId, rideId);
|
.sendSummaryToServer(driverId, rideId);
|
||||||
|
|
||||||
Get.find<FirebaseMessagesController>().sendNotificationToDriverMAP(
|
Get.find<FirebaseMessagesController>().sendNotificationToDriverMAP(
|
||||||
"Driver Finish Trip".tr,
|
"Driver Finish Trip",
|
||||||
'${'you will pay to Driver'.tr} $paymentAmount \$',
|
'${'you will pay to Driver'.tr} $paymentAmount \$',
|
||||||
tokenPassenger,
|
tokenPassenger,
|
||||||
[
|
[
|
||||||
@@ -1619,7 +1618,7 @@ class MapDriverController extends GetxController {
|
|||||||
if (distance < 300) {
|
if (distance < 300) {
|
||||||
// 300 متر قبل الوجهة
|
// 300 متر قبل الوجهة
|
||||||
Get.find<FirebaseMessagesController>().sendNotificationToDriverMAP(
|
Get.find<FirebaseMessagesController>().sendNotificationToDriverMAP(
|
||||||
"You are near the destination".tr,
|
"You are near the destination",
|
||||||
"You are near the destination".tr,
|
"You are near the destination".tr,
|
||||||
tokenPassenger,
|
tokenPassenger,
|
||||||
[
|
[
|
||||||
|
|||||||
@@ -1,4 +1,5 @@
|
|||||||
import 'dart:convert';
|
import 'dart:convert';
|
||||||
|
import 'dart:io';
|
||||||
|
|
||||||
import 'package:flutter/cupertino.dart';
|
import 'package:flutter/cupertino.dart';
|
||||||
import 'package:flutter_overlay_window/flutter_overlay_window.dart';
|
import 'package:flutter_overlay_window/flutter_overlay_window.dart';
|
||||||
@@ -37,10 +38,13 @@ class OrderRequestController extends GetxController {
|
|||||||
Future<void> onInit() async {
|
Future<void> onInit() async {
|
||||||
print('OrderRequestController onInit called');
|
print('OrderRequestController onInit called');
|
||||||
await initializeOrderPage();
|
await initializeOrderPage();
|
||||||
bool isOverlayActive = await FlutterOverlayWindow.isActive();
|
if (Platform.isAndroid) {
|
||||||
if (isOverlayActive) {
|
bool isOverlayActive = await FlutterOverlayWindow.isActive();
|
||||||
await FlutterOverlayWindow.closeOverlay();
|
if (isOverlayActive) {
|
||||||
|
await FlutterOverlayWindow.closeOverlay();
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
addCustomStartIcon();
|
addCustomStartIcon();
|
||||||
addCustomEndIcon();
|
addCustomEndIcon();
|
||||||
startTimer(
|
startTimer(
|
||||||
@@ -61,6 +65,7 @@ class OrderRequestController extends GetxController {
|
|||||||
|
|
||||||
Future<void> initializeOrderPage() async {
|
Future<void> initializeOrderPage() async {
|
||||||
final myListString = Get.arguments['myListString'];
|
final myListString = Get.arguments['myListString'];
|
||||||
|
Log.print('myListString0000: ${myListString}');
|
||||||
|
|
||||||
if (Get.arguments['DriverList'] == null ||
|
if (Get.arguments['DriverList'] == null ||
|
||||||
Get.arguments['DriverList'].isEmpty) {
|
Get.arguments['DriverList'].isEmpty) {
|
||||||
|
|||||||
41
lib/controller/home/navigation/decode_polyline_isolate.dart
Normal file
41
lib/controller/home/navigation/decode_polyline_isolate.dart
Normal file
@@ -0,0 +1,41 @@
|
|||||||
|
import 'package:google_maps_flutter/google_maps_flutter.dart';
|
||||||
|
|
||||||
|
// تم تعديل الدالة لتقبل وسيط من نوع `dynamic` لحل مشكلة عدم تطابق الأنواع مع دالة `compute`.
|
||||||
|
// هذه الدالة لا تزال تعمل كدالة من المستوى الأعلى (Top-level function)
|
||||||
|
// وهو شرط أساسي لاستخدامها مع دالة compute.
|
||||||
|
List<LatLng> decodePolylineIsolate(dynamic encodedMessage) {
|
||||||
|
// التأكد من أن الرسالة المستقبلة هي من نوع String
|
||||||
|
if (encodedMessage is! String) {
|
||||||
|
// إرجاع قائمة فارغة أو إظهار خطأ إذا كان النوع غير صحيح
|
||||||
|
return [];
|
||||||
|
}
|
||||||
|
final String encoded = encodedMessage;
|
||||||
|
|
||||||
|
List<LatLng> points = [];
|
||||||
|
int index = 0, len = encoded.length;
|
||||||
|
int lat = 0, lng = 0;
|
||||||
|
|
||||||
|
while (index < len) {
|
||||||
|
int b, shift = 0, result = 0;
|
||||||
|
do {
|
||||||
|
b = encoded.codeUnitAt(index++) - 63;
|
||||||
|
result |= (b & 0x1f) << shift;
|
||||||
|
shift += 5;
|
||||||
|
} while (b >= 0x20);
|
||||||
|
int dlat = ((result & 1) != 0 ? ~(result >> 1) : (result >> 1));
|
||||||
|
lat += dlat;
|
||||||
|
|
||||||
|
shift = 0;
|
||||||
|
result = 0;
|
||||||
|
do {
|
||||||
|
b = encoded.codeUnitAt(index++) - 63;
|
||||||
|
result |= (b & 0x1f) << shift;
|
||||||
|
shift += 5;
|
||||||
|
} while (b >= 0x20);
|
||||||
|
int dlng = ((result & 1) != 0 ? ~(result >> 1) : (result >> 1));
|
||||||
|
lng += dlng;
|
||||||
|
|
||||||
|
points.add(LatLng(lat / 1E5, lng / 1E5));
|
||||||
|
}
|
||||||
|
return points;
|
||||||
|
}
|
||||||
@@ -1,12 +1,11 @@
|
|||||||
import 'dart:async';
|
import 'dart:async';
|
||||||
import 'dart:io';
|
import 'dart:io';
|
||||||
import 'dart:math';
|
import 'dart:math';
|
||||||
import 'dart:math' as math;
|
import 'package:flutter/foundation.dart'; // <<<--- إضافة مهمة لاستخدام دالة compute
|
||||||
import 'package:flutter/material.dart';
|
import 'package:flutter/material.dart';
|
||||||
import 'package:geolocator/geolocator.dart';
|
import 'package:geolocator/geolocator.dart';
|
||||||
import 'package:get/get.dart';
|
import 'package:get/get.dart';
|
||||||
import 'package:google_maps_flutter/google_maps_flutter.dart';
|
import 'package:google_maps_flutter/google_maps_flutter.dart';
|
||||||
import 'package:google_polyline_algorithm/google_polyline_algorithm.dart';
|
|
||||||
import 'package:sefer_driver/constant/colors.dart';
|
import 'package:sefer_driver/constant/colors.dart';
|
||||||
import 'package:sefer_driver/env/env.dart';
|
import 'package:sefer_driver/env/env.dart';
|
||||||
|
|
||||||
@@ -16,6 +15,7 @@ import '../../../constant/links.dart';
|
|||||||
import '../../../print.dart';
|
import '../../../print.dart';
|
||||||
import '../../functions/crud.dart';
|
import '../../functions/crud.dart';
|
||||||
import '../../functions/tts.dart';
|
import '../../functions/tts.dart';
|
||||||
|
import 'decode_polyline_isolate.dart';
|
||||||
|
|
||||||
class NavigationController extends GetxController {
|
class NavigationController extends GetxController {
|
||||||
// --- متغيرات الحالة العامة ---
|
// --- متغيرات الحالة العامة ---
|
||||||
@@ -319,24 +319,24 @@ class NavigationController extends GetxController {
|
|||||||
// ٤. دوال مساعدة وتجهيز البيانات
|
// ٤. دوال مساعدة وتجهيز البيانات
|
||||||
// =======================================================================
|
// =======================================================================
|
||||||
|
|
||||||
void _prepareStepData() {
|
// <<<--- التعديل الأول: تغيير الدالة لتكون async
|
||||||
|
Future<void> _prepareStepData() async {
|
||||||
_stepBounds.clear();
|
_stepBounds.clear();
|
||||||
_stepPolylines.clear();
|
_stepPolylines.clear();
|
||||||
if (routeSteps.isEmpty) return;
|
if (routeSteps.isEmpty) return;
|
||||||
for (final step in routeSteps) {
|
for (final step in routeSteps) {
|
||||||
final pointsString = step['polyline']['points'];
|
final pointsString = step['polyline']['points'];
|
||||||
final List<List<num>> points =
|
// <<<--- التعديل الثاني: استخدام compute لفك التشفير في خيط منفصل
|
||||||
decodePolyline(pointsString).cast<List<num>>();
|
// وتصحيح طريقة التعامل مع القائمة المُرجعة
|
||||||
final polylineCoordinates = points
|
final List<LatLng> polylineCoordinates = await compute(
|
||||||
.map((point) => LatLng(point[0].toDouble(), point[1].toDouble()))
|
decodePolylineIsolate as ComputeCallback<dynamic, List<LatLng>>,
|
||||||
.toList();
|
pointsString);
|
||||||
|
|
||||||
_stepPolylines.add(polylineCoordinates); // تخزين نقاط الخطوة
|
_stepPolylines.add(polylineCoordinates); // تخزين نقاط الخطوة
|
||||||
_stepBounds.add(_boundsFromLatLngList(polylineCoordinates));
|
_stepBounds.add(_boundsFromLatLngList(polylineCoordinates));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// ... باقي دوال الكنترولر بدون تغيير ...
|
|
||||||
// (selectDestination, onMapLongPressed, startNavigationTo, getRoute, etc.)
|
|
||||||
Future<void> selectDestination(dynamic place) async {
|
Future<void> selectDestination(dynamic place) async {
|
||||||
placeDestinationController.clear();
|
placeDestinationController.clear();
|
||||||
placesDestination = [];
|
placesDestination = [];
|
||||||
@@ -401,11 +401,11 @@ class NavigationController extends GetxController {
|
|||||||
}
|
}
|
||||||
|
|
||||||
Future<void> getRoute(LatLng origin, LatLng destination) async {
|
Future<void> getRoute(LatLng origin, LatLng destination) async {
|
||||||
final String key = Platform.isAndroid ? Env.mapAPIKEY : Env.mapAPIKEYIOS;
|
final String key = Env.mapAPIKEY;
|
||||||
final url =
|
final url =
|
||||||
'${AppLink.googleMapsLink}directions/json?language=ar&destination=${destination.latitude},${destination.longitude}&origin=${origin.latitude},${origin.longitude}&key=${key}&mode=driving';
|
'${AppLink.googleMapsLink}directions/json?language=ar&destination=${destination.latitude},${destination.longitude}&origin=${origin.latitude},${origin.longitude}&key=${key}&mode=driving';
|
||||||
var response = await CRUD().getGoogleApi(link: url, payload: {});
|
var response = await CRUD().getGoogleApi(link: url, payload: {});
|
||||||
Log.print('response: ${response}');
|
// Log.print('response: ${response}');
|
||||||
|
|
||||||
if (response == null || response['routes'].isEmpty) {
|
if (response == null || response['routes'].isEmpty) {
|
||||||
Get.snackbar('خطأ', 'لم يتم العثور على مسار.');
|
Get.snackbar('خطأ', 'لم يتم العثور على مسار.');
|
||||||
@@ -414,11 +414,11 @@ class NavigationController extends GetxController {
|
|||||||
|
|
||||||
polylines.clear();
|
polylines.clear();
|
||||||
final pointsString = response['routes'][0]['overview_polyline']['points'];
|
final pointsString = response['routes'][0]['overview_polyline']['points'];
|
||||||
final List<List<num>> points =
|
|
||||||
decodePolyline(pointsString).cast<List<num>>();
|
// <<<--- التعديل الثالث: استخدام compute هنا أيضًا للمسار الرئيسي
|
||||||
_fullRouteCoordinates = points
|
_fullRouteCoordinates = await compute(
|
||||||
.map((point) => LatLng(point[0].toDouble(), point[1].toDouble()))
|
decodePolylineIsolate as ComputeCallback<dynamic, List<LatLng>>,
|
||||||
.toList();
|
pointsString);
|
||||||
|
|
||||||
polylines.add(
|
polylines.add(
|
||||||
Polyline(
|
Polyline(
|
||||||
@@ -441,7 +441,9 @@ class NavigationController extends GetxController {
|
|||||||
|
|
||||||
routeSteps = List<Map<String, dynamic>>.from(
|
routeSteps = List<Map<String, dynamic>>.from(
|
||||||
response['routes'][0]['legs'][0]['steps']);
|
response['routes'][0]['legs'][0]['steps']);
|
||||||
_prepareStepData();
|
|
||||||
|
// <<<--- التعديل الرابع: انتظار انتهاء الدالة بعد تحويلها إلى async
|
||||||
|
await _prepareStepData();
|
||||||
|
|
||||||
currentStepIndex = 0;
|
currentStepIndex = 0;
|
||||||
_nextInstructionSpoken = false;
|
_nextInstructionSpoken = false;
|
||||||
@@ -531,57 +533,32 @@ class NavigationController extends GetxController {
|
|||||||
String _parseInstruction(String html) =>
|
String _parseInstruction(String html) =>
|
||||||
html.replaceAll(RegExp(r'<[^>]*>'), ' ');
|
html.replaceAll(RegExp(r'<[^>]*>'), ' ');
|
||||||
|
|
||||||
double _haversineKm(double lat1, double lon1, double lat2, double lon2) {
|
// =======================================================================
|
||||||
const R = 6371.0; // km
|
// ٥. دالة البحث عن الأماكن المحدثة والدوال المساعدة لها
|
||||||
final dLat = (lat2 - lat1) * math.pi / 180.0;
|
// =======================================================================
|
||||||
final dLon = (lon2 - lon1) * math.pi / 180.0;
|
|
||||||
final a = math.sin(dLat / 2) * math.sin(dLat / 2) +
|
|
||||||
math.cos(lat1 * math.pi / 180.0) *
|
|
||||||
math.cos(lat2 * math.pi / 180.0) *
|
|
||||||
math.sin(dLon / 2) *
|
|
||||||
math.sin(dLon / 2);
|
|
||||||
final c = 2 * math.atan2(math.sqrt(a), math.sqrt(1 - a));
|
|
||||||
return R * c;
|
|
||||||
}
|
|
||||||
|
|
||||||
/// تحويل نصف قطر بالكيلومتر إلى دلتا درجات عرض
|
|
||||||
double _kmToLatDelta(double km) => km / 111.0;
|
|
||||||
|
|
||||||
/// تحويل نصف قطر بالكيلومتر إلى دلتا درجات طول (تعتمد على خط العرض)
|
|
||||||
double _kmToLngDelta(double km, double atLat) =>
|
|
||||||
km / (111.320 * math.cos(atLat * math.pi / 180.0)).abs().clamp(1e-6, 1e9);
|
|
||||||
|
|
||||||
/// حساب درجة التطابق النصي (كل كلمة تبدأ بها الاسم = 2 نقاط، يحتويها = 1 نقطة)
|
|
||||||
double _relevanceScore(String name, String query) {
|
|
||||||
final n = name.toLowerCase();
|
|
||||||
final parts =
|
|
||||||
query.toLowerCase().split(RegExp(r'\s+')).where((p) => p.length >= 2);
|
|
||||||
double s = 0.0;
|
|
||||||
for (final p in parts) {
|
|
||||||
if (n.startsWith(p)) {
|
|
||||||
s += 2.0;
|
|
||||||
} else if (n.contains(p)) {
|
|
||||||
s += 1.0;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return s;
|
|
||||||
}
|
|
||||||
|
|
||||||
|
/// الدالة المحدثة للبحث عن الأماكن
|
||||||
Future<void> getPlaces() async {
|
Future<void> getPlaces() async {
|
||||||
final q = placeDestinationController.text.trim();
|
final q = placeDestinationController.text.trim();
|
||||||
if (q.isEmpty) {
|
if (q.isEmpty || q.length < 3) {
|
||||||
placesDestination = [];
|
placesDestination = [];
|
||||||
update();
|
update();
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// التأكد من أن الموقع الحالي ليس null
|
||||||
|
if (myLocation == null) {
|
||||||
|
print('myLocation is null, cannot search for places.');
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
final lat = myLocation!.latitude;
|
final lat = myLocation!.latitude;
|
||||||
final lng = myLocation!.longitude;
|
final lng = myLocation!.longitude;
|
||||||
|
|
||||||
// نصف قطر البحث بالكيلومتر (عدّل حسب رغبتك)
|
// نصف قطر البحث بالكيلومتر
|
||||||
const radiusKm = 200.0;
|
const radiusKm = 200.0;
|
||||||
|
|
||||||
// حساب الباوند الصحيح (درجات، وليس 2.2 درجة ثابتة)
|
// حساب النطاق الجغرافي (Bounding Box) لإرساله للسيرفر
|
||||||
final latDelta = _kmToLatDelta(radiusKm);
|
final latDelta = _kmToLatDelta(radiusKm);
|
||||||
final lngDelta = _kmToLngDelta(radiusKm, lat);
|
final lngDelta = _kmToLngDelta(radiusKm, lat);
|
||||||
|
|
||||||
@@ -591,6 +568,7 @@ class NavigationController extends GetxController {
|
|||||||
final lngMax = lng + lngDelta;
|
final lngMax = lng + lngDelta;
|
||||||
|
|
||||||
try {
|
try {
|
||||||
|
// استدعاء الـ API
|
||||||
final response = await CRUD().post(
|
final response = await CRUD().post(
|
||||||
link: AppLink.getPlacesSyria,
|
link: AppLink.getPlacesSyria,
|
||||||
payload: {
|
payload: {
|
||||||
@@ -602,53 +580,57 @@ class NavigationController extends GetxController {
|
|||||||
},
|
},
|
||||||
);
|
);
|
||||||
|
|
||||||
// يدعم شكلي استجابة: إما {"...","message":[...]} أو قائمة مباشرة [...]
|
// معالجة الاستجابة من السيرفر بشكل يوافق {"status":"success", "message":[...]}
|
||||||
List list;
|
List list;
|
||||||
if (response is Map && response['message'] is List) {
|
if (response is Map) {
|
||||||
list = List.from(response['message'] as List);
|
if (response['status'] == 'success' && response['message'] is List) {
|
||||||
|
list = List.from(response['message'] as List);
|
||||||
|
} else if (response['status'] == 'failure') {
|
||||||
|
print('Server Error: ${response['message']}');
|
||||||
|
return;
|
||||||
|
} else {
|
||||||
|
print('Unexpected Map shape from server');
|
||||||
|
return;
|
||||||
|
}
|
||||||
} else if (response is List) {
|
} else if (response is List) {
|
||||||
|
// للتعامل مع الحالات التي قد يرجع فيها السيرفر قائمة مباشرة
|
||||||
list = List.from(response);
|
list = List.from(response);
|
||||||
} else {
|
} else {
|
||||||
print('Unexpected response shape');
|
print('Unexpected response shape from server');
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
// جهّز الحقول المحتملة للأسماء
|
// دالة مساعدة لاختيار أفضل اسم متاح
|
||||||
String _bestName(Map p) {
|
String _bestName(Map p) {
|
||||||
return (p['name'] ?? p['name_ar'] ?? p['name_en'] ?? '').toString();
|
return (p['name_ar'] ?? p['name'] ?? p['name_en'] ?? '').toString();
|
||||||
}
|
}
|
||||||
|
|
||||||
// احسب المسافة ودرجة التطابق والنقاط
|
// حساب المسافة والصلة والنقاط النهائية لكل نتيجة
|
||||||
for (final p in list) {
|
for (final p in list) {
|
||||||
final plat = double.tryParse(p['latitude']?.toString() ?? '') ?? 0.0;
|
final plat = double.tryParse(p['latitude']?.toString() ?? '0.0') ?? 0.0;
|
||||||
final plng = double.tryParse(p['longitude']?.toString() ?? '') ?? 0.0;
|
final plng =
|
||||||
|
double.tryParse(p['longitude']?.toString() ?? '0.0') ?? 0.0;
|
||||||
|
|
||||||
final d = _haversineKm(lat, lng, plat, plng);
|
final distance = _haversineKm(lat, lng, plat, plng);
|
||||||
final rel = _relevanceScore(_bestName(p), q);
|
final relevance = _relevanceScore(_bestName(p), q);
|
||||||
|
|
||||||
// معادلة ترتيب ذكية: مسافة أقل + تطابق أعلى = نقاط أعلى
|
// معادلة الترتيب: (الأولوية للمسافة الأقرب) * (ثم الصلة الأعلى)
|
||||||
// تضيف +1 لضمان عدم وصول الوزن للصفر عند عدم وجود تطابق
|
final score = (1.0 / (1.0 + distance)) * (1.0 + relevance);
|
||||||
final score = (1.0 / (1.0 + d)) * (1.0 + rel);
|
|
||||||
|
|
||||||
p['distanceKm'] = d;
|
p['distanceKm'] = distance;
|
||||||
p['relevance'] = rel;
|
p['relevance'] = relevance;
|
||||||
p['score'] = score;
|
p['score'] = score;
|
||||||
}
|
}
|
||||||
|
|
||||||
// رتّب حسب score تنازليًا، ثم المسافة تصاعديًا كحسم
|
// ترتيب القائمة النهائية حسب النقاط (الأعلى أولاً)
|
||||||
list.sort((a, b) {
|
list.sort((a, b) {
|
||||||
final sa = (a['score'] ?? 0.0) as double;
|
final sa = (a['score'] ?? 0.0) as double;
|
||||||
final sb = (b['score'] ?? 0.0) as double;
|
final sb = (b['score'] ?? 0.0) as double;
|
||||||
final cmp = sb.compareTo(sa);
|
return sb.compareTo(sa);
|
||||||
if (cmp != 0) return cmp;
|
|
||||||
final da = (a['distanceKm'] ?? 1e9) as double;
|
|
||||||
final db = (b['distanceKm'] ?? 1e9) as double;
|
|
||||||
return da.compareTo(db);
|
|
||||||
});
|
});
|
||||||
|
|
||||||
// خذ أول 10–15 للعرض (اختياري)، أو اعرض الكل
|
placesDestination = list;
|
||||||
placesDestination = list.take(15).toList();
|
Log.print('Updated places: $placesDestination');
|
||||||
Log.print('placesDestination: $placesDestination');
|
|
||||||
update();
|
update();
|
||||||
} catch (e) {
|
} catch (e) {
|
||||||
print('Exception in getPlaces: $e');
|
print('Exception in getPlaces: $e');
|
||||||
@@ -659,4 +641,44 @@ class NavigationController extends GetxController {
|
|||||||
if (_debounce?.isActive ?? false) _debounce!.cancel();
|
if (_debounce?.isActive ?? false) _debounce!.cancel();
|
||||||
_debounce = Timer(const Duration(milliseconds: 700), () => getPlaces());
|
_debounce = Timer(const Duration(milliseconds: 700), () => getPlaces());
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// -----------------------------------------------------------------
|
||||||
|
// --== دوال مساعدة (محدثة) ==--
|
||||||
|
// -----------------------------------------------------------------
|
||||||
|
|
||||||
|
/// تحسب المسافة بين نقطتين بالكيلومتر (معادلة هافرساين)
|
||||||
|
double _haversineKm(double lat1, double lon1, double lat2, double lon2) {
|
||||||
|
const R = 6371.0; // نصف قطر الأرض بالكيلومتر
|
||||||
|
final dLat = (lat2 - lat1) * (pi / 180.0);
|
||||||
|
final dLon = (lon2 - lon1) * (pi / 180.0);
|
||||||
|
final rLat1 = lat1 * (pi / 180.0);
|
||||||
|
final rLat2 = lat2 * (pi / 180.0);
|
||||||
|
|
||||||
|
final a = sin(dLat / 2) * sin(dLat / 2) +
|
||||||
|
cos(rLat1) * cos(rLat2) * sin(dLon / 2) * sin(dLon / 2);
|
||||||
|
final c = 2 * atan2(sqrt(a), sqrt(1 - a));
|
||||||
|
return R * c;
|
||||||
|
}
|
||||||
|
|
||||||
|
/// تحسب درجة تطابق بسيطة بين اسم المكان وكلمة البحث
|
||||||
|
double _relevanceScore(String placeName, String query) {
|
||||||
|
if (placeName.isEmpty || query.isEmpty) return 0.0;
|
||||||
|
final pLower = placeName.toLowerCase();
|
||||||
|
final qLower = query.toLowerCase();
|
||||||
|
if (pLower.startsWith(qLower)) return 1.0; // تطابق كامل في البداية
|
||||||
|
if (pLower.contains(qLower)) return 0.5; // تحتوي على الكلمة
|
||||||
|
return 0.0;
|
||||||
|
}
|
||||||
|
|
||||||
|
/// تحويل كيلومتر إلى فرق درجات لخط العرض
|
||||||
|
double _kmToLatDelta(double km) {
|
||||||
|
const kmInDegree = 111.32;
|
||||||
|
return km / kmInDegree;
|
||||||
|
}
|
||||||
|
|
||||||
|
/// تحويل كيلومتر إلى فرق درجات لخط الطول (يعتمد على خط العرض الحالي)
|
||||||
|
double _kmToLngDelta(double km, double latitude) {
|
||||||
|
const kmInDegree = 111.32;
|
||||||
|
return km / (kmInDegree * cos(latitude * (pi / 180.0)));
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -133,6 +133,8 @@ class MyTranslation extends Translations {
|
|||||||
"How to use Intaleq": "كيفية استخدام Intaleq",
|
"How to use Intaleq": "كيفية استخدام Intaleq",
|
||||||
"What are the order details we provide to you?":
|
"What are the order details we provide to you?":
|
||||||
"ما هي تفاصيل الطلب التي نوفرها لك؟",
|
"ما هي تفاصيل الطلب التي نوفرها لك؟",
|
||||||
|
'An OTP has been sent to your number.':
|
||||||
|
'تم إرسال رمز التحقق إلى رقمك.',
|
||||||
"Intaleq Wallet Features:\n\nTransfer money multiple times.\nTransfer to anyone.\nMake purchases.\nCharge your account.\nCharge a friend's Intaleq account.\nStore your money with us and receive it in your bank as a monthly salary.":
|
"Intaleq Wallet Features:\n\nTransfer money multiple times.\nTransfer to anyone.\nMake purchases.\nCharge your account.\nCharge a friend's Intaleq account.\nStore your money with us and receive it in your bank as a monthly salary.":
|
||||||
"ميزات محفظة Intaleq:\n\nتحويل الأموال عدة مرات.\nالتحويل إلى أي شخص.\nإجراء عمليات شراء.\nشحن حسابك.\nشحن حساب Intaleq لصديق.\nقم بتخزين أموالك معنا واستلامها في بنكك كراتب شهري.",
|
"ميزات محفظة Intaleq:\n\nتحويل الأموال عدة مرات.\nالتحويل إلى أي شخص.\nإجراء عمليات شراء.\nشحن حسابك.\nشحن حساب Intaleq لصديق.\nقم بتخزين أموالك معنا واستلامها في بنكك كراتب شهري.",
|
||||||
"What is the feature of our wallet?": "ما هي مميزات محفظتنا؟",
|
"What is the feature of our wallet?": "ما هي مميزات محفظتنا؟",
|
||||||
|
|||||||
@@ -3,21 +3,20 @@ import 'dart:math';
|
|||||||
|
|
||||||
import 'package:flutter/cupertino.dart';
|
import 'package:flutter/cupertino.dart';
|
||||||
import 'package:flutter/services.dart';
|
import 'package:flutter/services.dart';
|
||||||
import 'package:sefer_driver/constant/box_name.dart';
|
import 'package:flutter/widgets.dart'; // Import for WidgetsBinding
|
||||||
import 'package:sefer_driver/controller/functions/location_controller.dart';
|
|
||||||
import 'package:geolocator/geolocator.dart';
|
import 'package:geolocator/geolocator.dart';
|
||||||
import 'package:get/get.dart';
|
import 'package:get/get.dart';
|
||||||
import 'package:google_maps_flutter/google_maps_flutter.dart';
|
import 'package:google_maps_flutter/google_maps_flutter.dart';
|
||||||
|
|
||||||
import '../../constant/links.dart';
|
import '../../constant/links.dart';
|
||||||
import '../../main.dart';
|
|
||||||
import '../../print.dart';
|
|
||||||
import '../../views/widgets/mydialoug.dart';
|
|
||||||
import '../functions/crud.dart';
|
import '../functions/crud.dart';
|
||||||
|
import '../functions/location_controller.dart';
|
||||||
|
|
||||||
class RideAvailableController extends GetxController {
|
class RideAvailableController extends GetxController {
|
||||||
bool isLoading = false;
|
bool isLoading = false;
|
||||||
Map rideAvailableMap = {};
|
// FIX 1: Initialize the map with a default structure.
|
||||||
|
// This prevents `rideAvailableMap['message']` from ever being null in the UI.
|
||||||
|
Map rideAvailableMap = {'message': []};
|
||||||
late LatLng southwest;
|
late LatLng southwest;
|
||||||
late LatLng northeast;
|
late LatLng northeast;
|
||||||
|
|
||||||
@@ -30,19 +29,15 @@ class RideAvailableController extends GetxController {
|
|||||||
|
|
||||||
double minLat = lat - latDelta;
|
double minLat = lat - latDelta;
|
||||||
double maxLat = lat + latDelta;
|
double maxLat = lat + latDelta;
|
||||||
|
|
||||||
double minLng = lng - lngDelta;
|
double minLng = lng - lngDelta;
|
||||||
double maxLng = lng + lngDelta;
|
double maxLng = lng + lngDelta;
|
||||||
|
|
||||||
// Ensure the latitude is between -90 and 90
|
|
||||||
minLat = max(-90.0, minLat);
|
minLat = max(-90.0, minLat);
|
||||||
maxLat = min(90.0, maxLat);
|
maxLat = min(90.0, maxLat);
|
||||||
|
|
||||||
// Ensure the longitude is between -180 and 180
|
|
||||||
minLng = (minLng + 180) % 360 - 180;
|
minLng = (minLng + 180) % 360 - 180;
|
||||||
maxLng = (maxLng + 180) % 360 - 180;
|
maxLng = (maxLng + 180) % 360 - 180;
|
||||||
|
|
||||||
// Ensure the bounds are in the correct order
|
|
||||||
if (minLng > maxLng) {
|
if (minLng > maxLng) {
|
||||||
double temp = minLng;
|
double temp = minLng;
|
||||||
minLng = maxLng;
|
minLng = maxLng;
|
||||||
@@ -60,7 +55,6 @@ class RideAvailableController extends GetxController {
|
|||||||
double startLatitude = double.parse(startLocationParts[0]);
|
double startLatitude = double.parse(startLocationParts[0]);
|
||||||
double startLongitude = double.parse(startLocationParts[1]);
|
double startLongitude = double.parse(startLocationParts[1]);
|
||||||
|
|
||||||
// Assuming currentLocation is the driver's location
|
|
||||||
double currentLatitude = Get.find<LocationController>().myLocation.latitude;
|
double currentLatitude = Get.find<LocationController>().myLocation.latitude;
|
||||||
double currentLongitude =
|
double currentLongitude =
|
||||||
Get.find<LocationController>().myLocation.longitude;
|
Get.find<LocationController>().myLocation.longitude;
|
||||||
@@ -73,22 +67,28 @@ class RideAvailableController extends GetxController {
|
|||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
// void sortRidesByDistance() {
|
// A helper function to safely show dialogs after the build cycle is complete.
|
||||||
// rideAvailableMap['message'].sort((a, b) {
|
void _showDialogAfterBuild(Widget dialog) {
|
||||||
// double distanceA = calculateDistance(a['start_location']);
|
// FIX 2: Use addPostFrameCallback to ensure dialogs are shown after the build process.
|
||||||
// double distanceB = calculateDistance(b['start_location']);
|
// This resolves the "visitChildElements() called during build" error.
|
||||||
// return distanceA.compareTo(distanceB);
|
WidgetsBinding.instance.addPostFrameCallback((_) {
|
||||||
// });
|
Get.dialog(
|
||||||
// }
|
dialog,
|
||||||
|
barrierDismissible: true,
|
||||||
|
transitionCurve: Curves.easeOutBack,
|
||||||
|
transitionDuration: const Duration(milliseconds: 200),
|
||||||
|
);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
getRideAvailable() async {
|
Future<void> getRideAvailable() async {
|
||||||
try {
|
try {
|
||||||
isLoading = true;
|
isLoading = true;
|
||||||
update();
|
update();
|
||||||
|
|
||||||
LatLngBounds bounds = calculateBounds(
|
LatLngBounds bounds = calculateBounds(
|
||||||
Get.find<LocationController>().myLocation!.latitude,
|
Get.find<LocationController>().myLocation.latitude,
|
||||||
Get.find<LocationController>().myLocation!.longitude,
|
Get.find<LocationController>().myLocation.longitude,
|
||||||
4000);
|
4000);
|
||||||
|
|
||||||
var payload = {
|
var payload = {
|
||||||
@@ -101,85 +101,108 @@ class RideAvailableController extends GetxController {
|
|||||||
var res =
|
var res =
|
||||||
await CRUD().get(link: AppLink.getRideWaiting, payload: payload);
|
await CRUD().get(link: AppLink.getRideWaiting, payload: payload);
|
||||||
|
|
||||||
|
isLoading = false; // Request is complete, stop loading indicator.
|
||||||
|
|
||||||
if (res != 'failure') {
|
if (res != 'failure') {
|
||||||
rideAvailableMap = jsonDecode(res);
|
final decodedResponse = jsonDecode(res);
|
||||||
isLoading = false;
|
// Check for valid response structure
|
||||||
update();
|
if (decodedResponse is Map &&
|
||||||
|
decodedResponse.containsKey('message') &&
|
||||||
|
decodedResponse['message'] is List) {
|
||||||
|
rideAvailableMap = decodedResponse;
|
||||||
|
// If the list of rides is empty, show the "No Rides" dialog
|
||||||
|
if ((rideAvailableMap['message'] as List).isEmpty) {
|
||||||
|
_showDialogAfterBuild(_buildNoRidesDialog());
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
// If response format is unexpected, treat as no rides and show dialog
|
||||||
|
rideAvailableMap = {'message': []};
|
||||||
|
_showDialogAfterBuild(_buildNoRidesDialog());
|
||||||
|
}
|
||||||
|
update(); // Update the UI with new data (or empty list)
|
||||||
} else {
|
} else {
|
||||||
|
// This block now handles network/server errors correctly
|
||||||
HapticFeedback.lightImpact();
|
HapticFeedback.lightImpact();
|
||||||
Get.dialog(
|
update(); // Update UI to turn off loader
|
||||||
CupertinoAlertDialog(
|
// Show a proper error dialog instead of "No Rides"
|
||||||
title: Column(
|
_showDialogAfterBuild(
|
||||||
mainAxisSize: MainAxisSize.min,
|
_buildErrorDialog("Failed to fetch rides. Please try again.".tr));
|
||||||
children: [
|
|
||||||
const Icon(
|
|
||||||
CupertinoIcons.car,
|
|
||||||
size: 44,
|
|
||||||
color: CupertinoColors.systemGrey,
|
|
||||||
),
|
|
||||||
const SizedBox(height: 12),
|
|
||||||
Text(
|
|
||||||
"No Rides Available".tr,
|
|
||||||
style: const TextStyle(
|
|
||||||
fontSize: 17,
|
|
||||||
fontWeight: FontWeight.w600,
|
|
||||||
),
|
|
||||||
),
|
|
||||||
],
|
|
||||||
),
|
|
||||||
content: Padding(
|
|
||||||
padding: const EdgeInsets.only(top: 8),
|
|
||||||
child: Text(
|
|
||||||
"Please check back later for available rides.".tr,
|
|
||||||
style: const TextStyle(
|
|
||||||
fontSize: 13,
|
|
||||||
color: CupertinoColors.systemGrey,
|
|
||||||
),
|
|
||||||
),
|
|
||||||
),
|
|
||||||
actions: [
|
|
||||||
CupertinoDialogAction(
|
|
||||||
onPressed: () {
|
|
||||||
Get.back();
|
|
||||||
Get.back();
|
|
||||||
},
|
|
||||||
child: Text('OK'.tr),
|
|
||||||
),
|
|
||||||
],
|
|
||||||
),
|
|
||||||
barrierDismissible: true,
|
|
||||||
transitionCurve: Curves.easeOutBack,
|
|
||||||
transitionDuration: const Duration(milliseconds: 200),
|
|
||||||
);
|
|
||||||
}
|
}
|
||||||
} catch (e) {
|
} catch (e) {
|
||||||
isLoading = false;
|
isLoading = false;
|
||||||
update();
|
update();
|
||||||
Get.dialog(
|
// This catches other exceptions like JSON parsing errors
|
||||||
CupertinoAlertDialog(
|
_showDialogAfterBuild(
|
||||||
title: const Icon(
|
_buildErrorDialog("An unexpected error occurred.".tr));
|
||||||
CupertinoIcons.exclamationmark_triangle_fill,
|
|
||||||
color: CupertinoColors.systemRed,
|
|
||||||
size: 44,
|
|
||||||
),
|
|
||||||
content: Text(
|
|
||||||
"Error fetching rides. Please try again.".tr,
|
|
||||||
style: const TextStyle(fontSize: 14),
|
|
||||||
),
|
|
||||||
actions: [
|
|
||||||
CupertinoDialogAction(
|
|
||||||
onPressed: () => Get.back(),
|
|
||||||
child: Text('OK'.tr),
|
|
||||||
),
|
|
||||||
],
|
|
||||||
),
|
|
||||||
);
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Extracted dialogs into builder methods for cleanliness.
|
||||||
|
Widget _buildNoRidesDialog() {
|
||||||
|
return CupertinoAlertDialog(
|
||||||
|
title: Column(
|
||||||
|
mainAxisSize: MainAxisSize.min,
|
||||||
|
children: [
|
||||||
|
const Icon(
|
||||||
|
CupertinoIcons.car,
|
||||||
|
size: 44,
|
||||||
|
color: CupertinoColors.systemGrey,
|
||||||
|
),
|
||||||
|
const SizedBox(height: 12),
|
||||||
|
Text(
|
||||||
|
"No Rides Available".tr,
|
||||||
|
style: const TextStyle(fontSize: 17, fontWeight: FontWeight.w600),
|
||||||
|
),
|
||||||
|
],
|
||||||
|
),
|
||||||
|
content: Padding(
|
||||||
|
padding: const EdgeInsets.only(top: 8),
|
||||||
|
child: Text(
|
||||||
|
"Please check back later for available rides.".tr,
|
||||||
|
style:
|
||||||
|
const TextStyle(fontSize: 13, color: CupertinoColors.systemGrey),
|
||||||
|
),
|
||||||
|
),
|
||||||
|
actions: [
|
||||||
|
CupertinoDialogAction(
|
||||||
|
onPressed: () {
|
||||||
|
Get.back(); // Close dialog
|
||||||
|
Get.back(); // Go back from AvailableRidesPage
|
||||||
|
},
|
||||||
|
child: Text('OK'.tr),
|
||||||
|
),
|
||||||
|
],
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
Widget _buildErrorDialog(String error) {
|
||||||
|
// You can log the error here for debugging.
|
||||||
|
// print("Error fetching rides: $error");
|
||||||
|
return CupertinoAlertDialog(
|
||||||
|
title: const Icon(
|
||||||
|
CupertinoIcons.exclamationmark_triangle_fill,
|
||||||
|
color: CupertinoColors.systemRed,
|
||||||
|
size: 44,
|
||||||
|
),
|
||||||
|
content: Text(
|
||||||
|
error, // Display the specific error message passed to the function
|
||||||
|
style: const TextStyle(fontSize: 14),
|
||||||
|
),
|
||||||
|
actions: [
|
||||||
|
CupertinoDialogAction(
|
||||||
|
onPressed: () {
|
||||||
|
Get.back(); // Close dialog
|
||||||
|
Get.back(); // Go back from AvailableRidesPage
|
||||||
|
},
|
||||||
|
child: Text('OK'.tr),
|
||||||
|
),
|
||||||
|
],
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
@override
|
@override
|
||||||
void onInit() {
|
void onInit() {
|
||||||
getRideAvailable();
|
|
||||||
super.onInit();
|
super.onInit();
|
||||||
|
getRideAvailable();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
350
lib/controller/payment/mtn_new/mtn_payment_new_screen.dart
Normal file
350
lib/controller/payment/mtn_new/mtn_payment_new_screen.dart
Normal file
@@ -0,0 +1,350 @@
|
|||||||
|
import 'dart:async';
|
||||||
|
import 'package:flutter/material.dart';
|
||||||
|
// import 'package:http/http.dart' as http;
|
||||||
|
import 'package:intl/intl.dart';
|
||||||
|
import 'package:sefer_driver/constant/links.dart'; // افترض وجود هذا الملف
|
||||||
|
import 'package:sefer_driver/controller/functions/crud.dart'; // افترض وجود هذا الملف
|
||||||
|
import '../../../main.dart'; // افترض وجود box هنا
|
||||||
|
import '../../../constant/box_name.dart'; // افترض وجود هذا الملف
|
||||||
|
|
||||||
|
// Service class to handle MTN payment logic
|
||||||
|
class MtnPaymentService {
|
||||||
|
final String _baseUrl =
|
||||||
|
"${AppLink.paymentServer}/ride/mtn_new"; // تأكد من تعديل المسار
|
||||||
|
|
||||||
|
// Function to create a new invoice
|
||||||
|
Future<String?> createInvoice({
|
||||||
|
required String userId,
|
||||||
|
required String userType, // 'driver' or 'passenger'
|
||||||
|
required double amount,
|
||||||
|
required String mtnPhone,
|
||||||
|
}) async {
|
||||||
|
final url = "$_baseUrl/create_mtn_invoice.php";
|
||||||
|
try {
|
||||||
|
final response = await CRUD().postWallet(
|
||||||
|
// استخدام نفس دالة CRUD
|
||||||
|
link: url,
|
||||||
|
payload: {
|
||||||
|
'user_id': userId,
|
||||||
|
'user_type': userType,
|
||||||
|
'amount': amount.toString(),
|
||||||
|
'mtn_phone': mtnPhone,
|
||||||
|
},
|
||||||
|
).timeout(const Duration(seconds: 15));
|
||||||
|
|
||||||
|
if (response != 'failure') {
|
||||||
|
final data = response;
|
||||||
|
if (data['status'] == 'success' && data['invoice_number'] != null) {
|
||||||
|
debugPrint("MTN Invoice created: ${data['invoice_number']}");
|
||||||
|
return data['invoice_number'].toString();
|
||||||
|
} else {
|
||||||
|
debugPrint("Failed to create MTN invoice: ${data['message']}");
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
debugPrint("Server error during MTN invoice creation.");
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
} catch (e) {
|
||||||
|
debugPrint("Exception during MTN invoice creation: $e");
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Function to check invoice status (polling)
|
||||||
|
Future<bool> checkInvoiceStatus(String invoiceNumber) async {
|
||||||
|
// This should point to a new script on your server that checks mtn_invoices table
|
||||||
|
final url = "$_baseUrl/check_mtn_invoice_status.php";
|
||||||
|
try {
|
||||||
|
final response = await CRUD().postWallet(link: url, payload: {
|
||||||
|
'invoice_number': invoiceNumber,
|
||||||
|
}).timeout(const Duration(seconds: 10));
|
||||||
|
|
||||||
|
if (response != 'failure') {
|
||||||
|
final data = response;
|
||||||
|
return data['status'] == 'success' &&
|
||||||
|
data['invoice_status'] == 'completed';
|
||||||
|
}
|
||||||
|
return false;
|
||||||
|
} catch (e) {
|
||||||
|
debugPrint("Error checking MTN invoice status: $e");
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
enum PaymentStatus {
|
||||||
|
creatingInvoice,
|
||||||
|
waitingForPayment,
|
||||||
|
paymentSuccess,
|
||||||
|
paymentTimeout,
|
||||||
|
paymentError
|
||||||
|
}
|
||||||
|
|
||||||
|
class PaymentScreenMtn extends StatefulWidget {
|
||||||
|
final double amount;
|
||||||
|
// يمكنك إضافة متغير لتحديد هل المستخدم سائق أم راكب
|
||||||
|
final String userType; // 'driver' or 'passenger'
|
||||||
|
|
||||||
|
const PaymentScreenMtn({
|
||||||
|
super.key,
|
||||||
|
required this.amount,
|
||||||
|
required this.userType,
|
||||||
|
});
|
||||||
|
|
||||||
|
@override
|
||||||
|
_PaymentScreenMtnState createState() => _PaymentScreenMtnState();
|
||||||
|
}
|
||||||
|
|
||||||
|
class _PaymentScreenMtnState extends State<PaymentScreenMtn> {
|
||||||
|
final MtnPaymentService _paymentService = MtnPaymentService();
|
||||||
|
Timer? _pollingTimer;
|
||||||
|
PaymentStatus _status = PaymentStatus.creatingInvoice;
|
||||||
|
String? _invoiceNumber;
|
||||||
|
// جلب البيانات من الـ box
|
||||||
|
final String userId =
|
||||||
|
box.read(BoxName.driverID) ?? box.read(BoxName.passengerID);
|
||||||
|
final String phone = box.read(BoxName.phoneWallet);
|
||||||
|
|
||||||
|
@override
|
||||||
|
void initState() {
|
||||||
|
super.initState();
|
||||||
|
_createAndPollInvoice();
|
||||||
|
}
|
||||||
|
|
||||||
|
@override
|
||||||
|
void dispose() {
|
||||||
|
_pollingTimer?.cancel();
|
||||||
|
super.dispose();
|
||||||
|
}
|
||||||
|
|
||||||
|
void _createAndPollInvoice() async {
|
||||||
|
setState(() => _status = PaymentStatus.creatingInvoice);
|
||||||
|
|
||||||
|
final invoiceNumber = await _paymentService.createInvoice(
|
||||||
|
userId: userId,
|
||||||
|
userType: widget.userType,
|
||||||
|
amount: widget.amount,
|
||||||
|
mtnPhone: phone,
|
||||||
|
);
|
||||||
|
|
||||||
|
if (invoiceNumber != null && mounted) {
|
||||||
|
setState(() {
|
||||||
|
_invoiceNumber = invoiceNumber;
|
||||||
|
_status = PaymentStatus.waitingForPayment;
|
||||||
|
});
|
||||||
|
_startPolling(invoiceNumber);
|
||||||
|
} else if (mounted) {
|
||||||
|
setState(() => _status = PaymentStatus.paymentError);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
void _startPolling(String invoiceNumber) {
|
||||||
|
const timeoutDuration = Duration(minutes: 15); // زيادة المهلة
|
||||||
|
var elapsed = Duration.zero;
|
||||||
|
|
||||||
|
_pollingTimer = Timer.periodic(const Duration(seconds: 5), (timer) async {
|
||||||
|
elapsed += const Duration(seconds: 5);
|
||||||
|
if (elapsed >= timeoutDuration) {
|
||||||
|
timer.cancel();
|
||||||
|
if (mounted) setState(() => _status = PaymentStatus.paymentTimeout);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
debugPrint("Polling... Checking MTN invoice: $invoiceNumber");
|
||||||
|
final isCompleted =
|
||||||
|
await _paymentService.checkInvoiceStatus(invoiceNumber);
|
||||||
|
if (isCompleted && mounted) {
|
||||||
|
timer.cancel();
|
||||||
|
setState(() => _status = PaymentStatus.paymentSuccess);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
@override
|
||||||
|
Widget build(BuildContext context) {
|
||||||
|
return PopScope(
|
||||||
|
canPop: _status != PaymentStatus.waitingForPayment,
|
||||||
|
onPopInvoked: (didPop) async {
|
||||||
|
if (didPop) return;
|
||||||
|
if (_status == PaymentStatus.waitingForPayment) {
|
||||||
|
final shouldPop = await showDialog<bool>(
|
||||||
|
context: context,
|
||||||
|
builder: (context) => AlertDialog(
|
||||||
|
title: const Text('هل أنت متأكد؟'),
|
||||||
|
content: const Text(
|
||||||
|
'إذا خرجت الآن، قد تفشل عملية الدفع. عليك إتمامها من تطبيق MTN.'),
|
||||||
|
actions: [
|
||||||
|
TextButton(
|
||||||
|
onPressed: () => Navigator.of(context).pop(false),
|
||||||
|
child: const Text('البقاء')),
|
||||||
|
TextButton(
|
||||||
|
onPressed: () => Navigator.of(context).pop(true),
|
||||||
|
child: const Text('الخروج')),
|
||||||
|
],
|
||||||
|
),
|
||||||
|
);
|
||||||
|
if (shouldPop ?? false) {
|
||||||
|
Navigator.of(context).pop();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
child: Scaffold(
|
||||||
|
appBar: AppBar(title: const Text("الدفع عبر MTN Cash")),
|
||||||
|
body: Padding(
|
||||||
|
padding: const EdgeInsets.all(16.0),
|
||||||
|
child: Center(child: _buildContentByStatus()),
|
||||||
|
),
|
||||||
|
),
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
Widget _buildContentByStatus() {
|
||||||
|
switch (_status) {
|
||||||
|
case PaymentStatus.creatingInvoice:
|
||||||
|
return const Column(
|
||||||
|
mainAxisAlignment: MainAxisAlignment.center,
|
||||||
|
children: [
|
||||||
|
CircularProgressIndicator(),
|
||||||
|
SizedBox(height: 20),
|
||||||
|
Text("جاري إنشاء فاتورة دفع...", style: TextStyle(fontSize: 16)),
|
||||||
|
],
|
||||||
|
);
|
||||||
|
case PaymentStatus.waitingForPayment:
|
||||||
|
return _buildWaitingForPaymentUI();
|
||||||
|
case PaymentStatus.paymentSuccess:
|
||||||
|
return _buildSuccessUI();
|
||||||
|
case PaymentStatus.paymentTimeout:
|
||||||
|
case PaymentStatus.paymentError:
|
||||||
|
return _buildErrorUI();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
Widget _buildWaitingForPaymentUI() {
|
||||||
|
final currencyFormat = NumberFormat.decimalPattern('ar_SY');
|
||||||
|
|
||||||
|
return SingleChildScrollView(
|
||||||
|
child: Column(
|
||||||
|
children: [
|
||||||
|
// **مهم**: استبدل هذا المسار بمسار شعار MTN الصحيح في مشروعك
|
||||||
|
Image.asset('assets/images/cashMTN.png', width: 120),
|
||||||
|
const SizedBox(height: 24),
|
||||||
|
Text("تعليمات الدفع", style: Theme.of(context).textTheme.titleLarge),
|
||||||
|
const SizedBox(height: 12),
|
||||||
|
Text(
|
||||||
|
"المبلغ المطلوب: ${currencyFormat.format(widget.amount)} ل.س",
|
||||||
|
style: Theme.of(context)
|
||||||
|
.textTheme
|
||||||
|
.titleMedium
|
||||||
|
?.copyWith(fontWeight: FontWeight.bold),
|
||||||
|
),
|
||||||
|
const SizedBox(height: 16),
|
||||||
|
Card(
|
||||||
|
elevation: 1.5,
|
||||||
|
shape:
|
||||||
|
RoundedRectangleBorder(borderRadius: BorderRadius.circular(12)),
|
||||||
|
child: const Padding(
|
||||||
|
padding: EdgeInsets.all(16),
|
||||||
|
child: Column(
|
||||||
|
children: [
|
||||||
|
_StepTile(number: 1, text: "افتح تطبيق MTN Cash Mobile."),
|
||||||
|
_StepTile(
|
||||||
|
number: 2,
|
||||||
|
text: "اذهب إلى قسم 'دفع الفواتير' أو 'خدمات الدفع'."),
|
||||||
|
_StepTile(
|
||||||
|
number: 3,
|
||||||
|
text: "ابحث عن 'Intaleq App' في قائمة المفوترين."),
|
||||||
|
_StepTile(
|
||||||
|
number: 4,
|
||||||
|
text:
|
||||||
|
"أدخل رقم هاتفك المسجل لدينا للاستعلام عن الفاتورة."),
|
||||||
|
_StepTile(
|
||||||
|
number: 5,
|
||||||
|
text:
|
||||||
|
"ستظهر لك فاتورة بالمبلغ المطلوب. قم بتأكيد الدفع."),
|
||||||
|
],
|
||||||
|
),
|
||||||
|
),
|
||||||
|
),
|
||||||
|
const SizedBox(height: 24),
|
||||||
|
const LinearProgressIndicator(minHeight: 2),
|
||||||
|
const SizedBox(height: 12),
|
||||||
|
Text("بانتظار تأكيد الدفع من MTN...",
|
||||||
|
style: TextStyle(color: Colors.grey.shade700)),
|
||||||
|
const SizedBox(height: 4),
|
||||||
|
const Text("هذه الشاشة ستتحدث تلقائيًا عند اكتمال الدفع",
|
||||||
|
style: TextStyle(color: Colors.grey),
|
||||||
|
textAlign: TextAlign.center),
|
||||||
|
],
|
||||||
|
),
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
Widget _buildSuccessUI() {
|
||||||
|
return Column(
|
||||||
|
mainAxisAlignment: MainAxisAlignment.center,
|
||||||
|
children: [
|
||||||
|
const Icon(Icons.check_circle, color: Colors.green, size: 80),
|
||||||
|
const SizedBox(height: 20),
|
||||||
|
const Text("تم الدفع بنجاح!",
|
||||||
|
style: TextStyle(fontSize: 22, fontWeight: FontWeight.bold)),
|
||||||
|
const SizedBox(height: 8),
|
||||||
|
const Text("تمت إضافة النقاط إلى حسابك.",
|
||||||
|
style: TextStyle(fontSize: 16)),
|
||||||
|
const SizedBox(height: 20),
|
||||||
|
ElevatedButton(
|
||||||
|
onPressed: () => Navigator.of(context).pop(),
|
||||||
|
child: const Text("العودة إلى المحفظة"),
|
||||||
|
),
|
||||||
|
],
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
Widget _buildErrorUI() {
|
||||||
|
return Column(
|
||||||
|
mainAxisAlignment: MainAxisAlignment.center,
|
||||||
|
children: [
|
||||||
|
const Icon(Icons.error, color: Colors.red, size: 80),
|
||||||
|
const SizedBox(height: 20),
|
||||||
|
Text(
|
||||||
|
_status == PaymentStatus.paymentTimeout
|
||||||
|
? "انتهى الوقت المحدد للدفع"
|
||||||
|
: "حدث خطأ أثناء إنشاء الفاتورة",
|
||||||
|
style: const TextStyle(fontSize: 22, fontWeight: FontWeight.bold),
|
||||||
|
textAlign: TextAlign.center,
|
||||||
|
),
|
||||||
|
const SizedBox(height: 8),
|
||||||
|
const Text("يرجى المحاولة مرة أخرى.", style: TextStyle(fontSize: 16)),
|
||||||
|
const SizedBox(height: 20),
|
||||||
|
ElevatedButton(
|
||||||
|
onPressed: _createAndPollInvoice,
|
||||||
|
child: const Text("المحاولة مرة أخرى"),
|
||||||
|
),
|
||||||
|
],
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// ويدجت مساعد لعرض خطوات التعليمات بشكل أنيق
|
||||||
|
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: const EdgeInsets.symmetric(vertical: 4.0),
|
||||||
|
leading: CircleAvatar(
|
||||||
|
radius: 14,
|
||||||
|
backgroundColor: Theme.of(context).primaryColor,
|
||||||
|
child: Text("$number",
|
||||||
|
style: const TextStyle(
|
||||||
|
fontSize: 12,
|
||||||
|
color: Colors.white,
|
||||||
|
fontWeight: FontWeight.bold)),
|
||||||
|
),
|
||||||
|
title: Text(text),
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -16,7 +16,7 @@ import '../../../main.dart';
|
|||||||
|
|
||||||
/// خدمة لإدارة عمليات الدفع المتعلقة بنظام الدفع عبر الرسائل القصيرة
|
/// خدمة لإدارة عمليات الدفع المتعلقة بنظام الدفع عبر الرسائل القصيرة
|
||||||
class PaymentService {
|
class PaymentService {
|
||||||
final String _baseUrl = "${AppLink.seferPaymentServer}/sms_webhook";
|
final String _baseUrl = "${AppLink.paymentServer}/sms_webhook";
|
||||||
|
|
||||||
Future<String?> createInvoice({
|
Future<String?> createInvoice({
|
||||||
required String userPhone,
|
required String userPhone,
|
||||||
|
|||||||
@@ -227,7 +227,7 @@ class _MyAppState extends State<MyApp> with WidgetsBindingObserver {
|
|||||||
}
|
}
|
||||||
|
|
||||||
if (!Get.isRegistered<FirebaseMessagesController>()) {
|
if (!Get.isRegistered<FirebaseMessagesController>()) {
|
||||||
Get.put(FirebaseMessagesController());
|
Get.put(FirebaseMessagesController()).getToken();
|
||||||
}
|
}
|
||||||
|
|
||||||
await FirebaseMessaging.instance.requestPermission();
|
await FirebaseMessaging.instance.requestPermission();
|
||||||
|
|||||||
@@ -15,16 +15,6 @@ import '../../../print.dart';
|
|||||||
// Assuming you have an AppColor class defined in your project.
|
// Assuming you have an AppColor class defined in your project.
|
||||||
// import 'path/to/your/app_color.dart';
|
// import 'path/to/your/app_color.dart';
|
||||||
|
|
||||||
// --- Placeholder AppColor Class ---
|
|
||||||
// This is used to make the code runnable.
|
|
||||||
// You should use the one from your project.
|
|
||||||
// class AppColor {
|
|
||||||
// static const Color primaryColor = Color(0xFF1DA1F2);
|
|
||||||
// static const Color greenColor = Color(0xFF34A853); // Google Green
|
|
||||||
// static const Color secondaryColor = Colors.white;
|
|
||||||
// }
|
|
||||||
// --- End of Placeholder ---
|
|
||||||
|
|
||||||
/// A visually revamped authentication screen with a light, glassy effect,
|
/// A visually revamped authentication screen with a light, glassy effect,
|
||||||
/// themed for the driver application using a green primary color.
|
/// themed for the driver application using a green primary color.
|
||||||
class AuthScreen extends StatelessWidget {
|
class AuthScreen extends StatelessWidget {
|
||||||
|
|||||||
@@ -47,7 +47,7 @@ class PassengerLocationMapPage extends StatelessWidget {
|
|||||||
|
|
||||||
// 4. نافذة معلومات الراكب في الأسفل (تظهر قبل بدء الرحلة)
|
// 4. نافذة معلومات الراكب في الأسفل (تظهر قبل بدء الرحلة)
|
||||||
|
|
||||||
const PassengerInfoWindow(),
|
PassengerInfoWindow(),
|
||||||
// 3. زر إلغاء الرحلة في الأعلى يسارًا
|
// 3. زر إلغاء الرحلة في الأعلى يسارًا
|
||||||
|
|
||||||
CancelWidget(mapDriverController: mapDriverController),
|
CancelWidget(mapDriverController: mapDriverController),
|
||||||
@@ -56,7 +56,7 @@ class PassengerLocationMapPage extends StatelessWidget {
|
|||||||
driverEndRideBar(),
|
driverEndRideBar(),
|
||||||
|
|
||||||
// 6. أزرار الطوارئ والاتصال
|
// 6. أزرار الطوارئ والاتصال
|
||||||
const SosConnect(),
|
SosConnect(),
|
||||||
|
|
||||||
// 7. دائرة عرض السرعة
|
// 7. دائرة عرض السرعة
|
||||||
speedCircle(),
|
speedCircle(),
|
||||||
|
|||||||
@@ -1,5 +1,7 @@
|
|||||||
|
import 'dart:io';
|
||||||
import 'dart:ui';
|
import 'dart:ui';
|
||||||
|
|
||||||
|
import 'package:bubble_head/bubble.dart';
|
||||||
import 'package:google_maps_flutter/google_maps_flutter.dart';
|
import 'package:google_maps_flutter/google_maps_flutter.dart';
|
||||||
import 'package:flutter/material.dart';
|
import 'package:flutter/material.dart';
|
||||||
import 'package:flutter/services.dart';
|
import 'package:flutter/services.dart';
|
||||||
@@ -8,6 +10,7 @@ import 'package:flutter_font_icons/flutter_font_icons.dart';
|
|||||||
import 'package:sefer_driver/views/home/Captin/home_captain/drawer_captain.dart';
|
import 'package:sefer_driver/views/home/Captin/home_captain/drawer_captain.dart';
|
||||||
import 'package:sefer_driver/views/widgets/mycircular.dart';
|
import 'package:sefer_driver/views/widgets/mycircular.dart';
|
||||||
|
|
||||||
|
import '../../../../constant/box_name.dart';
|
||||||
import '../../../../constant/colors.dart';
|
import '../../../../constant/colors.dart';
|
||||||
import '../../../../constant/info.dart';
|
import '../../../../constant/info.dart';
|
||||||
import '../../../../constant/style.dart';
|
import '../../../../constant/style.dart';
|
||||||
@@ -15,7 +18,11 @@ import '../../../../controller/functions/location_controller.dart';
|
|||||||
import '../../../../controller/functions/overlay_permisssion.dart';
|
import '../../../../controller/functions/overlay_permisssion.dart';
|
||||||
import '../../../../controller/functions/package_info.dart';
|
import '../../../../controller/functions/package_info.dart';
|
||||||
import '../../../../controller/home/captin/home_captain_controller.dart';
|
import '../../../../controller/home/captin/home_captain_controller.dart';
|
||||||
|
import '../../../../controller/home/captin/map_driver_controller.dart';
|
||||||
|
import '../../../../main.dart';
|
||||||
|
import '../../../notification/available_rides_page.dart';
|
||||||
import '../../../widgets/circle_container.dart';
|
import '../../../widgets/circle_container.dart';
|
||||||
|
import '../driver_map_page.dart';
|
||||||
import 'widget/connect.dart';
|
import 'widget/connect.dart';
|
||||||
import 'widget/left_menu_map_captain.dart';
|
import 'widget/left_menu_map_captain.dart';
|
||||||
|
|
||||||
@@ -51,7 +58,7 @@ class HomeCaptain extends StatelessWidget {
|
|||||||
|
|
||||||
// 2. The new floating "Status Pod" at the bottom.
|
// 2. The new floating "Status Pod" at the bottom.
|
||||||
const _StatusPodOverlay(),
|
const _StatusPodOverlay(),
|
||||||
|
FloatingActionButtons(),
|
||||||
// This widget from the original code remains.
|
// This widget from the original code remains.
|
||||||
leftMainMenuCaptainIcons(),
|
leftMainMenuCaptainIcons(),
|
||||||
],
|
],
|
||||||
@@ -465,44 +472,112 @@ class _MapControlButton extends StatelessWidget {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/// NOTE: The _FloatingActionButtons and _MapControlButton widgets have been removed
|
class FloatingActionButtons extends StatelessWidget {
|
||||||
/// as their functionality is now integrated into the _HomeAppBar.
|
const FloatingActionButtons();
|
||||||
///
|
|
||||||
/// You will still need to modify your existing `ConnectWidget`
|
|
||||||
/// to accept an `isCompact` boolean flag as mentioned in the previous design.
|
|
||||||
/*
|
|
||||||
class ConnectWidget extends StatelessWidget {
|
|
||||||
final bool isCompact;
|
|
||||||
const ConnectWidget({super.key, this.isCompact = false});
|
|
||||||
|
|
||||||
@override
|
@override
|
||||||
Widget build(BuildContext context) {
|
Widget build(BuildContext context) {
|
||||||
// ... your existing controller logic
|
// نفس الكود الأصلي للأزرار
|
||||||
|
return Positioned(
|
||||||
if (isCompact) {
|
bottom: Get.height * .2,
|
||||||
// Return a smaller version for the pod
|
right: 6,
|
||||||
return Container(
|
child:
|
||||||
padding: const EdgeInsets.symmetric(horizontal: 12, vertical: 8),
|
GetBuilder<HomeCaptainController>(builder: (homeCaptainController) {
|
||||||
decoration: BoxDecoration(
|
return Column(
|
||||||
color: controller.isConnect ? AppColor.greenColor : AppColor.accentColor,
|
|
||||||
borderRadius: BorderRadius.circular(16),
|
|
||||||
),
|
|
||||||
child: Row(
|
|
||||||
mainAxisSize: MainAxisSize.min,
|
|
||||||
children: [
|
children: [
|
||||||
Icon(controller.isConnect ? Icons.wifi_tethering_rounded : Icons.wifi_tethering_off_rounded, color: Colors.white, size: 20),
|
Platform.isAndroid
|
||||||
const SizedBox(width: 8),
|
? AnimatedContainer(
|
||||||
Text(
|
duration: const Duration(microseconds: 200),
|
||||||
controller.isConnect ? 'Online'.tr : 'Offline'.tr,
|
width: homeCaptainController.widthMapTypeAndTraffic,
|
||||||
style: AppStyle.title.copyWith(color: Colors.white, fontSize: 14),
|
decoration: BoxDecoration(
|
||||||
|
border: Border.all(color: AppColor.blueColor),
|
||||||
|
color: AppColor.secondaryColor,
|
||||||
|
borderRadius: BorderRadius.circular(15)),
|
||||||
|
child: IconButton(
|
||||||
|
onPressed: () async {
|
||||||
|
Bubble().startBubbleHead(sendAppToBackground: true);
|
||||||
|
},
|
||||||
|
icon: Image.asset(
|
||||||
|
'assets/images/logo.png',
|
||||||
|
fit: BoxFit.cover,
|
||||||
|
width: 35,
|
||||||
|
height: 35,
|
||||||
|
),
|
||||||
|
),
|
||||||
|
)
|
||||||
|
: const SizedBox(),
|
||||||
|
const SizedBox(
|
||||||
|
height: 5,
|
||||||
),
|
),
|
||||||
|
AnimatedContainer(
|
||||||
|
duration: const Duration(microseconds: 200),
|
||||||
|
width: homeCaptainController.widthMapTypeAndTraffic,
|
||||||
|
decoration: BoxDecoration(
|
||||||
|
border: Border.all(color: AppColor.blueColor),
|
||||||
|
color: AppColor.secondaryColor,
|
||||||
|
borderRadius: BorderRadius.circular(15)),
|
||||||
|
child: IconButton(
|
||||||
|
onPressed: () {
|
||||||
|
Get.to(() => const AvailableRidesPage());
|
||||||
|
},
|
||||||
|
icon: const Icon(
|
||||||
|
Icons.train_sharp,
|
||||||
|
size: 29,
|
||||||
|
color: AppColor.blueColor,
|
||||||
|
),
|
||||||
|
),
|
||||||
|
),
|
||||||
|
const SizedBox(
|
||||||
|
height: 5,
|
||||||
|
),
|
||||||
|
box.read(BoxName.rideStatus) == 'Applied' ||
|
||||||
|
box.read(BoxName.rideStatus) == 'Begin'
|
||||||
|
? Positioned(
|
||||||
|
bottom: Get.height * .2,
|
||||||
|
right: 6,
|
||||||
|
child: AnimatedContainer(
|
||||||
|
duration: const Duration(microseconds: 200),
|
||||||
|
width: homeCaptainController.widthMapTypeAndTraffic,
|
||||||
|
decoration: BoxDecoration(
|
||||||
|
border: Border.all(color: AppColor.blueColor),
|
||||||
|
color: AppColor.secondaryColor,
|
||||||
|
borderRadius: BorderRadius.circular(15)),
|
||||||
|
child: GestureDetector(
|
||||||
|
onLongPress: () {
|
||||||
|
box.write(BoxName.rideStatus, 'delete');
|
||||||
|
homeCaptainController.update();
|
||||||
|
},
|
||||||
|
child: IconButton(
|
||||||
|
onPressed: () {
|
||||||
|
box.read(BoxName.rideStatus) == 'Applied'
|
||||||
|
? {
|
||||||
|
Get.to(() => PassengerLocationMapPage(),
|
||||||
|
arguments:
|
||||||
|
box.read(BoxName.rideArguments)),
|
||||||
|
Get.put(MapDriverController())
|
||||||
|
.changeRideToBeginToPassenger()
|
||||||
|
}
|
||||||
|
: {
|
||||||
|
Get.to(() => PassengerLocationMapPage(),
|
||||||
|
arguments:
|
||||||
|
box.read(BoxName.rideArguments)),
|
||||||
|
Get.put(MapDriverController())
|
||||||
|
.startRideFromStartApp()
|
||||||
|
};
|
||||||
|
},
|
||||||
|
icon: const Icon(
|
||||||
|
Icons.directions_rounded,
|
||||||
|
size: 29,
|
||||||
|
color: AppColor.blueColor,
|
||||||
|
),
|
||||||
|
),
|
||||||
|
),
|
||||||
|
),
|
||||||
|
)
|
||||||
|
: const SizedBox()
|
||||||
],
|
],
|
||||||
),
|
);
|
||||||
);
|
}),
|
||||||
}
|
);
|
||||||
|
|
||||||
// Return the original, larger button
|
|
||||||
return ElevatedButton.icon(...)
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
*/
|
|
||||||
|
|||||||
@@ -1,9 +1,8 @@
|
|||||||
|
import 'package:flutter_overlay_window/flutter_overlay_window.dart';
|
||||||
import 'package:sefer_driver/constant/box_name.dart';
|
import 'package:sefer_driver/constant/box_name.dart';
|
||||||
import 'package:sefer_driver/controller/firebase/local_notification.dart';
|
import 'package:sefer_driver/controller/firebase/local_notification.dart';
|
||||||
import 'package:sefer_driver/controller/functions/network/net_guard.dart';
|
|
||||||
import 'package:sefer_driver/controller/functions/sms_egypt_controller.dart';
|
|
||||||
import 'package:sefer_driver/main.dart';
|
import 'package:sefer_driver/main.dart';
|
||||||
import 'package:sefer_driver/views/auth/captin/login_captin.dart';
|
import 'package:sefer_driver/views/auth/captin/otp_page.dart';
|
||||||
import 'package:sefer_driver/views/home/Captin/driver_map_page.dart';
|
import 'package:sefer_driver/views/home/Captin/driver_map_page.dart';
|
||||||
import 'package:sefer_driver/views/home/Captin/orderCaptin/vip_order_page.dart';
|
import 'package:sefer_driver/views/home/Captin/orderCaptin/vip_order_page.dart';
|
||||||
import 'package:flutter/material.dart';
|
import 'package:flutter/material.dart';
|
||||||
@@ -16,13 +15,10 @@ import '../../../../../constant/colors.dart';
|
|||||||
import '../../../../../constant/links.dart';
|
import '../../../../../constant/links.dart';
|
||||||
import '../../../../../controller/firebase/firbase_messge.dart';
|
import '../../../../../controller/firebase/firbase_messge.dart';
|
||||||
import '../../../../../controller/functions/crud.dart';
|
import '../../../../../controller/functions/crud.dart';
|
||||||
import '../../../../../controller/functions/encrypt_decrypt.dart';
|
|
||||||
import '../../../../../controller/home/captin/order_request_controller.dart';
|
import '../../../../../controller/home/captin/order_request_controller.dart';
|
||||||
import '../../../../../controller/home/navigation/navigation_view.dart';
|
import '../../../../../controller/home/navigation/navigation_view.dart';
|
||||||
|
import '../../../../../print.dart';
|
||||||
import '../../../../Rate/ride_calculate_driver.dart';
|
import '../../../../Rate/ride_calculate_driver.dart';
|
||||||
import '../../../../auth/captin/otp_page.dart';
|
|
||||||
import '../../../../auth/syria/registration_view.dart';
|
|
||||||
import '../../../../widgets/error_snakbar.dart';
|
|
||||||
|
|
||||||
GetBuilder<HomeCaptainController> leftMainMenuCaptainIcons() {
|
GetBuilder<HomeCaptainController> leftMainMenuCaptainIcons() {
|
||||||
final firebaseMessagesController =
|
final firebaseMessagesController =
|
||||||
@@ -186,7 +182,8 @@ GetBuilder<HomeCaptainController> leftMainMenuCaptainIcons() {
|
|||||||
// child: Builder(builder: (context) {
|
// child: Builder(builder: (context) {
|
||||||
// return IconButton(
|
// return IconButton(
|
||||||
// onPressed: () async {
|
// onPressed: () async {
|
||||||
// Get.to(PhoneNumberScreen());
|
// Get.to(() => const PhoneNumberScreen());
|
||||||
|
// // box.write(BoxName.statusDriverLocation, 'off');
|
||||||
// },
|
// },
|
||||||
// icon: const Icon(
|
// icon: const Icon(
|
||||||
// FontAwesome5.grin_tears,
|
// FontAwesome5.grin_tears,
|
||||||
|
|||||||
@@ -13,8 +13,10 @@ import '../../../../main.dart';
|
|||||||
|
|
||||||
// Changed: إعادة تصميم كاملة لتصبح شريط معلومات علوي مدمج
|
// Changed: إعادة تصميم كاملة لتصبح شريط معلومات علوي مدمج
|
||||||
class PassengerInfoWindow extends StatelessWidget {
|
class PassengerInfoWindow extends StatelessWidget {
|
||||||
const PassengerInfoWindow({super.key});
|
PassengerInfoWindow({super.key});
|
||||||
|
final fcm = Get.isRegistered<FirebaseMessagesController>()
|
||||||
|
? Get.find<FirebaseMessagesController>()
|
||||||
|
: Get.put(FirebaseMessagesController());
|
||||||
@override
|
@override
|
||||||
Widget build(BuildContext context) {
|
Widget build(BuildContext context) {
|
||||||
return GetBuilder<MapDriverController>(
|
return GetBuilder<MapDriverController>(
|
||||||
@@ -152,8 +154,7 @@ class PassengerInfoWindow extends StatelessWidget {
|
|||||||
if (await controller
|
if (await controller
|
||||||
.calculateDistanceBetweenDriverAndPassengerLocation() <
|
.calculateDistanceBetweenDriverAndPassengerLocation() <
|
||||||
140) {
|
140) {
|
||||||
Get.find<FirebaseMessagesController>()
|
fcm.sendNotificationToDriverMAP(
|
||||||
.sendNotificationToDriverMAP(
|
|
||||||
'Hi ,I Arrive your site',
|
'Hi ,I Arrive your site',
|
||||||
'I Arrive at your site'.tr,
|
'I Arrive at your site'.tr,
|
||||||
controller.tokenPassenger,
|
controller.tokenPassenger,
|
||||||
@@ -238,7 +239,7 @@ class PassengerInfoWindow extends StatelessWidget {
|
|||||||
kolor: AppColor.deepPurpleAccent,
|
kolor: AppColor.deepPurpleAccent,
|
||||||
onPressed: () {
|
onPressed: () {
|
||||||
MyDialog().getDialog('Are you sure to cancel?'.tr, '', () async {
|
MyDialog().getDialog('Are you sure to cancel?'.tr, '', () async {
|
||||||
Get.find<FirebaseMessagesController>().sendNotificationToDriverMAP(
|
fcm.sendNotificationToDriverMAP(
|
||||||
'Driver Cancelled Your Trip',
|
'Driver Cancelled Your Trip',
|
||||||
'You will need to pay the cost to the driver, or it will be deducted from your next trip'
|
'You will need to pay the cost to the driver, or it will be deducted from your next trip'
|
||||||
.tr,
|
.tr,
|
||||||
|
|||||||
@@ -207,8 +207,10 @@ import '../../../../main.dart';
|
|||||||
|
|
||||||
// Changed: إعادة تصميم وتغيير موضع أزرار التواصل والطوارئ
|
// Changed: إعادة تصميم وتغيير موضع أزرار التواصل والطوارئ
|
||||||
class SosConnect extends StatelessWidget {
|
class SosConnect extends StatelessWidget {
|
||||||
const SosConnect({super.key});
|
SosConnect({super.key});
|
||||||
|
final fcm = Get.isRegistered<FirebaseMessagesController>()
|
||||||
|
? Get.find<FirebaseMessagesController>()
|
||||||
|
: Get.put(FirebaseMessagesController());
|
||||||
@override
|
@override
|
||||||
Widget build(BuildContext context) {
|
Widget build(BuildContext context) {
|
||||||
return GetBuilder<MapDriverController>(
|
return GetBuilder<MapDriverController>(
|
||||||
@@ -337,25 +339,23 @@ class SosConnect extends StatelessWidget {
|
|||||||
_buildMessageTile(
|
_buildMessageTile(
|
||||||
text: "Where are you, sir?".tr,
|
text: "Where are you, sir?".tr,
|
||||||
onTap: () {
|
onTap: () {
|
||||||
Get.find<FirebaseMessagesController>()
|
fcm.sendNotificationToDriverMAP(
|
||||||
.sendNotificationToDriverMAP(
|
'message From Driver',
|
||||||
'message From Driver',
|
"Where are you, sir?".tr,
|
||||||
"Where are you, sir?".tr,
|
controller.tokenPassenger,
|
||||||
controller.tokenPassenger,
|
[],
|
||||||
[],
|
'ding.wav');
|
||||||
'ding.wav');
|
|
||||||
Get.back();
|
Get.back();
|
||||||
}),
|
}),
|
||||||
_buildMessageTile(
|
_buildMessageTile(
|
||||||
text: "I've been trying to reach you but your phone is off.".tr,
|
text: "I've been trying to reach you but your phone is off.".tr,
|
||||||
onTap: () {
|
onTap: () {
|
||||||
Get.find<FirebaseMessagesController>()
|
fcm.sendNotificationToDriverMAP(
|
||||||
.sendNotificationToDriverMAP(
|
'message From Driver',
|
||||||
'message From Driver',
|
"I've been trying to reach you but your phone is off.".tr,
|
||||||
"I've been trying to reach you but your phone is off.".tr,
|
controller.tokenPassenger,
|
||||||
controller.tokenPassenger,
|
[],
|
||||||
[],
|
'ding.wav');
|
||||||
'ding.wav');
|
|
||||||
Get.back();
|
Get.back();
|
||||||
}),
|
}),
|
||||||
const SizedBox(height: 16),
|
const SizedBox(height: 16),
|
||||||
@@ -374,13 +374,12 @@ class SosConnect extends StatelessWidget {
|
|||||||
),
|
),
|
||||||
IconButton(
|
IconButton(
|
||||||
onPressed: () {
|
onPressed: () {
|
||||||
Get.find<FirebaseMessagesController>()
|
fcm.sendNotificationToDriverMAP(
|
||||||
.sendNotificationToDriverMAP(
|
'message From Driver',
|
||||||
'message From Driver',
|
controller.messageToPassenger.text,
|
||||||
controller.messageToPassenger.text,
|
controller.tokenPassenger,
|
||||||
controller.tokenPassenger,
|
[],
|
||||||
[],
|
'ding.wav');
|
||||||
'ding.wav');
|
|
||||||
controller.messageToPassenger.clear();
|
controller.messageToPassenger.clear();
|
||||||
Get.back();
|
Get.back();
|
||||||
},
|
},
|
||||||
|
|||||||
@@ -13,6 +13,7 @@ import 'package:webview_flutter/webview_flutter.dart';
|
|||||||
import '../../../constant/box_name.dart';
|
import '../../../constant/box_name.dart';
|
||||||
import '../../../constant/links.dart';
|
import '../../../constant/links.dart';
|
||||||
import '../../../controller/functions/crud.dart';
|
import '../../../controller/functions/crud.dart';
|
||||||
|
import '../../../controller/payment/mtn_new/mtn_payment_new_screen.dart';
|
||||||
import '../../../main.dart';
|
import '../../../main.dart';
|
||||||
import '../../../print.dart';
|
import '../../../print.dart';
|
||||||
import '../../widgets/elevated_btn.dart';
|
import '../../widgets/elevated_btn.dart';
|
||||||
@@ -65,48 +66,49 @@ class PointsCaptain extends StatelessWidget {
|
|||||||
color: AppColor.blueColor, size: 70),
|
color: AppColor.blueColor, size: 70),
|
||||||
],
|
],
|
||||||
)),
|
)),
|
||||||
GestureDetector(
|
// GestureDetector(
|
||||||
onTap: () async {
|
// onTap: () async {
|
||||||
Get.back();
|
// Get.back();
|
||||||
Get.defaultDialog(
|
// Get.defaultDialog(
|
||||||
barrierDismissible: false,
|
// barrierDismissible: false,
|
||||||
title: 'Insert Wallet phone number'.tr,
|
// title: 'Insert Wallet phone number'.tr,
|
||||||
content: Form(
|
// content: Form(
|
||||||
key: paymentController.formKey,
|
// key: paymentController.formKey,
|
||||||
child: MyTextForm(
|
// child: MyTextForm(
|
||||||
controller:
|
// controller:
|
||||||
paymentController.walletphoneController,
|
// paymentController.walletphoneController,
|
||||||
label: 'Insert Wallet phone number'.tr,
|
// label: 'Insert Wallet phone number'.tr,
|
||||||
hint: '963941234567',
|
// hint: '963941234567',
|
||||||
type: TextInputType.phone)),
|
// type: TextInputType.phone)),
|
||||||
confirm: MyElevatedButton(
|
// confirm: MyElevatedButton(
|
||||||
title: 'OK'.tr,
|
// title: 'OK'.tr,
|
||||||
onPressed: () async {
|
// onPressed: () async {
|
||||||
Get.back();
|
// Get.back();
|
||||||
if (paymentController.formKey.currentState!
|
// if (paymentController.formKey.currentState!
|
||||||
.validate()) {
|
// .validate()) {
|
||||||
box.write(
|
// box.write(
|
||||||
BoxName.phoneWallet,
|
// BoxName.phoneWallet,
|
||||||
paymentController
|
// paymentController
|
||||||
.walletphoneController.text);
|
// .walletphoneController.text);
|
||||||
await payWithMTNWallet(
|
// await payWithMTNWallet(
|
||||||
context, pricePoint.toString(), 'SYP');
|
// context, pricePoint.toString(), 'SYP');
|
||||||
}
|
// }
|
||||||
}));
|
// }));
|
||||||
},
|
// },
|
||||||
child: Row(
|
// child: Row(
|
||||||
mainAxisAlignment: MainAxisAlignment.spaceBetween,
|
// mainAxisAlignment: MainAxisAlignment.spaceBetween,
|
||||||
children: [
|
// children: [
|
||||||
Text('Pay by MTN Wallet'.tr),
|
// Text('Pay by MTN Wallet'.tr),
|
||||||
const SizedBox(width: 10),
|
// const SizedBox(width: 10),
|
||||||
Image.asset(
|
// Image.asset(
|
||||||
'assets/images/cashMTN.png',
|
// 'assets/images/cashMTN.png',
|
||||||
width: 70,
|
// width: 70,
|
||||||
height: 70,
|
// height: 70,
|
||||||
fit: BoxFit.fill,
|
// fit: BoxFit.fill,
|
||||||
),
|
// ),
|
||||||
],
|
// ],
|
||||||
)),
|
// )),
|
||||||
|
|
||||||
GestureDetector(
|
GestureDetector(
|
||||||
onTap: () async {
|
onTap: () async {
|
||||||
Get.back();
|
Get.back();
|
||||||
@@ -210,6 +212,69 @@ class PointsCaptain extends StatelessWidget {
|
|||||||
),
|
),
|
||||||
],
|
],
|
||||||
)),
|
)),
|
||||||
|
GestureDetector(
|
||||||
|
onTap: () async {
|
||||||
|
Get.back();
|
||||||
|
Get.defaultDialog(
|
||||||
|
barrierDismissible: false,
|
||||||
|
title: 'Insert Wallet phone number'.tr,
|
||||||
|
content: Form(
|
||||||
|
key: paymentController.formKey,
|
||||||
|
child: MyTextForm(
|
||||||
|
controller:
|
||||||
|
paymentController.walletphoneController,
|
||||||
|
label: 'Insert Wallet phone number'.tr,
|
||||||
|
hint: '963941234567',
|
||||||
|
type: TextInputType.phone)),
|
||||||
|
confirm: MyElevatedButton(
|
||||||
|
title: 'OK'.tr,
|
||||||
|
onPressed: () async {
|
||||||
|
Get.back();
|
||||||
|
if (paymentController.formKey.currentState!
|
||||||
|
.validate()) {
|
||||||
|
box.write(
|
||||||
|
BoxName.phoneWallet,
|
||||||
|
paymentController
|
||||||
|
.walletphoneController.text);
|
||||||
|
// await payWithSyriaTelWallet(
|
||||||
|
// context, pricePoint.toString(), 'SYP');
|
||||||
|
bool isAuthSupported =
|
||||||
|
await LocalAuthentication()
|
||||||
|
.isDeviceSupported();
|
||||||
|
if (isAuthSupported) {
|
||||||
|
bool didAuthenticate =
|
||||||
|
await LocalAuthentication()
|
||||||
|
.authenticate(
|
||||||
|
localizedReason:
|
||||||
|
'استخدم بصمة الإصبع أو الوجه لتأكيد الدفع',
|
||||||
|
);
|
||||||
|
if (!didAuthenticate) {
|
||||||
|
if (Get.isDialogOpen ?? false) Get.back();
|
||||||
|
print(
|
||||||
|
"❌ User did not authenticate with biometrics");
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
Get.to(() => PaymentScreenMtn(
|
||||||
|
amount: pricePoint,
|
||||||
|
userType: 'Driver',
|
||||||
|
));
|
||||||
|
}
|
||||||
|
}));
|
||||||
|
},
|
||||||
|
child: Row(
|
||||||
|
mainAxisAlignment: MainAxisAlignment.spaceBetween,
|
||||||
|
children: [
|
||||||
|
Text('Pay by MTN Wallet'.tr),
|
||||||
|
const SizedBox(width: 10),
|
||||||
|
Image.asset(
|
||||||
|
'assets/images/cashMTN.png',
|
||||||
|
width: 70,
|
||||||
|
height: 70,
|
||||||
|
fit: BoxFit.fill,
|
||||||
|
),
|
||||||
|
],
|
||||||
|
)),
|
||||||
],
|
],
|
||||||
));
|
));
|
||||||
},
|
},
|
||||||
|
|||||||
@@ -1,104 +1,49 @@
|
|||||||
import 'dart:convert';
|
import 'package:flutter/cupertino.dart';
|
||||||
|
|
||||||
import 'package:sefer_driver/constant/colors.dart';
|
|
||||||
import 'package:sefer_driver/constant/style.dart';
|
|
||||||
import 'package:sefer_driver/controller/notification/ride_available_controller.dart';
|
|
||||||
import 'package:sefer_driver/views/widgets/my_scafold.dart';
|
|
||||||
import 'package:sefer_driver/views/widgets/mycircular.dart';
|
|
||||||
import 'package:flutter/material.dart';
|
import 'package:flutter/material.dart';
|
||||||
import 'package:get/get.dart';
|
import 'package:get/get.dart';
|
||||||
|
import 'dart:math';
|
||||||
|
|
||||||
import '../../constant/box_name.dart';
|
import '../../constant/box_name.dart';
|
||||||
|
import '../../constant/colors.dart';
|
||||||
import '../../constant/links.dart';
|
import '../../constant/links.dart';
|
||||||
|
import '../../constant/style.dart';
|
||||||
import '../../controller/firebase/firbase_messge.dart';
|
import '../../controller/firebase/firbase_messge.dart';
|
||||||
import '../../controller/functions/crud.dart';
|
import '../../controller/functions/crud.dart';
|
||||||
import '../../controller/home/captin/home_captain_controller.dart';
|
import '../../controller/home/captin/home_captain_controller.dart';
|
||||||
|
import '../../controller/notification/ride_available_controller.dart';
|
||||||
import '../../main.dart';
|
import '../../main.dart';
|
||||||
import '../home/Captin/driver_map_page.dart';
|
import '../home/Captin/driver_map_page.dart';
|
||||||
|
import '../widgets/my_scafold.dart';
|
||||||
|
import '../widgets/mycircular.dart';
|
||||||
import '../widgets/mydialoug.dart';
|
import '../widgets/mydialoug.dart';
|
||||||
|
|
||||||
|
// --- Placeholder Classes and Variables (for demonstration) ---
|
||||||
|
// These are dummy implementations to make the code runnable.
|
||||||
|
// You should use your actual project files.
|
||||||
|
|
||||||
|
// --- End of Placeholder Classes ---
|
||||||
|
|
||||||
class AvailableRidesPage extends StatelessWidget {
|
class AvailableRidesPage extends StatelessWidget {
|
||||||
const AvailableRidesPage({super.key});
|
const AvailableRidesPage({super.key});
|
||||||
|
|
||||||
@override
|
@override
|
||||||
Widget build(BuildContext context) {
|
Widget build(BuildContext context) {
|
||||||
Get.put(RideAvailableController());
|
// Use findOrPut to avoid re-creating the controller on rebuilds
|
||||||
|
Get.lazyPut(() => RideAvailableController());
|
||||||
|
Get.lazyPut(() => HomeCaptainController());
|
||||||
|
|
||||||
return GetBuilder<RideAvailableController>(
|
return GetBuilder<RideAvailableController>(
|
||||||
builder: (rideAvailableController) {
|
builder: (rideAvailableController) {
|
||||||
// rideAvailableController.sortRidesByDistance();
|
// rideAvailableController.sortRidesByDistance(); // Original logic
|
||||||
return MyScafolld(
|
return MyScafolld(
|
||||||
title: 'Available for rides'.tr,
|
title: 'Available for rides'.tr,
|
||||||
body: [
|
body: [
|
||||||
rideAvailableController.isLoading
|
rideAvailableController.isLoading
|
||||||
? const MyCircularProgressIndicator()
|
? const MyCircularProgressIndicator()
|
||||||
:
|
: Builder(
|
||||||
// : ListView.builder(
|
builder: (context) {
|
||||||
// itemCount: rideAvailableController
|
// Filtering logic remains the same
|
||||||
// .rideAvailableMap['message']
|
final filteredRides = rideAvailableController
|
||||||
// .where((rideInfo) {
|
|
||||||
// var driverType =
|
|
||||||
// box.read(BoxName.carTypeOfDriver).toString();
|
|
||||||
// return (driverType == 'Comfort' &&
|
|
||||||
// ['Speed', 'Comfort']
|
|
||||||
// .contains(rideInfo['carType'])) ||
|
|
||||||
// (driverType == 'Speed' &&
|
|
||||||
// rideInfo['carType'] == 'Speed') ||
|
|
||||||
// (driverType == 'Scooter' &&
|
|
||||||
// rideInfo['carType'] == 'Scooter') ||
|
|
||||||
// (driverType == 'Awfar Car' &&
|
|
||||||
// rideInfo['carType'] == 'Awfar Car') ||
|
|
||||||
// (driverType == 'Lady' &&
|
|
||||||
// ['Comfort', 'Speed', 'Lady']
|
|
||||||
// .contains(rideInfo['carType']));
|
|
||||||
// }).length,
|
|
||||||
// itemBuilder: (context, index) {
|
|
||||||
// var filteredRides = rideAvailableController
|
|
||||||
// .rideAvailableMap['message']
|
|
||||||
// .where((rideInfo) {
|
|
||||||
// var driverType =
|
|
||||||
// box.read(BoxName.carTypeOfDriver).toString();
|
|
||||||
// return (driverType == 'Comfort' &&
|
|
||||||
// ['Speed', 'Comfort']
|
|
||||||
// .contains(rideInfo['carType'])) ||
|
|
||||||
// (driverType == 'Speed' &&
|
|
||||||
// rideInfo['carType'] == 'Speed') ||
|
|
||||||
// (driverType == 'Awfar Car' &&
|
|
||||||
// rideInfo['carType'] == 'Awfar Car') ||
|
|
||||||
// (driverType == 'Scooter' &&
|
|
||||||
// rideInfo['carType'] == 'Scooter') ||
|
|
||||||
// (driverType == 'Lady' &&
|
|
||||||
// ['Comfort', 'Speed', 'Lady']
|
|
||||||
// .contains(rideInfo['carType']));
|
|
||||||
// }).toList();
|
|
||||||
|
|
||||||
// return RideAvailableCard(
|
|
||||||
// rideInfo: filteredRides[index],
|
|
||||||
// );
|
|
||||||
// },
|
|
||||||
// )
|
|
||||||
ListView.builder(
|
|
||||||
itemCount: rideAvailableController
|
|
||||||
.rideAvailableMap['message']
|
|
||||||
.where((rideInfo) {
|
|
||||||
var driverType =
|
|
||||||
box.read(BoxName.carTypeOfDriver).toString();
|
|
||||||
switch (driverType) {
|
|
||||||
case 'Comfort':
|
|
||||||
return ['Speed', 'Comfort']
|
|
||||||
.contains(rideInfo['carType']);
|
|
||||||
case 'Speed':
|
|
||||||
case 'Scooter':
|
|
||||||
case 'Awfar Car':
|
|
||||||
return rideInfo['carType'] == driverType;
|
|
||||||
case 'Lady':
|
|
||||||
return ['Comfort', 'Speed', 'Lady']
|
|
||||||
.contains(rideInfo['carType']);
|
|
||||||
default:
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
}).length,
|
|
||||||
itemBuilder: (context, index) {
|
|
||||||
var filteredRides = rideAvailableController
|
|
||||||
.rideAvailableMap['message']
|
.rideAvailableMap['message']
|
||||||
.where((rideInfo) {
|
.where((rideInfo) {
|
||||||
var driverType =
|
var driverType =
|
||||||
@@ -119,21 +64,27 @@ class AvailableRidesPage extends StatelessWidget {
|
|||||||
}
|
}
|
||||||
}).toList();
|
}).toList();
|
||||||
|
|
||||||
return RideAvailableCard(
|
if (filteredRides.isEmpty) {
|
||||||
rideInfo: filteredRides[index],
|
return Center(
|
||||||
|
child: Text(
|
||||||
|
"No rides available for your vehicle type.".tr,
|
||||||
|
style: AppStyle.subtitle,
|
||||||
|
),
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
return ListView.builder(
|
||||||
|
padding: const EdgeInsets.symmetric(
|
||||||
|
horizontal: 12, vertical: 16),
|
||||||
|
itemCount: filteredRides.length,
|
||||||
|
itemBuilder: (context, index) {
|
||||||
|
return RideAvailableCard(
|
||||||
|
rideInfo: filteredRides[index],
|
||||||
|
);
|
||||||
|
},
|
||||||
);
|
);
|
||||||
},
|
},
|
||||||
)
|
)
|
||||||
// rideAvailableController.isLoading
|
|
||||||
// ? const MyCircularProgressIndicator()
|
|
||||||
// : ListView.builder(
|
|
||||||
// itemCount: rideAvailableController
|
|
||||||
// .rideAvailableMap['message'].length,
|
|
||||||
// itemBuilder: (context, index) => RideAvailableCard(
|
|
||||||
// rideInfo: rideAvailableController
|
|
||||||
// .rideAvailableMap['message'][index],
|
|
||||||
// ),
|
|
||||||
// )
|
|
||||||
],
|
],
|
||||||
isleading: true);
|
isleading: true);
|
||||||
});
|
});
|
||||||
@@ -147,90 +98,189 @@ class RideAvailableCard extends StatelessWidget {
|
|||||||
|
|
||||||
@override
|
@override
|
||||||
Widget build(BuildContext context) {
|
Widget build(BuildContext context) {
|
||||||
|
// The main card with improved styling
|
||||||
return Card(
|
return Card(
|
||||||
margin: const EdgeInsets.all(8.0),
|
margin: const EdgeInsets.only(bottom: 16.0),
|
||||||
shape: RoundedRectangleBorder(borderRadius: BorderRadius.circular(12)),
|
shape: RoundedRectangleBorder(borderRadius: BorderRadius.circular(16)),
|
||||||
elevation: 4,
|
elevation: 5,
|
||||||
child: Padding(
|
shadowColor: Colors.black.withOpacity(0.1),
|
||||||
padding: const EdgeInsets.all(16.0),
|
child: InkWell(
|
||||||
child: Column(
|
borderRadius: BorderRadius.circular(16),
|
||||||
crossAxisAlignment: CrossAxisAlignment.start,
|
onTap: () {
|
||||||
children: [
|
// You can add an action here, e.g., show ride details on a map
|
||||||
_buildLocationRow('↑', rideInfo['startName'], AppColor.greenColor),
|
},
|
||||||
const SizedBox(height: 8),
|
child: Padding(
|
||||||
_buildLocationRow('↓', rideInfo['endName'], Colors.red),
|
padding: const EdgeInsets.all(16.0),
|
||||||
const SizedBox(height: 16),
|
child: Column(
|
||||||
_buildInfoRow(),
|
crossAxisAlignment: CrossAxisAlignment.start,
|
||||||
const SizedBox(height: 16),
|
children: [
|
||||||
_buildActionRow(),
|
_buildHeader(),
|
||||||
],
|
const SizedBox(height: 16),
|
||||||
|
_buildRouteInfo(),
|
||||||
|
const Divider(height: 32),
|
||||||
|
_buildRideDetails(),
|
||||||
|
const SizedBox(height: 20),
|
||||||
|
_buildAcceptButton(),
|
||||||
|
],
|
||||||
|
),
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
Widget _buildLocationRow(String icon, String location, Color iconColor) {
|
// Header section with Price and Car Type
|
||||||
return Row(
|
Widget _buildHeader() {
|
||||||
children: [
|
|
||||||
Text(
|
|
||||||
icon,
|
|
||||||
style: TextStyle(
|
|
||||||
fontSize: 20, fontWeight: FontWeight.bold, color: iconColor),
|
|
||||||
),
|
|
||||||
const SizedBox(width: 8),
|
|
||||||
Expanded(
|
|
||||||
child: Text(
|
|
||||||
location,
|
|
||||||
style: AppStyle.subtitle,
|
|
||||||
maxLines: 2,
|
|
||||||
overflow: TextOverflow.ellipsis,
|
|
||||||
),
|
|
||||||
),
|
|
||||||
],
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
Widget _buildInfoRow() {
|
|
||||||
return Row(
|
|
||||||
mainAxisAlignment: MainAxisAlignment.spaceBetween,
|
|
||||||
children: [
|
|
||||||
Text('${'Price:'.tr} ${rideInfo['price']} \$', style: AppStyle.title),
|
|
||||||
Text(
|
|
||||||
rideInfo['carType'],
|
|
||||||
style: AppStyle.title.copyWith(color: AppColor.greenColor),
|
|
||||||
),
|
|
||||||
],
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
Widget _buildActionRow() {
|
|
||||||
return Row(
|
return Row(
|
||||||
mainAxisAlignment: MainAxisAlignment.spaceBetween,
|
mainAxisAlignment: MainAxisAlignment.spaceBetween,
|
||||||
|
crossAxisAlignment: CrossAxisAlignment.start,
|
||||||
children: [
|
children: [
|
||||||
Column(
|
Column(
|
||||||
crossAxisAlignment: CrossAxisAlignment.start,
|
crossAxisAlignment: CrossAxisAlignment.start,
|
||||||
children: [
|
children: [
|
||||||
Text('📈 ${rideInfo['passengerRate']}', style: AppStyle.title),
|
Text('Fare'.tr, style: AppStyle.subtitle.copyWith(fontSize: 12)),
|
||||||
const SizedBox(height: 4),
|
const SizedBox(height: 4),
|
||||||
Text(
|
Text('${rideInfo['price']} \$',
|
||||||
'📍 ${rideInfo['distance']} ${'KM'.tr}',
|
style: AppStyle.title
|
||||||
style: AppStyle.title.copyWith(color: AppColor.greenColor),
|
.copyWith(fontSize: 24, color: AppColor.primaryColor)),
|
||||||
),
|
|
||||||
],
|
],
|
||||||
),
|
),
|
||||||
ElevatedButton(
|
Container(
|
||||||
onPressed: () => _acceptRide(),
|
padding: const EdgeInsets.symmetric(horizontal: 12, vertical: 6),
|
||||||
style: ElevatedButton.styleFrom(
|
decoration: BoxDecoration(
|
||||||
backgroundColor: AppColor.greenColor,
|
color: AppColor.greenColor.withOpacity(0.1),
|
||||||
shape:
|
borderRadius: BorderRadius.circular(20),
|
||||||
RoundedRectangleBorder(borderRadius: BorderRadius.circular(8)),
|
),
|
||||||
|
child: Text(
|
||||||
|
rideInfo['carType'],
|
||||||
|
style: AppStyle.title
|
||||||
|
.copyWith(color: AppColor.greenColor, fontSize: 12),
|
||||||
),
|
),
|
||||||
child: Text('Accept'.tr),
|
|
||||||
),
|
),
|
||||||
],
|
],
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Visual representation of the pickup and dropoff route
|
||||||
|
Widget _buildRouteInfo() {
|
||||||
|
return Row(
|
||||||
|
crossAxisAlignment: CrossAxisAlignment.start,
|
||||||
|
children: [
|
||||||
|
// Dotted line and icons column
|
||||||
|
Column(
|
||||||
|
children: [
|
||||||
|
const Icon(CupertinoIcons.circle_fill,
|
||||||
|
color: AppColor.greenColor, size: 20),
|
||||||
|
...List.generate(
|
||||||
|
4,
|
||||||
|
(index) => Container(
|
||||||
|
height: 4,
|
||||||
|
width: 2,
|
||||||
|
color: AppColor.writeColor,
|
||||||
|
margin: const EdgeInsets.symmetric(vertical: 2),
|
||||||
|
)),
|
||||||
|
const Icon(CupertinoIcons.location_solid,
|
||||||
|
color: Colors.red, size: 20),
|
||||||
|
],
|
||||||
|
),
|
||||||
|
const SizedBox(width: 16),
|
||||||
|
// Location text column
|
||||||
|
Expanded(
|
||||||
|
child: Column(
|
||||||
|
crossAxisAlignment: CrossAxisAlignment.start,
|
||||||
|
children: [
|
||||||
|
_buildLocationText(rideInfo['startName'], 'Pickup'.tr),
|
||||||
|
const SizedBox(height: 20),
|
||||||
|
_buildLocationText(rideInfo['endName'], 'Dropoff'.tr),
|
||||||
|
],
|
||||||
|
),
|
||||||
|
)
|
||||||
|
],
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Helper for location text
|
||||||
|
Widget _buildLocationText(String location, String label) {
|
||||||
|
return Column(
|
||||||
|
crossAxisAlignment: CrossAxisAlignment.start,
|
||||||
|
mainAxisSize: MainAxisSize.min,
|
||||||
|
children: [
|
||||||
|
Text(label, style: AppStyle.subtitle.copyWith(fontSize: 12)),
|
||||||
|
const SizedBox(height: 2),
|
||||||
|
Text(
|
||||||
|
location,
|
||||||
|
style: AppStyle.title.copyWith(fontWeight: FontWeight.normal),
|
||||||
|
maxLines: 2,
|
||||||
|
overflow: TextOverflow.ellipsis,
|
||||||
|
),
|
||||||
|
],
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Ride details section with Distance and Passenger Rating
|
||||||
|
Widget _buildRideDetails() {
|
||||||
|
return Row(
|
||||||
|
mainAxisAlignment: MainAxisAlignment.spaceAround,
|
||||||
|
children: [
|
||||||
|
_buildInfoChip(
|
||||||
|
icon: CupertinoIcons.map_pin_ellipse,
|
||||||
|
value: '${rideInfo['distance']} ${'KM'.tr}',
|
||||||
|
label: 'Distance'.tr,
|
||||||
|
color: AppColor.primaryColor,
|
||||||
|
),
|
||||||
|
_buildInfoChip(
|
||||||
|
icon: CupertinoIcons.star_fill,
|
||||||
|
value: '${rideInfo['passengerRate']}',
|
||||||
|
label: 'Rating'.tr,
|
||||||
|
color: Colors.amber,
|
||||||
|
),
|
||||||
|
],
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
// A reusable chip for displaying info with an icon
|
||||||
|
Widget _buildInfoChip(
|
||||||
|
{required IconData icon,
|
||||||
|
required String value,
|
||||||
|
required String label,
|
||||||
|
required Color color}) {
|
||||||
|
return Column(
|
||||||
|
children: [
|
||||||
|
Row(
|
||||||
|
children: [
|
||||||
|
Icon(icon, color: color, size: 16),
|
||||||
|
const SizedBox(width: 8),
|
||||||
|
Text(value, style: AppStyle.title),
|
||||||
|
],
|
||||||
|
),
|
||||||
|
const SizedBox(height: 4),
|
||||||
|
Text(label, style: AppStyle.subtitle.copyWith(fontSize: 12)),
|
||||||
|
],
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
// The accept button with improved styling
|
||||||
|
Widget _buildAcceptButton() {
|
||||||
|
return SizedBox(
|
||||||
|
width: double.infinity,
|
||||||
|
child: ElevatedButton.icon(
|
||||||
|
icon: const Icon(Icons.check_circle_outline, color: Colors.white),
|
||||||
|
label: Text('Accept'.tr,
|
||||||
|
style: const TextStyle(
|
||||||
|
color: Colors.white, fontWeight: FontWeight.bold)),
|
||||||
|
onPressed: _acceptRide,
|
||||||
|
style: ElevatedButton.styleFrom(
|
||||||
|
backgroundColor: AppColor.greenColor,
|
||||||
|
padding: const EdgeInsets.symmetric(vertical: 14),
|
||||||
|
shape:
|
||||||
|
RoundedRectangleBorder(borderRadius: BorderRadius.circular(12)),
|
||||||
|
elevation: 2,
|
||||||
|
),
|
||||||
|
),
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
// --- Ride Acceptance Logic ---
|
||||||
|
// This logic is copied exactly from your original code.
|
||||||
void _acceptRide() async {
|
void _acceptRide() async {
|
||||||
var res = await CRUD().post(link: AppLink.updateStausFromSpeed, payload: {
|
var res = await CRUD().post(link: AppLink.updateStausFromSpeed, payload: {
|
||||||
'id': rideInfo['id'],
|
'id': rideInfo['id'],
|
||||||
@@ -249,8 +299,6 @@ class RideAvailableCard extends StatelessWidget {
|
|||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
// .then((value) {
|
|
||||||
// var json = jsonDecode(res);
|
|
||||||
if (res != "failure") {
|
if (res != "failure") {
|
||||||
List<String> bodyToPassenger = [
|
List<String> bodyToPassenger = [
|
||||||
box.read(BoxName.driverID).toString(),
|
box.read(BoxName.driverID).toString(),
|
||||||
@@ -276,7 +324,6 @@ class RideAvailableCard extends StatelessWidget {
|
|||||||
link: '${AppLink.endPoint}/driver_order/add.php',
|
link: '${AppLink.endPoint}/driver_order/add.php',
|
||||||
payload: {
|
payload: {
|
||||||
'driver_id': box.read(BoxName.driverID),
|
'driver_id': box.read(BoxName.driverID),
|
||||||
// box.read(BoxName.driverID).toString(),
|
|
||||||
'order_id': rideInfo['id'],
|
'order_id': rideInfo['id'],
|
||||||
'status': 'Apply'
|
'status': 'Apply'
|
||||||
});
|
});
|
||||||
@@ -294,9 +341,7 @@ class RideAvailableCard extends StatelessWidget {
|
|||||||
FirebaseMessagesController().sendNotificationToPassengerToken(
|
FirebaseMessagesController().sendNotificationToPassengerToken(
|
||||||
"Accepted Ride".tr,
|
"Accepted Ride".tr,
|
||||||
'your ride is Accepted'.tr,
|
'your ride is Accepted'.tr,
|
||||||
// arguments['DriverList'][9].toString(),
|
|
||||||
rideInfo['passengerToken'].toString(),
|
rideInfo['passengerToken'].toString(),
|
||||||
// box.read(BoxName.tokenDriver).toString(),
|
|
||||||
bodyToPassenger,
|
bodyToPassenger,
|
||||||
'start.wav');
|
'start.wav');
|
||||||
Get.back();
|
Get.back();
|
||||||
@@ -319,10 +364,7 @@ class RideAvailableCard extends StatelessWidget {
|
|||||||
'driverId': box.read(BoxName.driverID).toString(),
|
'driverId': box.read(BoxName.driverID).toString(),
|
||||||
'durationOfRideValue': rideInfo['duration'].toString(),
|
'durationOfRideValue': rideInfo['duration'].toString(),
|
||||||
'paymentAmount': rideInfo['price'].toString(),
|
'paymentAmount': rideInfo['price'].toString(),
|
||||||
'paymentMethod': 'cash'.toString() == //todo fix payment method
|
'paymentMethod': 'cash'.toString() == 'true' ? 'visa' : 'cash',
|
||||||
'true'
|
|
||||||
? 'visa'
|
|
||||||
: 'cash',
|
|
||||||
'isHaveSteps': 'startEnd'.toString(),
|
'isHaveSteps': 'startEnd'.toString(),
|
||||||
'step0': ''.toString(),
|
'step0': ''.toString(),
|
||||||
'step1': ''.toString(),
|
'step1': ''.toString(),
|
||||||
|
|||||||
Reference in New Issue
Block a user