diff --git a/android/app/src/main/AndroidManifest.xml b/android/app/src/main/AndroidManifest.xml
index d9c5314..4950264 100644
--- a/android/app/src/main/AndroidManifest.xml
+++ b/android/app/src/main/AndroidManifest.xml
@@ -10,6 +10,7 @@
+
diff --git a/lib/controller/auth/captin/login_captin_controller.dart b/lib/controller/auth/captin/login_captin_controller.dart
index 23e88e6..e2e59b0 100755
--- a/lib/controller/auth/captin/login_captin_controller.dart
+++ b/lib/controller/auth/captin/login_captin_controller.dart
@@ -384,7 +384,7 @@ class LoginDriverController extends GetxController {
textConfirm: 'Verify'.tr,
confirmTextColor: Colors.white,
onConfirm: () {
- // Get.back();
+ Get.back();
// انتقل لصفحة OTP الجديدة
Get.to(
() => OtpVerificationPage(
diff --git a/lib/controller/auth/captin/opt_token_controller.dart b/lib/controller/auth/captin/opt_token_controller.dart
index 931d362..976860e 100644
--- a/lib/controller/auth/captin/opt_token_controller.dart
+++ b/lib/controller/auth/captin/opt_token_controller.dart
@@ -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()
- ? Get.find()
- : 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,
diff --git a/lib/controller/firebase/firbase_messge.dart b/lib/controller/firebase/firbase_messge.dart
index 9e44f26..77b448b 100755
--- a/lib/controller/firebase/firbase_messge.dart
+++ b/lib/controller/firebase/firbase_messge.dart
@@ -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;
diff --git a/lib/controller/firebase/local_notification.dart b/lib/controller/firebase/local_notification.dart
index 6b8caae..25c7b09 100755
--- a/lib/controller/firebase/local_notification.dart
+++ b/lib/controller/firebase/local_notification.dart
@@ -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 initNotifications() async {
+ // إعدادات الأندرويد
const AndroidInitializationSettings android =
AndroidInitializationSettings('@mipmap/launcher_icon');
- DarwinInitializationSettings ios = DarwinInitializationSettings(
+
+ // إعدادات أزرار الآيفون (Categories)
+ // هذا الجزء ضروري لظهور الأزرار في iOS
+ final List 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 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: [
+ 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 handleNotificationResponse(NotificationResponse response) async {
+ final payload = response.payload;
+ if (payload == null) return;
+
+ final payloadData = jsonDecode(payload) as Map;
+ final rawData = payloadData['data'];
+
+ List 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 _processAcceptOrder(List 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()) {
+ print("♻️ Reviving HomeCaptainController...");
+ Get.put(HomeCaptainController());
+ } else {
+ Get.find().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 _processRejectOrder(List 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 _buildRideArgs(List 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 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 _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 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 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);
}
}
diff --git a/lib/controller/home/captin/order_request_controller.dart b/lib/controller/home/captin/order_request_controller.dart
index 73c4aa8..4f0d2f1 100755
--- a/lib/controller/home/captin/order_request_controller.dart
+++ b/lib/controller/home/captin/order_request_controller.dart
@@ -513,11 +513,12 @@ class OrderRequestController extends GetxController
}
}
- // Accept Order Logic
+// Accept Order Logic
Future acceptOrder() async {
endTimer();
_stopAudio();
+ // 1. إرسال الطلب
var res = await CRUD()
.post(link: "${AppLink.ride}/rides/acceptRide.php", payload: {
'id': _safeGet(16),
@@ -527,13 +528,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()) {
+ Get.put(HomeCaptainController());
+ } else {
+ Get.find().changeRideId();
+ }
+
box.write(BoxName.statusDriverLocation, 'on');
changeApplied();
@@ -573,6 +596,8 @@ class OrderRequestController extends GetxController
};
box.write(BoxName.rideArguments, rideArgs);
+
+ // الانتقال النهائي
Get.off(() => PassengerLocationMapPage(), arguments: rideArgs);
}
}
diff --git a/lib/main.dart b/lib/main.dart
index 9ad84c2..6b9522f 100755
--- a/lib/main.dart
+++ b/lib/main.dart
@@ -155,68 +155,103 @@ Future createAllNotificationChannels() async {
}
/// ============ Handlers: Background ============
+// في ملف main.dart (خارج كلاس MyApp)
@pragma('vm:entry-point')
Future backgroundMessageHandler(RemoteMessage message) async {
+ // 1. تهيئة بيئة فلاتر في الخلفية
WidgetsFlutterBinding.ensureInitialized();
- await initFirebaseIfNeeded();
- await GetStorage.init();
- if (!Get.isRegistered()) {
- Get.put(NotificationController());
- }
- if (!Get.isRegistered()) {
- 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 myList;
- try {
- myList = jsonDecode(myListString) as List;
- } 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',
- myListString,
- );
- } else {
- FirebaseMessagesController().fireBaseTitles(message);
- }
- }
+ // 4. شرط الأمان: التأكد من وجود البيانات المطلوبة
+ if (title != null && body != null && myListString != null) {
+ // 5. عرض الإشعار المحلي
+ notificationController.showOrderNotification(
+ title,
+ body,
+ 'ding.wav',
+ myListString,
+ );
+ } else {
+ print("⚠️ Received empty data message or missing fields.");
}
}
+// @pragma('vm:entry-point')
+// Future backgroundMessageHandler(RemoteMessage message) async {
+// WidgetsFlutterBinding.ensureInitialized();
+// await initFirebaseIfNeeded();
+// await GetStorage.init();
+
+// if (!Get.isRegistered()) {
+// Get.put(NotificationController());
+// }
+// if (!Get.isRegistered()) {
+// 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 myList;
+// try {
+// myList = jsonDecode(myListString) as List;
+// } 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) {
diff --git a/lib/views/home/Captin/home_captain/widget/left_menu_map_captain.dart b/lib/views/home/Captin/home_captain/widget/left_menu_map_captain.dart
index 5a11610..d78a971 100755
--- a/lib/views/home/Captin/home_captain/widget/left_menu_map_captain.dart
+++ b/lib/views/home/Captin/home_captain/widget/left_menu_map_captain.dart
@@ -103,7 +103,7 @@ GetBuilder 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',
diff --git a/lib/views/home/Captin/orderCaptin/order_over_lay.dart b/lib/views/home/Captin/orderCaptin/order_over_lay.dart
index a29290b..4c0dc54 100755
--- a/lib/views/home/Captin/orderCaptin/order_over_lay.dart
+++ b/lib/views/home/Captin/orderCaptin/order_over_lay.dart
@@ -216,29 +216,15 @@ class _OrderOverlayState extends State
await _closeOverlay();
return;
}
- var res = await CRUD().post(
- link: "${AppLink.ride}/rides/updateStausFromSpeed.php",
- payload: {
- 'id': orderData!.orderId,
- 'rideTimeStart': DateTime.now().toString(),
- 'status': 'Apply',
- 'driver_id': box.read(BoxName.driverID),
- });
- List bodyToPassenger = [
- _getData(6).toString(),
- _getData(8).toString(),
- _getData(9).toString(),
- ];
+ 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),
+ });
- 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
'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
);
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(