Update alarm notification when app in foreground, background and terminate
This commit is contained in:
@@ -1,25 +1,29 @@
|
||||
<manifest xmlns:android="http://schemas.android.com/apk/res/android">
|
||||
|
||||
<manifest xmlns:tools="http://schemas.android.com/tools"
|
||||
xmlns:android="http://schemas.android.com/apk/res/android">
|
||||
|
||||
<uses-feature
|
||||
android:name="android.hardware.camera"
|
||||
android:required="false"
|
||||
tools:targetApi="5" />
|
||||
<uses-permission android:name="android.permission.INTERNET"/>
|
||||
<uses-permission android:name="android.permission.RECEIVE_BOOT_COMPLETED"/>
|
||||
<uses-permission android:name="android.permission.VIBRATE" />
|
||||
<!-- <uses-permission android:name="android.permission.USE_FULL_SCREEN_INTENT" /> -->
|
||||
<!-- NOTE: the example app requests USE_EXACT_ALARM to make it easier to run the app.
|
||||
Developers will need to check if their own app needs to use SCHEDULE_EXACT_ALARM instead -->
|
||||
<!-- <uses-permission android:name="android.permission.USE_EXACT_ALARM" /> -->
|
||||
<uses-permission android:name="android.permission.POST_NOTIFICATIONS"/>
|
||||
<uses-permission android:name="android.permission.FOREGROUND_SERVICE" />
|
||||
<!-- NOTE: Special use was selected as it's the closest match for this example app.
|
||||
apps should specify the appropriate permission for their use cases. -->
|
||||
<!-- <uses-permission android:name="android.permission.FOREGROUND_SERVICE_SPECIAL_USE" /> -->
|
||||
<!-- START Permissions Package -->
|
||||
<!-- Permissions options for the `camera` group -->
|
||||
<uses-permission android:name="android.permission.CAMERA"/>
|
||||
<!-- Permissions options for the `location` group -->
|
||||
<uses-permission android:name="android.permission.ACCESS_FINE_LOCATION" />
|
||||
<uses-permission android:name="android.permission.ACCESS_COARSE_LOCATION" />
|
||||
|
||||
<!-- Permissions options for the `alarm` group -->
|
||||
<uses-permission android:name="android.permission.RECEIVE_BOOT_COMPLETED"/>
|
||||
<uses-permission android:name="android.permission.WAKE_LOCK"/>
|
||||
<uses-permission android:name="android.permission.VIBRATE" />
|
||||
<uses-permission android:name="android.permission.USE_FULL_SCREEN_INTENT"/>
|
||||
<uses-permission android:name="android.permission.FOREGROUND_SERVICE" />
|
||||
<uses-permission android:name="android.permission.ACCESS_NOTIFICATION_POLICY" />
|
||||
<uses-permission android:name="android.permission.POST_NOTIFICATIONS"/>
|
||||
<uses-permission android:name="android.permission.READ_EXTERNAL_STORAGE"/>
|
||||
<uses-permission android:name="android.permission.REQUEST_IGNORE_BATTERY_OPTIMIZATIONS"/>
|
||||
<uses-permission android:name="android.permission.USE_EXACT_ALARM"
|
||||
tools:ignore="ExactAlarm" />
|
||||
<uses-permission android:name="android.permission.SCHEDULE_EXACT_ALARM"/>
|
||||
<!-- END Permissions Package -->
|
||||
<application
|
||||
android:label="SFM"
|
||||
@@ -63,7 +67,25 @@
|
||||
<service
|
||||
android:name="com.dexterous.flutterlocalnotifications.ForegroundService"
|
||||
android:exported="false"
|
||||
android:stopWithTask="false"/>
|
||||
android:stopWithTask="false"
|
||||
android:foregroundServiceType="specialUse" />
|
||||
<service android:name="com.gdelataillade.alarm.services.NotificationOnKillService" />
|
||||
|
||||
<service
|
||||
android:name="dev.fluttercommunity.plus.androidalarmmanager.AlarmService"
|
||||
android:permission="android.permission.BIND_JOB_SERVICE"
|
||||
android:exported="false"/>
|
||||
<receiver
|
||||
android:name="dev.fluttercommunity.plus.androidalarmmanager.AlarmBroadcastReceiver"
|
||||
android:exported="false"/>
|
||||
<receiver
|
||||
android:name="dev.fluttercommunity.plus.androidalarmmanager.RebootBroadcastReceiver"
|
||||
android:enabled="false"
|
||||
android:exported="false">
|
||||
<intent-filter>
|
||||
<action android:name="android.intent.action.BOOT_COMPLETED" />
|
||||
</intent-filter>
|
||||
</receiver>
|
||||
<receiver android:exported="false" android:name="com.dexterous.flutterlocalnotifications.ActionBroadcastReceiver" />
|
||||
<receiver android:exported="false" android:name="com.dexterous.flutterlocalnotifications.ScheduledNotificationReceiver" />
|
||||
<receiver android:exported="false" android:name="com.dexterous.flutterlocalnotifications.ScheduledNotificationBootReceiver">
|
||||
|
||||
BIN
android/app/src/main/res/drawable/ic_launcher.png
Normal file
BIN
android/app/src/main/res/drawable/ic_launcher.png
Normal file
Binary file not shown.
|
After Width: | Height: | Size: 442 B |
@@ -1,5 +1,5 @@
|
||||
plugins {
|
||||
id 'com.google.gms.google-services' version '4.4.2' apply false
|
||||
id 'com.google.gms.google-services' version '4.3.15' apply false
|
||||
}
|
||||
allprojects {
|
||||
repositories {
|
||||
|
||||
@@ -18,8 +18,11 @@ pluginManagement {
|
||||
|
||||
plugins {
|
||||
id "dev.flutter.flutter-plugin-loader" version "1.0.0"
|
||||
id "com.android.application" version "8.3.0" apply false
|
||||
id "org.jetbrains.kotlin.android" version "1.8.22" apply false
|
||||
id "com.android.application" version "8.3.2" apply false
|
||||
// START: FlutterFire Configuration
|
||||
id "com.google.gms.google-services" version "4.3.15" apply false
|
||||
// END: FlutterFire Configuration
|
||||
id "org.jetbrains.kotlin.android" version "2.1.0" apply false
|
||||
}
|
||||
|
||||
include ":app"
|
||||
|
||||
BIN
assets/sounds/warning_alarm.mp3
Normal file
BIN
assets/sounds/warning_alarm.mp3
Normal file
Binary file not shown.
@@ -39,6 +39,7 @@
|
||||
<key>UIBackgroundModes</key>
|
||||
<array>
|
||||
<string>fetch</string>
|
||||
<string>audio</string>
|
||||
<string>remote-notification</string>
|
||||
</array>
|
||||
<key>UILaunchStoryboardName</key>
|
||||
@@ -59,6 +60,10 @@
|
||||
<string>UIInterfaceOrientationLandscapeRight</string>
|
||||
</array>
|
||||
<key>UIViewControllerBasedStatusBarAppearance</key>
|
||||
<key>BGTaskSchedulerPermittedIdentifiers</key>
|
||||
<array>
|
||||
<string>com.gdelataillade.fetch</string>
|
||||
</array>
|
||||
<false/>
|
||||
</dict>
|
||||
</plist>
|
||||
|
||||
@@ -8,7 +8,9 @@ import 'package:flutter_local_notifications/flutter_local_notifications.dart';
|
||||
import 'package:go_router/go_router.dart';
|
||||
import 'package:badges/badges.dart' as badges;
|
||||
import 'package:persistent_bottom_nav_bar_v2/persistent_bottom_nav_bar_v2.dart';
|
||||
import 'package:sfm_app/product/shared/shared_snack_bar.dart';
|
||||
|
||||
import '../../product/services/notification_services.dart';
|
||||
import '../../product/utils/permission_handler.dart';
|
||||
import '../../product/permission/notification_permission.dart';
|
||||
import '../settings/profile/profile_model.dart';
|
||||
@@ -43,20 +45,10 @@ class MainScreen extends StatefulWidget {
|
||||
State<MainScreen> createState() => _MainScreenState();
|
||||
}
|
||||
|
||||
// @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(controller);
|
||||
// await notificationServices.showNotification(message);
|
||||
// log("Background message handled: ${message.data['title']}");
|
||||
// }
|
||||
|
||||
class _MainScreenState extends State<MainScreen> with WidgetsBindingObserver {
|
||||
PersistentTabController controller = PersistentTabController(initialIndex: 0);
|
||||
|
||||
APIServices apiServices = APIServices();
|
||||
// final NotificationServices notificationServices = NotificationServices();
|
||||
final NotificationServices notificationServices = NotificationServices();
|
||||
late MainBloc mainBloc;
|
||||
bool isVN = true;
|
||||
bool isLight = true;
|
||||
@@ -84,7 +76,12 @@ class _MainScreenState extends State<MainScreen> with WidgetsBindingObserver {
|
||||
mainBloc.sinkIsVNIcon.add(isVN);
|
||||
mainBloc.sinkThemeMode.add(isLight);
|
||||
checkAndRequestPermission();
|
||||
NotificationServices.requestNotificationPermission();
|
||||
NotificationPermission.instance.checkNotificationPermission(context);
|
||||
bool? notificationStatus = await NotificationPermission.instance.requestNotificationPermission();
|
||||
if(!notificationStatus!){
|
||||
showNoIconTopSnackBar(context, "Yêu cầu quyền thông báo không thành công", Colors.orange, Colors.white12);
|
||||
}
|
||||
}
|
||||
|
||||
@override
|
||||
@@ -103,44 +100,11 @@ class _MainScreenState extends State<MainScreen> with WidgetsBindingObserver {
|
||||
mainBloc.sendNotificationToken(newToken);
|
||||
});
|
||||
|
||||
FirebaseMessaging.onMessage.listen((RemoteMessage message) {
|
||||
RemoteNotification? notification = message.notification;
|
||||
AndroidNotification? android = message.notification?.android;
|
||||
if (notification != null && android != null) {
|
||||
const AndroidNotificationDetails androidPlatformChannelSpecifics =
|
||||
AndroidNotificationDetails('your channel id', 'your channel name',
|
||||
importance: Importance.max,
|
||||
priority: Priority.high,
|
||||
ticker: 'ticker');
|
||||
const NotificationDetails platformChannelSpecifics =
|
||||
NotificationDetails(android: androidPlatformChannelSpecifics);
|
||||
flutterLocalNotificationsPlugin.show(
|
||||
notification.hashCode,
|
||||
notification.title,
|
||||
notification.body,
|
||||
platformChannelSpecifics,
|
||||
);
|
||||
}
|
||||
});
|
||||
// notificationServices.initLocalNotifications(controller);
|
||||
// notificationServices.firebaseInit(context);
|
||||
// NotificationServices().setupInteractMessage(controller);
|
||||
}
|
||||
|
||||
@override
|
||||
void didChangeAppLifecycleState(AppLifecycleState state) {
|
||||
super.didChangeAppLifecycleState(state);
|
||||
if (state == AppLifecycleState.inactive) {
|
||||
log("App Inactive");
|
||||
} else if (state == AppLifecycleState.resumed) {
|
||||
log("App Resumed");
|
||||
} else if (state == AppLifecycleState.paused) {
|
||||
log("App paused");
|
||||
} else if (state == AppLifecycleState.detached) {
|
||||
log("App detached");
|
||||
}
|
||||
}
|
||||
|
||||
@override
|
||||
void dispose() {
|
||||
super.dispose();
|
||||
|
||||
103
lib/main.dart
103
lib/main.dart
@@ -1,12 +1,12 @@
|
||||
import 'dart:developer';
|
||||
|
||||
import 'package:alarm/alarm.dart';
|
||||
import 'package:firebase_core/firebase_core.dart';
|
||||
import 'package:firebase_messaging/firebase_messaging.dart';
|
||||
import 'package:flutter/foundation.dart';
|
||||
import 'package:flutter/material.dart';
|
||||
import 'package:flutter_local_notifications/flutter_local_notifications.dart';
|
||||
import 'firebase_options.dart';
|
||||
import 'package:persistent_bottom_nav_bar_v2/persistent_bottom_nav_bar_v2.dart' show PersistentTabController;
|
||||
|
||||
import 'firebase_options.dart';
|
||||
import 'product/lang/l10n/app_localizations.dart';
|
||||
import 'product/services/api_services.dart';
|
||||
import 'product/services/notification_services.dart';
|
||||
@@ -16,88 +16,16 @@ import 'bloc/main_bloc.dart';
|
||||
import 'product/base/bloc/base_bloc.dart';
|
||||
import 'product/constant/navigation/navigation_router.dart';
|
||||
|
||||
@pragma('vm:entry-point')
|
||||
Future<void> _firebaseMessagingBackgroundHandler(RemoteMessage message) async {
|
||||
await Firebase.initializeApp(options: DefaultFirebaseOptions.currentPlatform);
|
||||
await setupFlutterNotifications();
|
||||
showFlutterNotification(message);
|
||||
print('Handling a background message ${message.messageId}');
|
||||
}
|
||||
|
||||
|
||||
/// Create a [AndroidNotificationChannel] for heads up notifications
|
||||
late AndroidNotificationChannel channel;
|
||||
PersistentTabController controller = PersistentTabController(initialIndex: 0);
|
||||
|
||||
bool isFlutterLocalNotificationsInitialized = false;
|
||||
|
||||
Future<void> setupFlutterNotifications() async {
|
||||
if (isFlutterLocalNotificationsInitialized) {
|
||||
return;
|
||||
}
|
||||
channel = const AndroidNotificationChannel(
|
||||
'high_importance_channel', // id
|
||||
'High Importance Notifications', // title
|
||||
description:
|
||||
'This channel is used for important notifications.', // description
|
||||
importance: Importance.high,
|
||||
);
|
||||
|
||||
flutterLocalNotificationsPlugin = FlutterLocalNotificationsPlugin();
|
||||
|
||||
/// Create an Android Notification Channel.
|
||||
///
|
||||
/// We use this channel in the `AndroidManifest.xml` file to override the
|
||||
/// default FCM channel to enable heads up notifications.
|
||||
await flutterLocalNotificationsPlugin
|
||||
.resolvePlatformSpecificImplementation<
|
||||
AndroidFlutterLocalNotificationsPlugin>()
|
||||
?.createNotificationChannel(channel);
|
||||
|
||||
/// Update the iOS foreground notification presentation options to allow
|
||||
/// heads up notifications.
|
||||
await FirebaseMessaging.instance.setForegroundNotificationPresentationOptions(
|
||||
alert: true,
|
||||
badge: true,
|
||||
sound: true,
|
||||
);
|
||||
isFlutterLocalNotificationsInitialized = true;
|
||||
}
|
||||
|
||||
void showFlutterNotification(RemoteMessage message) {
|
||||
RemoteNotification? notification = message.notification;
|
||||
AndroidNotification? android = message.notification?.android;
|
||||
if (notification != null && android != null && !kIsWeb) {
|
||||
flutterLocalNotificationsPlugin.show(
|
||||
notification.hashCode,
|
||||
notification.title,
|
||||
notification.body,
|
||||
NotificationDetails(
|
||||
android: AndroidNotificationDetails(
|
||||
channel.id,
|
||||
channel.name,
|
||||
channelDescription: channel.description,
|
||||
// TODO add a proper drawable resource to android, for now using
|
||||
// one that already exists in example app.
|
||||
icon: 'launch_background',
|
||||
),
|
||||
),
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
/// Initialize the [FlutterLocalNotificationsPlugin] package.
|
||||
late FlutterLocalNotificationsPlugin flutterLocalNotificationsPlugin;
|
||||
|
||||
void main() async {
|
||||
WidgetsFlutterBinding.ensureInitialized();
|
||||
await Firebase.initializeApp();
|
||||
// Set the background messaging handler early on, as a named top-level function
|
||||
FirebaseMessaging.onBackgroundMessage(_firebaseMessagingBackgroundHandler);
|
||||
|
||||
if (!kIsWeb) {
|
||||
await setupFlutterNotifications();
|
||||
}
|
||||
|
||||
await Firebase.initializeApp(options: DefaultFirebaseOptions.currentPlatform, name: "sfm-notification");
|
||||
FirebaseMessaging.onBackgroundMessage(firebaseMessagingBackgroundHandler);
|
||||
await Alarm.init();
|
||||
runApp(
|
||||
BlocProvider(
|
||||
child: const MyApp(),
|
||||
@@ -129,7 +57,7 @@ class _MyAppState extends State<MyApp> {
|
||||
late MainBloc mainBloc;
|
||||
LanguageServices languageServices = LanguageServices();
|
||||
ThemeServices themeServices = ThemeServices();
|
||||
// final NotificationServices notificationServices = NotificationServices();
|
||||
final NotificationServices notificationServices = NotificationServices();
|
||||
APIServices apiServices = APIServices();
|
||||
setLocale(Locale locale) {
|
||||
_locale = locale;
|
||||
@@ -145,20 +73,11 @@ class _MyAppState extends State<MyApp> {
|
||||
void initState() {
|
||||
super.initState();
|
||||
mainBloc = BlocProvider.of(context);
|
||||
// // notificationServices.initLocalNotifications();
|
||||
// // notificationServices.firebaseInit(context);
|
||||
// // notificationServices.setupInteractMessage();
|
||||
// notificationServices.getDeviceToken().then((token){
|
||||
// log("Firebase Token: $token");
|
||||
// sendNotificationToken(token);
|
||||
// });
|
||||
notificationServices.initialize();
|
||||
notificationServices.firebaseInit(context);
|
||||
notificationServices.setupInteractMessage(controller);
|
||||
}
|
||||
|
||||
// void sendNotificationToken (String token) async {
|
||||
// int statusCode = await apiServices.sendNotificationToken(token);
|
||||
// log("Notification Send StatusCode : $statusCode");
|
||||
// }
|
||||
|
||||
@override
|
||||
void didChangeDependencies() {
|
||||
languageServices.getLocale().then((locale) => {setLocale(locale)});
|
||||
|
||||
@@ -1,8 +1,10 @@
|
||||
import 'dart:developer';
|
||||
import 'dart:io';
|
||||
|
||||
import 'package:app_settings/app_settings.dart';
|
||||
import 'package:firebase_messaging/firebase_messaging.dart';
|
||||
import 'package:flutter/material.dart';
|
||||
import 'package:flutter_local_notifications/flutter_local_notifications.dart';
|
||||
import 'package:permission_handler/permission_handler.dart';
|
||||
import '../base/widget/dialog/request_permission_dialog.dart';
|
||||
|
||||
@@ -11,7 +13,7 @@ class NotificationPermission {
|
||||
static NotificationPermission? _instance;
|
||||
static NotificationPermission get instance =>
|
||||
_instance ??= NotificationPermission._init();
|
||||
|
||||
static final FlutterLocalNotificationsPlugin _notificationsPlugin = FlutterLocalNotificationsPlugin();
|
||||
Future<bool> checkNotificationPermission(context) async {
|
||||
var status = await Permission.notification.status;
|
||||
log("Status: $status");
|
||||
@@ -44,4 +46,21 @@ class NotificationPermission {
|
||||
Icons.location_on_outlined, "ABCDE", AppSettingsType.notification);
|
||||
}
|
||||
}
|
||||
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) {
|
||||
log("Error requesting notification permission: $e");
|
||||
return null;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
34
lib/product/services/alarm_services.dart
Normal file
34
lib/product/services/alarm_services.dart
Normal file
@@ -0,0 +1,34 @@
|
||||
import 'dart:io';
|
||||
import 'package:alarm/alarm.dart';
|
||||
|
||||
class AlarmServices {
|
||||
Future<void> showAlarm(String title, String body) async {
|
||||
final DateTime now = DateTime.now();
|
||||
final AlarmSettings alarmSettings = AlarmSettings(
|
||||
id: 42,
|
||||
dateTime: now,
|
||||
assetAudioPath: 'assets/sounds/warning_alarm.mp3',
|
||||
loopAudio: true,
|
||||
vibrate: true,
|
||||
warningNotificationOnKill: Platform.isIOS,
|
||||
androidFullScreenIntent: true,
|
||||
allowAlarmOverlap: true,
|
||||
volumeSettings: VolumeSettings.fade(
|
||||
volume: 1.0,
|
||||
fadeDuration: const Duration(seconds: 3),
|
||||
volumeEnforced: true,
|
||||
),
|
||||
notificationSettings: NotificationSettings(
|
||||
title: title,
|
||||
body: body,
|
||||
stopButton: 'Dừng cảnh báo',
|
||||
icon: 'ic_launcher',
|
||||
),
|
||||
);
|
||||
await Alarm.set(alarmSettings: alarmSettings);
|
||||
}
|
||||
|
||||
void cancelAlarm({int id = 42}) async {
|
||||
await Alarm.stop(id);
|
||||
}
|
||||
}
|
||||
@@ -1,15 +1,30 @@
|
||||
import 'dart:developer' as dev;
|
||||
import 'dart:io';
|
||||
import 'dart:math' as math;
|
||||
import 'package:firebase_messaging/firebase_messaging.dart';
|
||||
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 {
|
||||
FirebaseMessaging messaging = FirebaseMessaging.instance;
|
||||
final FlutterLocalNotificationsPlugin _flutterLocalNotificationsPlugin = FlutterLocalNotificationsPlugin();
|
||||
static final FlutterLocalNotificationsPlugin _notificationsPlugin = FlutterLocalNotificationsPlugin();
|
||||
final FirebaseMessaging _messaging = FirebaseMessaging.instance;
|
||||
AlarmServices alarmServices = AlarmServices();
|
||||
|
||||
Future<void> initLocalNotifications(PersistentTabController controller) async {
|
||||
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(
|
||||
@@ -17,7 +32,7 @@ class NotificationServices {
|
||||
iOS: iosInitializationSettings,
|
||||
);
|
||||
|
||||
await _flutterLocalNotificationsPlugin.initialize(
|
||||
await _notificationsPlugin.initialize(
|
||||
initializationSettings,
|
||||
onDidReceiveNotificationResponse: (NotificationResponse response) {
|
||||
dev.log("Người dùng click thông báo ở foreground với payload: ${response.payload}");
|
||||
@@ -25,93 +40,123 @@ class NotificationServices {
|
||||
},
|
||||
);
|
||||
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);
|
||||
_showNotification(message);
|
||||
});
|
||||
}
|
||||
|
||||
Future<String> getDeviceToken() async {
|
||||
print("GET FB TOKEN");
|
||||
String? token = await messaging.getToken();
|
||||
print("GET FB: ${token}");
|
||||
return token!;
|
||||
}
|
||||
|
||||
void isTokenRefresh() {
|
||||
messaging.onTokenRefresh.listen((newToken) {
|
||||
_messaging.onTokenRefresh.listen((newToken) {
|
||||
dev.log("Refresh Firebase Messaging Token: $newToken");
|
||||
});
|
||||
}
|
||||
|
||||
Future<void> showNotification(RemoteMessage message) async {
|
||||
dev.log(message.toString());
|
||||
dev.log(message.data.toString());
|
||||
dev.log(message.data["notification"].toString());
|
||||
String? title = message.data['title'];
|
||||
String? body = message.data['body'];
|
||||
String type = message.data['type'] ?? "normal";
|
||||
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 due to missing title or body");
|
||||
dev.log('Skipping notification: missing title or body', name: 'Notification');
|
||||
return;
|
||||
}
|
||||
|
||||
AndroidNotificationChannel androidNotificationChannel = AndroidNotificationChannel(
|
||||
math.Random.secure().nextInt(1000000).toString(),
|
||||
'High Importance Notification',
|
||||
// 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 = _flutterLocalNotificationsPlugin.resolvePlatformSpecificImplementation<AndroidFlutterLocalNotificationsPlugin>();
|
||||
await androidPlugin?.deleteNotificationChannel(androidNotificationChannel.id);
|
||||
final androidPlugin = _notificationsPlugin
|
||||
.resolvePlatformSpecificImplementation<AndroidFlutterLocalNotificationsPlugin>();
|
||||
|
||||
AndroidNotificationDetails androidNotificationDetails = AndroidNotificationDetails(
|
||||
androidNotificationChannel.id,
|
||||
androidNotificationChannel.name,
|
||||
channelDescription: "Channel description",
|
||||
// Delete existing channel to prevent conflicts
|
||||
await androidPlugin?.deleteNotificationChannel(channelId);
|
||||
|
||||
// Configure notification details
|
||||
final androidDetails = AndroidNotificationDetails(
|
||||
channelId,
|
||||
channelName,
|
||||
channelDescription: channelDescription,
|
||||
sound: getSound(type),
|
||||
importance: androidNotificationChannel.importance,
|
||||
importance: Importance.max,
|
||||
priority: Priority.high,
|
||||
ticker: 'ticker',
|
||||
actions: type == "smoke_warning"
|
||||
? [
|
||||
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 = DarwinNotificationDetails(
|
||||
const iosDetails = DarwinNotificationDetails(
|
||||
presentAlert: true,
|
||||
presentBadge: true,
|
||||
presentBanner: true,
|
||||
presentSound: true,
|
||||
);
|
||||
|
||||
NotificationDetails notificationDetails = NotificationDetails(
|
||||
android: androidNotificationDetails,
|
||||
iOS: darwinNotificationDetails,
|
||||
final notificationDetails = NotificationDetails(
|
||||
android: androidDetails,
|
||||
iOS: iosDetails,
|
||||
);
|
||||
|
||||
// Truyền payload vào thông báo
|
||||
String payload = message.data['type'] ?? "default";
|
||||
await _flutterLocalNotificationsPlugin.show(
|
||||
math.Random.secure().nextInt(1000000),
|
||||
// Show notification
|
||||
await _notificationsPlugin.show(
|
||||
int.parse(channelId),
|
||||
title,
|
||||
body,
|
||||
notificationDetails,
|
||||
payload: payload,
|
||||
payload: type,
|
||||
);
|
||||
dev.log("Displayed notification with title: $title, body: $body, type: $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) {
|
||||
@@ -155,4 +200,120 @@ class NotificationServices {
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
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");
|
||||
}
|
||||
}
|
||||
16
pubspec.lock
16
pubspec.lock
@@ -9,6 +9,14 @@ packages:
|
||||
url: "https://pub.dev"
|
||||
source: hosted
|
||||
version: "1.3.54"
|
||||
alarm:
|
||||
dependency: "direct main"
|
||||
description:
|
||||
name: alarm
|
||||
sha256: "1eef91f0b803a2370137e0dada9c7c24cc31edf4f1c30b06442dcf486cc192e0"
|
||||
url: "https://pub.dev"
|
||||
source: hosted
|
||||
version: "5.1.4"
|
||||
app_settings:
|
||||
dependency: "direct main"
|
||||
description:
|
||||
@@ -294,6 +302,14 @@ packages:
|
||||
url: "https://pub.dev"
|
||||
source: hosted
|
||||
version: "3.0.8"
|
||||
flutter_fgbg:
|
||||
dependency: transitive
|
||||
description:
|
||||
name: flutter_fgbg
|
||||
sha256: eb6da9b2047372566a6e17b505975fe5bace94af01f6fc825c4b6f81baa6c447
|
||||
url: "https://pub.dev"
|
||||
source: hosted
|
||||
version: "0.7.1"
|
||||
flutter_launcher_icons:
|
||||
dependency: "direct dev"
|
||||
description:
|
||||
|
||||
@@ -22,7 +22,7 @@ dependencies:
|
||||
permission_handler: ^11.0.1
|
||||
# flex_color_scheme: ^7.2.0
|
||||
flex_color_scheme: ^8.2.0
|
||||
go_router: ^15.2.3
|
||||
go_router: ^15.1.2
|
||||
http: ^1.3.0
|
||||
top_snackbar_flutter: ^3.1.0
|
||||
badges: ^3.1.2
|
||||
@@ -48,6 +48,7 @@ dependencies:
|
||||
app_settings: ^5.1.1
|
||||
lottie: ^3.3.1
|
||||
logger: ^2.5.0
|
||||
alarm: ^5.1.4
|
||||
|
||||
dev_dependencies:
|
||||
flutter_test:
|
||||
|
||||
Reference in New Issue
Block a user