import 'dart:developer' as dev; import 'dart:io'; import 'dart:math' as math; import 'package:alarm/alarm.dart'; import 'package:firebase_core/firebase_core.dart'; import 'package:firebase_messaging/firebase_messaging.dart' hide NotificationSettings; import 'package:flutter/material.dart'; import 'package:persistent_bottom_nav_bar_v2/persistent_bottom_nav_bar_v2.dart'; import 'package:flutter_local_notifications/flutter_local_notifications.dart'; import '../utils/app_logger_utils.dart'; import '../../firebase_options.dart'; import '../../main.dart'; import 'alarm_services.dart'; class NotificationServices { static final FlutterLocalNotificationsPlugin _notificationsPlugin = FlutterLocalNotificationsPlugin(); final FirebaseMessaging _messaging = FirebaseMessaging.instance; AlarmServices alarmServices = AlarmServices(); Future initialize() async { await initializeLocalNotifications(); dev.log("NotificationService initialized"); } Future initializeLocalNotifications() async { try { const AndroidInitializationSettings androidInitializationSettings = AndroidInitializationSettings('@mipmap/ic_launcher'); const DarwinInitializationSettings iosInitializationSettings = DarwinInitializationSettings(); const InitializationSettings initializationSettings = InitializationSettings( android: androidInitializationSettings, iOS: iosInitializationSettings, ); await _notificationsPlugin.initialize( initializationSettings, onDidReceiveNotificationResponse: (NotificationResponse response) { dev.log( "Người dùng click thông báo ở foreground với payload: ${response.payload}"); handleMessage(response.payload, controller); }, ); dev.log("Local notifications initialized"); } catch (e) { dev.log("Error initializing local notifications: $e"); } } void firebaseInit(BuildContext context) { FirebaseMessaging.onMessage.listen((message) { dev.log("Foreground message payload: ${message.toMap()}"); _showNotification(message); }); } void isTokenRefresh() { _messaging.onTokenRefresh.listen((newToken) { dev.log("Refresh Firebase Messaging Token: $newToken"); }); } static Future requestNotificationPermission() async { try { if (Platform.isAndroid) { return await _notificationsPlugin .resolvePlatformSpecificImplementation< AndroidFlutterLocalNotificationsPlugin>() ?.requestNotificationsPermission(); } else if (Platform.isIOS) { return await _notificationsPlugin .resolvePlatformSpecificImplementation< IOSFlutterLocalNotificationsPlugin>() ?.requestPermissions(alert: true, sound: true, badge: true); } return null; } catch (e) { dev.log("Error requesting notification permission: $e"); return null; } } Future _showNotification(RemoteMessage message) async { try { // Early validation of notification data final title = message.notification?.title ?? "Title" as String?; final body = message.notification?.body ?? "Body" as String?; final type = message.data['type'] as String? ?? 'normal'; if (title == null || body == null) { dev.log('Skipping notification: missing title or body', name: 'Notification'); return; } // Handle smoke warning notifications if (type == 'smoke_warning') { await alarmServices.showAlarm(title, body); dev.log('Displayed smoke warning notification', name: 'Notification'); return; } // Create notification channel final channelId = math.Random.secure().nextInt(1000000).toString(); const channelName = 'High Importance Notification'; const channelDescription = 'Channel description'; // final androidChannel = AndroidNotificationChannel( // channelId, // channelName, // importance: Importance.max, // description: channelDescription, // ); final androidPlugin = _notificationsPlugin.resolvePlatformSpecificImplementation< AndroidFlutterLocalNotificationsPlugin>(); // Delete existing channel to prevent conflicts await androidPlugin?.deleteNotificationChannel(channelId); // Configure notification details final androidDetails = AndroidNotificationDetails( channelId, channelName, channelDescription: channelDescription, sound: getSound(type), importance: Importance.max, priority: Priority.high, ticker: 'ticker', ); const iosDetails = DarwinNotificationDetails( presentAlert: true, presentBadge: true, presentBanner: true, presentSound: true, ); final notificationDetails = NotificationDetails( android: androidDetails, iOS: iosDetails, ); // Show notification await _notificationsPlugin.show( int.parse(channelId), title, body, notificationDetails, payload: type, ); dev.log( 'Displayed notification - title: $title, body: $body, type: $type', name: 'Notification', ); } catch (e, stackTrace) { dev.log( 'Failed to show notification: $e', name: 'Notification', error: e, stackTrace: stackTrace, ); } } AndroidNotificationSound getSound(String type) { if (type == "welcome") { return const RawResourceAndroidNotificationSound("welcome"); } else if (type == "success") { return const RawResourceAndroidNotificationSound("success_alert"); } else if (type == "smoke_warning") { return const RawResourceAndroidNotificationSound("warning_alarm"); } else if (type == "battery_warning") { return const RawResourceAndroidNotificationSound("new_alarm"); } else { return const RawResourceAndroidNotificationSound("normal"); } } void handleMessage(String? payload, PersistentTabController controller) { dev.log("Handling notification tap with payload: $payload"); controller.jumpToTab(1); } Future setupInteractMessage(PersistentTabController controller) async { // Khi app terminated RemoteMessage? initialMessage = await FirebaseMessaging.instance.getInitialMessage(); if (initialMessage != null) { WidgetsBinding.instance.addPostFrameCallback((_) { try { handleMessage(initialMessage.data['type'], controller); } catch (e, stack) { dev.log("Error handling initial message: $e\n$stack"); } }); } // Khi app ở background FirebaseMessaging.onMessageOpenedApp.listen((message) { try { handleMessage(message.data['type'], controller); } catch (e, stack) { dev.log("Error in onMessageOpenedApp: $e\n$stack"); } }); } Future showBackgroundOrTerminateNotification( RemoteMessage message) async { try { // Early validation of notification data final title = message.data['title'] as String?; final body = message.data['body'] as String?; final type = message.data['type'] as String? ?? 'normal'; if (title == null || body == null) { dev.log('Skipping notification: missing title or body', name: 'Notification'); return; } // Create notification channel final channelId = math.Random.secure().nextInt(1000000).toString(); const channelName = 'High Importance Notification'; const channelDescription = 'Channel description'; // Configure notification details final androidDetails = AndroidNotificationDetails( channelId, channelName, channelDescription: channelDescription, sound: getSound(type), importance: Importance.max, priority: Priority.high, ticker: 'ticker', ); const iosDetails = DarwinNotificationDetails( presentAlert: true, presentBadge: true, presentBanner: true, presentSound: true, ); final notificationDetails = NotificationDetails( android: androidDetails, iOS: iosDetails, ); // Show notification await _notificationsPlugin.show( int.parse(channelId), title, body, notificationDetails, payload: type, ); dev.log( 'Displayed notification - title: $title, body: $body, type: $type', name: 'Notification', ); } catch (e, stackTrace) { dev.log( 'Failed to show notification: $e', name: 'Notification', error: e, stackTrace: stackTrace, ); } } } @pragma('vm:entry-point') Future firebaseMessagingBackgroundHandler(RemoteMessage message) async { try { AppLoggerUtils.warning("Background handler started: ${message.data}"); await Alarm.init(); if (Firebase.apps.isEmpty) { await Firebase.initializeApp( options: DefaultFirebaseOptions.currentPlatform, name: "sfm-notification"); AppLoggerUtils.warning( "Firebase đã được khởi tạo trong background handler"); } else { AppLoggerUtils.warning("Firebase đã được khởi tạo trước đó"); } AppLoggerUtils.warning(message.toString()); AppLoggerUtils.warning(message.data.toString()); AppLoggerUtils.warning(message.data["notification"].toString()); String? title = message.data['title']; String? body = message.data['body']; String type = message.data['type'] ?? "normal"; final notificationService = NotificationServices(); await notificationService.initialize(); final alarmSettings = AlarmSettings( id: 42, dateTime: DateTime.now(), assetAudioPath: 'assets/sounds/warning_alarm.mp3', loopAudio: true, vibrate: true, warningNotificationOnKill: Platform.isIOS, androidFullScreenIntent: true, volumeSettings: VolumeSettings.fade( volume: 0.8, fadeDuration: const Duration(seconds: 5), volumeEnforced: true, ), notificationSettings: NotificationSettings( title: title ?? "SFM", body: body ?? "", stopButton: 'Dừng thông báo', icon: "ic_launcher", ), ); if (type == "smoke_warning") { await Alarm.set(alarmSettings: alarmSettings); } else { await notificationService.showBackgroundOrTerminateNotification(message); } } catch (e) { AppLoggerUtils.warning("Error in background handler: $e"); } }