diff --git a/.vscode/settings.json b/.vscode/settings.json
new file mode 100644
index 0000000..af9a6ad
--- /dev/null
+++ b/.vscode/settings.json
@@ -0,0 +1,3 @@
+{
+ "java.configuration.updateBuildConfiguration": "disabled"
+}
diff --git a/android/app/build.gradle b/android/app/build.gradle
index f98c0dc..1671741 100644
--- a/android/app/build.gradle
+++ b/android/app/build.gradle
@@ -27,7 +27,7 @@ android {
externalNativeBuild {
cmake {
path "src/main/cpp/CMakeLists.txt"
- version "3.31.5"
+ version "3.22.1"
}
}
@@ -47,8 +47,8 @@ android {
// For more information, see: https://flutter.dev/to/review-gradle-config.
minSdk = 29
targetSdk = 36
- versionCode = 13
- versionName = '1.0.13'
+ versionCode = 14
+ versionName = '1.0.14'
multiDexEnabled = true
ndk {
abiFilters "armeabi-v7a", "arm64-v8a", "x86", "x86_64"
diff --git a/android/app/src/main/AndroidManifest.xml b/android/app/src/main/AndroidManifest.xml
index d7cc69f..6f3d903 100644
--- a/android/app/src/main/AndroidManifest.xml
+++ b/android/app/src/main/AndroidManifest.xml
@@ -45,14 +45,18 @@
-
+
-
-
+
+
+
diff --git a/android/build/reports/problems/problems-report.html b/android/build/reports/problems/problems-report.html
index e1e2989..b6e01b6 100644
--- a/android/build/reports/problems/problems-report.html
+++ b/android/build/reports/problems/problems-report.html
@@ -650,7 +650,7 @@ code + .copy-button {
diff --git a/android/gradle.properties b/android/gradle.properties
index e6860ca..67660bc 100644
--- a/android/gradle.properties
+++ b/android/gradle.properties
@@ -5,4 +5,5 @@ android.defaults.buildfeatures.buildconfig=true
android.nonTransitiveRClass=true
android.nonFinalResIds=true
dart.obfuscation=true
-android.enableR8.fullMode=true
\ No newline at end of file
+android.enableR8.fullMode=true
+org.gradle.java.home=/Library/Java/JavaVirtualMachines/temurin-17.jdk/Contents/Home
diff --git a/assets/images/cashMTN.png b/assets/images/cashMTN.png
new file mode 100644
index 0000000..bae958b
Binary files /dev/null and b/assets/images/cashMTN.png differ
diff --git a/assets/images/syriatel.png b/assets/images/syriatel.png
new file mode 100644
index 0000000..2facd27
Binary files /dev/null and b/assets/images/syriatel.png differ
diff --git a/lib/constant/links.dart b/lib/constant/links.dart
index a728c22..82b618f 100644
--- a/lib/constant/links.dart
+++ b/lib/constant/links.dart
@@ -133,6 +133,10 @@ class AppLink {
"$seferPaymentServer/ride/mtn/passenger/mtn_confirm.php";
static String payWithMTNStart =
"$seferPaymentServer/ride/mtn/passenger/mtn_start.php";
+ static String payWithSyriatelConfirm =
+ "$seferPaymentServer/ride/syriatel/passenger/confirm_payment.php";
+ static String payWithSyriatelStart =
+ "$seferPaymentServer/ride/syriatel/passenger/start_payment.php";
static String getDriverpaymentToday =
"$seferPaymentServer/ride/payment/get.php";
static String getCountRide =
diff --git a/lib/controller/auth/login_controller.dart b/lib/controller/auth/login_controller.dart
index 84b3a8f..65a638d 100644
--- a/lib/controller/auth/login_controller.dart
+++ b/lib/controller/auth/login_controller.dart
@@ -182,13 +182,13 @@ class LoginController extends GetxController {
loginUsingCredentials(String passengerID, email) async {
isloading = true;
update();
- bool isTokenExpired = JwtDecoder.isExpired(
- r(box.read(BoxName.jwt)).toString().split(AppInformation.addd)[0]);
+ // bool isTokenExpired = JwtDecoder.isExpired(
+ // r(box.read(BoxName.jwt)).toString().split(AppInformation.addd)[0]);
- if (isTokenExpired) {
- // Log.print('isTokenExpired loginUsingCredentials: ${isTokenExpired}');
- await getJWT();
- }
+ // if (isTokenExpired) {
+ // // Log.print('isTokenExpired loginUsingCredentials: ${isTokenExpired}');
+ // await getJWT();
+ // }
var res =
await CRUD().get(link: AppLink.loginFromGooglePassenger, payload: {
diff --git a/lib/controller/auth/token_otp_change_controller.dart b/lib/controller/auth/token_otp_change_controller.dart
index 8a037c6..68c2ba6 100644
--- a/lib/controller/auth/token_otp_change_controller.dart
+++ b/lib/controller/auth/token_otp_change_controller.dart
@@ -94,6 +94,14 @@ class OtpVerificationController extends GetxController {
[],
'cancel.wav',
);
+ CRUD().post(
+ link:
+ '${AppLink.seferPaymentServer}/auth/token/update_passenger_token.php',
+ payload: {
+ 'token': box.read(BoxName.tokenDriver).toString(),
+ 'fingerPrint': finger.toString(),
+ 'passengerID': box.read(BoxName.passengerID).toString(),
+ });
Get.offAll(() => const MapPagePassenger());
} else {
Get.snackbar('Verification Failed', 'OTP is incorrect or expired');
diff --git a/lib/controller/functions/crud.dart b/lib/controller/functions/crud.dart
index 2db9301..3585d7c 100644
--- a/lib/controller/functions/crud.dart
+++ b/lib/controller/functions/crud.dart
@@ -99,8 +99,8 @@ class CRUD {
return jsonData;
} else {
// Log API logical errors (e.g., "Customer not found")
- if (response.body == 'failure') {
- return 'failure';
+ if (jsonData['status'] == 'failure') {
+ // return 'failure';
} else {
addError(
'API Logic Error: ${jsonData['status']}',
@@ -122,6 +122,8 @@ class CRUD {
} else if (response.statusCode == 401) {
var jsonData = jsonDecode(response.body);
if (jsonData['error'] == 'Token expired') {
+ await Get.put(LoginController()).getJWT();
+ // mySnackbarSuccess('please order now'.tr);
return 'token_expired';
} else {
addError(
@@ -161,10 +163,10 @@ class CRUD {
Map? payload,
}) async {
String token = r(box.read(BoxName.jwt)).toString().split(Env.addd)[0];
- if (JwtDecoder.isExpired(token)) {
- await Get.put(LoginController()).getJWT();
- token = r(box.read(BoxName.jwt)).toString().split(Env.addd)[0];
- }
+ // if (JwtDecoder.isExpired(token)) {
+ // await Get.put(LoginController()).getJWT();
+ // token = r(box.read(BoxName.jwt)).toString().split(Env.addd)[0];
+ // }
final headers = {
"Content-Type": "application/x-www-form-urlencoded",
@@ -185,10 +187,10 @@ class CRUD {
Map? payload,
}) async {
String token = r(box.read(BoxName.jwt)).toString().split(Env.addd)[0];
- if (JwtDecoder.isExpired(token)) {
- await Get.put(LoginController()).getJWT();
- token = r(box.read(BoxName.jwt)).toString().split(Env.addd)[0];
- }
+ // if (JwtDecoder.isExpired(token)) {
+ // await Get.put(LoginController()).getJWT();
+ // token = r(box.read(BoxName.jwt)).toString().split(Env.addd)[0];
+ // }
final headers = {
"Content-Type": "application/x-www-form-urlencoded",
diff --git a/lib/controller/home/deep_link_controller.dart b/lib/controller/home/deep_link_controller.dart
new file mode 100644
index 0000000..56c9e2c
--- /dev/null
+++ b/lib/controller/home/deep_link_controller.dart
@@ -0,0 +1,63 @@
+import 'dart:async';
+import 'package:app_links/app_links.dart';
+import 'package:get/get.dart';
+import 'package:google_maps_flutter/google_maps_flutter.dart';
+
+class DeepLinkController extends GetxController {
+ // استخدم AppLinks للتعامل مع الروابط
+ final _appLinks = AppLinks();
+ StreamSubscription? _linkSubscription;
+
+ // متغير لتخزين الإحداثيات القادمة من الرابط
+ final Rx deepLinkLatLng = Rx(null);
+
+ @override
+ void onInit() {
+ super.onInit();
+ // ابدأ بالاستماع للروابط عند تشغيل التطبيق
+ initDeepLinks();
+ }
+
+ Future initDeepLinks() async {
+ // الاستماع إلى الروابط القادمة
+ _linkSubscription = _appLinks.uriLinkStream.listen((uri) {
+ print('Received deep link: $uri');
+ _handleLink(uri);
+ });
+
+ // جلب الرابط الأولي الذي قد يكون فتح التطبيق
+ final initialUri = await _appLinks.getInitialLink();
+ if (initialUri != null) {
+ print('Received initial deep link: $initialUri');
+ _handleLink(initialUri);
+ }
+ }
+
+ void _handleLink(Uri uri) {
+ // تحقق من أن الرابط يتبع النمط المتوقع (مثال: intaleq://route?lat=xx&lng=yy)
+ if (uri.scheme == 'intaleq' && uri.host == 'route') {
+ // استخراج خطوط الطول والعرض من الرابط
+ final latString = uri.queryParameters['lat'];
+ final lngString = uri.queryParameters['lng'];
+
+ if (latString != null && lngString != null) {
+ final double? lat = double.tryParse(latString);
+ final double? lng = double.tryParse(lngString);
+
+ if (lat != null && lng != null) {
+ // إذا كانت الإحداثيات صالحة، قم بتحديث المتغير
+ // ستستمع وحدة التحكم في الخريطة لهذا التغيير
+ deepLinkLatLng.value = LatLng(lat, lng);
+ print('Parsed LatLng from deep link: ${deepLinkLatLng.value}');
+ }
+ }
+ }
+ }
+
+ @override
+ void onClose() {
+ // تأكد من إلغاء الاشتراك عند إغلاق وحدة التحكم
+ _linkSubscription?.cancel();
+ super.onClose();
+ }
+}
diff --git a/lib/controller/home/map_passenger_controller.dart b/lib/controller/home/map_passenger_controller.dart
index e359ac2..92594bf 100644
--- a/lib/controller/home/map_passenger_controller.dart
+++ b/lib/controller/home/map_passenger_controller.dart
@@ -6,7 +6,6 @@ import 'dart:math' as math;
import 'dart:ui';
import 'dart:convert';
import 'package:device_info_plus/device_info_plus.dart';
-import 'package:flutter/services.dart';
import 'package:http/http.dart' as http;
import 'package:Intaleq/constant/univeries_polygon.dart';
@@ -14,7 +13,6 @@ import 'package:Intaleq/controller/firebase/local_notification.dart';
import 'package:Intaleq/controller/functions/encrypt_decrypt.dart';
import 'package:flutter/cupertino.dart';
import 'package:flutter_confetti/flutter_confetti.dart';
-import 'package:uni_links/uni_links.dart';
import 'package:vector_math/vector_math.dart' show radians, degrees;
import 'package:Intaleq/controller/functions/tts.dart';
@@ -54,14 +52,11 @@ import '../functions/launch.dart';
import '../functions/package_info.dart';
import '../functions/secure_storage.dart';
import '../payment/payment_controller.dart';
+import 'deep_link_controller.dart';
import 'device_tier.dart';
import 'vip_waitting_page.dart';
class MapPassengerController extends GetxController {
- // --- START: DEEP LINKING ADDITIONS ---
- StreamSubscription? _linkSubscription;
- // --- END: DEEP LINKING ADDITIONS ---
-
bool isLoading = true;
TextEditingController placeDestinationController = TextEditingController();
TextEditingController increasFeeFromPassenger = TextEditingController();
@@ -276,6 +271,11 @@ class MapPassengerController extends GetxController {
late DateTime newTime = DateTime.now();
int hours = 0;
int minutes = 0;
+
+ // --- إضافة جديدة: للوصول إلى وحدة التحكم بالروابط ---
+ final DeepLinkController _deepLinkController = Get.find();
+ // ------------------------------------------------
+
void onChangedPassengerCount(int newValue) {
selectedPassengerCount = newValue;
update();
@@ -286,92 +286,6 @@ class MapPassengerController extends GetxController {
update();
}
- /// Initializes the deep link listener.
- /// It checks for the initial link when the app starts and then listens for subsequent links.
- Future _initUniLinks() async {
- try {
- // Get the initial link that opened the app
- final initialLink = await getInitialUri();
- if (initialLink != null) {
- handleDeepLink(initialLink);
- }
- } on PlatformException {
- print('Failed to get initial deep link.');
- } on FormatException {
- print('Invalid initial deep link format.');
- }
-
- // Listen for incoming links while the app is running
- _linkSubscription = uriLinkStream.listen((Uri? link) {
- handleDeepLink(link);
- }, onError: (err) {
- print('Error listening to deep links: $err');
- });
- }
-
- /// Parses the incoming deep link and triggers the route initiation.
- void handleDeepLink(Uri? link) {
- if (link == null) return;
-
- // Check if the link matches your app's scheme and path
- // e.g., intaleq://map?lat=31.9539&lng=35.9106
- if (link.scheme == 'intaleq' && link.host == 'map') {
- final latString = link.queryParameters['lat'];
- final lngString = link.queryParameters['lng'];
-
- if (latString != null && lngString != null) {
- final double? lat = double.tryParse(latString);
- final double? lng = double.tryParse(lngString);
-
- if (lat != null && lng != null) {
- final destination = LatLng(lat, lng);
- print('Deep link received. Destination: $destination');
- initiateRouteFromDeepLink(destination);
- } else {
- print('Failed to parse lat/lng from deep link.');
- }
- }
- }
- }
-
- /// Sets the destination from the deep link and updates the UI to show the map.
- void initiateRouteFromDeepLink(LatLng destination) async {
- // Wait for map controller to be ready
- if (mapController == null) {
- await Future.delayed(const Duration(seconds: 1));
- if (mapController == null) {
- print("Map controller is not available to handle deep link.");
- return;
- }
- }
-
- myDestination = destination;
-
- // Animate camera to user's current location to show the starting point
- await mapController?.animateCamera(CameraUpdate.newLatLng(
- LatLng(passengerLocation.latitude, passengerLocation.longitude)));
-
- // Ensure the main menu is visible to start the booking process
- if (isMainBottomMenuMap) {
- changeMainBottomMenuMap();
- }
-
- passengerStartLocationFromMap = true;
- isPickerShown = true;
- hintTextDestinationPoint = "Destination from external link".tr;
- update();
-
- // The user can now see the destination and proceed to get the route and price.
- Get.snackbar(
- "Location Received".tr,
- "The destination has been set from the link.".tr,
- backgroundColor: AppColor.greenColor,
- colorText: Colors.white,
- );
- }
-
- // --- END: DEEP LINKING METHODS ---
-
void getCurrentLocationFormString() async {
currentLocationToFormPlaces = true;
currentLocationString = 'Waiting for your location'.tr;
@@ -3282,8 +3196,6 @@ class MapPassengerController extends GetxController {
print(
"--- MapPassengerController: Closing and cleaning up all resources. ---");
- _linkSubscription?.cancel();
-
// 1. إلغاء المؤقتات الفردية
// Using ?.cancel() is safe even if the timer is null
markerReloadingTimer.cancel();
@@ -3616,6 +3528,8 @@ class MapPassengerController extends GetxController {
update();
}
+ /// تحويل نصف قطر بالكيلومتر إلى دلتا درجات عرض
+
double _haversineKm(double lat1, double lon1, double lat2, double lon2) {
const R = 6371.0; // km
final dLat = (lat2 - lat1) * math.pi / 180.0;
@@ -3629,6 +3543,29 @@ class MapPassengerController extends GetxController {
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 getPlaces() async {
final q = placeDestinationController.text.trim();
if (q.isEmpty) {
@@ -3640,11 +3577,17 @@ class MapPassengerController extends GetxController {
final lat = passengerLocation.latitude;
final lng = passengerLocation.longitude;
- const range = 2.2;
- final latMin = lat - range;
- final latMax = lat + range;
- final lngMin = lng - range;
- final lngMax = lng + range;
+ // نصف قطر البحث بالكيلومتر (عدّل حسب رغبتك)
+ const radiusKm = 200.0;
+
+ // حساب الباوند الصحيح (درجات، وليس 2.2 درجة ثابتة)
+ final latDelta = _kmToLatDelta(radiusKm);
+ final lngDelta = _kmToLngDelta(radiusKm, lat);
+
+ final latMin = lat - latDelta;
+ final latMax = lat + latDelta;
+ final lngMin = lng - lngDelta;
+ final lngMax = lng + lngDelta;
try {
final response = await CRUD().post(
@@ -3658,43 +3601,59 @@ class MapPassengerController extends GetxController {
},
);
- if (response != 'failure') {
- final list = (response['message'] as List?) ?? [];
-
- // احسب المسافة وألصقها بكل عنصر
- for (final p in list) {
- final plat = double.tryParse(p['latitude']?.toString() ?? '') ?? 0.0;
- final plng = double.tryParse(p['longitude']?.toString() ?? '') ?? 0.0;
- p['distanceKm'] = _haversineKm(lat, lng, plat, plng);
- }
-
- // رتّب حسب الأقرب
- list.sort((a, b) {
- final da = (a['distanceKm'] ?? 1e9) as double;
- final db = (b['distanceKm'] ?? 1e9) as double;
- final cmp = da.compareTo(db);
- if (cmp != 0) return cmp;
-
- // تعادل؟ فضّل من يطابق الاسم
- final nameA =
- (a['name'] ?? a['name_ar'] ?? a['name_en'] ?? '').toString();
- final nameB =
- (b['name'] ?? b['name_ar'] ?? b['name_en'] ?? '').toString();
- final qLower = q.toLowerCase();
- final hitA = nameA.toLowerCase().contains(qLower) ? 0 : 1;
- final hitB = nameB.toLowerCase().contains(qLower) ? 0 : 1;
- return hitA.compareTo(hitB);
- });
-
- placesDestination = list;
- update();
+ // يدعم شكلي استجابة: إما {"...","message":[...]} أو قائمة مباشرة [...]
+ List list;
+ if (response is Map && response['message'] is List) {
+ list = List.from(response['message'] as List);
+ } else if (response is List) {
+ list = List.from(response);
} else {
- print('Server error');
+ print('Unexpected response shape');
+ return;
}
+
+ // جهّز الحقول المحتملة للأسماء
+ String _bestName(Map p) {
+ return (p['name'] ?? p['name_ar'] ?? p['name_en'] ?? '').toString();
+ }
+
+ // احسب المسافة ودرجة التطابق والنقاط
+ for (final p in list) {
+ final plat = double.tryParse(p['latitude']?.toString() ?? '') ?? 0.0;
+ final plng = double.tryParse(p['longitude']?.toString() ?? '') ?? 0.0;
+
+ final d = _haversineKm(lat, lng, plat, plng);
+ final rel = _relevanceScore(_bestName(p), q);
+
+ // معادلة ترتيب ذكية: مسافة أقل + تطابق أعلى = نقاط أعلى
+ // تضيف +1 لضمان عدم وصول الوزن للصفر عند عدم وجود تطابق
+ final score = (1.0 / (1.0 + d)) * (1.0 + rel);
+
+ p['distanceKm'] = d;
+ p['relevance'] = rel;
+ p['score'] = score;
+ }
+
+ // رتّب حسب score تنازليًا، ثم المسافة تصاعديًا كحسم
+ list.sort((a, b) {
+ final sa = (a['score'] ?? 0.0) as double;
+ final sb = (b['score'] ?? 0.0) as double;
+ final cmp = sb.compareTo(sa);
+ if (cmp != 0) return cmp;
+ final da = (a['distanceKm'] ?? 1e9) as double;
+ final db = (b['distanceKm'] ?? 1e9) as double;
+ return da.compareTo(db);
+ });
+
+ // خذ أول 10–15 للعرض (اختياري)، أو اعرض الكل
+ placesDestination = list.take(15).toList();
+ Log.print('placesDestination: $placesDestination');
+ update();
} catch (e) {
- print('Exception: $e');
+ print('Exception in getPlaces: $e');
}
- } // Future getPlaces() async {
+ }
+
// var languageCode;
// // تحديد اللغة حسب الإدخال
@@ -5804,16 +5763,45 @@ class MapPassengerController extends GetxController {
}
}
+ // --- دالة جديدة للاستماع ومعالجة الرابط ---
+ void _listenForDeepLink() {
+ // استمع إلى أي تغيير في الإحداثيات القادمة من الرابط
+ ever(_deepLinkController.deepLinkLatLng, (LatLng? latLng) {
+ if (latLng != null) {
+ print('MapPassengerController detected deep link LatLng: $latLng');
+ // عندما يتم استلام إحداثيات جديدة، عينها كوجهة
+ myDestination = latLng;
+
+ // قم بتشغيل المنطق الخاص بك لعرض المسار
+ // (تأكد من أن `passengerLocation` لديها قيمة أولاً)
+ if (passengerLocation != null) {
+ getDirectionMap(
+ '${passengerLocation.latitude},${passengerLocation.longitude}',
+ '${myDestination.latitude},${myDestination.longitude}');
+ } else {
+ // يمكنك إظهار رسالة للمستخدم لتمكين الموقع أولاً
+ print(
+ 'Cannot process deep link route yet, passenger location is null.');
+ }
+
+ // إعادة تعيين القيمة إلى null لمنع التشغيل مرة أخرى عند إعادة بناء الواجهة
+ _deepLinkController.deepLinkLatLng.value = null;
+ }
+ });
+ }
+
@override
void onInit() async {
super.onInit();
-
+// // --- إضافة جديدة: تهيئة وحدة التحكم في الروابط العميقة ---
+// Get.put(DeepLinkController(), permanent: true);
+// // ----------------------------------------------------
// مرحلة 0: الضروري جداً لعرض الخريطة سريعاً
mapAPIKEY = await storage.read(key: BoxName.mapAPIKEY);
await initilizeGetStorage(); // إعداد سريع
await _initMinimalIcons(); // start/end فقط
await addToken(); // لو لازم للمصادقة
- await _initUniLinks();
+ _listenForDeepLink();
await getLocation(); // لتحديد الكاميرا
box.write(BoxName.carType, 'yet');
box.write(BoxName.tipPercentage, '0');
diff --git a/lib/controller/local/translations.dart b/lib/controller/local/translations.dart
index edefc8b..408b4cd 100644
--- a/lib/controller/local/translations.dart
+++ b/lib/controller/local/translations.dart
@@ -179,6 +179,8 @@ class MyTranslation extends Translations {
"Contacts Loaded": "تم تحميل جهات الاتصال",
"Showing": "يتم عرض",
"of": "من",
+ 'Pay by MTN Wallet': 'الدفع عبر محفظة MTN',
+ 'Pay by Syriatel Wallet': 'الدفع عبر محفظة سيريتل',
"Customer not found": "العميل غير موجود",
"Wallet is blocked": "المحفظة محظورة",
"Customer phone is not active": "هاتف العميل غير نشط",
diff --git a/lib/controller/payment/payment_controller.dart b/lib/controller/payment/payment_controller.dart
index aff1439..2050a06 100644
--- a/lib/controller/payment/payment_controller.dart
+++ b/lib/controller/payment/payment_controller.dart
@@ -819,6 +819,162 @@ class PaymentController extends GetxController {
}
}
+ Future payWithSyriaTelWallet(
+ BuildContext context, String amount, String currency) async {
+ // Show a loading indicator for better user experience
+ Get.dialog(const Center(child: CircularProgressIndicator()),
+ barrierDismissible: false);
+
+ try {
+ String phone = box.read(BoxName.phoneWallet);
+ String driverID = box.read(BoxName.driverID).toString();
+ String formattedAmount = double.parse(amount).toStringAsFixed(0);
+
+ // --- CHANGE 1: Updated log messages for clarity ---
+ print("🚀 Starting Syriatel payment process");
+ print(
+ "📦 Payload: driverID: $driverID, amount: $formattedAmount, phone: $phone");
+
+ // Optional: Biometric authentication
+ 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;
+ }
+ }
+
+ // --- CHANGE 2: Updated API link and payload for starting payment ---
+ // Make sure you have defined `payWithSyriatelStart` in your AppLink class
+ var responseData = await CRUD().postWalletMtn(
+ link: AppLink.payWithSyriatelStart, // Use the new Syriatel start link
+ payload: {
+ "amount": formattedAmount,
+ "driverId": driverID, // Key changed from 'passengerId' to 'driverId'
+ "phone": phone,
+ "lang": box.read(BoxName.lang) ?? 'ar',
+ },
+ );
+
+ print("✅ Server response (start_payment.php):");
+ print(responseData);
+
+ // Robustly parse the server's JSON response
+ Map startRes;
+ if (responseData is Map) {
+ startRes = responseData;
+ } else if (responseData is String) {
+ try {
+ startRes = json.decode(responseData);
+ } catch (e) {
+ throw Exception(
+ "Failed to parse server response. Response: $responseData");
+ }
+ } else {
+ throw Exception("Received an unexpected data type from the server.");
+ }
+
+ if (startRes['status'] != 'success') {
+ String errorMsg = startRes['message']?.toString() ??
+ "Failed to start the payment process. Please try again.";
+ throw Exception(errorMsg);
+ }
+
+ // --- CHANGE 3: Extract `transactionID` from the response ---
+ // The response structure is now simpler. We only need the transaction ID.
+ final messageData = startRes["message"];
+ final transactionID = messageData["transactionID"].toString();
+
+ print("📄 TransactionID: $transactionID");
+
+ if (Get.isDialogOpen == true) Get.back(); // Close loading indicator
+
+ // Show the OTP input dialog
+ String? otp = await showDialog(
+ context: context,
+ barrierDismissible: false,
+ builder: (context) {
+ String input = "";
+ return AlertDialog(
+ title: const Text("أدخل كود التحقق"),
+ content: TextField(
+ keyboardType: TextInputType.number,
+ decoration: const InputDecoration(hintText: "كود OTP"),
+ onChanged: (val) => input = val,
+ ),
+ actions: [
+ TextButton(
+ child: const Text("تأكيد"),
+ onPressed: () => Navigator.of(context).pop(input),
+ ),
+ TextButton(
+ child: const Text("إلغاء"),
+ onPressed: () => Navigator.of(context).pop(),
+ ),
+ ],
+ );
+ },
+ );
+
+ if (otp == null || otp.isEmpty) {
+ print("❌ OTP was not entered.");
+ return;
+ }
+ print("🔐 OTP entered: $otp");
+
+ Get.dialog(const Center(child: CircularProgressIndicator()),
+ barrierDismissible: false);
+
+ // --- CHANGE 4: Updated API link and payload for confirming payment ---
+ // Make sure you have defined `payWithSyriatelConfirm` in your AppLink class
+ var confirmRes = await CRUD().postWallet(
+ // Changed from postWalletMtn if they are different
+ link:
+ AppLink.payWithSyriatelConfirm, // Use the new Syriatel confirm link
+ payload: {
+ "transactionID": transactionID, // Use the transaction ID
+ "otp": otp,
+ // The other parameters (phone, guid, etc.) are no longer needed
+ },
+ );
+
+ if (Get.isDialogOpen ?? false) Get.back();
+
+ print("✅ Response from confirm_payment.php:");
+ Log.print('confirmRes: ${confirmRes}');
+
+ if (confirmRes != null && confirmRes['status'] == 'success') {
+ Get.defaultDialog(
+ title: "✅ نجاح",
+ content: const Text("تمت عملية الدفع وإضافة الرصيد إلى محفظتك."),
+ );
+ } else {
+ // --- CHANGE 5: Simplified error message extraction ---
+ // The new PHP script sends the error directly in the 'message' field.
+ String errorMsg =
+ confirmRes?['message']?.toString() ?? "فشل في تأكيد الدفع";
+ Get.defaultDialog(
+ title: "❌ فشل",
+ content: Text(errorMsg.tr),
+ );
+ }
+ } catch (e, s) {
+ // --- CHANGE 6: Updated general error log message ---
+ print("🔥 Error during Syriatel Wallet payment:");
+ print(e);
+ print(s);
+ if (Get.isDialogOpen ?? false) Get.back();
+ Get.defaultDialog(
+ title: 'حدث خطأ',
+ content: Text(e.toString().replaceFirst("Exception: ", "")),
+ );
+ }
+ }
+
@override
void onInit() {
timestamp = now.millisecondsSinceEpoch;
diff --git a/lib/main.dart b/lib/main.dart
index c5d9880..438d329 100644
--- a/lib/main.dart
+++ b/lib/main.dart
@@ -25,6 +25,7 @@ import 'controller/firebase/firbase_messge.dart';
import 'controller/firebase/local_notification.dart';
import 'controller/functions/encrypt_decrypt.dart';
import 'controller/functions/secure_storage.dart';
+import 'controller/home/deep_link_controller.dart';
import 'controller/local/local_controller.dart';
import 'controller/local/translations.dart';
import 'controller/payment/paymob/paymob_wallet.dart';
@@ -52,7 +53,9 @@ void main() async {
WakelockPlus.enable();
await GetStorage.init();
-
+// --- إضافة جديدة: تهيئة وحدة التحكم في الروابط العميقة ---
+ Get.put(DeepLinkController(), permanent: true);
+ // ----------------------------------------------------
final AppInitializer initializer = AppInitializer();
await initializer.initializeApp();
diff --git a/lib/views/home/my_wallet/passenger_wallet_dialoge.dart b/lib/views/home/my_wallet/passenger_wallet_dialoge.dart
index dcd541b..a0a1f78 100644
--- a/lib/views/home/my_wallet/passenger_wallet_dialoge.dart
+++ b/lib/views/home/my_wallet/passenger_wallet_dialoge.dart
@@ -337,72 +337,111 @@ void showPaymentOptions(BuildContext context, PaymentController controller) {
// },
// ),
GestureDetector(
- onTap: () async {
- Get.back();
- // final formKey = GlobalKey();
- // final phoneController = TextEditingController();
+ onTap: () async {
+ Get.back();
+ Get.defaultDialog(
+ barrierDismissible: false,
+ title: 'Insert Wallet phone number'.tr,
+ content: Form(
+ key: controller.formKey,
+ child: MyTextForm(
+ controller: controller.walletphoneController,
+ label: 'Insert Wallet phone number'.tr,
+ hint: '963941234567',
+ type: TextInputType.phone)),
+ confirm: MyElevatedButton(
+ title: 'OK'.tr,
+ onPressed: () async {
+ Get.back();
+ if (controller.formKey.currentState!.validate()) {
+ if (controller.selectedAmount != 0) {
+ controller.isLoading = true;
+ controller.update();
+ box.write(BoxName.phoneWallet,
+ (controller.walletphoneController.text));
+ Get.back();
+ await controller.payWithMTNWallet(
+ context,
+ controller.selectedAmount.toString(),
+ 'SYP',
+ );
+ await controller.getPassengerWallet();
- Get.defaultDialog(
- barrierDismissible: false,
- title: 'Insert Wallet phone number'.tr,
- content: Form(
- key: controller.formKey,
- child: TextFormField(
- controller: controller.walletphoneController,
- keyboardType: TextInputType.phone,
- decoration: InputDecoration(
- labelText: 'Insert Wallet phone number'.tr,
- hintText: '963941234567',
- border: OutlineInputBorder(),
+ controller.isLoading = false;
+ controller.update();
+ } else {
+ Toast.show(
+ context,
+ '⚠️ You need to choose an amount!'.tr,
+ AppColor.redColor,
+ );
+ }
+ }
+ }));
+ },
+ child: Padding(
+ padding: const EdgeInsets.all(8.0),
+ child: Row(
+ mainAxisAlignment: MainAxisAlignment.spaceBetween,
+ children: [
+ Text(
+ 'Pay by MTN Wallet'.tr,
+ style: AppStyle.title,
),
- validator: (value) {
- if (value == null || value.isEmpty) {
- return '⚠️ Please enter phone number'.tr;
- } else if (value.length != 12) {
- return '⚠️ Phone number must be 12 digits'.tr;
- }
- return null;
- },
- ),
+ const SizedBox(width: 10),
+ Image.asset(
+ 'assets/images/cashMTN.png',
+ width: 70,
+ height: 70,
+ fit: BoxFit.contain,
+ ),
+ ],
),
- confirm: ElevatedButton(
- child: Text('OK'.tr),
- onPressed: () async {
- if (controller.formKey.currentState!.validate()) {
- if (controller.selectedAmount != 0) {
- controller.isLoading = true;
- controller.update();
- box.write(BoxName.phoneWallet,
- (controller.walletphoneController.text));
- Get.back();
- await controller.payWithMTNWallet(
- context,
- controller.selectedAmount.toString(),
- 'SYP',
- );
- await controller.getPassengerWallet();
-
- controller.isLoading = false;
- controller.update();
- } else {
- Toast.show(
- context,
- '⚠️ You need to choose an amount!'.tr,
- AppColor.redColor,
- );
- }
- }
- },
+ )),
+ GestureDetector(
+ onTap: () async {
+ Get.back();
+ Get.defaultDialog(
+ barrierDismissible: false,
+ title: 'Insert Wallet phone number'.tr,
+ content: Form(
+ key: controller.formKey,
+ child: MyTextForm(
+ controller: controller.walletphoneController,
+ label: 'Insert Wallet phone number'.tr,
+ hint: '963941234567',
+ type: TextInputType.phone)),
+ confirm: MyElevatedButton(
+ title: 'OK'.tr,
+ onPressed: () async {
+ Get.back();
+ if (controller.formKey.currentState!.validate()) {
+ box.write(BoxName.phoneWallet,
+ controller.walletphoneController.text);
+ await controller.payWithSyriaTelWallet(context,
+ controller.selectedAmount.toString(), 'SYP');
+ }
+ }));
+ },
+ child: Padding(
+ padding: const EdgeInsets.all(8.0),
+ child: Row(
+ mainAxisAlignment: MainAxisAlignment.spaceBetween,
+ children: [
+ Text(
+ 'Pay by Syriatel Wallet'.tr,
+ style: AppStyle.title,
+ ),
+ const SizedBox(width: 10),
+ Image.asset(
+ 'assets/images/syriatel.png',
+ width: 70,
+ height: 70,
+ fit: BoxFit.fill,
+ ),
+ ],
),
- );
- },
- child: Image.asset(
- 'assets/images/mtn.png',
- width: 70,
- height: 70,
- fit: BoxFit.contain,
- ),
- )
+ )),
],
cancelButton: CupertinoActionSheetAction(
child: Text('Cancel'.tr),
diff --git a/pubspec.lock b/pubspec.lock
index 44d64d7..9d3d2cc 100644
--- a/pubspec.lock
+++ b/pubspec.lock
@@ -33,6 +33,38 @@ packages:
url: "https://pub.dev"
source: hosted
version: "4.2.3"
+ app_links:
+ dependency: "direct main"
+ description:
+ name: app_links
+ sha256: "5f88447519add627fe1cbcab4fd1da3d4fed15b9baf29f28b22535c95ecee3e8"
+ url: "https://pub.dev"
+ source: hosted
+ version: "6.4.1"
+ app_links_linux:
+ dependency: transitive
+ description:
+ name: app_links_linux
+ sha256: f5f7173a78609f3dfd4c2ff2c95bd559ab43c80a87dc6a095921d96c05688c81
+ url: "https://pub.dev"
+ source: hosted
+ version: "1.0.3"
+ app_links_platform_interface:
+ dependency: transitive
+ description:
+ name: app_links_platform_interface
+ sha256: "05f5379577c513b534a29ddea68176a4d4802c46180ee8e2e966257158772a3f"
+ url: "https://pub.dev"
+ source: hosted
+ version: "2.0.2"
+ app_links_web:
+ dependency: transitive
+ description:
+ name: app_links_web
+ sha256: af060ed76183f9e2b87510a9480e56a5352b6c249778d07bd2c95fc35632a555
+ url: "https://pub.dev"
+ source: hosted
+ version: "1.0.4"
archive:
dependency: transitive
description:
@@ -992,6 +1024,14 @@ packages:
url: "https://pub.dev"
source: hosted
version: "2.3.2"
+ gtk:
+ dependency: transitive
+ description:
+ name: gtk
+ sha256: e8ce9ca4b1df106e4d72dad201d345ea1a036cc12c360f1a7d5a758f78ffa42c
+ url: "https://pub.dev"
+ source: hosted
+ version: "2.1.0"
html:
dependency: transitive
description:
@@ -1940,30 +1980,6 @@ packages:
url: "https://pub.dev"
source: hosted
version: "1.4.0"
- uni_links:
- dependency: "direct main"
- description:
- name: uni_links
- sha256: "051098acfc9e26a9fde03b487bef5d3d228ca8f67693480c6f33fd4fbb8e2b6e"
- url: "https://pub.dev"
- source: hosted
- version: "0.5.1"
- uni_links_platform_interface:
- dependency: transitive
- description:
- name: uni_links_platform_interface
- sha256: "929cf1a71b59e3b7c2d8a2605a9cf7e0b125b13bc858e55083d88c62722d4507"
- url: "https://pub.dev"
- source: hosted
- version: "1.0.0"
- uni_links_web:
- dependency: transitive
- description:
- name: uni_links_web
- sha256: "7539db908e25f67de2438e33cc1020b30ab94e66720b5677ba6763b25f6394df"
- url: "https://pub.dev"
- source: hosted
- version: "0.1.0"
url_launcher:
dependency: "direct main"
description:
diff --git a/pubspec.yaml b/pubspec.yaml
index a3a4edf..026725e 100644
--- a/pubspec.yaml
+++ b/pubspec.yaml
@@ -59,7 +59,7 @@ dependencies:
sign_in_with_apple: ^6.1.0
firebase_auth: ^5.1.2
device_info_plus: ^11.3.0
- uni_links: ^0.5.1
+ # uni_links: ^0.5.1
googleapis_auth: ^1.6.0
flutter_confetti: ^0.3.0
# intl_phone_field: ^3.1.0
@@ -77,6 +77,7 @@ dependencies:
asn1lib: ^1.6.5
internet_connection_checker: ^3.0.1
connectivity_plus: ^6.1.5
+ app_links: ^6.4.1
# home_widget: ^0.7.0+1
dev_dependencies: