26-1-22/1
This commit is contained in:
@@ -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,10 @@ 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> _scheduleNotificationForTime(
|
||||
@@ -283,30 +494,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 +521,7 @@ class NotificationController extends GetxController {
|
||||
androidScheduleMode: AndroidScheduleMode.exact,
|
||||
uiLocalNotificationDateInterpretation:
|
||||
UILocalNotificationDateInterpretation.absoluteTime,
|
||||
matchDateTimeComponents:
|
||||
null, // Don't repeat automatically; we handle 7 days manually
|
||||
matchDateTimeComponents: null,
|
||||
);
|
||||
|
||||
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);
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user