25-09-22/1

This commit is contained in:
Hamza-Ayed
2025-09-22 17:28:19 +03:00
parent 13d77e118c
commit 7595be8067
19 changed files with 536 additions and 246 deletions

3
.vscode/settings.json vendored Normal file
View File

@@ -0,0 +1,3 @@
{
"java.configuration.updateBuildConfiguration": "disabled"
}

View File

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

View File

@@ -45,14 +45,18 @@
</intent-filter>
<!-- Deep Linking Intent Filter -->
<!-- ... other intent-filters ... -->
<intent-filter>
<action android:name="android.intent.action.VIEW" />
<category android:name="android.intent.category.DEFAULT" />
<category android:name="android.intent.category.BROWSABLE" />
<!-- Accepts the custom scheme 'intaleq' and host 'map' -->
<data android:scheme="intaleq" android:host="map" />
<!--
هنا تضع الرابط المخصص لتطبيقك.
مثال: عند الضغط على رابط `intaleq://route?lat=...` سيتم فتح التطبيق.
-->
<data android:scheme="intaleq" android:host="route" />
</intent-filter>
</activity>
<!-- Existing meta-data and other activities remain unchanged -->

File diff suppressed because one or more lines are too long

View File

@@ -5,4 +5,5 @@ android.defaults.buildfeatures.buildconfig=true
android.nonTransitiveRClass=true
android.nonFinalResIds=true
dart.obfuscation=true
android.enableR8.fullMode=true
android.enableR8.fullMode=true
org.gradle.java.home=/Library/Java/JavaVirtualMachines/temurin-17.jdk/Contents/Home

BIN
assets/images/cashMTN.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 53 KiB

BIN
assets/images/syriatel.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 6.8 KiB

View File

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

View File

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

View File

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

View File

@@ -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<String, dynamic>? 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<String, dynamic>? 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",

View File

@@ -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<Uri>? _linkSubscription;
// متغير لتخزين الإحداثيات القادمة من الرابط
final Rx<LatLng?> deepLinkLatLng = Rx<LatLng?>(null);
@override
void onInit() {
super.onInit();
// ابدأ بالاستماع للروابط عند تشغيل التطبيق
initDeepLinks();
}
Future<void> 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();
}
}

View File

@@ -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<void> _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<void> 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);
});
// خذ أول 1015 للعرض (اختياري)، أو اعرض الكل
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');

View File

@@ -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": "هاتف العميل غير نشط",

View File

@@ -819,6 +819,162 @@ class PaymentController extends GetxController {
}
}
Future<void> 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<String, dynamic> startRes;
if (responseData is Map<String, dynamic>) {
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<String>(
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;

View File

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

View File

@@ -337,72 +337,111 @@ void showPaymentOptions(BuildContext context, PaymentController controller) {
// },
// ),
GestureDetector(
onTap: () async {
Get.back();
// final formKey = GlobalKey<FormState>();
// 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),

View File

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

View File

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