Files
sfm_app_final/lib/product/services/notification_services.dart

319 lines
10 KiB
Dart

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:flutter_local_notifications/flutter_local_notifications.dart';
import 'package:persistent_bottom_nav_bar_v2/persistent_bottom_nav_bar_v2.dart';
import 'package:sfm_app/product/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<void> initialize() async {
await initializeLocalNotifications();
dev.log("NotificationService initialized");
}
Future<void> 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<bool?> 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<void> _showNotification(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;
}
// 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<void> 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<void> 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<void> 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");
}
}