26-1-22/1
This commit is contained in:
@@ -10,6 +10,7 @@
|
|||||||
<uses-permission android:name="android.permission.FOREGROUND_SERVICE_TYPE_REMOTE_MESSAGING" />
|
<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.USE_BIOMETRIC" />
|
||||||
<uses-permission android:name="android.permission.VIBRATE" />
|
<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.ACCESS_NETWORK_STATE" />
|
||||||
<uses-permission android:name="android.permission.CAMERA" />
|
<uses-permission android:name="android.permission.CAMERA" />
|
||||||
<uses-permission android:name="android.permission.RECORD_AUDIO" />
|
<uses-permission android:name="android.permission.RECORD_AUDIO" />
|
||||||
|
|||||||
@@ -384,7 +384,7 @@ class LoginDriverController extends GetxController {
|
|||||||
textConfirm: 'Verify'.tr,
|
textConfirm: 'Verify'.tr,
|
||||||
confirmTextColor: Colors.white,
|
confirmTextColor: Colors.white,
|
||||||
onConfirm: () {
|
onConfirm: () {
|
||||||
// Get.back();
|
Get.back();
|
||||||
// انتقل لصفحة OTP الجديدة
|
// انتقل لصفحة OTP الجديدة
|
||||||
Get.to(
|
Get.to(
|
||||||
() => OtpVerificationPage(
|
() => OtpVerificationPage(
|
||||||
|
|||||||
@@ -90,7 +90,7 @@ class OtpVerificationController extends GetxController {
|
|||||||
|
|
||||||
if (response != 'failure') {
|
if (response != 'failure') {
|
||||||
Log.print('response: ${response}');
|
Log.print('response: ${response}');
|
||||||
Get.back(); // توجه إلى الصفحة التالية
|
// Get.back(); // توجه إلى الصفحة التالية
|
||||||
await CRUD().post(
|
await CRUD().post(
|
||||||
link: '${AppLink.paymentServer}/auth/token/update_driver_auth.php',
|
link: '${AppLink.paymentServer}/auth/token/update_driver_auth.php',
|
||||||
payload: {
|
payload: {
|
||||||
@@ -98,17 +98,7 @@ class OtpVerificationController extends GetxController {
|
|||||||
'fingerPrint': finger.toString(),
|
'fingerPrint': finger.toString(),
|
||||||
'captain_id': box.read(BoxName.driverID).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(
|
await NotificationService.sendNotification(
|
||||||
target: ptoken.toString(),
|
target: ptoken.toString(),
|
||||||
title: 'token change'.tr,
|
title: 'token change'.tr,
|
||||||
|
|||||||
@@ -110,9 +110,9 @@ class FirebaseMessagesController extends GetxController {
|
|||||||
switch (category) {
|
switch (category) {
|
||||||
case 'ORDER':
|
case 'ORDER':
|
||||||
case 'Order': // Handle both cases for backward compatibility
|
case 'Order': // Handle both cases for backward compatibility
|
||||||
if (Platform.isAndroid) {
|
// if (Platform.isAndroid) {
|
||||||
notificationController.showNotification(title, body, 'order', '');
|
// notificationController.showNotification(title, body, 'order', '');
|
||||||
}
|
// }
|
||||||
var myListString = message.data['DriverList'];
|
var myListString = message.data['DriverList'];
|
||||||
if (myListString != null) {
|
if (myListString != null) {
|
||||||
var myList = jsonDecode(myListString) as List<dynamic>;
|
var myList = jsonDecode(myListString) as List<dynamic>;
|
||||||
|
|||||||
@@ -1,62 +1,410 @@
|
|||||||
import 'dart:async';
|
import 'dart:async';
|
||||||
import 'dart:convert';
|
import 'dart:convert';
|
||||||
import 'dart:io';
|
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:flutter_local_notifications/flutter_local_notifications.dart';
|
||||||
import 'package:get/get.dart';
|
import 'package:get/get.dart';
|
||||||
import 'package:permission_handler/permission_handler.dart';
|
import 'package:permission_handler/permission_handler.dart';
|
||||||
|
|
||||||
import 'package:timezone/data/latest.dart' as tz;
|
import 'package:timezone/data/latest.dart' as tz;
|
||||||
import 'package:timezone/timezone.dart' as tz;
|
import 'package:timezone/timezone.dart' as tz;
|
||||||
|
|
||||||
import '../../constant/box_name.dart';
|
import '../../constant/box_name.dart';
|
||||||
import '../../main.dart';
|
import '../../constant/links.dart';
|
||||||
|
import '../../main.dart'; // للوصول لـ box
|
||||||
import '../../print.dart';
|
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';
|
import '../home/captin/home_captain_controller.dart';
|
||||||
|
|
||||||
class NotificationController extends GetxController {
|
class NotificationController extends GetxController {
|
||||||
final FlutterLocalNotificationsPlugin _flutterLocalNotificationsPlugin =
|
final FlutterLocalNotificationsPlugin _flutterLocalNotificationsPlugin =
|
||||||
FlutterLocalNotificationsPlugin();
|
FlutterLocalNotificationsPlugin();
|
||||||
|
|
||||||
|
// ==============================================================================
|
||||||
|
// 1. تهيئة الإشعارات (إعداد القنوات والأزرار للآيفون والأندرويد)
|
||||||
|
// ==============================================================================
|
||||||
Future<void> initNotifications() async {
|
Future<void> initNotifications() async {
|
||||||
|
// إعدادات الأندرويد
|
||||||
const AndroidInitializationSettings android =
|
const AndroidInitializationSettings android =
|
||||||
AndroidInitializationSettings('@mipmap/launcher_icon');
|
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,
|
requestAlertPermission: true,
|
||||||
requestBadgePermission: true,
|
requestBadgePermission: true,
|
||||||
requestSoundPermission: true,
|
requestSoundPermission: true,
|
||||||
// onDidReceiveLocalNotification:
|
notificationCategories: darwinNotificationCategories, // تسجيل الأزرار
|
||||||
// (int id, String? title, String? body, String? payload) async {},
|
|
||||||
);
|
);
|
||||||
|
|
||||||
InitializationSettings initializationSettings =
|
InitializationSettings initializationSettings =
|
||||||
InitializationSettings(android: android, iOS: ios);
|
InitializationSettings(android: android, iOS: ios);
|
||||||
|
|
||||||
tz.initializeTimeZones();
|
tz.initializeTimeZones();
|
||||||
print('Notifications initialized');
|
print('✅ Notifications initialized with Action Buttons Support');
|
||||||
|
|
||||||
await _flutterLocalNotificationsPlugin.initialize(
|
await _flutterLocalNotificationsPlugin.initialize(
|
||||||
initializationSettings,
|
initializationSettings,
|
||||||
onDidReceiveNotificationResponse: onDidReceiveNotificationResponse,
|
onDidReceiveNotificationResponse: onDidReceiveNotificationResponse,
|
||||||
onDidReceiveBackgroundNotificationResponse: notificationTapBackground,
|
onDidReceiveBackgroundNotificationResponse: notificationTapBackground,
|
||||||
);
|
);
|
||||||
|
|
||||||
// Create a notification channel
|
// إنشاء قناة الأندرويد ذات الأهمية القصوى
|
||||||
const AndroidNotificationChannel channel = AndroidNotificationChannel(
|
const AndroidNotificationChannel channel = AndroidNotificationChannel(
|
||||||
'high_importance_channel', // Use the same ID as in strings.xml
|
'high_importance_channel',
|
||||||
'High Importance Notifications',
|
'High Importance Notifications',
|
||||||
description: 'This channel is used for important 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
|
await _flutterLocalNotificationsPlugin
|
||||||
.resolvePlatformSpecificImplementation<
|
.resolvePlatformSpecificImplementation<
|
||||||
AndroidFlutterLocalNotificationsPlugin>()
|
AndroidFlutterLocalNotificationsPlugin>()
|
||||||
?.createNotificationChannel(channel);
|
?.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(
|
void showNotification(
|
||||||
String title, String message, String tone, String payLoad) async {
|
String title, String message, String tone, String payLoad) async {
|
||||||
|
// هذه الدالة القديمة للإشعارات البسيطة (ليس الطلبات)
|
||||||
BigTextStyleInformation bigTextStyleInformation = BigTextStyleInformation(
|
BigTextStyleInformation bigTextStyleInformation = BigTextStyleInformation(
|
||||||
message,
|
message,
|
||||||
contentTitle: title.tr,
|
contentTitle: title.tr,
|
||||||
@@ -68,7 +416,7 @@ class NotificationController extends GetxController {
|
|||||||
'High Importance Notifications',
|
'High Importance Notifications',
|
||||||
importance: Importance.max,
|
importance: Importance.max,
|
||||||
priority: Priority.high,
|
priority: Priority.high,
|
||||||
sound: RawResourceAndroidNotificationSound(tone),
|
sound: RawResourceAndroidNotificationSound(tone.split('.').first),
|
||||||
);
|
);
|
||||||
|
|
||||||
DarwinNotificationDetails ios = const DarwinNotificationDetails(
|
DarwinNotificationDetails ios = const DarwinNotificationDetails(
|
||||||
@@ -84,126 +432,6 @@ class NotificationController extends GetxController {
|
|||||||
payload: jsonEncode({'title': title, 'data': payLoad}));
|
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(
|
void scheduleNotificationsForSevenDays(
|
||||||
String title, String message, String tone) async {
|
String title, String message, String tone) async {
|
||||||
final AndroidNotificationDetails android = AndroidNotificationDetails(
|
final AndroidNotificationDetails android = AndroidNotificationDetails(
|
||||||
@@ -211,7 +439,7 @@ class NotificationController extends GetxController {
|
|||||||
'High Importance Notifications',
|
'High Importance Notifications',
|
||||||
importance: Importance.max,
|
importance: Importance.max,
|
||||||
priority: Priority.high,
|
priority: Priority.high,
|
||||||
sound: RawResourceAndroidNotificationSound(tone),
|
sound: RawResourceAndroidNotificationSound(tone.split('.').first),
|
||||||
);
|
);
|
||||||
|
|
||||||
const DarwinNotificationDetails ios = DarwinNotificationDetails(
|
const DarwinNotificationDetails ios = DarwinNotificationDetails(
|
||||||
@@ -224,35 +452,24 @@ class NotificationController extends GetxController {
|
|||||||
final NotificationDetails details =
|
final NotificationDetails details =
|
||||||
NotificationDetails(android: android, iOS: ios);
|
NotificationDetails(android: android, iOS: ios);
|
||||||
|
|
||||||
// Check for the exact alarm permission on Android 12 and above
|
|
||||||
if (Platform.isAndroid) {
|
if (Platform.isAndroid) {
|
||||||
if (await Permission.scheduleExactAlarm.isDenied) {
|
if (await Permission.scheduleExactAlarm.isDenied) {
|
||||||
if (await Permission.scheduleExactAlarm.request().isGranted) {
|
await Permission.scheduleExactAlarm.request();
|
||||||
print('SCHEDULE_EXACT_ALARM permission granted');
|
|
||||||
} else {
|
|
||||||
print('SCHEDULE_EXACT_ALARM permission denied');
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// Schedule notifications for the next 7 days
|
|
||||||
for (int day = 0; day < 7; day++) {
|
for (int day = 0; day < 7; day++) {
|
||||||
// List of notification times
|
|
||||||
final notificationTimes = [
|
final notificationTimes = [
|
||||||
{'hour': 8, 'minute': 0, 'id': day * 1000 + 1}, // 8:00 AM
|
{'hour': 8, 'minute': 0, 'id': day * 1000 + 1},
|
||||||
{'hour': 15, 'minute': 0, 'id': day * 1000 + 2}, // 3:00 PM
|
{'hour': 15, 'minute': 0, 'id': day * 1000 + 2},
|
||||||
{'hour': 20, 'minute': 0, 'id': day * 1000 + 3}, // 8:00 PM
|
{'hour': 20, 'minute': 0, 'id': day * 1000 + 3},
|
||||||
];
|
];
|
||||||
|
|
||||||
for (var time in notificationTimes) {
|
for (var time in notificationTimes) {
|
||||||
final notificationId = time['id'] as int;
|
final notificationId = time['id'] as int;
|
||||||
|
|
||||||
// Check if this notification ID is already stored
|
|
||||||
bool isScheduled = box.read('notification_$notificationId') ?? false;
|
bool isScheduled = box.read('notification_$notificationId') ?? false;
|
||||||
|
|
||||||
if (!isScheduled) {
|
if (!isScheduled) {
|
||||||
// Schedule the notification if not already scheduled
|
|
||||||
await _scheduleNotificationForTime(
|
await _scheduleNotificationForTime(
|
||||||
day,
|
day,
|
||||||
time['hour'] as int,
|
time['hour'] as int,
|
||||||
@@ -262,16 +479,10 @@ class NotificationController extends GetxController {
|
|||||||
details,
|
details,
|
||||||
notificationId,
|
notificationId,
|
||||||
);
|
);
|
||||||
|
|
||||||
// Mark this notification ID as scheduled in GetStorage
|
|
||||||
box.write('notification_$notificationId', true);
|
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> _scheduleNotificationForTime(
|
Future<void> _scheduleNotificationForTime(
|
||||||
@@ -283,30 +494,26 @@ class NotificationController extends GetxController {
|
|||||||
NotificationDetails details,
|
NotificationDetails details,
|
||||||
int notificationId,
|
int notificationId,
|
||||||
) async {
|
) async {
|
||||||
// Initialize and set Cairo timezone
|
|
||||||
tz.initializeTimeZones();
|
tz.initializeTimeZones();
|
||||||
var cairoLocation = tz.getLocation('Africa/Cairo');
|
var cairoLocation =
|
||||||
|
tz.getLocation('Africa/Cairo'); // تأكد من المنطقة الزمنية
|
||||||
|
|
||||||
final now = tz.TZDateTime.now(cairoLocation);
|
final now = tz.TZDateTime.now(cairoLocation);
|
||||||
tz.TZDateTime scheduledDate = tz.TZDateTime(
|
tz.TZDateTime scheduledDate = tz.TZDateTime(
|
||||||
cairoLocation,
|
cairoLocation,
|
||||||
now.year,
|
now.year,
|
||||||
now.month,
|
now.month,
|
||||||
now.day + dayOffset, // Add offset to schedule for the next days
|
now.day + dayOffset,
|
||||||
hour,
|
hour,
|
||||||
minute,
|
minute,
|
||||||
);
|
);
|
||||||
|
|
||||||
// If the scheduled time is in the past, move it to the next day
|
|
||||||
if (scheduledDate.isBefore(now)) {
|
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(
|
await _flutterLocalNotificationsPlugin.zonedSchedule(
|
||||||
notificationId, // Unique ID for each notification
|
notificationId,
|
||||||
title,
|
title,
|
||||||
message,
|
message,
|
||||||
scheduledDate,
|
scheduledDate,
|
||||||
@@ -314,243 +521,7 @@ class NotificationController extends GetxController {
|
|||||||
androidScheduleMode: AndroidScheduleMode.exact,
|
androidScheduleMode: AndroidScheduleMode.exact,
|
||||||
uiLocalNotificationDateInterpretation:
|
uiLocalNotificationDateInterpretation:
|
||||||
UILocalNotificationDateInterpretation.absoluteTime,
|
UILocalNotificationDateInterpretation.absoluteTime,
|
||||||
matchDateTimeComponents:
|
matchDateTimeComponents: null,
|
||||||
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(),
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
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);
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -513,11 +513,12 @@ class OrderRequestController extends GetxController
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// Accept Order Logic
|
// Accept Order Logic
|
||||||
Future<void> acceptOrder() async {
|
Future<void> acceptOrder() async {
|
||||||
endTimer();
|
endTimer();
|
||||||
_stopAudio();
|
_stopAudio();
|
||||||
|
|
||||||
|
// 1. إرسال الطلب
|
||||||
var res = await CRUD()
|
var res = await CRUD()
|
||||||
.post(link: "${AppLink.ride}/rides/acceptRide.php", payload: {
|
.post(link: "${AppLink.ride}/rides/acceptRide.php", payload: {
|
||||||
'id': _safeGet(16),
|
'id': _safeGet(16),
|
||||||
@@ -527,13 +528,35 @@ class OrderRequestController extends GetxController
|
|||||||
'driver_id': box.read(BoxName.driverID),
|
'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("عذراً، الطلب أخذه سائق آخر.", '', () {
|
MyDialog().getDialog("عذراً، الطلب أخذه سائق آخر.", '', () {
|
||||||
Get.back();
|
Get.back(); // إغلاق الديالوج
|
||||||
Get.back();
|
Get.back(); // العودة للصفحة الرئيسية (إغلاق صفحة الطلب)
|
||||||
});
|
});
|
||||||
} else {
|
} else {
|
||||||
Get.put(HomeCaptainController()).changeRideId();
|
// ✅ حالة النجاح
|
||||||
|
|
||||||
|
// حماية من الكراش: التأكد من وجود HomeCaptainController قبل استخدامه
|
||||||
|
if (!Get.isRegistered<HomeCaptainController>()) {
|
||||||
|
Get.put(HomeCaptainController());
|
||||||
|
} else {
|
||||||
|
Get.find<HomeCaptainController>().changeRideId();
|
||||||
|
}
|
||||||
|
|
||||||
box.write(BoxName.statusDriverLocation, 'on');
|
box.write(BoxName.statusDriverLocation, 'on');
|
||||||
changeApplied();
|
changeApplied();
|
||||||
|
|
||||||
@@ -573,6 +596,8 @@ class OrderRequestController extends GetxController
|
|||||||
};
|
};
|
||||||
|
|
||||||
box.write(BoxName.rideArguments, rideArgs);
|
box.write(BoxName.rideArguments, rideArgs);
|
||||||
|
|
||||||
|
// الانتقال النهائي
|
||||||
Get.off(() => PassengerLocationMapPage(), arguments: rideArgs);
|
Get.off(() => PassengerLocationMapPage(), arguments: rideArgs);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
137
lib/main.dart
137
lib/main.dart
@@ -155,68 +155,103 @@ Future<void> createAllNotificationChannels() async {
|
|||||||
}
|
}
|
||||||
|
|
||||||
/// ============ Handlers: Background ============
|
/// ============ Handlers: Background ============
|
||||||
|
// في ملف main.dart (خارج كلاس MyApp)
|
||||||
|
|
||||||
@pragma('vm:entry-point')
|
@pragma('vm:entry-point')
|
||||||
Future<void> backgroundMessageHandler(RemoteMessage message) async {
|
Future<void> backgroundMessageHandler(RemoteMessage message) async {
|
||||||
|
// 1. تهيئة بيئة فلاتر في الخلفية
|
||||||
WidgetsFlutterBinding.ensureInitialized();
|
WidgetsFlutterBinding.ensureInitialized();
|
||||||
await initFirebaseIfNeeded();
|
|
||||||
await GetStorage.init();
|
|
||||||
|
|
||||||
if (!Get.isRegistered<NotificationController>()) {
|
// 2. تهيئة الكونترولر (لأنه isolate منفصل)
|
||||||
Get.put(NotificationController());
|
// ملاحظة: تأكد أنك لا تعتمد على Context هنا
|
||||||
}
|
final NotificationController notificationController =
|
||||||
if (!Get.isRegistered<FirebaseMessagesController>()) {
|
NotificationController();
|
||||||
Get.put(FirebaseMessagesController());
|
|
||||||
}
|
|
||||||
|
|
||||||
if (!await FlutterOverlayWindow.isPermissionGranted()) {
|
// مهم جداً: إعادة تهيئة الإشعارات داخل هذه العملية المنفصلة
|
||||||
Log.print("Overlay permission not granted; showing only notification.");
|
await notificationController.initNotifications();
|
||||||
}
|
|
||||||
|
|
||||||
if (Platform.isAndroid) {
|
print("🟢 Background Message Received: ${message.data}");
|
||||||
String category = message.data['category'] ?? '';
|
|
||||||
if (message.notification != null) {
|
|
||||||
Log.print('message.notification!.title: ${message.notification!.title}');
|
|
||||||
|
|
||||||
if (category == 'Order' || category == 'OrderSpeed') {
|
// 3. استخراج البيانات (الآن العنوان والنص داخل data وليس notification)
|
||||||
final myListString = message.data['DriverList'] ?? '[]';
|
String? title = message.data['title'];
|
||||||
Log.print('myListString: $myListString');
|
String? body = message.data['body'];
|
||||||
|
String? tone = message.data['tone'] ?? 'order';
|
||||||
|
String? myListString = message.data['DriverList'];
|
||||||
|
|
||||||
List<dynamic> myList;
|
// 4. شرط الأمان: التأكد من وجود البيانات المطلوبة
|
||||||
try {
|
if (title != null && body != null && myListString != null) {
|
||||||
myList = jsonDecode(myListString) as List<dynamic>;
|
// 5. عرض الإشعار المحلي
|
||||||
} catch (e) {
|
notificationController.showOrderNotification(
|
||||||
Log.print('Error decoding JSON: $e');
|
title,
|
||||||
myList = [];
|
body,
|
||||||
}
|
'ding.wav',
|
||||||
|
myListString,
|
||||||
final isOverlayActive = await FlutterOverlayWindow.isActive();
|
);
|
||||||
if (isOverlayActive) {
|
} else {
|
||||||
await FlutterOverlayWindow.shareData(myList);
|
print("⚠️ Received empty data message or missing fields.");
|
||||||
} 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',
|
|
||||||
myListString,
|
|
||||||
);
|
|
||||||
} else {
|
|
||||||
FirebaseMessagesController().fireBaseTitles(message);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
// @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')
|
@pragma('vm:entry-point')
|
||||||
void notificationTapBackground(NotificationResponse notificationResponse) {
|
void notificationTapBackground(NotificationResponse notificationResponse) {
|
||||||
|
|||||||
@@ -103,7 +103,7 @@ GetBuilder<HomeCaptainController> leftMainMenuCaptainIcons() {
|
|||||||
// Get.snackbar(
|
// Get.snackbar(
|
||||||
// '${'Please wait'.tr} $minutesLeft ${"minutes before trying again.".tr}',
|
// '${'Please wait'.tr} $minutesLeft ${"minutes before trying again.".tr}',
|
||||||
// '');
|
// '');
|
||||||
NotificationController1().showNotification(
|
NotificationController().showNotification(
|
||||||
'Intaleq Driver'.tr,
|
'Intaleq Driver'.tr,
|
||||||
'${'Please wait'.tr} $minutesLeft ${"minutes before trying again.".tr}',
|
'${'Please wait'.tr} $minutesLeft ${"minutes before trying again.".tr}',
|
||||||
'ding',
|
'ding',
|
||||||
|
|||||||
@@ -216,29 +216,15 @@ class _OrderOverlayState extends State<OrderOverlay>
|
|||||||
await _closeOverlay();
|
await _closeOverlay();
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
var res = await CRUD().post(
|
var res = await CRUD()
|
||||||
link: "${AppLink.ride}/rides/updateStausFromSpeed.php",
|
.post(link: "${AppLink.ride}/rides/acceptRide.php", payload: {
|
||||||
payload: {
|
'id': orderData!.orderId,
|
||||||
'id': orderData!.orderId,
|
'rideTimeStart': DateTime.now().toString(),
|
||||||
'rideTimeStart': DateTime.now().toString(),
|
'status': 'Apply',
|
||||||
'status': 'Apply',
|
'driver_id': box.read(BoxName.driverID),
|
||||||
'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 = {
|
final payload = {
|
||||||
// بيانات أساسية
|
// بيانات أساسية
|
||||||
'driver_id': driverId,
|
'driver_id': driverId,
|
||||||
@@ -275,7 +261,6 @@ class _OrderOverlayState extends State<OrderOverlay>
|
|||||||
'timeOfOrder': DateTime.now().toIso8601String(),
|
'timeOfOrder': DateTime.now().toIso8601String(),
|
||||||
'totalPassenger': _getData(2),
|
'totalPassenger': _getData(2),
|
||||||
};
|
};
|
||||||
Log.print('myList: ${myList}');
|
|
||||||
Log.print('payload: ${payload}');
|
Log.print('payload: ${payload}');
|
||||||
CRUD().post(
|
CRUD().post(
|
||||||
link: AppLink.addOverLayStatus,
|
link: AppLink.addOverLayStatus,
|
||||||
@@ -283,11 +268,6 @@ class _OrderOverlayState extends State<OrderOverlay>
|
|||||||
);
|
);
|
||||||
if (res != "failure") {
|
if (res != "failure") {
|
||||||
// Using rideId (_getData(16)) for order_id consistently
|
// 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.");
|
_log("Server update successful. Writing to storage.");
|
||||||
notificationController.showNotification(
|
notificationController.showNotification(
|
||||||
|
|||||||
Reference in New Issue
Block a user