26-1-21/1

This commit is contained in:
Hamza-Ayed
2026-01-21 17:01:45 +03:00
parent 11dfe94bbb
commit 3e89e1f1f0
32 changed files with 101957 additions and 12193 deletions

View File

@@ -105,6 +105,9 @@ class FirebaseMessagesController extends GetxController {
// اقرأ "النوع" من حمولة البيانات، وليس من العنوان
String category = message.data['category'] ?? '';
final mapCtrl = Get.isRegistered<MapPassengerController>()
? Get.find<MapPassengerController>()
: null;
// اقرأ العنوان (للعرض)
String title = message.notification?.title ?? '';
String body = message.notification?.body ?? '';
@@ -119,17 +122,25 @@ class FirebaseMessagesController extends GetxController {
// ... داخل معالج الإشعارات في تطبيق الراكب ...
else if (category == 'Accepted Ride') {
// <-- كان 'Accepted Ride'
var driverListJson = message.data['driverList'];
if (driverListJson != null) {
var myList = jsonDecode(driverListJson) as List<dynamic>;
final controller = Get.find<MapPassengerController>();
// controller.currentRideState.value = RideState.driverApplied;
await controller.processRideAcceptance(
driverIdFromFCM: myList[0].toString(),
rideIdFromFCM: myList[3].toString());
} else {
Log.print('❌ خطأ: RIDE_ACCEPTED وصل بدون driverList');
if (mapCtrl != null) {
Map<String, dynamic>? driverInfoMap;
// 2. معالجة driver_info (تأتي كـ String JSON من PHP)
if (message.data['driver_info'] != null) {
try {
String rawJson = message.data['driver_info'];
// 🔥 فك التشفير: تحويل الـ String إلى Map
driverInfoMap = jsonDecode(rawJson);
} catch (e) {
print("❌ Error decoding FCM driver_info: $e");
}
}
// 3. تمرير البيانات الجاهزة للكنترولر
await mapCtrl.processRideAcceptance(
driverData: driverInfoMap,
source: "FCM",
);
}
} else if (category == 'Promo') {
// <-- كان 'Promo'.tr
@@ -142,7 +153,7 @@ class FirebaseMessagesController extends GetxController {
if (Platform.isAndroid) {
notificationController.showNotification(title, body, 'iphone_ringtone');
}
var myListString = message.data['DriverList'];
var myListString = message.data['passengerList'];
var myList = jsonDecode(myListString) as List<dynamic>;
Get.to(() => TripMonitor(), arguments: {
'rideId': myList[0].toString(),
@@ -161,7 +172,7 @@ class FirebaseMessagesController extends GetxController {
if (Platform.isAndroid) {
notificationController.showNotification(title, body, 'tone1');
}
} else if (category == 'message From passenger') {
} else if (category == 'MSG_FROM_PASSENGER') {
// <-- كان 'message From passenger'
if (Platform.isAndroid) {
notificationController.showNotification(title, body, 'ding');
@@ -178,78 +189,33 @@ class FirebaseMessagesController extends GetxController {
} else if (category == 'Trip is Begin') {
// <-- كان 'Trip is Begin'
Log.print('[FCM] استقبل إشعار "TRIP_BEGUN".');
final controller = Get.find<MapPassengerController>();
controller.processRideBegin();
// استدعاء الحارس
mapCtrl!.processRideBegin(source: "FCM");
} else if (category == 'Hi ,I will go now') {
// <-- كان 'Hi ,I will go now'.tr
if (Platform.isAndroid) {
notificationController.showNotification(title, body, 'ding');
}
update();
} else if (category == 'Hi ,I Arrive your site') {
// <-- كان 'Hi ,I Arrive your site'.tr
final controller = Get.find<MapPassengerController>();
// if (controller.currentRideState.value == RideState.driverApplied) {
Log.print('[FCM] السائق وصل. تغيير الحالة إلى driverArrived');
controller.currentRideState.value = RideState.driverArrived;
// }
} else if (category == 'Cancel Trip from driver') {
// <-- كان "Cancel Trip from driver"
Get.back();
if (Platform.isAndroid) {
notificationController.showNotification(title, body, 'cancel');
}
Get.defaultDialog(
title: "The driver canceled your ride.".tr, // العنوان المترجم للعرض
middleText: "We will look for a new driver.\nPlease wait.".tr,
confirm: MyElevatedButton(
kolor: AppColor.greenColor,
title: 'Ok'.tr,
onPressed: () async {
Get.back();
await Get.find<MapPassengerController>()
.reSearchAfterCanceledFromDriver();
},
),
cancel: MyElevatedButton(
title: 'Cancel'.tr,
kolor: AppColor.redColor,
onPressed: () {
Get.offAll(() => const MapPagePassenger());
},
));
} else if (category == "Arrive Ride") {
// استدعاء الحارس
mapCtrl!.processDriverArrival("FCM");
} else if (category == 'Driver Finish Trip') {
// <-- كان 'Driver Finish Trip'.tr
final rawData = message.data['DriverList'];
List<dynamic> driverList = [];
if (rawData != null && rawData is String) {
// ✅ معالجة آمنة للبيانات
var rawData = message.data['DriverList'];
if (rawData != null && rawData.isNotEmpty) {
try {
driverList = jsonDecode(rawData);
driverList = jsonDecode(rawData) as List<dynamic>;
} catch (e) {
Log.print('Error decoding DriverList JSON: $e');
print("Error decoding DriverList: $e");
}
} else {
Log.print('Error: DriverList data is null or not a String.');
}
if (driverList.length >= 3) {
if (Platform.isAndroid) {
notificationController.showNotification(
title,
'${'you will pay to Driver'.tr} ${driverList[3].toString()} \$',
'tone1');
}
Get.find<AudioRecorderController>().stopRecording();
// ... (باقي كود المحفظة) ...
Get.find<MapPassengerController>().tripFinishedFromDriver();
// ... (إشعار "لا تنسى متعلقاتك") ...
Get.to(() => RateDriverFromPassenger(), arguments: {
'driverId': driverList[0].toString(),
'rideId': driverList[1].toString(),
'price': driverList[3].toString()
});
} else {
Log.print('Error: TRIP_FINISHED decoded list error.');
if (driverList.isNotEmpty) {
Get.find<MapPassengerController>()
.processRideFinished(driverList, source: "FCM");
}
} else if (category == 'Finish Monitor') {
// <-- كان "Finish Monitor".tr
@@ -262,19 +228,21 @@ class FirebaseMessagesController extends GetxController {
onPressed: () {
Get.offAll(() => const MapPagePassenger());
}));
} else if (category == 'Driver Cancelled Your Trip') {
} else if (category == 'Cancel Trip from driver') {
Log.print("🔔 FCM: Ride Cancelled by Driver received.");
// لا داعي لكتابة منطق التنظيف هنا، الكنترولر يتكفل بكل شيء
if (Get.isRegistered<MapPassengerController>()) {
// استدعاء الحارس (سيتجاهل الأمر إذا كان السوكيت قد سبقه)
Get.find<MapPassengerController>()
.processRideCancelledByDriver(message.data, source: "FCM");
}
// إشعار محلي (اختياري، لأن الديالوج سيظهر)
if (Platform.isAndroid) {
notificationController.showNotification(
'Driver Cancelled Your Trip'.tr,
'you will pay to Driver you will be pay the cost of driver time look to your Intaleq Wallet'
.tr,
'cancel');
'Trip Cancelled'.tr, 'The driver cancelled the trip.'.tr, 'cancel');
}
box.write(BoxName.parentTripSelected, false);
box.remove(BoxName.tokenParent);
Get.find<MapPassengerController>().restCounter();
Get.offAll(() => const MapPagePassenger());
}
// ... (باقي الحالات مثل Call Income, Call End, إلخ) ...
// ... بنفس الطريقة ...
@@ -617,7 +585,7 @@ class FirebaseMessagesController extends GetxController {
Future<dynamic> passengerDialog(String message) {
return Get.defaultDialog(
barrierDismissible: false,
title: 'message From Driver'.tr,
title: message.tr,
titleStyle: AppStyle.title,
middleTextStyle: AppStyle.title,
middleText: message.tr,

View File

@@ -1,41 +1,44 @@
import 'package:Intaleq/print.dart';
import 'package:http/http.dart' as http;
import 'dart:convert';
import 'package:http/http.dart' as http;
import 'package:get/get.dart'; // للترجمة .tr
class NotificationService {
// استبدل هذا الرابط بالرابط الصحيح لملف PHP على السيرفر الخاص بك
static const String _serverUrl =
'https://syria.intaleq.xyz/intaleq/fcm/send_fcm.php';
static const String _batchServerUrl =
'https://syria.intaleq.xyz/intaleq/fcm/send_fcm_batch.php';
'https://api.intaleq.xyz/intaleq/ride/firebase/send_fcm.php';
static Future<void> sendNotification({
required String target,
required String title,
required String body,
required String? category, // <-- [الإضافة الأولى]
required String category, // إلزامي للتصنيف
String? tone,
List<String>? driverList, // <-- [تعديل 1] : إضافة المتغير الجديد
List<String>? driverList,
bool isTopic = false,
}) async {
try {
final Map<String, dynamic> payload = {
// 1. تجهيز البيانات المخصصة (Data Payload)
Map<String, dynamic> customData = {};
customData['category'] = category;
// إذا كان هناك قائمة سائقين/ركاب، نضعها هنا
if (driverList != null && driverList.isNotEmpty) {
// نرسلها كـ JSON String لأن FCM v1 يدعم String Values فقط في الـ data
customData['driverList'] = jsonEncode(driverList);
}
// 2. تجهيز الطلب الرئيسي للسيرفر
final Map<String, dynamic> requestPayload = {
'target': target,
'title': title,
'body': body,
'isTopic': isTopic,
'data':
customData, // 🔥🔥 التغيير الجوهري: وضعنا البيانات داخل "data" 🔥🔥
};
if (category != null) {
payload['category'] =
category; // <-- [الإضافة الثانية] (النص الثابت للتحكم)
}
// نضيف النغمة فقط إذا لم تكن فارغة
if (tone != null) {
payload['tone'] = tone;
}
// <-- [تعديل 2] : نضيف قائمة البيانات بعد تشفيرها إلى JSON
if (driverList != null) {
payload['driverList'] = jsonEncode(driverList);
if (tone != null) {
requestPayload['tone'] = tone;
}
final response = await http.post(
@@ -43,71 +46,18 @@ class NotificationService {
headers: {
'Content-Type': 'application/json; charset=UTF-8',
},
body: jsonEncode(payload),
body: jsonEncode(requestPayload),
);
if (response.statusCode == 200) {
print('✅ Notification sent successfully.');
print('Server Response: ${response.body}');
// print('Response: ${response.body}');
} else {
print(
'❌ Failed to send notification. Status code: ${response.statusCode}');
print('Server Error: ${response.body}');
print('❌ Failed to send notification. Code: ${response.statusCode}');
print('Error Body: ${response.body}');
}
} catch (e) {
print('An error occurred while sending notification: $e');
}
}
/// [4] !! دالة جديدة مضافة !!
/// ترسل إشعاراً "مجمعاً" إلى قائمة من السائقين
static Future<void> sendBatchNotification({
required List<String> targets, // <-- قائمة التوكينز
required String title,
required String body,
String? tone,
List<String>? driverList, // <-- بيانات الرحلة (نفسها للجميع)
}) async {
// لا ترسل شيئاً إذا كانت القائمة فارغة
if (targets.isEmpty) {
Log.print('⚠️ [Batch] No targets to send to. Skipped.');
return;
}
try {
final Map<String, dynamic> payload = {
// "targets" بدلاً من "target"
'targets': jsonEncode(targets), // تشفير قائمة التوكينز
'title': title,
'body': body,
};
if (tone != null) {
payload['tone'] = tone;
}
// بيانات الرحلة (DriverList)
if (driverList != null) {
payload['driverList'] = jsonEncode(driverList);
}
final response = await http.post(
Uri.parse(_batchServerUrl), // <-- !! تستخدم الرابط الجديد
headers: {
'Content-Type': 'application/json; charset=UTF-8',
},
body: jsonEncode(payload),
);
if (response.statusCode == 200) {
Log.print('✅ [Batch] Notifications sent successfully.');
Log.print('Server Response: ${response.body}');
} else {
Log.print('❌ [Batch] Failed to send. Status: ${response.statusCode}');
Log.print('Server Error: ${response.body}');
}
} catch (e) {
Log.print('❌ [Batch] An error occurred: $e');
print('Error sending notification: $e');
}
}
}

View File

@@ -78,7 +78,7 @@ class AudioRecorderController extends GetxController {
// Stop recording
Future<void> stopRecording() async {
await recorder.stop();
recorder.stop();
isRecording = false;
isPaused = false;
update();

View File

@@ -100,9 +100,8 @@ class CRUD {
}
final sc = response.statusCode;
Log.print('sc: ${sc}');
Log.print('request: ${response.request}');
final body = response.body;
Log.print('request: ${response.request}');
Log.print('body: ${body}');
// 2xx
@@ -188,9 +187,9 @@ class CRUD {
'Bearer ${r(box.read(BoxName.jwt)).toString().split(Env.addd)[0]}'
},
);
Log.print('response.body: ${response.body}');
Log.print('response.request: ${response.request}');
Log.print('response.payload: ${payload}');
Log.print('request: ${response.request}');
Log.print('body: ${response.body}');
Log.print('payload: ${payload}');
if (response.statusCode == 200) {
var jsonData = jsonDecode(response.body);

View File

@@ -11,19 +11,17 @@ Future<void> makePhoneCall(String phoneNumber) async {
// 1. تنظيف الرقم (إزالة المسافات والفواصل)
String formattedNumber = phoneNumber.replaceAll(RegExp(r'\s+'), '');
// 2. التحقق من طول الرقم لتحديد طريقة التنسيق
// 2. منطق التنسيق (مع الحفاظ على الأرقام القصيرة مثل 112 كما هي)
if (formattedNumber.length > 6) {
// --- التعديل المطلوب ---
if (formattedNumber.startsWith('09')) {
// إذا كان يبدأ بـ 09 (رقم موبايل سوري محلي)
// نحذف أول خانة (الصفر) ونضيف +963
// إذا كان يبدأ بـ 09 (رقم موبايل سوري محلي) -> +963
formattedNumber = '+963${formattedNumber.substring(1)}';
} else if (!formattedNumber.startsWith('+')) {
// إذا لم يكن يبدأ بـ + (ولم يكن يبدأ بـ 09)، نضيف + في البداية
// هذا للحفاظ على منطقك القديم للأرقام الدولية الأخرى
// إذا لم يكن دولياً ولا محلياً معروفاً -> إضافة + فقط
formattedNumber = '+$formattedNumber';
}
}
// ملاحظة: الأرقام القصيرة (مثل 112) ستتجاوز الشرط أعلاه وتبقى "112" وهو الصحيح
// 3. التنفيذ (Launch)
final Uri launchUri = Uri(
@@ -31,8 +29,19 @@ Future<void> makePhoneCall(String phoneNumber) async {
path: formattedNumber,
);
if (await canLaunchUrl(launchUri)) {
await launchUrl(launchUri);
try {
// استخدام LaunchMode.externalApplication هو الحل الجذري لمشاكل الـ Intent
// لأنه يجبر النظام على تسليم الرابط لتطبيق الهاتف بدلاً من محاولة فتحه داخل تطبيقك
if (await canLaunchUrl(launchUri)) {
await launchUrl(launchUri, mode: LaunchMode.externalApplication);
} else {
// في بعض الأجهزة canLaunchUrl تعود بـ false مع الـ tel ومع ذلك يعمل launchUrl
// لذا نجرب الإطلاق المباشر كاحتياط
await launchUrl(launchUri, mode: LaunchMode.externalApplication);
}
} catch (e) {
// طباعة الخطأ في حال الفشل التام
print("Error launching call: $e");
}
}

View File

@@ -0,0 +1,127 @@
import 'dart:io';
import 'package:device_info_plus/device_info_plus.dart';
class DevicePerformanceManager {
/// القائمة البيضاء لموديلات الهواتف القوية (Flagships Only)
/// أي هاتف يبدأ موديله بأحد هذه الرموز سيعتبر قوياً
static const List<String> _highEndSamsungModels = [
'SM-S', // سلسلة Galaxy S21, S22, S23, S24 (S901, S908, S911...)
'SM-F', // سلسلة Fold و Flip (Z Fold, Z Flip)
'SM-N9', // سلسلة Note 9, Note 10, Note 20
'SM-G9', // سلسلة S10, S20 (G970, G980...)
];
static const List<String> _highEndGoogleModels = [
'Pixel 6',
'Pixel 7',
'Pixel 8',
'Pixel 9',
'Pixel Fold'
];
static const List<String> _highEndHuaweiModels = [
'ELS-', // P40 Pro
'ANA-', // P40
'HMA-', // Mate 20
'LYA-', // Mate 20 Pro
'VOG-', // P30 Pro
'ELE-', // P30
'NOH-', // Mate 40 Pro
'AL00', // Mate X series (some)
];
static const List<String> _highEndXiaomiModels = [
'2201122', // Xiaomi 12 series patterns often look like this
'2210132', // Xiaomi 13
'2304FPN', // Xiaomi 13 Ultra
'M2007J1', // Mi 10 series
'M2102K1', // Mi 11 Ultra
];
static const List<String> _highEndOnePlusModels = [
'GM19', // OnePlus 7
'HD19', // OnePlus 7T
'IN20', // OnePlus 8
'KB20', // OnePlus 8T
'LE21', // OnePlus 9
'NE22', // OnePlus 10
'PHB110', // OnePlus 11
'CPH', // Newer OnePlus models
];
/// دالة الفحص الرئيسية
static Future<bool> isHighEndDevice() async {
// 1. الآيفون دائماً قوي (نظام الرسوميات فيه متفوق حتى في الموديلات القديمة)
if (Platform.isIOS) return true;
if (Platform.isAndroid) {
try {
final androidInfo = await DeviceInfoPlugin().androidInfo;
String manufacturer = androidInfo.manufacturer.toLowerCase();
String model =
androidInfo.model.toUpperCase(); // نحوله لحروف كبيرة للمقارنة
String hardware = androidInfo.hardware.toLowerCase(); // المعالج
// --- الفحص العكسي (الحظر المباشر) ---
// إذا كان المعالج من الفئات الضعيفة جداً المشهورة في الهواتف المقلدة
// mt65xx, mt6735, sc77xx هي معالجات رخيصة جداً
if (hardware.contains('mt65') ||
hardware.contains('mt6735') ||
hardware.contains('sc77')) {
return false;
}
// --- فحص القائمة البيضاء (Whitelist) ---
// 1. Samsung Flagships
if (manufacturer.contains('samsung')) {
for (var prefix in _highEndSamsungModels) {
if (model.startsWith(prefix)) return true;
}
}
// 2. Google Pixel (6 and above)
if (manufacturer.contains('google')) {
for (var prefix in _highEndGoogleModels) {
if (model.contains(prefix.toUpperCase())) return true;
}
}
// 3. Huawei Flagships
if (manufacturer.contains('huawei')) {
for (var prefix in _highEndHuaweiModels) {
if (model.startsWith(prefix)) return true;
}
}
// 4. OnePlus Flagships
if (manufacturer.contains('oneplus')) {
for (var prefix in _highEndOnePlusModels) {
if (model.startsWith(prefix)) return true;
}
}
// 5. Xiaomi Flagships
if (manufacturer.contains('xiaomi') ||
manufacturer.contains('redmi') ||
manufacturer.contains('poco')) {
// شاومي تسميتها معقدة، لذا سنعتمد على فحص الرام كعامل مساعد هنا فقط
// لأن هواتف شاومي القوية عادة لا تزور الرام
// الرام يجب أن يكون أكبر من 6 جيجا (بايت)
double ramGB = (androidInfo.availableRamSize) / (1024 * 1024 * 1024);
if (ramGB > 7.5)
return true; // 8GB RAM or more is usually safe for Xiaomi high-end
}
// إذا لم يكن من ضمن القوائم أعلاه، نعتبره جهازاً متوسطاً/ضعيفاً ونعرض الرسم البسيط
return false;
} catch (e) {
// في حال حدوث خطأ في الفحص، نعود للوضع الآمن (الرسم البسيط)
return false;
}
}
return false;
}
}

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

View File

@@ -36,7 +36,7 @@ class RateController extends GetxController {
update();
}
void addRateToDriver() async {
addRateToDriver() async {
if (selectedRateItemId < 1) {
Get.defaultDialog(
title: 'You Should choose rate figure'.tr,