notification bundling added, with data notification

ai_enhancement_fcm_grouping
Sikander Saleem 2 months ago
parent 3352c73e1d
commit 08cf8ed05f

@ -3,6 +3,9 @@ android.useAndroidX=true
android.enableJetifier=true
dev.steenbakker.mobile_scanner.useUnbundled=true
# Java 21 for Android Gradle Plugin
org.gradle.java.home=/Users/devsikander/Library/Java/JavaVirtualMachines/corretto-21.0.8/Contents/Home
# Gradle optimization and caching
org.gradle.caching=true
org.gradle.parallel=true

@ -1,9 +1,10 @@
import 'dart:convert';
import 'dart:developer';
import 'dart:io';
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 'package:google_api_availability/google_api_availability.dart';
import 'package:huawei_push/huawei_push.dart' as h_push;
import 'package:test_sa/controllers/notification/notification_manger.dart';
@ -19,13 +20,108 @@ import 'package:test_sa/modules/tm_module/gas_refill/gas_refill_details.dart';
import 'package:test_sa/new_views/common_widgets/default_app_bar.dart';
import 'package:test_sa/views/widgets/loaders/no_data_found.dart';
/// Background notification handler - runs in separate isolate
/// Cannot access BuildContext or UI, must use flutter_local_notifications directly
@pragma('vm:entry-point')
Future<void> firebaseMessagingBackgroundHandler(RemoteMessage message) async {}
Future<void> firebaseMessagingBackgroundHandler(RemoteMessage message) async {
if (kDebugMode) {
print('Background message received: ${message.messageId}');
print('Has notification field: ${message.notification != null}');
print('Data: ${message.data}');
}
// Skip if it's a notification that should not be shown
if (message.data["notificationType"] == 'NurseConfirmArrive') {
return;
}
// For Android: Show notification manually
// Even if android.notification config exists in payload, if message.notification is null,
// we need to show it using data from the data field
if (Platform.isAndroid) {
await _showBackgroundNotification(message);
}
// For iOS, system handles background notifications automatically
// if the payload includes the 'notification' field
}
/// Show background notification with grouping support
/// This runs in background isolate, so we use flutter_local_notifications directly
Future<void> _showBackgroundNotification(RemoteMessage message) async {
final FlutterLocalNotificationsPlugin notificationsPlugin = FlutterLocalNotificationsPlugin();
// Initialize plugin in background isolate
const AndroidInitializationSettings initializationSettingsAndroid =
AndroidInitializationSettings('@drawable/ic_stat_name');
const InitializationSettings initializationSettings =
InitializationSettings(android: initializationSettingsAndroid);
await notificationsPlugin.initialize(initializationSettings);
// Create notification channel in background handler
final AndroidFlutterLocalNotificationsPlugin? androidImplementation =
notificationsPlugin.resolvePlatformSpecificImplementation<AndroidFlutterLocalNotificationsPlugin>();
if (androidImplementation != null) {
const AndroidNotificationChannel channel = AndroidNotificationChannel(
'com.hmg.atoms',
'ATOMS',
description: 'Push notification service for ATOMS',
importance: Importance.max,
playSound: true,
enableVibration: true,
showBadge: true,
);
await androidImplementation.createNotificationChannel(channel);
}
// Extract data from message
final String title = message.notification?.title ?? message.data['title']?.toString() ?? 'ATOMS';
final String body = message.notification?.body ?? message.data['body']?.toString() ?? '';
final String? groupId = message.data['group-id']?.toString();
final int notificationId = DateTime.now().millisecondsSinceEpoch % 100000;
if (kDebugMode) {
print('Background notification - Title: $title, Body: $body, GroupId: $groupId');
print('message.data[title]: ${message.data['title']}');
print('message.data[body]: ${message.data['body']}');
print('message.data[group-id]: ${message.data['group-id']}');
}
// Create notification details with grouping if group-id is provided
final AndroidNotificationDetails androidDetails = AndroidNotificationDetails(
'com.hmg.atoms',
'ATOMS',
channelDescription: 'Push notification service for ATOMS',
importance: Importance.max,
priority: Priority.max,
icon: '@drawable/ic_stat_name',
playSound: true,
enableVibration: true,
groupKey: groupId, // Use group-id to separate different notification groups
setAsGroupSummary: false, // Individual notification, not summary
);
final NotificationDetails notificationDetails = NotificationDetails(android: androidDetails);
await notificationsPlugin.show(
notificationId,
title,
body,
notificationDetails,
payload: json.encode(message.data),
);
}
class FirebaseNotificationManger {
static FirebaseMessaging messaging = FirebaseMessaging.instance;
static String? token;
// Store initial notification payload when app opened from killed/background state
static Map<String, dynamic>? _pendingNotificationPayload;
static Future<void> getToken() async {
NotificationSettings settings = await messaging.requestPermission(alert: true, announcement: false, badge: true, carPlay: false, criticalAlert: false, provisional: false, sound: true);
@ -33,7 +129,7 @@ class FirebaseNotificationManger {
try {
if (!(await isGoogleServicesAvailable())) {
h_push.Push.enableLogger();
final result = await h_push.Push.setAutoInitEnabled(true);
await h_push.Push.setAutoInitEnabled(true);
h_push.Push.onMessageReceivedStream.listen(_onMessageReceived, onError: _onMessageReceiveError);
h_push.Push.getTokenStream.listen((hToken) {
@ -65,7 +161,27 @@ class FirebaseNotificationManger {
}
static void _onMessageReceived(h_push.RemoteMessage remoteMessage) {
print("onMessageReceivedStream:${remoteMessage.toMap()}");
if (kDebugMode) {
print("onMessageReceivedStream:${remoteMessage.toMap()}");
}
// Handle Huawei push notification display with grouping support
// Extract notification data and group-id from the message
try {
final messageMap = remoteMessage.toMap();
final dataPayload = messageMap['dataPayload'] as Map<String, dynamic>?;
if (dataPayload != null && dataPayload["notificationType"] != 'NurseConfirmArrive') {
String? groupId = dataPayload["group-id"]?.toString();
// You may need to show notification here for Huawei devices
// depending on your Huawei Push implementation
}
} catch (e) {
if (kDebugMode) {
print("Error processing Huawei message: $e");
}
}
}
static void _onMessageReceiveError(Object error) {
@ -199,10 +315,10 @@ class FirebaseNotificationManger {
static initialized(BuildContext context) async {
//TOD0 add platform check here also
if (!(await isGoogleServicesAvailable()) && Platform.isAndroid) {
var initialNotification = await h_push.Push.getInitialNotification();
if (initialNotification != null) {
Map<String, dynamic> remoteData = Map<String, dynamic>.from(initialNotification["extras"] as Map);
handleMessage(context, remoteData);
// Huawei device - initial notification already captured in main.dart
// Only set up the listener for when app is opened from background
if (kDebugMode) {
print('[FCM] Huawei device - setting up onNotificationOpenedApp listener');
}
h_push.Push.onNotificationOpenedApp.listen((message) {
@ -243,17 +359,44 @@ class FirebaseNotificationManger {
await FirebaseMessaging.instance.setAutoInitEnabled(true);
await FirebaseMessaging.instance.setForegroundNotificationPresentationOptions(alert: true, badge: true, sound: true);
FirebaseMessaging.instance.getInitialMessage().then((initialMessage) {
if (initialMessage != null) {
handleMessage(context, initialMessage.data);
}
});
// Note: getInitialMessage() is now called in main.dart BEFORE runApp()
// This ensures we capture the notification that launched the app
if (kDebugMode) {
print('[FCM] initialized() - listeners set up. Initial message already captured in main.dart');
}
FirebaseMessaging.onMessage.listen((RemoteMessage message) {
if (Platform.isAndroid) {
if (message.data["notificationType"] != 'NurseConfirmArrive') {
// Extract group-id from data payload
String? groupId = message.data["group-id"]?.toString();
NotificationManger.showNotification(
title: message.notification?.title ?? "", subtext: message.notification?.body ?? "", hashcode: int.tryParse("1234" ?? "") ?? 1, payload: json.encode(message.data), context: context);
title: message.notification?.title ?? message.data["title"] ?? "",
subtext: message.notification?.body ?? message.data["body"] ?? "",
hashcode: DateTime.now().millisecondsSinceEpoch % 100000, // Generate unique ID
payload: json.encode(message.data),
context: context,
groupId: groupId, // Pass group-id for notification grouping
);
}
} else if (Platform.isIOS) {
// For iOS data-only notifications, manually show notification with thread-id
if (message.data["notificationType"] != 'NurseConfirmArrive') {
String? groupId = message.data["group-id"]?.toString();
// Only show notification manually if it's a data-only notification
// iOS system handles notification display for notifications with 'notification' payload
if (message.notification == null && message.data.isNotEmpty) {
NotificationManger.showNotification(
title: message.data["title"]?.toString() ?? "ATOMS",
subtext: message.data["body"]?.toString() ?? "",
hashcode: message.data.hashCode,
payload: json.encode(message.data),
context: context,
groupId: groupId, // Will be used as threadIdentifier on iOS
);
}
}
}
return;
@ -261,6 +404,24 @@ class FirebaseNotificationManger {
FirebaseMessaging.onMessageOpenedApp.listen((RemoteMessage message) {
handleMessage(context, message.data);
});
FirebaseMessaging.onBackgroundMessage(firebaseMessagingBackgroundHandler);
// Background message handler is registered in main.dart before runApp()
}
/// Check if there's a pending notification payload from app launch
/// Returns the payload and clears it
static Map<String, dynamic>? getPendingNotificationPayload() {
final payload = _pendingNotificationPayload;
_pendingNotificationPayload = null; // Clear after retrieving
return payload;
}
/// Set the initial notification payload (called from main.dart)
static void setInitialNotification(Map<String, dynamic> payload) {
_pendingNotificationPayload = payload;
}
/// Check if there's a pending notification without consuming it
static bool hasPendingNotification() {
return _pendingNotificationPayload != null;
}
}

@ -11,6 +11,13 @@ class NotificationManger {
NotificationManger._();
static late FlutterLocalNotificationsPlugin localNotificationsPlugin;
// Track notification groups for Android grouping
static final Map<String, List<Map<String, dynamic>>> _notificationGroups = {};
static final Map<String, int> _groupSummaryIds = {};
// Store initial notification details when app launched by tapping notification
static NotificationResponse? _initialNotificationResponse;
/// initialisation setting for all platform
/// onNotificationPressed action when notification pressed to open tap
@ -30,6 +37,20 @@ class NotificationManger {
await localNotificationsPlugin.resolvePlatformSpecificImplementation<IOSFlutterLocalNotificationsPlugin>()?.requestPermissions(alert: true, badge: true, sound: true);
} else if (Platform.isAndroid) {
final AndroidFlutterLocalNotificationsPlugin? androidImplementation = localNotificationsPlugin.resolvePlatformSpecificImplementation<AndroidFlutterLocalNotificationsPlugin>();
// Create notification channel explicitly for Android
const AndroidNotificationChannel channel = AndroidNotificationChannel(
'com.hmg.atoms', // id
'ATOMS', // title
description: 'Push notification service for ATOMS',
importance: Importance.max,
playSound: true,
enableVibration: true,
showBadge: true,
);
await androidImplementation?.createNotificationChannel(channel);
final bool granted = await androidImplementation?.requestNotificationsPermission() ?? false;
if (!granted) {
if (kDebugMode) {
@ -40,9 +61,31 @@ class NotificationManger {
}
}
// CRITICAL: Get the notification that launched the app (if any)
// This must be done BEFORE setting up the callback
final notificationAppLaunchDetails = await localNotificationsPlugin.getNotificationAppLaunchDetails();
if (notificationAppLaunchDetails?.didNotificationLaunchApp ?? false) {
_initialNotificationResponse = notificationAppLaunchDetails!.notificationResponse;
if (kDebugMode) {
print('[NOTIFICATION_MANAGER] App launched from notification tap!');
print('[NOTIFICATION_MANAGER] Initial notification payload: ${_initialNotificationResponse?.payload}');
}
}
await localNotificationsPlugin.initialize(initializationSettings, onDidReceiveNotificationResponse: onNotificationPressed);
} // push new notification
/// Get the initial notification response if app was launched by tapping a notification
/// Returns null if app was opened normally
static NotificationResponse? getInitialNotificationResponse() {
final response = _initialNotificationResponse;
_initialNotificationResponse = null; // Clear after retrieving
if (kDebugMode) {
print('[NOTIFICATION_MANAGER] getInitialNotificationResponse() returning: ${response?.payload}');
}
return response;
}
static const AndroidNotificationDetails androidNotificationDetails = AndroidNotificationDetails(
'com.hmg.atoms',
'ATOMS',
@ -61,28 +104,211 @@ class NotificationManger {
categoryIdentifier: "atoms",
);
static Future<void> showNotification({required BuildContext context, required String title, required String subtext, required int hashcode, String? payload}) async {
// const AndroidNotificationDetails androidChannel = AndroidNotificationDetails(
// 'com.hmg.atoms',
// 'ATOMS',
// channelDescription: 'Push notification service for ATOMS',
// importance: Importance.max,
// priority: Priority.max,
// icon: "@drawable/ic_stat_name",
// playSound: true,
// channelShowBadge: true,
// visibility: NotificationVisibility.public,
// enableVibration: true,
// groupKey: 'com.hmg.atoms',
// );
// const DarwinNotificationDetails iosNotificationDetails = DarwinNotificationDetails(
// categoryIdentifier: "atoms",
// );
const NotificationDetails platformChannel = NotificationDetails(android: androidNotificationDetails, iOS: iosNotificationDetails, macOS: iosNotificationDetails);
await localNotificationsPlugin.show(hashcode, title, subtext, platformChannel, payload: payload);
static Future<void> showNotification({
required BuildContext context,
required String title,
required String subtext,
required int hashcode,
String? payload,
String? groupId, // New parameter for grouping
}) async {
if (groupId != null && groupId.isNotEmpty) {
// Show grouped notification
await _showGroupedNotification(
context: context,
title: title,
subtext: subtext,
hashcode: hashcode,
payload: payload,
groupId: groupId,
);
} else {
// Show regular notification without grouping
const NotificationDetails platformChannel = NotificationDetails(
android: androidNotificationDetails,
iOS: iosNotificationDetails,
macOS: iosNotificationDetails,
);
await localNotificationsPlugin.show(hashcode, title, subtext, platformChannel, payload: payload);
}
}
/// Show grouped notification with Android grouping and iOS thread support
static Future<void> _showGroupedNotification({
required BuildContext context,
required String title,
required String subtext,
required int hashcode,
String? payload,
required String groupId,
}) async {
// Store notification in group
_notificationGroups.putIfAbsent(groupId, () => []);
_notificationGroups[groupId]!.add({
'title': title,
'body': subtext,
'id': hashcode,
'payload': payload,
'timestamp': DateTime.now().millisecondsSinceEpoch,
});
if (Platform.isAndroid) {
// Android: Show individual notification + summary notification
await _showAndroidGroupedNotification(
title: title,
subtext: subtext,
hashcode: hashcode,
payload: payload,
groupId: groupId,
);
} else if (Platform.isIOS) {
// iOS: Use thread identifier for grouping
await _showIOSGroupedNotification(
title: title,
subtext: subtext,
hashcode: hashcode,
payload: payload,
groupId: groupId,
);
}
}
/// Android-specific grouped notification implementation
static Future<void> _showAndroidGroupedNotification({
required String title,
required String subtext,
required int hashcode,
String? payload,
required String groupId,
}) async {
final notifications = _notificationGroups[groupId]!;
final notificationCount = notifications.length;
// Create individual notification with group key
final AndroidNotificationDetails androidDetails = AndroidNotificationDetails(
'com.hmg.atoms',
'ATOMS',
channelDescription: 'Push notification service for ATOMS',
importance: Importance.max,
priority: Priority.max,
icon: "@drawable/ic_stat_name",
playSound: true,
channelShowBadge: true,
visibility: NotificationVisibility.public,
enableVibration: true,
groupKey: groupId, // Use dynamic group key
setAsGroupSummary: false,
);
const DarwinNotificationDetails iosDetails = DarwinNotificationDetails(
categoryIdentifier: "atoms",
);
final NotificationDetails notificationDetails = NotificationDetails(
android: androidDetails,
iOS: iosDetails,
macOS: iosDetails,
);
// Show individual notification
await localNotificationsPlugin.show(
hashcode,
title,
subtext,
notificationDetails,
payload: payload,
);
// Create and show summary notification
await _showAndroidSummaryNotification(groupId, notificationCount);
}
/// Create summary notification for Android group
static Future<void> _showAndroidSummaryNotification(String groupId, int count) async {
// Generate or retrieve summary notification ID
if (!_groupSummaryIds.containsKey(groupId)) {
_groupSummaryIds[groupId] = groupId.hashCode.abs();
}
final summaryId = _groupSummaryIds[groupId]!;
final notifications = _notificationGroups[groupId]!;
// Build inbox style summary
final InboxStyleInformation inboxStyle = InboxStyleInformation(
notifications.map((n) => '${n['title']}: ${n['body']}').toList(),
contentTitle: '$count new notifications',
summaryText: 'ATOMS',
);
final AndroidNotificationDetails summaryDetails = AndroidNotificationDetails(
'com.hmg.atoms',
'ATOMS',
channelDescription: 'Push notification service for ATOMS',
importance: Importance.max,
priority: Priority.max,
icon: "@drawable/ic_stat_name",
styleInformation: inboxStyle,
groupKey: groupId,
setAsGroupSummary: true, // This is the summary notification
playSound: false, // Don't play sound for summary
enableVibration: false, // Don't vibrate for summary
);
final NotificationDetails summaryNotificationDetails = NotificationDetails(
android: summaryDetails,
);
await localNotificationsPlugin.show(
summaryId,
'ATOMS',
'$count new notifications',
summaryNotificationDetails,
);
}
/// iOS-specific grouped notification with thread identifier
static Future<void> _showIOSGroupedNotification({
required String title,
required String subtext,
required int hashcode,
String? payload,
required String groupId,
}) async {
final DarwinNotificationDetails iosDetails = DarwinNotificationDetails(
categoryIdentifier: "atoms",
threadIdentifier: groupId, // iOS uses threadIdentifier for grouping
);
const AndroidNotificationDetails androidDetails = androidNotificationDetails;
final NotificationDetails notificationDetails = NotificationDetails(
android: androidDetails,
iOS: iosDetails,
macOS: iosDetails,
);
await localNotificationsPlugin.show(
hashcode,
title,
subtext,
notificationDetails,
payload: payload,
);
}
/// Clear notification group when tapped (optional utility)
static void clearNotificationGroup(String groupId) {
_notificationGroups.remove(groupId);
if (_groupSummaryIds.containsKey(groupId)) {
final summaryId = _groupSummaryIds[groupId]!;
localNotificationsPlugin.cancel(summaryId);
_groupSummaryIds.remove(groupId);
}
}
/// Get notifications in a group (for debugging or UI display)
static List<Map<String, dynamic>>? getNotificationsInGroup(String groupId) {
return _notificationGroups[groupId];
}
static Future scheduleNotification({int? id, String? title, String? body, String? payLoad, required DateTime scheduledNotificationDateTime}) async {

@ -6,6 +6,7 @@ import 'package:flutter/material.dart';
import 'package:flutter/services.dart';
import 'package:localization/localization.dart';
import 'package:flutter_timezone/flutter_timezone.dart';
import 'package:huawei_push/huawei_push.dart' as h_push;
import 'package:provider/provider.dart';
import 'package:test_sa/controllers/providers/api/all_requests_provider.dart';
import 'package:test_sa/controllers/providers/api/comments_provider.dart';
@ -139,6 +140,8 @@ import 'providers/lookups/request_type_lookup_provider.dart';
import 'providers/lookups/yes_no_lookup_provider.dart';
import 'providers/service_request_providers/loan_availability_provider.dart';
import 'providers/service_request_providers/reject_reason_provider.dart';
import 'controllers/notification/firebase_notification_manger.dart';
import 'package:firebase_messaging/firebase_messaging.dart';
// class MyHttpOverrides extends HttpOverrides {
// @override
@ -166,6 +169,46 @@ void main() async {
} else {
await Firebase.initializeApp();
}
// Register background message handler - CRITICAL: Must be done in main() before runApp()
// This allows notifications to be shown when app is in background or terminated
FirebaseMessaging.onBackgroundMessage(firebaseMessagingBackgroundHandler);
// CRITICAL: Capture initial notification IMMEDIATELY after Firebase init, before runApp()
// This is the ONLY place where getInitialMessage() works reliably
if (Platform.isAndroid) {
// Check if device has Google Play Services
final hasGoogleServices = await FirebaseNotificationManger.isGoogleServicesAvailable();
if (hasGoogleServices) {
// Firebase/Google device
final initialMessage = await FirebaseMessaging.instance.getInitialMessage();
if (initialMessage != null) {
FirebaseNotificationManger.setInitialNotification(initialMessage.data);
}
} else {
// Huawei device
try {
final huaweiPush = await h_push.Push.getInitialNotification();
if (huaweiPush != null) {
Map<String, dynamic> remoteData = Map<String, dynamic>.from(huaweiPush["extras"] as Map);
FirebaseNotificationManger.setInitialNotification(remoteData);
}
} catch (e) {
// Huawei push check failed, continue without notification
}
}
} else if (Platform.isIOS) {
// iOS device
final initialMessage = await FirebaseMessaging.instance.getInitialMessage();
if (initialMessage != null) {
FirebaseNotificationManger.setInitialNotification(initialMessage.data);
}
}
SystemChrome.setSystemUIOverlayStyle(const SystemUiOverlayStyle(
statusBarColor: Colors.transparent,
systemNavigationBarColor: Colors.white,
@ -197,6 +240,7 @@ final navigatorKey = GlobalKey<NavigatorState>();
class MyApp extends StatelessWidget {
const MyApp({Key? key}) : super(key: key);
// This widget is the root of your application.
@override
Widget build(BuildContext context) {

@ -1,6 +1,7 @@
import 'dart:convert';
import 'dart:io';
import 'package:flutter/foundation.dart';
import 'package:flutter/material.dart';
import 'package:provider/provider.dart';
import 'package:shared_preferences/shared_preferences.dart';
@ -53,11 +54,15 @@ class _LandPageState extends State<LandPage> {
}
void checkLocalAuth() async {
await checkLocalAuthWithCallback(null);
}
Future<void> checkLocalAuthWithCallback(VoidCallback? onComplete) async {
if (await Provider.of<SettingProvider>(context, listen: false).auth.isDeviceSupported()) {
SharedPreferences prefs = await SharedPreferences.getInstance();
if (!prefs.containsKey(ASettings.localAuth)) {
(await Provider.of<SettingProvider>(context, listen: false)).setAuth(false);
showModalBottomSheet(
await showModalBottomSheet(
context: context,
useSafeArea: true,
backgroundColor: Colors.transparent,
@ -100,6 +105,20 @@ class _LandPageState extends State<LandPage> {
)),
),
);
// After bottom sheet is dismissed, call the callback if provided
if (onComplete != null) {
onComplete();
}
} else {
// No bottom sheet shown, call callback immediately if provided
if (onComplete != null) {
onComplete();
}
}
} else {
// Device doesn't support biometric, call callback immediately if provided
if (onComplete != null) {
onComplete();
}
}
}
@ -132,9 +151,47 @@ class _LandPageState extends State<LandPage> {
FirebaseNotificationManger.handleMessage(context, json.decode(notificationDetails.payload!));
}, (id, title, body, payload) async {});
// Check if there's a pending notification from app launch (killed/background state)
// This handles the case when user taps notification, app opens, asks for login,
// and after successful login we need to navigate to the correct screen
Map<String, dynamic>? pendingPayload = FirebaseNotificationManger.getPendingNotificationPayload();
// If no Firebase notification, check local notification (for data-only notifications shown by background handler)
if (pendingPayload == null) {
final initialLocalNotification = NotificationManger.getInitialNotificationResponse();
if (initialLocalNotification != null && initialLocalNotification.payload != null) {
try {
pendingPayload = json.decode(initialLocalNotification.payload!) as Map<String, dynamic>;
} catch (e) {
// Silent fail - invalid payload
}
}
}
if (pendingPayload != null) {
// Capture the payload in a final variable for use in callback
final notificationPayload = pendingPayload;
// Show biometric prompt on dashboard FIRST, THEN navigate after user interaction
WidgetsBinding.instance.addPostFrameCallback((_) async {
// Show biometric bottom sheet on dashboard
await checkLocalAuthWithCallback(() {
// After biometric interaction completes (bottom sheet dismissed),
// navigate to notification detail screen
Future.delayed(const Duration(milliseconds: 300), () {
if (mounted) {
FirebaseNotificationManger.handleMessage(context, notificationPayload);
}
});
});
});
} else {
// No pending notification, show biometric prompt normally
checkLocalAuth();
}
isFCM = false;
}
checkLocalAuth();
}
return WillPopScope(

Loading…
Cancel
Save