You cannot select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
cloudsolutions-atoms/lib/controllers/notification/firebase_notification_mange...

428 lines
17 KiB
Dart

import 'dart:convert';
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';
import 'package:test_sa/models/device/device_transfer.dart';
import 'package:test_sa/models/new_models/gas_refill_model.dart';
import 'package:test_sa/modules/cm_module/cm_detail_page.dart';
import 'package:test_sa/modules/cx_module/survey/survey_page.dart';
import 'package:test_sa/modules/pm_module/ppm_wo/ppm_details_page.dart';
import 'package:test_sa/modules/pm_module/recurrent_wo/recurrent_work_order_view.dart';
import 'package:test_sa/modules/tm_module/device_transfer/device_transfer_details.dart';
import 'package:test_sa/modules/tm_module/tasks/task_request_detail_view.dart';
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 {
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);
if (Platform.isAndroid) {
try {
if (!(await isGoogleServicesAvailable())) {
h_push.Push.enableLogger();
await h_push.Push.setAutoInitEnabled(true);
h_push.Push.onMessageReceivedStream.listen(_onMessageReceived, onError: _onMessageReceiveError);
h_push.Push.getTokenStream.listen((hToken) {
// onToken(token);
// print("Huawei Token: ${hToken}");
token = hToken;
}, onError: (e) {
print("Huawei TokenError" + e.toString());
});
h_push.Push.getToken('');
} else {
if (settings.authorizationStatus == AuthorizationStatus.authorized) {
try {
token = await messaging.getToken();
} catch (ex) {}
}
}
} catch (ex) {
print("Notification Exception: " + ex.toString());
}
} else {
if (settings.authorizationStatus == AuthorizationStatus.authorized) {
try {
token = await messaging.getToken();
} catch (ex) {}
}
}
//print("pushToken:$token");
}
static void _onMessageReceived(h_push.RemoteMessage remoteMessage) {
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) {
print("onMessageReceivedStream:${error.toString()}");
}
static Future<bool> isGoogleServicesAvailable() async {
try {
GooglePlayServicesAvailability availability = await GoogleApiAvailability.instance.checkGooglePlayServicesAvailability();
String status = availability.toString().split('.').last;
if (status == "success") {
return true;
}
return false;
} catch (ex) {
return false;
}
}
static void handleMessage(context, Map<String, dynamic> messageData) {
if (messageData["requestType"] != null && messageData["requestNumber"] != null) {
Widget? serviceClass;
String? transactionType = messageData["transactionType"]?.toString();
if (transactionType == null) {
return;
} else if (transactionType == "17" && messageData["requestType"] == "chat") {
int moduleId = int.parse(messageData["moduleId"].toString());
int requestNumber = int.parse(messageData["requestNumber"].toString());
switch (moduleId) {
case 1: // cm
serviceClass = CMDetailPage(requestId: requestNumber, moduleId: moduleId);
break;
case 2: // gas refill
serviceClass = GasRefillDetailsPage(
priority: messageData["priority"],
date: messageData["createdOn"],
moduleId: moduleId,
model: GasRefillModel(id: requestNumber),
);
break;
case 3: //transfer
serviceClass = DeviceTransferDetails(model: DeviceTransfer(id: requestNumber), moduleId: moduleId);
break;
case 6: // task
serviceClass = TaskRequestDetailsView(
taskId: requestNumber,
moduleId: moduleId,
// requestDetails: RequestsDetails(nameOfType: requestData?.nameOfType, status: requestData?.statusName, priority: requestData?.priorityName, date: requestData?.transactionDate, ),
);
// ServiceRequestDetailMain(requestId: int.parse(messageData["requestNumber"].toString()), moduleId: 1);
break;
default:
serviceClass = const Scaffold(appBar: DefaultAppBar(), body: Center(child: NoDataFound()));
}
Navigator.of(context).push(MaterialPageRoute(builder: (_) => serviceClass!));
return;
}
// PPM=1,
// ServiceRequestEngineer = 3,
// AssetTransfer=7,
// SparePartTransaction= 8,
// GasRefill=9,
// TechnicalRetirmentWO = 11,
// Recurrent = 12,
switch (transactionType) {
case "1":
serviceClass = PpmDetailsPage(requestId: int.parse(messageData["requestNumber"].toString()));
break;
//these three request are same corrective maintenance....
case "3":
serviceClass = CMDetailPage(requestId: int.parse(messageData["requestNumber"].toString()), moduleId: 1);
break;
case "8":
serviceClass = CMDetailPage(requestId: int.parse(messageData["requestNumber"].toString()), moduleId: 1);
break;
case "11":
serviceClass = CMDetailPage(requestId: int.parse(messageData["requestNumber"].toString()), moduleId: 1);
break;
case "7":
serviceClass = DeviceTransferDetails(model: DeviceTransfer(id: int.parse(messageData["requestNumber"].toString())), moduleId: 3);
break;
case "9":
serviceClass = GasRefillDetailsPage(
priority: messageData["priority"],
date: messageData["createdOn"],
moduleId: 2,
model: GasRefillModel(id: int.parse(messageData["requestNumber"].toString())),
);
break;
case "12":
serviceClass = RecurrentWorkOrderView(taskId: int.parse(messageData["requestNumber"].toString()));
case "17":
serviceClass = SurveyPage(surveyId: int.parse(messageData["requestNumber"].toString()));
//Didn't handle task request yet...
// case 6:
// serviceClass = TaskRequestDetailsView(
// taskId: int.parse(messageData["requestNumber"].toString()),
// requestDetails: RequestsDetails(nameOfType: messageData["sourceName"], status: messageData["statusName"], priority: messageData["priorityName"], date: messageData["createdDate"]));
// return;
default:
serviceClass = const Scaffold(appBar: DefaultAppBar(), body: Center(child: NoDataFound()));
}
// if (messageData["requestType"] == "Service request to engineer") {
// serviceClass = ServiceRequestDetailMain(requestId: messageData["requestNumber"] ?? '');
// } else if (messageData["requestType"] == "Gas Refill") {
// serviceClass = GasRefillDetailsPage(
// priority: messageData["priority"],
// date: messageData["createdOn"],
// model: GasRefillModel(id: int.parse(messageData["requestNumber"].toString())),
// );
// } else if (messageData["requestType"] == "Asset Transfer") {
// serviceClass = DeviceTransferDetails(model: DeviceTransfer(id: int.parse(messageData["requestNumber"].toString())));
// } else if (messageData["requestType"] == "PPM") {
// serviceClass = PpmDetailsPage(requestId: int.parse(messageData["requestNumber"].toString()));
// }
// else if (data["requestType"] == "WorkOrder") {
//
// }
Navigator.of(context).push(MaterialPageRoute(builder: (_) => serviceClass!));
}
}
static initialized(BuildContext context) async {
//TOD0 add platform check here also
if (!(await isGoogleServicesAvailable()) && Platform.isAndroid) {
// 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) {
try {
if (message is Map<String, dynamic>) {
Map<String, dynamic> remoteData = message;
remoteData = remoteData["extras"];
handleMessage(context, remoteData);
}
} catch (ex) {
print("parsingError:$ex");
}
}, onError: (e) => print("onNotificationOpenedApp Error${e.toString()}"));
return;
}
NotificationSettings settings;
try {
settings = await messaging.requestPermission(
alert: true,
announcement: false,
badge: true,
carPlay: false,
criticalAlert: false,
provisional: false,
sound: true,
);
} catch (error) {
return;
}
if (settings.authorizationStatus != AuthorizationStatus.authorized) {
return;
}
await FirebaseMessaging.instance.setAutoInitEnabled(true);
await FirebaseMessaging.instance.setForegroundNotificationPresentationOptions(alert: true, badge: true, sound: true);
// 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 ?? 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;
});
FirebaseMessaging.onMessageOpenedApp.listen((RemoteMessage message) {
handleMessage(context, message.data);
});
// 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;
}
}