Files
Siro/siro_driver/lib/controller/food_delivery/food_notification_service.dart
T

157 lines
6.3 KiB
Dart

// food_notification_service.dart — إشعارات وحدة التوصيل، منفصلة كلياً عن إشعارات الرحلات
//
// الفصل مقصود على ثلاثة مستويات:
// 1) قناتان أندرويد خاصتان (food_delivery_offer_channel / food_delivery_status_channel)
// غير 'high_importance_channel' الخاص بالرحلات — فيقدر السائق يكتم أو يغيّر
// نغمة التوصيل من إعدادات النظام دون أن يمسّ تنبيه الرحلات إطلاقاً.
// 2) معرّفات إشعارات مستقلة (7001/7002) فلا يستبدل إشعار توصيل إشعارَ رحلة.
// 3) أزرار وحمولة خاصة ('FOOD_ACCEPT'/'FOOD_REJECT' + {"food_event": ...})
// يلتقطها فرع مستقل في handleNotificationResponse.
import 'dart:convert';
import 'dart:ui';
import 'package:flutter_local_notifications/flutter_local_notifications.dart';
import 'food_delivery_models.dart';
class FoodNotificationService {
FoodNotificationService._();
static final FoodNotificationService instance = FoodNotificationService._();
// نفس نسخة الإضافة المستخدمة في NotificationController (singleton داخل الحزمة)،
// لكن بقنوات ومعرّفات خاصة بنا — لا نستدعي initialize هنا إطلاقاً حتى لا
// نستبدل معالِج النقر العام الذي هيّأه تطبيق الرحلات عند الإقلاع.
final FlutterLocalNotificationsPlugin _plugin = FlutterLocalNotificationsPlugin();
static const String offerChannelId = 'food_delivery_offer_channel';
static const String statusChannelId = 'food_delivery_status_channel';
static const int offerNotificationId = 7001;
static const int statusNotificationId = 7002;
bool _channelsReady = false;
Future<void> ensureChannels() async {
if (_channelsReady) return;
final android = _plugin.resolvePlatformSpecificImplementation<
AndroidFlutterLocalNotificationsPlugin>();
if (android == null) {
_channelsReady = true; // iOS — لا قنوات
return;
}
const offerChannel = AndroidNotificationChannel(
offerChannelId,
'عروض التوصيل',
description: 'تنبيه وصول عرض توصيل طلب طعام جديد',
importance: Importance.max,
playSound: true,
sound: RawResourceAndroidNotificationSound('order1'),
enableVibration: true,
);
const statusChannel = AndroidNotificationChannel(
statusChannelId,
'تحديثات طلبات التوصيل',
description: 'تغيّر حالة طلب توصيل قائم (جاهز، ملغى، إلخ)',
importance: Importance.high,
playSound: true,
);
await android.createNotificationChannel(offerChannel);
await android.createNotificationChannel(statusChannel);
_channelsReady = true;
}
/// إشعار عرض توصيل — ملء الشاشة (fullScreenIntent) كإشعار المكالمة، لأن مهلة
/// العرض 20 ثانية ولا تحتمل أن يمرّ السائق على شريط الإشعارات لاحقاً.
Future<void> showOfferNotification(FoodDeliveryOffer offer) async {
await ensureChannels();
final body = '${offer.merchantNameAr}\n'
'💰 أجرة التوصيل: ${foodFormatPrice(offer.deliveryFee)}'
'${offer.itemsCount > 0 ? ' | 🧾 ${offer.itemsCount} صنف' : ''}'
'${offer.cashToCollect > 0 ? '\n💵 تحصيل نقدي: ${foodFormatPrice(offer.cashToCollect)}' : ''}';
final androidDetails = AndroidNotificationDetails(
offerChannelId,
'عروض التوصيل',
importance: Importance.max,
priority: Priority.max,
fullScreenIntent: true,
category: AndroidNotificationCategory.call,
visibility: NotificationVisibility.public,
timeoutAfter: 20000, // يختفي مع انتهاء مهلة العرض نفسها
sound: const RawResourceAndroidNotificationSound('order1'),
audioAttributesUsage: AudioAttributesUsage.alarm,
color: const Color(0xFFFF6B35),
styleInformation: BigTextStyleInformation(
body,
contentTitle: '🍔 عرض توصيل جديد',
summaryText: foodFormatPrice(offer.deliveryFee),
),
actions: const <AndroidNotificationAction>[
AndroidNotificationAction('FOOD_ACCEPT', '✅ قبول',
showsUserInterface: true, titleColor: Color(0xFF4CAF50)),
AndroidNotificationAction('FOOD_REJECT', '❌ رفض',
cancelNotification: true, titleColor: Color(0xFFE53935)),
],
);
const iosDetails = DarwinNotificationDetails(
sound: 'order1.wav',
presentAlert: true,
presentBadge: true,
presentSound: true,
categoryIdentifier: 'FOOD_OFFER_CATEGORY',
interruptionLevel: InterruptionLevel.timeSensitive,
);
await _plugin.show(
id: offerNotificationId,
title: '🍔 عرض توصيل جديد',
body: '${offer.merchantNameAr} — ${foodFormatPrice(offer.deliveryFee)}',
notificationDetails:
NotificationDetails(android: androidDetails, iOS: iosDetails),
payload: jsonEncode({
'food_event': 'offer',
'order_id': offer.orderId,
}),
);
}
Future<void> cancelOfferNotification() async {
await _plugin.cancel(id: offerNotificationId);
}
/// تحديث حالة مهمة قائمة (مثلاً إلغاء من النظام) — قناة أهدأ، بلا ملء شاشة
Future<void> showStatusNotification({
required int orderId,
required String title,
required String body,
}) async {
await ensureChannels();
const androidDetails = AndroidNotificationDetails(
statusChannelId,
'تحديثات طلبات التوصيل',
importance: Importance.high,
priority: Priority.high,
color: Color(0xFFFF6B35),
);
const iosDetails = DarwinNotificationDetails(
presentAlert: true,
presentSound: true,
);
await _plugin.show(
id: statusNotificationId,
title: title,
body: body,
notificationDetails:
const NotificationDetails(android: androidDetails, iOS: iosDetails),
payload: jsonEncode({'food_event': 'status', 'order_id': orderId}),
);
}
}