Compare commits
3 Commits
3c0ae4cf2f
...
main
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
38f7abcd59 | ||
|
|
12c5ce31a2 | ||
|
|
fbfde115a8 |
@@ -44,8 +44,8 @@ android {
|
||||
applicationId = "com.intaleq_driver"
|
||||
minSdkVersion = 23
|
||||
targetSdk = 36
|
||||
versionCode = 56
|
||||
versionName = '1.0.56' // I've used the higher version name
|
||||
versionCode = 57
|
||||
versionName = '1.1.57' // I've used the higher version name
|
||||
multiDexEnabled = true
|
||||
|
||||
ndk {
|
||||
|
||||
@@ -10,6 +10,7 @@
|
||||
<uses-permission android:name="android.permission.FOREGROUND_SERVICE_TYPE_REMOTE_MESSAGING" />
|
||||
<uses-permission android:name="android.permission.USE_BIOMETRIC" />
|
||||
<uses-permission android:name="android.permission.VIBRATE" />
|
||||
<uses-permission android:name="android.permission.USE_FULL_SCREEN_INTENT" />
|
||||
<uses-permission android:name="android.permission.ACCESS_NETWORK_STATE" />
|
||||
<uses-permission android:name="android.permission.CAMERA" />
|
||||
<uses-permission android:name="android.permission.RECORD_AUDIO" />
|
||||
|
||||
@@ -384,7 +384,7 @@ class LoginDriverController extends GetxController {
|
||||
textConfirm: 'Verify'.tr,
|
||||
confirmTextColor: Colors.white,
|
||||
onConfirm: () {
|
||||
// Get.back();
|
||||
Get.back();
|
||||
// انتقل لصفحة OTP الجديدة
|
||||
Get.to(
|
||||
() => OtpVerificationPage(
|
||||
|
||||
@@ -90,7 +90,7 @@ class OtpVerificationController extends GetxController {
|
||||
|
||||
if (response != 'failure') {
|
||||
Log.print('response: ${response}');
|
||||
Get.back(); // توجه إلى الصفحة التالية
|
||||
// Get.back(); // توجه إلى الصفحة التالية
|
||||
await CRUD().post(
|
||||
link: '${AppLink.paymentServer}/auth/token/update_driver_auth.php',
|
||||
payload: {
|
||||
@@ -98,17 +98,7 @@ class OtpVerificationController extends GetxController {
|
||||
'fingerPrint': finger.toString(),
|
||||
'captain_id': box.read(BoxName.driverID).toString(),
|
||||
});
|
||||
final fcm = Get.isRegistered<FirebaseMessagesController>()
|
||||
? Get.find<FirebaseMessagesController>()
|
||||
: Get.put(FirebaseMessagesController());
|
||||
|
||||
// await fcm.sendNotificationToDriverMAP(
|
||||
// 'token change',
|
||||
// 'change device'.tr,
|
||||
// ptoken.toString(),
|
||||
// [],
|
||||
// 'cancel.wav',
|
||||
// );
|
||||
await NotificationService.sendNotification(
|
||||
target: ptoken.toString(),
|
||||
title: 'token change'.tr,
|
||||
|
||||
@@ -110,9 +110,9 @@ class FirebaseMessagesController extends GetxController {
|
||||
switch (category) {
|
||||
case 'ORDER':
|
||||
case 'Order': // Handle both cases for backward compatibility
|
||||
if (Platform.isAndroid) {
|
||||
notificationController.showNotification(title, body, 'order', '');
|
||||
}
|
||||
// if (Platform.isAndroid) {
|
||||
// notificationController.showNotification(title, body, 'order', '');
|
||||
// }
|
||||
var myListString = message.data['DriverList'];
|
||||
if (myListString != null) {
|
||||
var myList = jsonDecode(myListString) as List<dynamic>;
|
||||
|
||||
@@ -1,62 +1,410 @@
|
||||
import 'dart:async';
|
||||
import 'dart:convert';
|
||||
import 'dart:io';
|
||||
import 'dart:ui'; // للألوان
|
||||
|
||||
import 'package:sefer_driver/views/home/Captin/orderCaptin/order_request_page.dart';
|
||||
import 'package:flutter/material.dart';
|
||||
import 'package:flutter_local_notifications/flutter_local_notifications.dart';
|
||||
import 'package:get/get.dart';
|
||||
import 'package:permission_handler/permission_handler.dart';
|
||||
|
||||
import 'package:timezone/data/latest.dart' as tz;
|
||||
import 'package:timezone/timezone.dart' as tz;
|
||||
|
||||
import '../../constant/box_name.dart';
|
||||
import '../../main.dart';
|
||||
import '../../constant/links.dart';
|
||||
import '../../main.dart'; // للوصول لـ box
|
||||
import '../../print.dart';
|
||||
import '../../views/notification/notification_captain.dart';
|
||||
import '../../views/home/Captin/driver_map_page.dart';
|
||||
import '../../views/home/Captin/orderCaptin/order_request_page.dart';
|
||||
import '../functions/crud.dart';
|
||||
import '../home/captin/home_captain_controller.dart';
|
||||
|
||||
class NotificationController extends GetxController {
|
||||
final FlutterLocalNotificationsPlugin _flutterLocalNotificationsPlugin =
|
||||
FlutterLocalNotificationsPlugin();
|
||||
|
||||
// ==============================================================================
|
||||
// 1. تهيئة الإشعارات (إعداد القنوات والأزرار للآيفون والأندرويد)
|
||||
// ==============================================================================
|
||||
Future<void> initNotifications() async {
|
||||
// إعدادات الأندرويد
|
||||
const AndroidInitializationSettings android =
|
||||
AndroidInitializationSettings('@mipmap/launcher_icon');
|
||||
DarwinInitializationSettings ios = DarwinInitializationSettings(
|
||||
|
||||
// إعدادات أزرار الآيفون (Categories)
|
||||
// هذا الجزء ضروري لظهور الأزرار في iOS
|
||||
final List<DarwinNotificationCategory> darwinNotificationCategories = [
|
||||
DarwinNotificationCategory(
|
||||
'ORDER_CATEGORY', // المعرف المستخدم لربط الإشعار بالأزرار
|
||||
actions: [
|
||||
DarwinNotificationAction.plain('ACCEPT_ORDER', '✅ قبول'),
|
||||
DarwinNotificationAction.plain('SHOW_DETAILS', '📄 تفاصيل'),
|
||||
DarwinNotificationAction.plain(
|
||||
'REJECT_ORDER',
|
||||
'❌ رفض',
|
||||
options: {
|
||||
DarwinNotificationActionOption.destructive
|
||||
}, // يظهر باللون الأحمر
|
||||
),
|
||||
],
|
||||
)
|
||||
];
|
||||
|
||||
// إعدادات الآيفون العامة
|
||||
final DarwinInitializationSettings ios = DarwinInitializationSettings(
|
||||
requestAlertPermission: true,
|
||||
requestBadgePermission: true,
|
||||
requestSoundPermission: true,
|
||||
// onDidReceiveLocalNotification:
|
||||
// (int id, String? title, String? body, String? payload) async {},
|
||||
notificationCategories: darwinNotificationCategories, // تسجيل الأزرار
|
||||
);
|
||||
|
||||
InitializationSettings initializationSettings =
|
||||
InitializationSettings(android: android, iOS: ios);
|
||||
|
||||
tz.initializeTimeZones();
|
||||
print('Notifications initialized');
|
||||
print('✅ Notifications initialized with Action Buttons Support');
|
||||
|
||||
await _flutterLocalNotificationsPlugin.initialize(
|
||||
initializationSettings,
|
||||
onDidReceiveNotificationResponse: onDidReceiveNotificationResponse,
|
||||
onDidReceiveBackgroundNotificationResponse: notificationTapBackground,
|
||||
);
|
||||
|
||||
// Create a notification channel
|
||||
// إنشاء قناة الأندرويد ذات الأهمية القصوى
|
||||
const AndroidNotificationChannel channel = AndroidNotificationChannel(
|
||||
'high_importance_channel', // Use the same ID as in strings.xml
|
||||
'high_importance_channel',
|
||||
'High Importance Notifications',
|
||||
description: 'This channel is used for important notifications.',
|
||||
importance: Importance.high,
|
||||
importance: Importance.max, // أقصى أهمية
|
||||
playSound: true,
|
||||
);
|
||||
|
||||
// Register the channel with the system
|
||||
await _flutterLocalNotificationsPlugin
|
||||
.resolvePlatformSpecificImplementation<
|
||||
AndroidFlutterLocalNotificationsPlugin>()
|
||||
?.createNotificationChannel(channel);
|
||||
}
|
||||
|
||||
// Displays a notification with the given title and message
|
||||
// ==============================================================================
|
||||
// 2. دالة عرض الإشعار المطور (شكل واضح + أزرار + صوت مخصص)
|
||||
// ==============================================================================
|
||||
void showOrderNotification(
|
||||
String title, String body, String tone, String myListString) async {
|
||||
// أ) تنسيق النص والبيانات بشكل جميل
|
||||
String formattedBigText = body;
|
||||
String summaryText = 'طلب جديد';
|
||||
String price = '';
|
||||
|
||||
try {
|
||||
List<dynamic> data = jsonDecode(myListString);
|
||||
// استخراج البيانات (تأكد أن الاندكسات مطابقة للباك إند عندك)
|
||||
price = _getVal(data, 26);
|
||||
String distance = _getVal(data, 5);
|
||||
String startLoc = _getVal(data, 29);
|
||||
String endLoc = _getVal(data, 30);
|
||||
String paxName = _getVal(data, 8);
|
||||
// String rating = _getVal(data, 33);
|
||||
|
||||
// تنسيق النص ليكون 4 أسطر واضحة
|
||||
formattedBigText = "👤 $paxName\n"
|
||||
"💰 $price ${'SYP'.tr} | 🛣️ $distance كم\n"
|
||||
"🟢 من: $startLoc\n"
|
||||
"🏁 إلى: $endLoc";
|
||||
|
||||
summaryText = 'سعر الرحلة: $price';
|
||||
} catch (e) {
|
||||
print("Error formatting notification text: $e");
|
||||
}
|
||||
|
||||
// ب) نمط النص الكبير (BigText) للأندرويد
|
||||
BigTextStyleInformation bigTextStyleInformation = BigTextStyleInformation(
|
||||
formattedBigText,
|
||||
contentTitle: '🚖 $title',
|
||||
summaryText: summaryText,
|
||||
htmlFormatContent: true,
|
||||
htmlFormatContentTitle: true,
|
||||
);
|
||||
|
||||
// ج) معالجة اسم الصوت (أندرويد بدون امتداد، آيفون مع امتداد)
|
||||
String soundNameAndroid = tone.contains('.') ? tone.split('.').first : tone;
|
||||
String soundNameIOS = tone.contains('.') ? tone : "$tone.wav";
|
||||
|
||||
// د) إعدادات الأندرويد (الأزرار + Full Screen)
|
||||
final androidDetails = AndroidNotificationDetails(
|
||||
'high_importance_channel',
|
||||
'High Importance Notifications',
|
||||
importance: Importance.max,
|
||||
priority: Priority.max,
|
||||
fullScreenIntent: true, // يفتح الشاشة وتظهر التفاصيل
|
||||
category: AndroidNotificationCategory.call, // يعامل كمكالمة (رنين مستمر)
|
||||
visibility: NotificationVisibility.public,
|
||||
ongoing: true, // يمنع الحذف بالسحب
|
||||
sound: RawResourceAndroidNotificationSound(soundNameAndroid),
|
||||
audioAttributesUsage: AudioAttributesUsage.alarm, // صوت عالٍ كالمنبه
|
||||
styleInformation: bigTextStyleInformation,
|
||||
color: const Color(0xFF1A252F),
|
||||
|
||||
// الأزرار الثلاثة
|
||||
actions: <AndroidNotificationAction>[
|
||||
const AndroidNotificationAction(
|
||||
'ACCEPT_ORDER',
|
||||
'✅ قبول فوري',
|
||||
showsUserInterface: true,
|
||||
titleColor: Color(0xFF4CAF50), // أخضر
|
||||
),
|
||||
const AndroidNotificationAction(
|
||||
'SHOW_DETAILS',
|
||||
'📄 التفاصيل',
|
||||
showsUserInterface: true,
|
||||
titleColor: Color(0xFF2196F3), // أزرق
|
||||
),
|
||||
const AndroidNotificationAction(
|
||||
'REJECT_ORDER',
|
||||
'❌ رفض',
|
||||
showsUserInterface: false, // لا يفتح التطبيق
|
||||
cancelNotification: true,
|
||||
titleColor: Color(0xFFE53935), // أحمر
|
||||
),
|
||||
],
|
||||
);
|
||||
|
||||
// هـ) إعدادات الآيفون
|
||||
final iosDetails = DarwinNotificationDetails(
|
||||
sound: soundNameIOS,
|
||||
presentAlert: true,
|
||||
presentBadge: true,
|
||||
presentSound: true,
|
||||
categoryIdentifier: 'ORDER_CATEGORY', // ربط الأزرار
|
||||
interruptionLevel: InterruptionLevel.critical, // محاولة لكسر الصامت
|
||||
);
|
||||
|
||||
final details =
|
||||
NotificationDetails(android: androidDetails, iOS: iosDetails);
|
||||
|
||||
// عرض الإشعار
|
||||
await _flutterLocalNotificationsPlugin.show(
|
||||
1001, // ID ثابت لاستبدال الإشعار القديم
|
||||
title,
|
||||
"$price - مسافة $formattedBigText", // نص مختصر يظهر في البار العلوي
|
||||
details,
|
||||
payload: jsonEncode({
|
||||
'type': 'Order',
|
||||
'data': myListString,
|
||||
}),
|
||||
);
|
||||
}
|
||||
|
||||
// ==============================================================================
|
||||
// 3. معالجة الاستجابة (عند الضغط على الأزرار)
|
||||
// ==============================================================================
|
||||
Future<void> handleNotificationResponse(NotificationResponse response) async {
|
||||
final payload = response.payload;
|
||||
if (payload == null) return;
|
||||
|
||||
final payloadData = jsonDecode(payload) as Map<String, dynamic>;
|
||||
final rawData = payloadData['data'];
|
||||
|
||||
List<dynamic> listData = [];
|
||||
if (rawData is String) {
|
||||
listData = jsonDecode(rawData);
|
||||
} else if (rawData is List) {
|
||||
listData = rawData;
|
||||
}
|
||||
|
||||
print("🔔 Notification Action: ${response.actionId}");
|
||||
|
||||
// أ) زر القبول
|
||||
if (response.actionId == 'ACCEPT_ORDER') {
|
||||
await _flutterLocalNotificationsPlugin.cancel(1001); // حذف الإشعار
|
||||
_processAcceptOrder(listData);
|
||||
}
|
||||
|
||||
// ب) زر التفاصيل
|
||||
else if (response.actionId == 'SHOW_DETAILS') {
|
||||
// await _flutterLocalNotificationsPlugin.cancel(1001); // اختياري: حذف الإشعار
|
||||
Get.to(() => OrderRequestPage(), arguments: {'myListString': rawData});
|
||||
}
|
||||
|
||||
// ج) زر الرفض
|
||||
else if (response.actionId == 'REJECT_ORDER') {
|
||||
await _flutterLocalNotificationsPlugin.cancel(1001); // حذف الإشعار
|
||||
_processRejectOrder(listData);
|
||||
}
|
||||
|
||||
// د) الضغط على الإشعار نفسه (بدون أزرار)
|
||||
else {
|
||||
Get.to(() => OrderRequestPage(), arguments: {'myListString': rawData});
|
||||
}
|
||||
}
|
||||
|
||||
// ==============================================================================
|
||||
// 4. منطق القبول الآمن (Safe Accept Logic)
|
||||
// ==============================================================================
|
||||
Future<void> _processAcceptOrder(List<dynamic> data) async {
|
||||
// إظهار Loading
|
||||
Get.dialog(
|
||||
WillPopScope(
|
||||
onWillPop: () async => false,
|
||||
child: const Center(
|
||||
child: CircularProgressIndicator(color: Colors.white),
|
||||
),
|
||||
),
|
||||
barrierDismissible: false,
|
||||
);
|
||||
|
||||
try {
|
||||
final driverId = box.read(BoxName.driverID);
|
||||
String orderId = _getVal(data, 16);
|
||||
String passengerToken = _getVal(data, 9);
|
||||
|
||||
print("🚀 Sending Accept Request for Order: $orderId");
|
||||
|
||||
var res = await CRUD().post(
|
||||
link: "${AppLink.ride}/rides/acceptRide.php",
|
||||
payload: {
|
||||
'id': orderId,
|
||||
'rideTimeStart': DateTime.now().toString(),
|
||||
'status': 'Apply',
|
||||
'passengerToken': passengerToken,
|
||||
'driver_id': driverId,
|
||||
},
|
||||
);
|
||||
|
||||
print("📥 Server Response: $res");
|
||||
|
||||
if (Get.isDialogOpen == true) Get.back(); // إغلاق اللودينج
|
||||
|
||||
// 🔴 فحص النتيجة بدقة (Map أو String)
|
||||
bool isFailure = false;
|
||||
if (res is Map && res['status'] == 'failure') {
|
||||
isFailure = true;
|
||||
} else if (res == 'failure') {
|
||||
isFailure = true;
|
||||
}
|
||||
|
||||
if (isFailure) {
|
||||
Get.defaultDialog(
|
||||
title: "تنبيه",
|
||||
middleText: "عذراً، الطلب أخذه سائق آخر.",
|
||||
confirmTextColor: Colors.white,
|
||||
onConfirm: () => Get.back(),
|
||||
textConfirm: "حسناً",
|
||||
);
|
||||
return; // توقف هنا ولا تكمل
|
||||
}
|
||||
|
||||
// ✅ نجاح -> تجهيز الانتقال
|
||||
|
||||
// حماية من الكراش: التأكد من وجود HomeCaptainController
|
||||
if (!Get.isRegistered<HomeCaptainController>()) {
|
||||
print("♻️ Reviving HomeCaptainController...");
|
||||
Get.put(HomeCaptainController());
|
||||
} else {
|
||||
Get.find<HomeCaptainController>().changeRideId();
|
||||
}
|
||||
|
||||
box.write(BoxName.statusDriverLocation, 'on');
|
||||
box.write(BoxName.rideStatus, 'Apply');
|
||||
|
||||
var rideArgs = _buildRideArgs(data);
|
||||
box.write(BoxName.rideArguments, rideArgs);
|
||||
|
||||
// استخدام offAll لمنع الرجوع لصفحة الطلب
|
||||
Get.offAll(() => PassengerLocationMapPage(), arguments: rideArgs);
|
||||
} catch (e) {
|
||||
if (Get.isDialogOpen == true) Get.back();
|
||||
print("❌ Error in accept process: $e");
|
||||
Get.snackbar("خطأ", "حدث خطأ غير متوقع");
|
||||
}
|
||||
}
|
||||
|
||||
// ==============================================================================
|
||||
// 5. منطق الرفض (يعمل في الخلفية بدون فتح صفحات)
|
||||
// ==============================================================================
|
||||
Future<void> _processRejectOrder(List<dynamic> data) async {
|
||||
try {
|
||||
final driverId = box.read(BoxName.driverID);
|
||||
String orderId = _getVal(data, 16);
|
||||
|
||||
if (driverId != null && orderId.isNotEmpty) {
|
||||
print("📤 Rejecting Order: $orderId");
|
||||
await CRUD().post(link: AppLink.addDriverOrder, payload: {
|
||||
'driver_id': driverId,
|
||||
'order_id': orderId,
|
||||
'status': 'Refused'
|
||||
});
|
||||
print("✅ Order Rejected Successfully");
|
||||
}
|
||||
} catch (e) {
|
||||
print("❌ Error rejecting order: $e");
|
||||
}
|
||||
}
|
||||
|
||||
// ==============================================================================
|
||||
// 6. دوال مساعدة (Helpers)
|
||||
// ==============================================================================
|
||||
|
||||
Map<String, dynamic> _buildRideArgs(List<dynamic> data) {
|
||||
return {
|
||||
'passengerLocation': '${_getVal(data, 0)},${_getVal(data, 1)}',
|
||||
'passengerDestination': '${_getVal(data, 3)},${_getVal(data, 4)}',
|
||||
'Duration': _getVal(data, 4), // انتبه: تأكد من الإندكس الصحيح للوقت
|
||||
'totalCost': _getVal(data, 26),
|
||||
'Distance': _getVal(data, 5),
|
||||
'name': _getVal(data, 8),
|
||||
'phone': _getVal(data, 10),
|
||||
'email': _getVal(data, 28),
|
||||
'WalletChecked': _getVal(data, 13),
|
||||
'tokenPassenger': _getVal(data, 9),
|
||||
'direction':
|
||||
'https://www.google.com/maps/dir/${_getVal(data, 0)}/${_getVal(data, 1)}/',
|
||||
'DurationToPassenger': _getVal(data, 15),
|
||||
'rideId': _getVal(data, 16),
|
||||
'passengerId': _getVal(data, 7),
|
||||
'driverId': _getVal(data, 18),
|
||||
'durationOfRideValue': _getVal(data, 19),
|
||||
'paymentAmount': _getVal(data, 2),
|
||||
'paymentMethod': _getVal(data, 13) == 'true' ? 'visa' : 'cash',
|
||||
'isHaveSteps': _getVal(data, 20),
|
||||
'step0': _getVal(data, 21),
|
||||
'step1': _getVal(data, 22),
|
||||
'step2': _getVal(data, 23),
|
||||
'step3': _getVal(data, 24),
|
||||
'step4': _getVal(data, 25),
|
||||
'passengerWalletBurc': _getVal(data, 26),
|
||||
'timeOfOrder': DateTime.now().toString(),
|
||||
'totalPassenger': _getVal(data, 2),
|
||||
'carType': _getVal(data, 31),
|
||||
'kazan': _getVal(data, 32),
|
||||
'startNameLocation': _getVal(data, 29),
|
||||
'endNameLocation': _getVal(data, 30),
|
||||
};
|
||||
}
|
||||
|
||||
String _getVal(List<dynamic> data, int index) {
|
||||
if (data.length > index && data[index] != null) {
|
||||
return data[index].toString();
|
||||
}
|
||||
return '';
|
||||
}
|
||||
|
||||
// Callbacks
|
||||
void onDidReceiveNotificationResponse(NotificationResponse response) {
|
||||
handleNotificationResponse(response);
|
||||
}
|
||||
|
||||
void onDidReceiveBackgroundNotificationResponse(
|
||||
NotificationResponse response) {
|
||||
handleNotificationResponse(response);
|
||||
}
|
||||
|
||||
// ==============================================================================
|
||||
// 7. الدوال القديمة (Old Scheduled Notifications) - لم يتم تغييرها
|
||||
// ==============================================================================
|
||||
|
||||
void showNotification(
|
||||
String title, String message, String tone, String payLoad) async {
|
||||
// هذه الدالة القديمة للإشعارات البسيطة (ليس الطلبات)
|
||||
BigTextStyleInformation bigTextStyleInformation = BigTextStyleInformation(
|
||||
message,
|
||||
contentTitle: title.tr,
|
||||
@@ -68,7 +416,7 @@ class NotificationController extends GetxController {
|
||||
'High Importance Notifications',
|
||||
importance: Importance.max,
|
||||
priority: Priority.high,
|
||||
sound: RawResourceAndroidNotificationSound(tone),
|
||||
sound: RawResourceAndroidNotificationSound(tone.split('.').first),
|
||||
);
|
||||
|
||||
DarwinNotificationDetails ios = const DarwinNotificationDetails(
|
||||
@@ -84,126 +432,6 @@ class NotificationController extends GetxController {
|
||||
payload: jsonEncode({'title': title, 'data': payLoad}));
|
||||
}
|
||||
|
||||
void scheduleNotificationAtSpecificTime(
|
||||
String title, String message, String tone, int hour, int minute) async {
|
||||
// Initialize and set Cairo time zone
|
||||
tz.initializeTimeZones();
|
||||
var cairoLocation;
|
||||
if (box.read(BoxName.countryCode).toString() == 'Egypt') {
|
||||
cairoLocation = tz.getLocation('Africa/Cairo');
|
||||
} else {} // todo get for location country
|
||||
|
||||
final AndroidNotificationDetails android = AndroidNotificationDetails(
|
||||
'high_importance_channel',
|
||||
'High Importance Notifications',
|
||||
importance: Importance.max,
|
||||
priority: Priority.high,
|
||||
showWhen: false,
|
||||
sound: RawResourceAndroidNotificationSound(tone),
|
||||
);
|
||||
|
||||
const DarwinNotificationDetails ios = DarwinNotificationDetails(
|
||||
sound: 'default',
|
||||
presentAlert: true,
|
||||
presentBadge: true,
|
||||
presentSound: true,
|
||||
);
|
||||
|
||||
final NotificationDetails details =
|
||||
NotificationDetails(android: android, iOS: ios);
|
||||
|
||||
final now =
|
||||
tz.TZDateTime.now(cairoLocation); // Use Cairo timezone for current time
|
||||
tz.TZDateTime scheduledTime = tz.TZDateTime(
|
||||
cairoLocation, now.year, now.month, now.day, hour, minute);
|
||||
|
||||
// If the scheduled time has already passed for today, schedule it for the next day
|
||||
if (scheduledTime.isBefore(now)) {
|
||||
scheduledTime = scheduledTime.add(const Duration(days: 1));
|
||||
}
|
||||
|
||||
if (Platform.isAndroid) {
|
||||
if (await Permission.scheduleExactAlarm.isDenied) {
|
||||
if (await Permission.scheduleExactAlarm.request().isGranted) {
|
||||
print('SCHEDULE_EXACT_ALARM permission granted');
|
||||
} else {
|
||||
print('SCHEDULE_EXACT_ALARM permission denied');
|
||||
return;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
print('Current time: $now');
|
||||
print('Scheduling notification for: $scheduledTime');
|
||||
await _flutterLocalNotificationsPlugin.zonedSchedule(
|
||||
0,
|
||||
title,
|
||||
message,
|
||||
scheduledTime,
|
||||
details,
|
||||
// androidAllowWhileIdle: true,
|
||||
uiLocalNotificationDateInterpretation:
|
||||
UILocalNotificationDateInterpretation.absoluteTime,
|
||||
matchDateTimeComponents: DateTimeComponents.time,
|
||||
androidScheduleMode:
|
||||
AndroidScheduleMode.alarmClock, // Triggers daily at the same time
|
||||
);
|
||||
print('Notification scheduled successfully');
|
||||
}
|
||||
|
||||
void scheduleNotificationAfter1Minute(
|
||||
String title, String message, String tone) async {
|
||||
final AndroidNotificationDetails android = AndroidNotificationDetails(
|
||||
'high_importance_channel',
|
||||
'High Importance Notifications',
|
||||
importance: Importance.max,
|
||||
priority: Priority.high,
|
||||
showWhen: false,
|
||||
sound: RawResourceAndroidNotificationSound(tone),
|
||||
);
|
||||
|
||||
const DarwinNotificationDetails ios = DarwinNotificationDetails(
|
||||
sound: 'default',
|
||||
presentAlert: true,
|
||||
presentBadge: true,
|
||||
presentSound: true,
|
||||
);
|
||||
|
||||
final NotificationDetails details =
|
||||
NotificationDetails(android: android, iOS: ios);
|
||||
|
||||
// Schedule the notification to be shown after 1 minute
|
||||
Timer.periodic(const Duration(seconds: 15), (timer) async {
|
||||
final now = tz.TZDateTime.now(tz.local);
|
||||
final scheduledTime = now.add(const Duration(seconds: 10));
|
||||
Log.print('scheduledTime: ${scheduledTime}');
|
||||
if (Platform.isAndroid) {
|
||||
if (await Permission.scheduleExactAlarm.isDenied) {
|
||||
if (await Permission.scheduleExactAlarm.request().isGranted) {
|
||||
print('SCHEDULE_EXACT_ALARM permission granted');
|
||||
} else {
|
||||
print('SCHEDULE_EXACT_ALARM permission denied');
|
||||
return;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
print('Scheduling notification for: $scheduledTime');
|
||||
await _flutterLocalNotificationsPlugin.zonedSchedule(
|
||||
0,
|
||||
title,
|
||||
message,
|
||||
scheduledTime,
|
||||
details,
|
||||
uiLocalNotificationDateInterpretation:
|
||||
UILocalNotificationDateInterpretation.absoluteTime,
|
||||
matchDateTimeComponents: DateTimeComponents.time,
|
||||
androidScheduleMode: AndroidScheduleMode.alarmClock,
|
||||
);
|
||||
print('Notification scheduled successfully');
|
||||
});
|
||||
}
|
||||
|
||||
void scheduleNotificationsForSevenDays(
|
||||
String title, String message, String tone) async {
|
||||
final AndroidNotificationDetails android = AndroidNotificationDetails(
|
||||
@@ -211,7 +439,7 @@ class NotificationController extends GetxController {
|
||||
'High Importance Notifications',
|
||||
importance: Importance.max,
|
||||
priority: Priority.high,
|
||||
sound: RawResourceAndroidNotificationSound(tone),
|
||||
sound: RawResourceAndroidNotificationSound(tone.split('.').first),
|
||||
);
|
||||
|
||||
const DarwinNotificationDetails ios = DarwinNotificationDetails(
|
||||
@@ -224,35 +452,24 @@ class NotificationController extends GetxController {
|
||||
final NotificationDetails details =
|
||||
NotificationDetails(android: android, iOS: ios);
|
||||
|
||||
// Check for the exact alarm permission on Android 12 and above
|
||||
if (Platform.isAndroid) {
|
||||
if (await Permission.scheduleExactAlarm.isDenied) {
|
||||
if (await Permission.scheduleExactAlarm.request().isGranted) {
|
||||
print('SCHEDULE_EXACT_ALARM permission granted');
|
||||
} else {
|
||||
print('SCHEDULE_EXACT_ALARM permission denied');
|
||||
return;
|
||||
}
|
||||
await Permission.scheduleExactAlarm.request();
|
||||
}
|
||||
}
|
||||
|
||||
// Schedule notifications for the next 7 days
|
||||
for (int day = 0; day < 7; day++) {
|
||||
// List of notification times
|
||||
final notificationTimes = [
|
||||
{'hour': 8, 'minute': 0, 'id': day * 1000 + 1}, // 8:00 AM
|
||||
{'hour': 15, 'minute': 0, 'id': day * 1000 + 2}, // 3:00 PM
|
||||
{'hour': 20, 'minute': 0, 'id': day * 1000 + 3}, // 8:00 PM
|
||||
{'hour': 8, 'minute': 0, 'id': day * 1000 + 1},
|
||||
{'hour': 15, 'minute': 0, 'id': day * 1000 + 2},
|
||||
{'hour': 20, 'minute': 0, 'id': day * 1000 + 3},
|
||||
];
|
||||
|
||||
for (var time in notificationTimes) {
|
||||
final notificationId = time['id'] as int;
|
||||
|
||||
// Check if this notification ID is already stored
|
||||
bool isScheduled = box.read('notification_$notificationId') ?? false;
|
||||
|
||||
if (!isScheduled) {
|
||||
// Schedule the notification if not already scheduled
|
||||
await _scheduleNotificationForTime(
|
||||
day,
|
||||
time['hour'] as int,
|
||||
@@ -262,16 +479,19 @@ class NotificationController extends GetxController {
|
||||
details,
|
||||
notificationId,
|
||||
);
|
||||
|
||||
// Mark this notification ID as scheduled in GetStorage
|
||||
box.write('notification_$notificationId', true);
|
||||
} else {
|
||||
print('Notification with ID $notificationId is already scheduled.');
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
print('Notifications scheduled successfully for the next 7 days');
|
||||
// ==============================================================================
|
||||
// دالة حذف إشعار الطلب (تستدعى عند أخذ الطلب من سائق آخر)
|
||||
// ==============================================================================
|
||||
Future<void> cancelOrderNotification() async {
|
||||
// 1001 هو نفس الآيدي الذي استخدمناه عند عرض الإشعار
|
||||
await _flutterLocalNotificationsPlugin.cancel(1001);
|
||||
print("🗑️ Order Notification Cancelled (Taken by another driver)");
|
||||
}
|
||||
|
||||
Future<void> _scheduleNotificationForTime(
|
||||
@@ -283,30 +503,26 @@ class NotificationController extends GetxController {
|
||||
NotificationDetails details,
|
||||
int notificationId,
|
||||
) async {
|
||||
// Initialize and set Cairo timezone
|
||||
tz.initializeTimeZones();
|
||||
var cairoLocation = tz.getLocation('Africa/Cairo');
|
||||
var cairoLocation =
|
||||
tz.getLocation('Africa/Cairo'); // تأكد من المنطقة الزمنية
|
||||
|
||||
final now = tz.TZDateTime.now(cairoLocation);
|
||||
tz.TZDateTime scheduledDate = tz.TZDateTime(
|
||||
cairoLocation,
|
||||
now.year,
|
||||
now.month,
|
||||
now.day + dayOffset, // Add offset to schedule for the next days
|
||||
now.day + dayOffset,
|
||||
hour,
|
||||
minute,
|
||||
);
|
||||
|
||||
// If the scheduled time is in the past, move it to the next day
|
||||
if (scheduledDate.isBefore(now)) {
|
||||
scheduledDate = scheduledDate.add(Duration(days: 1));
|
||||
scheduledDate = scheduledDate.add(const Duration(days: 1));
|
||||
}
|
||||
|
||||
print('Current time (Cairo): $now');
|
||||
print('Scheduling notification for: $scheduledDate');
|
||||
|
||||
await _flutterLocalNotificationsPlugin.zonedSchedule(
|
||||
notificationId, // Unique ID for each notification
|
||||
notificationId,
|
||||
title,
|
||||
message,
|
||||
scheduledDate,
|
||||
@@ -314,243 +530,7 @@ class NotificationController extends GetxController {
|
||||
androidScheduleMode: AndroidScheduleMode.exact,
|
||||
uiLocalNotificationDateInterpretation:
|
||||
UILocalNotificationDateInterpretation.absoluteTime,
|
||||
matchDateTimeComponents:
|
||||
null, // Don't repeat automatically; we handle 7 days manually
|
||||
);
|
||||
|
||||
print('Notification scheduled successfully for: $scheduledDate');
|
||||
}
|
||||
|
||||
void scheduleNotificationEvery10Hours(
|
||||
String title, String message, String tone) async {
|
||||
final AndroidNotificationDetails android = AndroidNotificationDetails(
|
||||
'high_importance_channel',
|
||||
'High Importance Notifications',
|
||||
importance: Importance.max,
|
||||
priority: Priority.high,
|
||||
showWhen: false,
|
||||
sound: RawResourceAndroidNotificationSound(tone),
|
||||
);
|
||||
|
||||
const DarwinNotificationDetails ios = DarwinNotificationDetails(
|
||||
sound: 'default',
|
||||
presentAlert: true,
|
||||
presentBadge: true,
|
||||
presentSound: true,
|
||||
);
|
||||
|
||||
final NotificationDetails details =
|
||||
NotificationDetails(android: android, iOS: ios);
|
||||
|
||||
if (Platform.isAndroid) {
|
||||
if (await Permission.scheduleExactAlarm.isDenied) {
|
||||
if (await Permission.scheduleExactAlarm.request().isGranted) {
|
||||
print('SCHEDULE_EXACT_ALARM permission granted');
|
||||
} else {
|
||||
print('SCHEDULE_EXACT_ALARM permission denied');
|
||||
return;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
Timer.periodic(const Duration(hours: 10), (timer) async {
|
||||
final now = tz.TZDateTime.now(tz.local);
|
||||
final scheduledTime = now.add(const Duration(minutes: 10));
|
||||
|
||||
print('Scheduling notification for: $scheduledTime');
|
||||
await _flutterLocalNotificationsPlugin.zonedSchedule(
|
||||
0,
|
||||
title.tr,
|
||||
message.tr,
|
||||
scheduledTime,
|
||||
details,
|
||||
// androidAllowWhileIdle: true,
|
||||
uiLocalNotificationDateInterpretation:
|
||||
UILocalNotificationDateInterpretation.absoluteTime,
|
||||
matchDateTimeComponents: DateTimeComponents.time,
|
||||
androidScheduleMode: AndroidScheduleMode.alarmClock,
|
||||
);
|
||||
});
|
||||
|
||||
print('Notifications scheduled every 5 seconds');
|
||||
}
|
||||
|
||||
void showTimerNotification(String title, String message, String tone) async {
|
||||
final FlutterLocalNotificationsPlugin flutterLocalNotificationsPlugin =
|
||||
FlutterLocalNotificationsPlugin();
|
||||
|
||||
// Setup Android notification
|
||||
final AndroidNotificationDetails android = AndroidNotificationDetails(
|
||||
'high_importance_channel',
|
||||
'High Importance Notifications',
|
||||
importance: Importance.max,
|
||||
priority: Priority.high,
|
||||
showWhen: false,
|
||||
sound: RawResourceAndroidNotificationSound(
|
||||
tone), // tone without the file extension
|
||||
);
|
||||
|
||||
// Setup iOS notification
|
||||
const DarwinNotificationDetails ios = DarwinNotificationDetails(
|
||||
sound: 'default',
|
||||
presentAlert: true,
|
||||
presentBadge: true,
|
||||
presentSound: true,
|
||||
);
|
||||
|
||||
final NotificationDetails details =
|
||||
NotificationDetails(android: android, iOS: ios);
|
||||
|
||||
// Request permission on Android
|
||||
if (Platform.isAndroid) {
|
||||
if (await Permission.scheduleExactAlarm.isDenied) {
|
||||
if (await Permission.scheduleExactAlarm.request().isGranted) {
|
||||
print('SCHEDULE_EXACT_ALARM permission granted');
|
||||
} else {
|
||||
print('SCHEDULE_EXACT_ALARM permission denied');
|
||||
return;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Timer duration (e.g., 120 seconds countdown)
|
||||
int countdown = 12;
|
||||
|
||||
// Display the notification initially with the full countdown time
|
||||
|
||||
// Timer to update the notification every second
|
||||
Timer.periodic(const Duration(seconds: 1), (timer) async {
|
||||
// if (countdown > 0) {
|
||||
// Update the existing notification with the updated countdown
|
||||
|
||||
// Decrease the countdown by 1
|
||||
countdown--;
|
||||
// } else {
|
||||
// // Cancel the timer when the countdown reaches zero
|
||||
// timer.cancel();
|
||||
// }
|
||||
});
|
||||
await flutterLocalNotificationsPlugin.show(
|
||||
0,
|
||||
title,
|
||||
'$message Remaining: $countdown seconds', // Initial message
|
||||
details,
|
||||
);
|
||||
print('Notification will update every second');
|
||||
}
|
||||
|
||||
// Callback when the notification is tapped
|
||||
void onDidReceiveNotificationResponse(NotificationResponse response) {
|
||||
handleNotificationResponse(response);
|
||||
}
|
||||
|
||||
// Callback when the notification is tapped while the app is in the background
|
||||
void onDidReceiveBackgroundNotificationResponse(
|
||||
NotificationResponse response) {
|
||||
handleNotificationResponse(response);
|
||||
}
|
||||
|
||||
// Handle notification response for both foreground and background
|
||||
void handleNotificationResponse(NotificationResponse response) {
|
||||
print('Notification tapped!');
|
||||
Log.print('response.payload: ${response.payload}');
|
||||
if (response.payload != null) {
|
||||
print('Notification payload: ${response.payload}');
|
||||
var payloadData = jsonDecode(response.payload.toString());
|
||||
|
||||
if (payloadData is Map<String, dynamic>) {
|
||||
String title = payloadData['title'];
|
||||
var data = payloadData['data'];
|
||||
|
||||
switch (title) {
|
||||
case 'Order':
|
||||
_handleOrderNotification(data);
|
||||
break;
|
||||
case 'OrderSpeed':
|
||||
_handleOrderSpeedNotification(data);
|
||||
break;
|
||||
case 'ADS':
|
||||
_handleADSNotification();
|
||||
break;
|
||||
default:
|
||||
Log.print('Unknown notification type');
|
||||
}
|
||||
} else {
|
||||
Log.print('Invalid payload format');
|
||||
}
|
||||
} else {
|
||||
Log.print('Payload is null');
|
||||
}
|
||||
}
|
||||
|
||||
void _handleOrderNotification(dynamic data) {
|
||||
if (data is String) {
|
||||
var orderData = jsonDecode(data);
|
||||
if (orderData is List && orderData.length == 34) {
|
||||
//closeOverLay();
|
||||
Get.put(HomeCaptainController()).changeRideId();
|
||||
Get.to(() => OrderRequestPage(), arguments: {'myListString': data});
|
||||
} else {
|
||||
Log.print('Invalid order data');
|
||||
}
|
||||
} else {
|
||||
Log.print('Invalid order payload');
|
||||
}
|
||||
}
|
||||
|
||||
void _handleOrderSpeedNotification(dynamic data) {
|
||||
if (data is String) {
|
||||
var orderData = jsonDecode(data);
|
||||
if (orderData is List && orderData.length == 34) {
|
||||
//closeOverLay();
|
||||
Get.put(HomeCaptainController()).changeRideId();
|
||||
Get.to(() => OrderRequestPage(), arguments: {'myListString': data});
|
||||
} else {
|
||||
Log.print('Invalid order data');
|
||||
}
|
||||
} else {
|
||||
Log.print('Invalid order payload');
|
||||
}
|
||||
}
|
||||
|
||||
void _handleADSNotification() {
|
||||
// var orderData = jsonDecode(data);
|
||||
//closeOverLay();
|
||||
Get.to(
|
||||
() => const NotificationCaptain(),
|
||||
matchDateTimeComponents: null,
|
||||
);
|
||||
}
|
||||
|
||||
void onDidReceiveLocalNotification(
|
||||
int id, String? title, String? body, String? payload) async {
|
||||
// display a dialog with the notification details, tap ok to go to another page
|
||||
}
|
||||
}
|
||||
|
||||
class NotificationController1 extends GetxController {
|
||||
final FlutterLocalNotificationsPlugin _flutterLocalNotificationsPlugin =
|
||||
FlutterLocalNotificationsPlugin();
|
||||
|
||||
// Initializes the local notifications plugin
|
||||
Future<void> initNotifications() async {
|
||||
const AndroidInitializationSettings android =
|
||||
AndroidInitializationSettings('@mipmap/launcher_icon');
|
||||
const InitializationSettings initializationSettings =
|
||||
InitializationSettings(android: android);
|
||||
await _flutterLocalNotificationsPlugin.initialize(initializationSettings);
|
||||
}
|
||||
|
||||
// Displays a notification with the given title and message
|
||||
void showNotification(
|
||||
String title, String message, String tone, String payLoad) async {
|
||||
AndroidNotificationDetails android = AndroidNotificationDetails(
|
||||
'your channel id', 'your channel name',
|
||||
importance: Importance.max,
|
||||
priority: Priority.high,
|
||||
showWhen: false,
|
||||
sound: RawResourceAndroidNotificationSound(tone));
|
||||
|
||||
NotificationDetails details = NotificationDetails(android: android);
|
||||
await _flutterLocalNotificationsPlugin.show(0, title, message, details);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -298,7 +298,7 @@ class LocationController extends GetxController with WidgetsBindingObserver {
|
||||
driverList.add(rideData[key.toString()]);
|
||||
}
|
||||
}
|
||||
|
||||
/////////
|
||||
// 3. التنقل (باستخدام الاسم لضمان عمل الشرط)
|
||||
try {
|
||||
if (await FlutterOverlayWindow.isActive()) {
|
||||
|
||||
@@ -18,6 +18,7 @@ import '../../../print.dart';
|
||||
import '../../../views/home/Captin/driver_map_page.dart';
|
||||
import '../../../views/home/Captin/orderCaptin/marker_generator.dart';
|
||||
import '../../../views/widgets/mydialoug.dart';
|
||||
import '../../firebase/local_notification.dart';
|
||||
import '../../functions/crud.dart';
|
||||
import '../../functions/location_controller.dart';
|
||||
import '../../home/captin/home_captain_controller.dart';
|
||||
@@ -484,6 +485,8 @@ class OrderRequestController extends GetxController
|
||||
if (takenRideId == myCurrentRideId && whoTookIt != myDriverId) {
|
||||
_isRideTakenHandled = true;
|
||||
endTimer();
|
||||
// 1. حذف الإشعار من شريط التنبيهات فوراً
|
||||
NotificationController().cancelOrderNotification();
|
||||
if (Get.isSnackbarOpen) Get.closeCurrentSnackbar();
|
||||
if (Get.isDialogOpen ?? false) Get.back();
|
||||
Get.back();
|
||||
@@ -518,6 +521,7 @@ class OrderRequestController extends GetxController
|
||||
endTimer();
|
||||
_stopAudio();
|
||||
|
||||
// 1. إرسال الطلب
|
||||
var res = await CRUD()
|
||||
.post(link: "${AppLink.ride}/rides/acceptRide.php", payload: {
|
||||
'id': _safeGet(16),
|
||||
@@ -527,13 +531,35 @@ class OrderRequestController extends GetxController
|
||||
'driver_id': box.read(BoxName.driverID),
|
||||
});
|
||||
|
||||
if (res == 'failure') {
|
||||
Log.print('res from orderrequestpage: ${res}');
|
||||
|
||||
// ============================================================
|
||||
// تصحيح: فحص الرد بدقة (Map أو String)
|
||||
// ============================================================
|
||||
bool isFailure = false;
|
||||
|
||||
if (res is Map && res['status'] == 'failure') {
|
||||
isFailure = true;
|
||||
} else if (res == 'failure') {
|
||||
isFailure = true;
|
||||
}
|
||||
|
||||
if (isFailure) {
|
||||
// ⛔ حالة الفشل: الطلب مأخوذ
|
||||
MyDialog().getDialog("عذراً، الطلب أخذه سائق آخر.", '', () {
|
||||
Get.back();
|
||||
Get.back();
|
||||
Get.back(); // إغلاق الديالوج
|
||||
Get.back(); // العودة للصفحة الرئيسية (إغلاق صفحة الطلب)
|
||||
});
|
||||
} else {
|
||||
Get.put(HomeCaptainController()).changeRideId();
|
||||
// ✅ حالة النجاح
|
||||
|
||||
// حماية من الكراش: التأكد من وجود HomeCaptainController قبل استخدامه
|
||||
if (!Get.isRegistered<HomeCaptainController>()) {
|
||||
Get.put(HomeCaptainController());
|
||||
} else {
|
||||
Get.find<HomeCaptainController>().changeRideId();
|
||||
}
|
||||
|
||||
box.write(BoxName.statusDriverLocation, 'on');
|
||||
changeApplied();
|
||||
|
||||
@@ -573,6 +599,8 @@ class OrderRequestController extends GetxController
|
||||
};
|
||||
|
||||
box.write(BoxName.rideArguments, rideArgs);
|
||||
|
||||
// الانتقال النهائي
|
||||
Get.off(() => PassengerLocationMapPage(), arguments: rideArgs);
|
||||
}
|
||||
}
|
||||
|
||||
131
lib/main.dart
131
lib/main.dart
@@ -155,68 +155,103 @@ Future<void> createAllNotificationChannels() async {
|
||||
}
|
||||
|
||||
/// ============ Handlers: Background ============
|
||||
// في ملف main.dart (خارج كلاس MyApp)
|
||||
|
||||
@pragma('vm:entry-point')
|
||||
Future<void> backgroundMessageHandler(RemoteMessage message) async {
|
||||
// 1. تهيئة بيئة فلاتر في الخلفية
|
||||
WidgetsFlutterBinding.ensureInitialized();
|
||||
await initFirebaseIfNeeded();
|
||||
await GetStorage.init();
|
||||
|
||||
if (!Get.isRegistered<NotificationController>()) {
|
||||
Get.put(NotificationController());
|
||||
}
|
||||
if (!Get.isRegistered<FirebaseMessagesController>()) {
|
||||
Get.put(FirebaseMessagesController());
|
||||
}
|
||||
// 2. تهيئة الكونترولر (لأنه isolate منفصل)
|
||||
// ملاحظة: تأكد أنك لا تعتمد على Context هنا
|
||||
final NotificationController notificationController =
|
||||
NotificationController();
|
||||
|
||||
if (!await FlutterOverlayWindow.isPermissionGranted()) {
|
||||
Log.print("Overlay permission not granted; showing only notification.");
|
||||
}
|
||||
// مهم جداً: إعادة تهيئة الإشعارات داخل هذه العملية المنفصلة
|
||||
await notificationController.initNotifications();
|
||||
|
||||
if (Platform.isAndroid) {
|
||||
String category = message.data['category'] ?? '';
|
||||
if (message.notification != null) {
|
||||
Log.print('message.notification!.title: ${message.notification!.title}');
|
||||
print("🟢 Background Message Received: ${message.data}");
|
||||
|
||||
if (category == 'Order' || category == 'OrderSpeed') {
|
||||
final myListString = message.data['DriverList'] ?? '[]';
|
||||
Log.print('myListString: $myListString');
|
||||
// 3. استخراج البيانات (الآن العنوان والنص داخل data وليس notification)
|
||||
String? title = message.data['title'];
|
||||
String? body = message.data['body'];
|
||||
String? tone = message.data['tone'] ?? 'order';
|
||||
String? myListString = message.data['DriverList'];
|
||||
|
||||
List<dynamic> myList;
|
||||
try {
|
||||
myList = jsonDecode(myListString) as List<dynamic>;
|
||||
} catch (e) {
|
||||
Log.print('Error decoding JSON: $e');
|
||||
myList = [];
|
||||
}
|
||||
|
||||
final isOverlayActive = await FlutterOverlayWindow.isActive();
|
||||
if (isOverlayActive) {
|
||||
await FlutterOverlayWindow.shareData(myList);
|
||||
} else {
|
||||
await FlutterOverlayWindow.showOverlay(
|
||||
enableDrag: true,
|
||||
flag: OverlayFlag.focusPointer,
|
||||
positionGravity: PositionGravity.auto,
|
||||
height: WindowSize.matchParent,
|
||||
width: WindowSize.matchParent,
|
||||
startPosition: const OverlayPosition(0, -30),
|
||||
);
|
||||
await FlutterOverlayWindow.shareData(myList);
|
||||
}
|
||||
|
||||
NotificationController().showNotification(
|
||||
message.notification!.title.toString(),
|
||||
message.notification!.body.toString(),
|
||||
'order',
|
||||
// 4. شرط الأمان: التأكد من وجود البيانات المطلوبة
|
||||
if (title != null && body != null && myListString != null) {
|
||||
// 5. عرض الإشعار المحلي
|
||||
notificationController.showOrderNotification(
|
||||
title,
|
||||
body,
|
||||
'ding.wav',
|
||||
myListString,
|
||||
);
|
||||
} else {
|
||||
FirebaseMessagesController().fireBaseTitles(message);
|
||||
}
|
||||
}
|
||||
print("⚠️ Received empty data message or missing fields.");
|
||||
}
|
||||
}
|
||||
// @pragma('vm:entry-point')
|
||||
// Future<void> backgroundMessageHandler(RemoteMessage message) async {
|
||||
// WidgetsFlutterBinding.ensureInitialized();
|
||||
// await initFirebaseIfNeeded();
|
||||
// await GetStorage.init();
|
||||
|
||||
// if (!Get.isRegistered<NotificationController>()) {
|
||||
// Get.put(NotificationController());
|
||||
// }
|
||||
// if (!Get.isRegistered<FirebaseMessagesController>()) {
|
||||
// Get.put(FirebaseMessagesController());
|
||||
// }
|
||||
|
||||
// if (!await FlutterOverlayWindow.isPermissionGranted()) {
|
||||
// Log.print("Overlay permission not granted; showing only notification.");
|
||||
// }
|
||||
|
||||
// if (Platform.isAndroid) {
|
||||
// String category = message.data['category'] ?? '';
|
||||
// Log.print('category: ${category}');
|
||||
// // if (message.notification != null) {
|
||||
// // Log.print('message.notification!.title: ${message.notification!.title}');
|
||||
|
||||
// if (category == 'Order' || category == 'OrderSpeed') {
|
||||
// final myListString = message.data['DriverList'] ?? '[]';
|
||||
// Log.print('myListString: $myListString');
|
||||
|
||||
// List<dynamic> myList;
|
||||
// try {
|
||||
// myList = jsonDecode(myListString) as List<dynamic>;
|
||||
// } catch (e) {
|
||||
// Log.print('Error decoding JSON: $e');
|
||||
// myList = [];
|
||||
// }
|
||||
|
||||
// // final isOverlayActive = await FlutterOverlayWindow.isActive();
|
||||
// // if (isOverlayActive) {
|
||||
// // await FlutterOverlayWindow.shareData(myList);
|
||||
// // } else {
|
||||
// // await FlutterOverlayWindow.showOverlay(
|
||||
// // enableDrag: true,
|
||||
// // flag: OverlayFlag.focusPointer,
|
||||
// // positionGravity: PositionGravity.auto,
|
||||
// // height: WindowSize.matchParent,
|
||||
// // width: WindowSize.matchParent,
|
||||
// // startPosition: const OverlayPosition(0, -30),
|
||||
// // );
|
||||
// // await FlutterOverlayWindow.shareData(myList);
|
||||
// // }
|
||||
// NotificationController().showOrderNotification(
|
||||
// message.notification?.title ?? "طلب جديد", // العنوان
|
||||
// message.notification?.body ?? "لديك طلب توصيل جديد", // النص الأساسي
|
||||
// 'ding', // اسم نغمة الإشعار (تأكد أنها موجودة في raw)
|
||||
// myListString, // البيانات القادمة من السيرفر (JSON String List)
|
||||
// );
|
||||
// } else {
|
||||
// FirebaseMessagesController().fireBaseTitles(message);
|
||||
// }
|
||||
// // }
|
||||
// }
|
||||
// }
|
||||
|
||||
@pragma('vm:entry-point')
|
||||
void notificationTapBackground(NotificationResponse notificationResponse) {
|
||||
|
||||
@@ -103,7 +103,7 @@ GetBuilder<HomeCaptainController> leftMainMenuCaptainIcons() {
|
||||
// Get.snackbar(
|
||||
// '${'Please wait'.tr} $minutesLeft ${"minutes before trying again.".tr}',
|
||||
// '');
|
||||
NotificationController1().showNotification(
|
||||
NotificationController().showNotification(
|
||||
'Intaleq Driver'.tr,
|
||||
'${'Please wait'.tr} $minutesLeft ${"minutes before trying again.".tr}',
|
||||
'ding',
|
||||
|
||||
@@ -216,29 +216,15 @@ class _OrderOverlayState extends State<OrderOverlay>
|
||||
await _closeOverlay();
|
||||
return;
|
||||
}
|
||||
var res = await CRUD().post(
|
||||
link: "${AppLink.ride}/rides/updateStausFromSpeed.php",
|
||||
payload: {
|
||||
var res = await CRUD()
|
||||
.post(link: "${AppLink.ride}/rides/acceptRide.php", payload: {
|
||||
'id': orderData!.orderId,
|
||||
'rideTimeStart': DateTime.now().toString(),
|
||||
'status': 'Apply',
|
||||
'driver_id': box.read(BoxName.driverID),
|
||||
'passengerToken': _getData(9),
|
||||
});
|
||||
List<String> bodyToPassenger = [
|
||||
_getData(6).toString(),
|
||||
_getData(8).toString(),
|
||||
_getData(9).toString(),
|
||||
];
|
||||
|
||||
NotificationService.sendNotification(
|
||||
target: _getData(9).toString(),
|
||||
title: "Accepted Ride".tr,
|
||||
body: 'your ride is Accepted'.tr,
|
||||
isTopic: false, // Important: this is a token
|
||||
tone: 'start',
|
||||
driverList: bodyToPassenger,
|
||||
category: 'Accepted Ride',
|
||||
);
|
||||
final payload = {
|
||||
// بيانات أساسية
|
||||
'driver_id': driverId,
|
||||
@@ -275,7 +261,6 @@ class _OrderOverlayState extends State<OrderOverlay>
|
||||
'timeOfOrder': DateTime.now().toIso8601String(),
|
||||
'totalPassenger': _getData(2),
|
||||
};
|
||||
Log.print('myList: ${myList}');
|
||||
Log.print('payload: ${payload}');
|
||||
CRUD().post(
|
||||
link: AppLink.addOverLayStatus,
|
||||
@@ -283,11 +268,6 @@ class _OrderOverlayState extends State<OrderOverlay>
|
||||
);
|
||||
if (res != "failure") {
|
||||
// Using rideId (_getData(16)) for order_id consistently
|
||||
CRUD().post(link: AppLink.addDriverOrder, payload: {
|
||||
'driver_id': driverId, // Driver ID from the order data
|
||||
'order_id': orderData!.orderId,
|
||||
'status': 'Apply'
|
||||
});
|
||||
|
||||
_log("Server update successful. Writing to storage.");
|
||||
notificationController.showNotification(
|
||||
|
||||
Reference in New Issue
Block a user