update sound when receive from FCM in foreground, background and terminate

This commit is contained in:
anhtunz
2025-03-07 16:44:55 +07:00
parent 314e32eaa9
commit a6fa3b1572
12 changed files with 185 additions and 67 deletions

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

BIN
assets/sounds/new_alarm.wav Normal file

Binary file not shown.

Binary file not shown.

View File

@@ -192,6 +192,8 @@ class _LoginScreenState extends State<LoginScreen> {
LocaleManager.instance LocaleManager.instance
.setString(PreferencesKeys.ROLE, loginModel.role!); .setString(PreferencesKeys.ROLE, loginModel.role!);
context.goNamed(AppRoutes.HOME.name); context.goNamed(AppRoutes.HOME.name);
// context.goNamed("notification");
} else { } else {
showErrorTopSnackBarCustom( showErrorTopSnackBarCustom(
context, appLocalization(context).login_incorrect_usernameOrPass); context, appLocalization(context).login_incorrect_usernameOrPass);
@@ -214,6 +216,7 @@ class _LoginScreenState extends State<LoginScreen> {
int timeNow = DateTime.now().millisecondsSinceEpoch ~/ 1000; int timeNow = DateTime.now().millisecondsSinceEpoch ~/ 1000;
if (token != "" && (exp - timeNow) > 7200) { if (token != "" && (exp - timeNow) > 7200) {
context.goNamed(AppRoutes.HOME.name); context.goNamed(AppRoutes.HOME.name);
// context.goNamed("notification");
} }
} }

View File

@@ -1,6 +1,10 @@
import 'dart:developer'; // ignore_for_file: avoid_print
import 'dart:math' as math;
import 'dart:developer' as dev;
import 'package:flutter/material.dart'; import 'package:flutter/material.dart';
import 'package:flutter_local_notifications/flutter_local_notifications.dart';
import 'notification_bloc.dart'; import 'notification_bloc.dart';
import '../../product/base/bloc/base_bloc.dart'; import '../../product/base/bloc/base_bloc.dart';
import '../../product/services/notification_services.dart'; import '../../product/services/notification_services.dart';
@@ -14,11 +18,12 @@ class NotificationScreen extends StatefulWidget {
class _NotificationScreenState extends State<NotificationScreen> { class _NotificationScreenState extends State<NotificationScreen> {
late NotificationBloc notificationBloc; late NotificationBloc notificationBloc;
NotificationServices notificationServices = NotificationServices(); final notificationPlugin = FlutterLocalNotificationsPlugin();
@override @override
void initState() { void initState() {
super.initState(); super.initState();
initNotification();
notificationBloc = BlocProvider.of<NotificationBloc>(context); notificationBloc = BlocProvider.of<NotificationBloc>(context);
} }
@@ -30,11 +35,43 @@ class _NotificationScreenState extends State<NotificationScreen> {
crossAxisAlignment: CrossAxisAlignment.center, crossAxisAlignment: CrossAxisAlignment.center,
children: [ children: [
TextButton( TextButton(
onPressed: () { onPressed: () async {
log("Token: ${notificationServices.getDeviceToken()}"); showNewAlarmSoundNotification("warning_alarm");
}, dev.log("Da vao day");
child: Text("Get Notification Token")) },
child: const Text("Show new Alarm Notification"),
),
], ],
)); ));
} }
Future<void> initNotification() async{
const isSettingAndroid = AndroidInitializationSettings('@mipmap/ic_launcher');
const initSetting = InitializationSettings(android: isSettingAndroid);
await notificationPlugin.initialize(initSetting);
}
Future<void> showNewAlarmSoundNotification(String sound) async {
AndroidNotificationChannel androidNotificationChannel =
AndroidNotificationChannel(
math.Random.secure().nextInt(1000000).toString(),
'high Important Notification',
importance: Importance.max);
AndroidNotificationDetails androidNotificationDetails =
AndroidNotificationDetails(
"androidNotificationChannel.id.toString()",
androidNotificationChannel.name.toString(),
sound: RawResourceAndroidNotificationSound(sound),
channelDescription: "Channel description",
importance: androidNotificationChannel.importance,
priority: Priority.high,
ticker: 'ticker',
);
final NotificationDetails notificationDetails =
NotificationDetails(android: androidNotificationDetails);
await FlutterLocalNotificationsPlugin().show(0, 'New Notification',
'Sound Notification Example', notificationDetails);
}
} }

View File

@@ -1,6 +1,10 @@
import 'dart:developer';
import 'package:firebase_core/firebase_core.dart'; import 'package:firebase_core/firebase_core.dart';
import 'package:firebase_messaging/firebase_messaging.dart';
import 'package:flutter/material.dart'; import 'package:flutter/material.dart';
import 'package:flutter_gen/gen_l10n/app_localizations.dart'; import 'package:flutter_gen/gen_l10n/app_localizations.dart';
import 'product/services/notification_services.dart';
import 'product/services/theme_services.dart'; import 'product/services/theme_services.dart';
import 'product/services/language_services.dart'; import 'product/services/language_services.dart';
import 'bloc/main_bloc.dart'; import 'bloc/main_bloc.dart';
@@ -10,6 +14,10 @@ import 'product/constant/navigation/navigation_router.dart';
void main() async { void main() async {
WidgetsFlutterBinding.ensureInitialized(); WidgetsFlutterBinding.ensureInitialized();
await Firebase.initializeApp(); await Firebase.initializeApp();
FirebaseMessaging
.onBackgroundMessage(_firebaseMessagingBackgroundHandler);
NotificationServices().setupInteractMessage();
runApp( runApp(
BlocProvider( BlocProvider(
child: const MyApp(), child: const MyApp(),
@@ -17,6 +25,16 @@ void main() async {
), ),
); );
} }
@pragma('vm:entry-point')
Future<void> _firebaseMessagingBackgroundHandler(RemoteMessage message) async {
log("Full background message payload: ${message.toMap()}");
await Firebase.initializeApp();
final notificationServices = NotificationServices();
await notificationServices.initLocalNotifications();
await notificationServices.showNotification(message);
log("Background message handled: ${message.data['title']}");
}
class MyApp extends StatefulWidget { class MyApp extends StatefulWidget {
const MyApp({super.key}); const MyApp({super.key});
@@ -39,6 +57,7 @@ class _MyAppState extends State<MyApp> {
late MainBloc mainBloc; late MainBloc mainBloc;
LanguageServices languageServices = LanguageServices(); LanguageServices languageServices = LanguageServices();
ThemeServices themeServices = ThemeServices(); ThemeServices themeServices = ThemeServices();
final NotificationServices notificationServices = NotificationServices();
setLocale(Locale locale) { setLocale(Locale locale) {
_locale = locale; _locale = locale;
@@ -49,11 +68,16 @@ class _MyAppState extends State<MyApp> {
_themeData = theme; _themeData = theme;
mainBloc.sinkTheme.add(_themeData); mainBloc.sinkTheme.add(_themeData);
} }
@override @override
void initState() { void initState() {
super.initState(); super.initState();
mainBloc = BlocProvider.of(context); mainBloc = BlocProvider.of(context);
notificationServices.initLocalNotifications();
notificationServices.firebaseInit(context);
// notificationServices.setupInteractMessage();
notificationServices.getDeviceToken().then((token){
print("Firebase Token: $token");
});
} }
@override @override

View File

@@ -1,6 +1,6 @@
import 'package:go_router/go_router.dart'; import 'package:go_router/go_router.dart';
import 'package:sfm_app/feature/sound_notification_test/notification_bloc.dart'; import '../../../feature/sound_notification_test/notification_bloc.dart';
import 'package:sfm_app/feature/sound_notification_test/notification_screen.dart'; import '../../../feature/sound_notification_test/notification_screen.dart';
import '../../../bloc/device_detail_bloc.dart'; import '../../../bloc/device_detail_bloc.dart';
import '../../../feature/devices/device_detail/device_detail_screen.dart'; import '../../../feature/devices/device_detail/device_detail_screen.dart';
import '../../../bloc/device_notification_settings_bloc.dart'; import '../../../bloc/device_notification_settings_bloc.dart';

View File

@@ -1,5 +1,3 @@
// ignore_for_file: use_build_context_synchronously
import 'dart:developer' as dev; import 'dart:developer' as dev;
import 'dart:math' as math; import 'dart:math' as math;
import 'package:firebase_messaging/firebase_messaging.dart'; import 'package:firebase_messaging/firebase_messaging.dart';
@@ -8,15 +6,34 @@ import 'package:flutter_local_notifications/flutter_local_notifications.dart';
class NotificationServices { class NotificationServices {
FirebaseMessaging messaging = FirebaseMessaging.instance; FirebaseMessaging messaging = FirebaseMessaging.instance;
final FlutterLocalNotificationsPlugin _flutterLocalNotificationsPlugin = final FlutterLocalNotificationsPlugin _flutterLocalNotificationsPlugin = FlutterLocalNotificationsPlugin();
FlutterLocalNotificationsPlugin();
void firebaseInit(BuildContext context) async { Future<void> initLocalNotifications() async {
const AndroidInitializationSettings androidInitializationSettings = AndroidInitializationSettings('@mipmap/ic_launcher');
const DarwinInitializationSettings iosInitializationSettings = DarwinInitializationSettings();
const InitializationSettings initializationSettings = InitializationSettings(
android: androidInitializationSettings,
iOS: iosInitializationSettings,
);
await _flutterLocalNotificationsPlugin.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);
},
);
dev.log("Local notifications initialized");
}
void firebaseInit(BuildContext context) {
FirebaseMessaging.onMessage.listen((message) { FirebaseMessaging.onMessage.listen((message) {
initNotifications(context, message); dev.log("Foreground message payload: ${message.toMap()}");
showNotification(message); if (WidgetsBinding.instance != null) {
dev.log( showNotification(message);
"Title: ${message.notification!.title}, Body: ${message.notification!.body} from ${message.sentTime}"); } else {
dev.log("App is in background, skipping foreground notification");
}
}); });
} }
@@ -25,80 +42,116 @@ class NotificationServices {
return token!; return token!;
} }
void isTokenRefresh() async { void isTokenRefresh() {
messaging.onTokenRefresh.listen((newToken) { messaging.onTokenRefresh.listen((newToken) {
dev.log("Refresh Firebase Messaging Token: $newToken"); dev.log("Refresh Firebase Messaging Token: $newToken");
}); });
} }
void initNotifications(BuildContext context, RemoteMessage message) async {
var androidInitializationSettings =
const AndroidInitializationSettings('app_icon');
var iosInitializationSettings = const DarwinInitializationSettings();
var initializationSettings = InitializationSettings(
android: androidInitializationSettings, iOS: iosInitializationSettings);
await _flutterLocalNotificationsPlugin.initialize(initializationSettings,
onDidReceiveNotificationResponse: (payload) {
handleMessage(context, message);
});
}
Future<void> showNotification(RemoteMessage message) async { Future<void> showNotification(RemoteMessage message) async {
AndroidNotificationChannel androidNotificationChannel = String? title = message.data['title'];
AndroidNotificationChannel( String? body = message.data['body'];
math.Random.secure().nextInt(1000000).toString(), String type = message.data['type'] ?? "normal";
'high Important Notification',
importance: Importance.max); if (title == null || body == null) {
AndroidNotificationDetails androidNotificationDetails = dev.log("Skipping notification due to missing title or body");
AndroidNotificationDetails( return;
androidNotificationChannel.id.toString(), }
androidNotificationChannel.name.toString(),
sound: RawResourceAndroidNotificationSound(message.data['sound']), AndroidNotificationChannel androidNotificationChannel = AndroidNotificationChannel(
math.Random.secure().nextInt(1000000).toString(),
'High Importance Notification',
importance: Importance.max,
);
final androidPlugin = _flutterLocalNotificationsPlugin.resolvePlatformSpecificImplementation<AndroidFlutterLocalNotificationsPlugin>();
await androidPlugin?.deleteNotificationChannel(androidNotificationChannel.id);
AndroidNotificationDetails androidNotificationDetails = AndroidNotificationDetails(
androidNotificationChannel.id,
androidNotificationChannel.name,
channelDescription: "Channel description", channelDescription: "Channel description",
sound: getSound(type),
importance: androidNotificationChannel.importance, importance: androidNotificationChannel.importance,
priority: Priority.high, priority: Priority.high,
ticker: 'ticker', ticker: 'ticker',
actions: type == "warn1"
? [
const AndroidNotificationAction(
"id1",
"Hogg xóa được",
// true thì khi nhấn vào button sẽ mở giao diện ra
showsUserInterface: true,
cancelNotification: false,
)
]
: null
); );
const DarwinNotificationDetails darwinNotificationDetails = const DarwinNotificationDetails darwinNotificationDetails = DarwinNotificationDetails(
DarwinNotificationDetails(
presentAlert: true, presentAlert: true,
presentBadge: true, presentBadge: true,
presentBanner: true, presentBanner: true,
presentSound: true, presentSound: true,
); );
NotificationDetails notificationDetails = NotificationDetails( NotificationDetails notificationDetails = NotificationDetails(
android: androidNotificationDetails, iOS: darwinNotificationDetails); android: androidNotificationDetails,
Future.delayed(Duration.zero, () { iOS: darwinNotificationDetails,
_flutterLocalNotificationsPlugin.show(0, message.notification!.title!, );
message.notification!.body, notificationDetails);
}); // Truyền payload vào thông báo
String payload = message.data['type'] ?? "default";
await _flutterLocalNotificationsPlugin.show(
math.Random.secure().nextInt(1000000),
title,
body,
notificationDetails,
payload: payload,
);
dev.log("Displayed notification with title: $title, body: $body, type: $type");
} }
void handleMessage(BuildContext context, RemoteMessage message) async { AndroidNotificationSound getSound(String type) {
if (message.data['type'] == "msj") { if (type == "welcome") {
// Navigator.push(context, return const RawResourceAndroidNotificationSound("welcome");
// MaterialPageRoute(builder: (context) => const MessageScreen())); } else if (type == "success") {
} else if (message.data['type'] == "warn") { return const RawResourceAndroidNotificationSound("success_alert");
// Navigator.push( } else if (type == "warn1") {
// context, MaterialPageRoute(builder: (context) => const MapScreen())); return const RawResourceAndroidNotificationSound("warning_alarm");
} else if (type == "warn2") {
return const RawResourceAndroidNotificationSound("new_alarm");
} else { } else {
dev.log("Not found data"); return const RawResourceAndroidNotificationSound("normal");
} }
} }
Future<void> setupInteractMessage(BuildContext context) async { void handleMessage(String? payload) {
// When app terminate dev.log("Handling notification tap with payload: $payload");
RemoteMessage? initialMessage = // Thêm logic xử lý khi nhấn thông báo ở đây
await FirebaseMessaging.instance.getInitialMessage(); // Ví dụ: Điều hướng màn hình hoặc xử lý dữ liệu
if (initialMessage != null) { }
// showNotification(initialMessage)
handleMessage(context, initialMessage);
}
// When app is inBackGround Future<void> setupInteractMessage() async {
// Khi app terminated
RemoteMessage? initialMessage = await FirebaseMessaging.instance.getInitialMessage();
if (initialMessage != null) {
WidgetsBinding.instance.addPostFrameCallback((_) {
try {
handleMessage(initialMessage.data['type']);
} catch (e, stack) {
dev.log("Error handling initial message: $e\n$stack");
}
});
}
// Khi app ở background
FirebaseMessaging.onMessageOpenedApp.listen((message) { FirebaseMessaging.onMessageOpenedApp.listen((message) {
handleMessage(context, message); try {
handleMessage(message.data['type']);
} catch (e, stack) {
dev.log("Error in onMessageOpenedApp: $e\n$stack");
}
}); });
} }
} }

View File

@@ -92,6 +92,7 @@ flutter:
- assets/images/ - assets/images/
- assets/icons/ - assets/icons/
- assets/map_themes/ - assets/map_themes/
- assets/sounds/
# An image asset can refer to one or more resolution-specific "variants", see # An image asset can refer to one or more resolution-specific "variants", see
# https://flutter.dev/assets-and-images/#resolution-aware # https://flutter.dev/assets-and-images/#resolution-aware