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.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 -->
|
<!-- Permissions options for the `camera` group -->
|
||||||
<uses-permission android:name="android.permission.CAMERA"/>
|
<uses-permission android:name="android.permission.CAMERA"/>
|
||||||
<!-- Permissions options for the `location` group -->
|
<!-- Permissions options for the `location` group -->
|
||||||
<uses-permission android:name="android.permission.ACCESS_FINE_LOCATION" />
|
<uses-permission android:name="android.permission.ACCESS_FINE_LOCATION" />
|
||||||
<uses-permission android:name="android.permission.ACCESS_COARSE_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 -->
|
<!-- END Permissions Package -->
|
||||||
<application
|
<application
|
||||||
android:label="SFM"
|
android:label="SFM"
|
||||||
@@ -63,7 +67,25 @@
|
|||||||
<service
|
<service
|
||||||
android:name="com.dexterous.flutterlocalnotifications.ForegroundService"
|
android:name="com.dexterous.flutterlocalnotifications.ForegroundService"
|
||||||
android:exported="false"
|
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.ActionBroadcastReceiver" />
|
||||||
<receiver android:exported="false" android:name="com.dexterous.flutterlocalnotifications.ScheduledNotificationReceiver" />
|
<receiver android:exported="false" android:name="com.dexterous.flutterlocalnotifications.ScheduledNotificationReceiver" />
|
||||||
<receiver android:exported="false" android:name="com.dexterous.flutterlocalnotifications.ScheduledNotificationBootReceiver">
|
<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 {
|
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 {
|
allprojects {
|
||||||
repositories {
|
repositories {
|
||||||
|
|||||||
@@ -18,8 +18,11 @@ pluginManagement {
|
|||||||
|
|
||||||
plugins {
|
plugins {
|
||||||
id "dev.flutter.flutter-plugin-loader" version "1.0.0"
|
id "dev.flutter.flutter-plugin-loader" version "1.0.0"
|
||||||
id "com.android.application" version "8.3.0" apply false
|
id "com.android.application" version "8.3.2" apply false
|
||||||
id "org.jetbrains.kotlin.android" version "1.8.22" 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"
|
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>
|
<key>UIBackgroundModes</key>
|
||||||
<array>
|
<array>
|
||||||
<string>fetch</string>
|
<string>fetch</string>
|
||||||
|
<string>audio</string>
|
||||||
<string>remote-notification</string>
|
<string>remote-notification</string>
|
||||||
</array>
|
</array>
|
||||||
<key>UILaunchStoryboardName</key>
|
<key>UILaunchStoryboardName</key>
|
||||||
@@ -59,6 +60,10 @@
|
|||||||
<string>UIInterfaceOrientationLandscapeRight</string>
|
<string>UIInterfaceOrientationLandscapeRight</string>
|
||||||
</array>
|
</array>
|
||||||
<key>UIViewControllerBasedStatusBarAppearance</key>
|
<key>UIViewControllerBasedStatusBarAppearance</key>
|
||||||
|
<key>BGTaskSchedulerPermittedIdentifiers</key>
|
||||||
|
<array>
|
||||||
|
<string>com.gdelataillade.fetch</string>
|
||||||
|
</array>
|
||||||
<false/>
|
<false/>
|
||||||
</dict>
|
</dict>
|
||||||
</plist>
|
</plist>
|
||||||
|
|||||||
@@ -8,7 +8,9 @@ import 'package:flutter_local_notifications/flutter_local_notifications.dart';
|
|||||||
import 'package:go_router/go_router.dart';
|
import 'package:go_router/go_router.dart';
|
||||||
import 'package:badges/badges.dart' as badges;
|
import 'package:badges/badges.dart' as badges;
|
||||||
import 'package:persistent_bottom_nav_bar_v2/persistent_bottom_nav_bar_v2.dart';
|
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/utils/permission_handler.dart';
|
||||||
import '../../product/permission/notification_permission.dart';
|
import '../../product/permission/notification_permission.dart';
|
||||||
import '../settings/profile/profile_model.dart';
|
import '../settings/profile/profile_model.dart';
|
||||||
@@ -43,20 +45,10 @@ class MainScreen extends StatefulWidget {
|
|||||||
State<MainScreen> createState() => _MainScreenState();
|
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 {
|
class _MainScreenState extends State<MainScreen> with WidgetsBindingObserver {
|
||||||
PersistentTabController controller = PersistentTabController(initialIndex: 0);
|
|
||||||
APIServices apiServices = APIServices();
|
APIServices apiServices = APIServices();
|
||||||
// final NotificationServices notificationServices = NotificationServices();
|
final NotificationServices notificationServices = NotificationServices();
|
||||||
late MainBloc mainBloc;
|
late MainBloc mainBloc;
|
||||||
bool isVN = true;
|
bool isVN = true;
|
||||||
bool isLight = true;
|
bool isLight = true;
|
||||||
@@ -84,7 +76,12 @@ class _MainScreenState extends State<MainScreen> with WidgetsBindingObserver {
|
|||||||
mainBloc.sinkIsVNIcon.add(isVN);
|
mainBloc.sinkIsVNIcon.add(isVN);
|
||||||
mainBloc.sinkThemeMode.add(isLight);
|
mainBloc.sinkThemeMode.add(isLight);
|
||||||
checkAndRequestPermission();
|
checkAndRequestPermission();
|
||||||
|
NotificationServices.requestNotificationPermission();
|
||||||
NotificationPermission.instance.checkNotificationPermission(context);
|
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
|
@override
|
||||||
@@ -103,44 +100,11 @@ class _MainScreenState extends State<MainScreen> with WidgetsBindingObserver {
|
|||||||
mainBloc.sendNotificationToken(newToken);
|
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.initLocalNotifications(controller);
|
||||||
// notificationServices.firebaseInit(context);
|
// notificationServices.firebaseInit(context);
|
||||||
// NotificationServices().setupInteractMessage(controller);
|
// 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
|
@override
|
||||||
void dispose() {
|
void dispose() {
|
||||||
super.dispose();
|
super.dispose();
|
||||||
|
|||||||
103
lib/main.dart
103
lib/main.dart
@@ -1,12 +1,12 @@
|
|||||||
import 'dart:developer';
|
import 'dart:developer';
|
||||||
|
|
||||||
|
import 'package:alarm/alarm.dart';
|
||||||
import 'package:firebase_core/firebase_core.dart';
|
import 'package:firebase_core/firebase_core.dart';
|
||||||
import 'package:firebase_messaging/firebase_messaging.dart';
|
import 'package:firebase_messaging/firebase_messaging.dart';
|
||||||
import 'package:flutter/foundation.dart';
|
|
||||||
import 'package:flutter/material.dart';
|
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' show PersistentTabController;
|
||||||
import 'firebase_options.dart';
|
|
||||||
|
|
||||||
|
import 'firebase_options.dart';
|
||||||
import 'product/lang/l10n/app_localizations.dart';
|
import 'product/lang/l10n/app_localizations.dart';
|
||||||
import 'product/services/api_services.dart';
|
import 'product/services/api_services.dart';
|
||||||
import 'product/services/notification_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/base/bloc/base_bloc.dart';
|
||||||
import 'product/constant/navigation/navigation_router.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
|
PersistentTabController controller = PersistentTabController(initialIndex: 0);
|
||||||
late AndroidNotificationChannel channel;
|
|
||||||
|
|
||||||
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 {
|
void main() async {
|
||||||
WidgetsFlutterBinding.ensureInitialized();
|
WidgetsFlutterBinding.ensureInitialized();
|
||||||
await Firebase.initializeApp();
|
await Firebase.initializeApp(options: DefaultFirebaseOptions.currentPlatform, name: "sfm-notification");
|
||||||
// Set the background messaging handler early on, as a named top-level function
|
FirebaseMessaging.onBackgroundMessage(firebaseMessagingBackgroundHandler);
|
||||||
FirebaseMessaging.onBackgroundMessage(_firebaseMessagingBackgroundHandler);
|
await Alarm.init();
|
||||||
|
|
||||||
if (!kIsWeb) {
|
|
||||||
await setupFlutterNotifications();
|
|
||||||
}
|
|
||||||
|
|
||||||
runApp(
|
runApp(
|
||||||
BlocProvider(
|
BlocProvider(
|
||||||
child: const MyApp(),
|
child: const MyApp(),
|
||||||
@@ -129,7 +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();
|
final NotificationServices notificationServices = NotificationServices();
|
||||||
APIServices apiServices = APIServices();
|
APIServices apiServices = APIServices();
|
||||||
setLocale(Locale locale) {
|
setLocale(Locale locale) {
|
||||||
_locale = locale;
|
_locale = locale;
|
||||||
@@ -145,20 +73,11 @@ class _MyAppState extends State<MyApp> {
|
|||||||
void initState() {
|
void initState() {
|
||||||
super.initState();
|
super.initState();
|
||||||
mainBloc = BlocProvider.of(context);
|
mainBloc = BlocProvider.of(context);
|
||||||
// // notificationServices.initLocalNotifications();
|
notificationServices.initialize();
|
||||||
// // notificationServices.firebaseInit(context);
|
notificationServices.firebaseInit(context);
|
||||||
// // notificationServices.setupInteractMessage();
|
notificationServices.setupInteractMessage(controller);
|
||||||
// notificationServices.getDeviceToken().then((token){
|
|
||||||
// log("Firebase Token: $token");
|
|
||||||
// sendNotificationToken(token);
|
|
||||||
// });
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// void sendNotificationToken (String token) async {
|
|
||||||
// int statusCode = await apiServices.sendNotificationToken(token);
|
|
||||||
// log("Notification Send StatusCode : $statusCode");
|
|
||||||
// }
|
|
||||||
|
|
||||||
@override
|
@override
|
||||||
void didChangeDependencies() {
|
void didChangeDependencies() {
|
||||||
languageServices.getLocale().then((locale) => {setLocale(locale)});
|
languageServices.getLocale().then((locale) => {setLocale(locale)});
|
||||||
|
|||||||
@@ -1,8 +1,10 @@
|
|||||||
import 'dart:developer';
|
import 'dart:developer';
|
||||||
|
import 'dart:io';
|
||||||
|
|
||||||
import 'package:app_settings/app_settings.dart';
|
import 'package:app_settings/app_settings.dart';
|
||||||
import 'package:firebase_messaging/firebase_messaging.dart';
|
import 'package:firebase_messaging/firebase_messaging.dart';
|
||||||
import 'package:flutter/material.dart';
|
import 'package:flutter/material.dart';
|
||||||
|
import 'package:flutter_local_notifications/flutter_local_notifications.dart';
|
||||||
import 'package:permission_handler/permission_handler.dart';
|
import 'package:permission_handler/permission_handler.dart';
|
||||||
import '../base/widget/dialog/request_permission_dialog.dart';
|
import '../base/widget/dialog/request_permission_dialog.dart';
|
||||||
|
|
||||||
@@ -11,7 +13,7 @@ class NotificationPermission {
|
|||||||
static NotificationPermission? _instance;
|
static NotificationPermission? _instance;
|
||||||
static NotificationPermission get instance =>
|
static NotificationPermission get instance =>
|
||||||
_instance ??= NotificationPermission._init();
|
_instance ??= NotificationPermission._init();
|
||||||
|
static final FlutterLocalNotificationsPlugin _notificationsPlugin = FlutterLocalNotificationsPlugin();
|
||||||
Future<bool> checkNotificationPermission(context) async {
|
Future<bool> checkNotificationPermission(context) async {
|
||||||
var status = await Permission.notification.status;
|
var status = await Permission.notification.status;
|
||||||
log("Status: $status");
|
log("Status: $status");
|
||||||
@@ -44,4 +46,21 @@ class NotificationPermission {
|
|||||||
Icons.location_on_outlined, "ABCDE", AppSettingsType.notification);
|
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:developer' as dev;
|
||||||
|
import 'dart:io';
|
||||||
import 'dart:math' as math;
|
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/material.dart';
|
||||||
import 'package:flutter_local_notifications/flutter_local_notifications.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: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 {
|
class NotificationServices {
|
||||||
FirebaseMessaging messaging = FirebaseMessaging.instance;
|
static final FlutterLocalNotificationsPlugin _notificationsPlugin = FlutterLocalNotificationsPlugin();
|
||||||
final FlutterLocalNotificationsPlugin _flutterLocalNotificationsPlugin = 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 AndroidInitializationSettings androidInitializationSettings = AndroidInitializationSettings('@mipmap/ic_launcher');
|
||||||
const DarwinInitializationSettings iosInitializationSettings = DarwinInitializationSettings();
|
const DarwinInitializationSettings iosInitializationSettings = DarwinInitializationSettings();
|
||||||
const InitializationSettings initializationSettings = InitializationSettings(
|
const InitializationSettings initializationSettings = InitializationSettings(
|
||||||
@@ -17,7 +32,7 @@ class NotificationServices {
|
|||||||
iOS: iosInitializationSettings,
|
iOS: iosInitializationSettings,
|
||||||
);
|
);
|
||||||
|
|
||||||
await _flutterLocalNotificationsPlugin.initialize(
|
await _notificationsPlugin.initialize(
|
||||||
initializationSettings,
|
initializationSettings,
|
||||||
onDidReceiveNotificationResponse: (NotificationResponse response) {
|
onDidReceiveNotificationResponse: (NotificationResponse response) {
|
||||||
dev.log("Người dùng click thông báo ở foreground với payload: ${response.payload}");
|
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");
|
dev.log("Local notifications initialized");
|
||||||
|
}catch(e){
|
||||||
|
dev.log("Error initializing local notifications: $e");
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
void firebaseInit(BuildContext context) {
|
void firebaseInit(BuildContext context) {
|
||||||
FirebaseMessaging.onMessage.listen((message) {
|
FirebaseMessaging.onMessage.listen((message) {
|
||||||
dev.log("Foreground message payload: ${message.toMap()}");
|
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() {
|
void isTokenRefresh() {
|
||||||
messaging.onTokenRefresh.listen((newToken) {
|
_messaging.onTokenRefresh.listen((newToken) {
|
||||||
dev.log("Refresh Firebase Messaging Token: $newToken");
|
dev.log("Refresh Firebase Messaging Token: $newToken");
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
Future<void> showNotification(RemoteMessage message) async {
|
static Future<bool?> requestNotificationPermission() async {
|
||||||
dev.log(message.toString());
|
try {
|
||||||
dev.log(message.data.toString());
|
if (Platform.isAndroid) {
|
||||||
dev.log(message.data["notification"].toString());
|
return await _notificationsPlugin
|
||||||
String? title = message.data['title'];
|
.resolvePlatformSpecificImplementation<AndroidFlutterLocalNotificationsPlugin>()
|
||||||
String? body = message.data['body'];
|
?.requestNotificationsPermission();
|
||||||
String type = message.data['type'] ?? "normal";
|
} 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) {
|
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;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
AndroidNotificationChannel androidNotificationChannel = AndroidNotificationChannel(
|
// Handle smoke warning notifications
|
||||||
math.Random.secure().nextInt(1000000).toString(),
|
if (type == 'smoke_warning') {
|
||||||
'High Importance Notification',
|
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,
|
importance: Importance.max,
|
||||||
|
description: channelDescription,
|
||||||
);
|
);
|
||||||
|
|
||||||
final androidPlugin = _flutterLocalNotificationsPlugin.resolvePlatformSpecificImplementation<AndroidFlutterLocalNotificationsPlugin>();
|
final androidPlugin = _notificationsPlugin
|
||||||
await androidPlugin?.deleteNotificationChannel(androidNotificationChannel.id);
|
.resolvePlatformSpecificImplementation<AndroidFlutterLocalNotificationsPlugin>();
|
||||||
|
|
||||||
AndroidNotificationDetails androidNotificationDetails = AndroidNotificationDetails(
|
// Delete existing channel to prevent conflicts
|
||||||
androidNotificationChannel.id,
|
await androidPlugin?.deleteNotificationChannel(channelId);
|
||||||
androidNotificationChannel.name,
|
|
||||||
channelDescription: "Channel description",
|
// Configure notification details
|
||||||
|
final androidDetails = AndroidNotificationDetails(
|
||||||
|
channelId,
|
||||||
|
channelName,
|
||||||
|
channelDescription: channelDescription,
|
||||||
sound: getSound(type),
|
sound: getSound(type),
|
||||||
importance: androidNotificationChannel.importance,
|
importance: Importance.max,
|
||||||
priority: Priority.high,
|
priority: Priority.high,
|
||||||
ticker: 'ticker',
|
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,
|
presentAlert: true,
|
||||||
presentBadge: true,
|
presentBadge: true,
|
||||||
presentBanner: true,
|
presentBanner: true,
|
||||||
presentSound: true,
|
presentSound: true,
|
||||||
);
|
);
|
||||||
|
|
||||||
NotificationDetails notificationDetails = NotificationDetails(
|
final notificationDetails = NotificationDetails(
|
||||||
android: androidNotificationDetails,
|
android: androidDetails,
|
||||||
iOS: darwinNotificationDetails,
|
iOS: iosDetails,
|
||||||
);
|
);
|
||||||
|
|
||||||
// Truyền payload vào thông báo
|
// Show notification
|
||||||
String payload = message.data['type'] ?? "default";
|
await _notificationsPlugin.show(
|
||||||
await _flutterLocalNotificationsPlugin.show(
|
int.parse(channelId),
|
||||||
math.Random.secure().nextInt(1000000),
|
|
||||||
title,
|
title,
|
||||||
body,
|
body,
|
||||||
notificationDetails,
|
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) {
|
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"
|
url: "https://pub.dev"
|
||||||
source: hosted
|
source: hosted
|
||||||
version: "1.3.54"
|
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:
|
app_settings:
|
||||||
dependency: "direct main"
|
dependency: "direct main"
|
||||||
description:
|
description:
|
||||||
@@ -294,6 +302,14 @@ packages:
|
|||||||
url: "https://pub.dev"
|
url: "https://pub.dev"
|
||||||
source: hosted
|
source: hosted
|
||||||
version: "3.0.8"
|
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:
|
flutter_launcher_icons:
|
||||||
dependency: "direct dev"
|
dependency: "direct dev"
|
||||||
description:
|
description:
|
||||||
|
|||||||
@@ -22,7 +22,7 @@ dependencies:
|
|||||||
permission_handler: ^11.0.1
|
permission_handler: ^11.0.1
|
||||||
# flex_color_scheme: ^7.2.0
|
# flex_color_scheme: ^7.2.0
|
||||||
flex_color_scheme: ^8.2.0
|
flex_color_scheme: ^8.2.0
|
||||||
go_router: ^15.2.3
|
go_router: ^15.1.2
|
||||||
http: ^1.3.0
|
http: ^1.3.0
|
||||||
top_snackbar_flutter: ^3.1.0
|
top_snackbar_flutter: ^3.1.0
|
||||||
badges: ^3.1.2
|
badges: ^3.1.2
|
||||||
@@ -48,6 +48,7 @@ dependencies:
|
|||||||
app_settings: ^5.1.1
|
app_settings: ^5.1.1
|
||||||
lottie: ^3.3.1
|
lottie: ^3.3.1
|
||||||
logger: ^2.5.0
|
logger: ^2.5.0
|
||||||
|
alarm: ^5.1.4
|
||||||
|
|
||||||
dev_dependencies:
|
dev_dependencies:
|
||||||
flutter_test:
|
flutter_test:
|
||||||
|
|||||||
Reference in New Issue
Block a user