25-10-5/1

This commit is contained in:
Hamza-Ayed
2025-10-05 14:57:32 +03:00
parent 95fb065bdb
commit 1cc66029a3
28 changed files with 1347 additions and 666 deletions

View File

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

View File

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

View File

@@ -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));

View File

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

View File

@@ -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,10 +387,11 @@ 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 jsonDecoeded = jsonDecode(res);
var d = jsonDecoeded['data'][0]; var d = jsonDecoeded['data'][0];
if (jsonDecoeded.isNotEmpty) { if (jsonDecoeded.isNotEmpty) {
@@ -435,28 +435,29 @@ class LoginDriverController extends GetxController {
} else if (int.parse(d['year'].toString()) < 2002) { } else if (int.parse(d['year'].toString()) < 2002) {
box.write(BoxName.carTypeOfDriver, 'Awfar Car'); box.write(BoxName.carTypeOfDriver, 'Awfar Car');
} }
updateAppTester(AppInformation.appName); // updateAppTester(AppInformation.appName);
var token = await CRUD().get( // var token = await CRUD().get(
link: AppLink.getDriverToken, // link: AppLink.getDriverToken,
payload: {'captain_id': (box.read(BoxName.driverID)).toString()}); // payload: {'captain_id': (box.read(BoxName.driverID)).toString()});
String fingerPrint = await DeviceHelper.getDeviceFingerprint(); // String fingerPrint = await DeviceHelper.getDeviceFingerprint();
await storage.write( // await storage.write(
key: BoxName.fingerPrint, value: fingerPrint.toString()); // key: BoxName.fingerPrint, value: fingerPrint.toString());
Get.off(() => HomeCaptain()); Get.off(() => HomeCaptain());
} else { // } else {
Get.offAll(() => PhoneNumberScreen()); // Get.offAll(() => PhoneNumberScreen());
isloading = false; // isloading = false;
update(); // update();
} // }
} else { // }
mySnackbarSuccess(''); // else {
// mySnackbarSuccess('');
isloading = false; // isloading = false;
update(); // update();
} }
} }
} }

View File

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

View File

@@ -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.');

View File

@@ -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 []);
// استبعد توكنك واحذف الفارغ
final targets = all.where((t) => t.isNotEmpty && t != myToken).toList();
// Save the list of tokens back to the box.
// box.write(BoxName.tokens, tokens);
tokens = box.read(BoxName.tokens);
for (var i = 0; i < tokens.length; i++) {
if (serviceAccountKeyJson.isEmpty) { if (serviceAccountKeyJson.isEmpty) {
print("🔴 Error: Service Account Key is empty"); print("🔴 Error: Service Account Key is empty");
return; return;
} }
// Initialize AccessTokenManager
final accessTokenManager = AccessTokenManager(serviceAccountKeyJson); final accessTokenManager = AccessTokenManager(serviceAccountKeyJson);
// Obtain an OAuth 2.0 access token
final accessToken = await accessTokenManager.getAccessToken(); final accessToken = await accessTokenManager.getAccessToken();
// Log.print('accessToken: ${accessToken}');
// 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({ body: jsonEncode({
'message': { 'message': {
'token': token, 'token': t,
'notification': { 'notification': {'title': title, 'body': body},
'title': title,
'body': body,
},
// 'data': {
// 'DriverList': jsonEncode(data),
// },
'android': { 'android': {
'priority': 'high', // Set priority to high 'priority': 'HIGH', // القيم الصحيحة: HIGH/NORMAL
'notification': { 'notification': {'sound': tone},
'sound': tone, // (اختياري) TTL لتجنّب رسائل قديمة
}, 'ttl': '30s',
}, },
'apns': { 'apns': {
'headers': { 'headers': {
'apns-priority': '10', // Set APNs priority to 10 'apns-priority': '10',
// لو iOS: حدد نوع الدفع
'apns-push-type': 'alert',
}, },
'payload': { 'payload': {
'aps': { 'aps': {'sound': tone}
'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,
}, },

View 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
);
}

View File

@@ -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);

View File

@@ -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');

View File

@@ -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,
[ [

View File

@@ -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();
if (Platform.isAndroid) {
bool isOverlayActive = await FlutterOverlayWindow.isActive(); bool isOverlayActive = await FlutterOverlayWindow.isActive();
if (isOverlayActive) { if (isOverlayActive) {
await FlutterOverlayWindow.closeOverlay(); 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) {

View 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;
}

View File

@@ -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) {
if (response['status'] == 'success' && response['message'] is List) {
list = List.from(response['message'] as 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);
}); });
// خذ أول 1015 للعرض (اختياري)، أو اعرض الكل 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)));
}
} }

View File

@@ -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?": "ما هي مميزات محفظتنا؟",

View File

@@ -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,14 +101,45 @@ 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);
// Check for valid response structure
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 {
// This block now handles network/server errors correctly
HapticFeedback.lightImpact();
update(); // Update UI to turn off loader
// Show a proper error dialog instead of "No Rides"
_showDialogAfterBuild(
_buildErrorDialog("Failed to fetch rides. Please try again.".tr));
}
} catch (e) {
isLoading = false; isLoading = false;
update(); update();
} else { // This catches other exceptions like JSON parsing errors
HapticFeedback.lightImpact(); _showDialogAfterBuild(
Get.dialog( _buildErrorDialog("An unexpected error occurred.".tr));
CupertinoAlertDialog( }
}
// Extracted dialogs into builder methods for cleanliness.
Widget _buildNoRidesDialog() {
return CupertinoAlertDialog(
title: Column( title: Column(
mainAxisSize: MainAxisSize.min, mainAxisSize: MainAxisSize.min,
children: [ children: [
@@ -120,10 +151,7 @@ class RideAvailableController extends GetxController {
const SizedBox(height: 12), const SizedBox(height: 12),
Text( Text(
"No Rides Available".tr, "No Rides Available".tr,
style: const TextStyle( style: const TextStyle(fontSize: 17, fontWeight: FontWeight.w600),
fontSize: 17,
fontWeight: FontWeight.w600,
),
), ),
], ],
), ),
@@ -131,55 +159,50 @@ class RideAvailableController extends GetxController {
padding: const EdgeInsets.only(top: 8), padding: const EdgeInsets.only(top: 8),
child: Text( child: Text(
"Please check back later for available rides.".tr, "Please check back later for available rides.".tr,
style: const TextStyle( style:
fontSize: 13, const TextStyle(fontSize: 13, color: CupertinoColors.systemGrey),
color: CupertinoColors.systemGrey,
),
), ),
), ),
actions: [ actions: [
CupertinoDialogAction( CupertinoDialogAction(
onPressed: () { onPressed: () {
Get.back(); Get.back(); // Close dialog
Get.back(); Get.back(); // Go back from AvailableRidesPage
}, },
child: Text('OK'.tr), child: Text('OK'.tr),
), ),
], ],
),
barrierDismissible: true,
transitionCurve: Curves.easeOutBack,
transitionDuration: const Duration(milliseconds: 200),
); );
} }
} catch (e) {
isLoading = false; Widget _buildErrorDialog(String error) {
update(); // You can log the error here for debugging.
Get.dialog( // print("Error fetching rides: $error");
CupertinoAlertDialog( return CupertinoAlertDialog(
title: const Icon( title: const Icon(
CupertinoIcons.exclamationmark_triangle_fill, CupertinoIcons.exclamationmark_triangle_fill,
color: CupertinoColors.systemRed, color: CupertinoColors.systemRed,
size: 44, size: 44,
), ),
content: Text( content: Text(
"Error fetching rides. Please try again.".tr, error, // Display the specific error message passed to the function
style: const TextStyle(fontSize: 14), style: const TextStyle(fontSize: 14),
), ),
actions: [ actions: [
CupertinoDialogAction( CupertinoDialogAction(
onPressed: () => Get.back(), onPressed: () {
Get.back(); // Close dialog
Get.back(); // Go back from AvailableRidesPage
},
child: Text('OK'.tr), child: Text('OK'.tr),
), ),
], ],
),
); );
} }
}
@override @override
void onInit() { void onInit() {
getRideAvailable();
super.onInit(); super.onInit();
getRideAvailable();
} }
} }

View 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),
);
}
}

View File

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

View File

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

View File

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

View File

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

View File

@@ -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(...)
}
} }
*/

View File

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

View File

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

View File

@@ -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,8 +339,7 @@ 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,
@@ -349,8 +350,7 @@ class SosConnect extends StatelessWidget {
_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,
@@ -374,8 +374,7 @@ 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,

View File

@@ -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,
),
],
)),
], ],
)); ));
}, },

View File

@@ -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();
if (filteredRides.isEmpty) {
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( return RideAvailableCard(
rideInfo: filteredRides[index], 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,
shadowColor: Colors.black.withOpacity(0.1),
child: InkWell(
borderRadius: BorderRadius.circular(16),
onTap: () {
// You can add an action here, e.g., show ride details on a map
},
child: Padding( child: Padding(
padding: const EdgeInsets.all(16.0), padding: const EdgeInsets.all(16.0),
child: Column( child: Column(
crossAxisAlignment: CrossAxisAlignment.start, crossAxisAlignment: CrossAxisAlignment.start,
children: [ children: [
_buildLocationRow('', rideInfo['startName'], AppColor.greenColor), _buildHeader(),
const SizedBox(height: 8),
_buildLocationRow('', rideInfo['endName'], Colors.red),
const SizedBox(height: 16), const SizedBox(height: 16),
_buildInfoRow(), _buildRouteInfo(),
const SizedBox(height: 16), const Divider(height: 32),
_buildActionRow(), _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(),