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: