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...

706 lines
33 KiB
Dart

This file contains invisible Unicode characters!

This file contains invisible Unicode characters that may be processed differently from what appears below. If your use case is intentional and legitimate, you can safely ignore this warning. Use the Escape button to reveal hidden characters.

This file contains ambiguous Unicode characters that may be confused with others in your current locale. If your use case is intentional and legitimate, you can safely ignore this warning. Use the Escape button to highlight these characters.

import 'dart:convert';
import 'dart:developer';
import 'dart:io';
import 'package:firebase_messaging/firebase_messaging.dart';
import 'package:flutter/material.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';
import 'package:test_sa/modules/cx_module/chat/services/pending_call_storage.dart';
import 'package:flutter_callkit_incoming/flutter_callkit_incoming.dart';
import 'package:flutter_callkit_incoming/entities/entities.dart';
@pragma('vm:entry-point')
Future<void> firebaseMessagingBackgroundHandler(RemoteMessage message) async {
try {
log('📱 [BACKGROUND] Data payload: ${message.data}', name: 'FirebaseNotificationManger');
final notificationType = message.data['notificationType'] as String? ??
message.data['type'] as String?;
final transactionType = message.data['transactionType'] as String?;
log('📱 [BACKGROUND] Notification Type: $notificationType', name: 'FirebaseNotificationManger');
// Handle incoming call notifications
if (notificationType == 'incoming_call' || transactionType == 'call') {
// Extract call data
final callId = message.data['callId'] as String? ??
message.data['sessionId'] as String? ??
DateTime.now().millisecondsSinceEpoch.toString();
final callerId = message.data['callerId'] as String? ??
message.data['callerEmployeeNumber'] as String? ??
message.data['sourceUserId'] as String? ??
'unknown';
final callerName = message.data['callerName'] as String? ??
message.data['callerUserName'] as String? ??
message.data['userName'] as String? ??
'Unknown Caller';
final callerEmployeeNumber = message.data['callerEmployeeNumber'] as String? ?? callerId;
final isVideoCall = () {
final value = message.data['isVideoCall'];
if (value is bool) return value;
if (value is String) return value.toLowerCase() == 'true';
if (message.data['callType'] == 'video') return true;
return false;
}();
final moduleId = message.data['moduleId'] as String? ??
message.data['applicationId']?.toString();
final referenceId = message.data['referenceId'] as String?;
final conversationId = message.data['conversationId'] as String?;
// CRITICAL FIX: DO NOT initialize any communication services in background isolate
// Only persist call data and show CallKit
///TODO need to find other solution to this after testing
await PendingCallStorage.savePendingCall(
callId: callId,
callerId: callerId,
callerName: callerName,
callerEmployeeNumber: callerEmployeeNumber,
isVideoCall: isVideoCall,
moduleId: moduleId,
referenceId: referenceId,
conversationId: conversationId,
);
// Show CallKit incoming call UI
log('📱 [BACKGROUND] Displaying CallKit UI...', name: 'FirebaseNotificationManger');
final callKitParams = CallKitParams(
id: callId,
nameCaller: callerName,
appName: 'ATOMS',
avatar: '',
handle: callerEmployeeNumber,
type: isVideoCall ? 1 : 0,
duration: 30000,
missedCallNotification: const NotificationParams(
showNotification: true,
isShowCallback: true,
subtitle: 'Missed call',
callbackText: 'Call back',
),
extra: {
'callId': callId,
'callerId': callerId,
'callerEmployeeNumber': callerEmployeeNumber,
'callerName': callerName,
'moduleId': moduleId ?? '',
'referenceId': referenceId ?? '',
'conversationId': conversationId ?? '',
'isVideoCall': isVideoCall,
'timestamp': DateTime.now().toIso8601String(),
},
android: const AndroidParams(
isCustomNotification: true,
isShowLogo: false,
ringtonePath: 'system_ringtone_default',
backgroundColor: '#0955fa',
backgroundUrl: '',
actionColor: '#4CAF50',
textColor: '#ffffff',
incomingCallNotificationChannelName: 'Incoming Calls',
missedCallNotificationChannelName: 'Missed Calls',
),
ios: const IOSParams(
iconName: 'CallKitLogo',
handleType: 'generic',
supportsVideo: true,
maximumCallGroups: 1,
maximumCallsPerCallGroup: 1,
audioSessionMode: 'default',
audioSessionActive: true,
audioSessionPreferredSampleRate: 44100.0,
audioSessionPreferredIOBufferDuration: 0.005,
supportsDTMF: true,
supportsHolding: false,
supportsGrouping: false,
supportsUngrouping: false,
ringtonePath: 'system_ringtone_default',
),
);
await FlutterCallkitIncoming.showCallkitIncoming(callKitParams);
log('✅ [BACKGROUND] CallKit UI displayed', name: 'FirebaseNotificationManger');
} else {
log('📱 [BACKGROUND] Non-call notification type', name: 'FirebaseNotificationManger');
}
log('═══════════════════════════════════════════', name: 'FirebaseNotificationManger');
} catch (e, stackTrace) {
log('❌ [BACKGROUND] Error handling background message: $e',
name: 'FirebaseNotificationManger', error: e, stackTrace: stackTrace);
}
}
class FirebaseNotificationManger {
static FirebaseMessaging messaging = FirebaseMessaging.instance;
static String? token;
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) {
}
static void _onMessageReceiveError(Object error) {
log('❌ [HUAWEI] Message receive error: ${error.toString()}', name: 'FirebaseNotificationManger');
}
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 Future<void> handleMessage(context, Map<String, dynamic> messageData) async {
log('═══════════════════════════════════════════', name: 'FirebaseNotificationManger');
log('📱 [HANDLE_MESSAGE] Processing notification', name: 'FirebaseNotificationManger');
log('📱 [HANDLE_MESSAGE] Full message data: $messageData', name: 'FirebaseNotificationManger');
// FIXED: Check if this is a call notification first - CHECK BOTH 'notificationType' AND 'type'
final notificationType = messageData['notificationType'] as String? ??
messageData['type'] as String?; // Backend uses 'type'
final transactionType = messageData['transactionType'] as String?;
log('📱 [HANDLE_MESSAGE] Notification Type: $notificationType', name: 'FirebaseNotificationManger');
if (notificationType == 'incoming_call' || transactionType == 'call') {
// CRITICAL FIX: Show CallKit UI in foreground just like background
final callId = messageData['callId'] as String? ??
messageData['sessionId'] as String? ??
DateTime.now().millisecondsSinceEpoch.toString();
final callerId = messageData['callerId'] as String? ??
messageData['callerEmployeeNumber'] as String? ??
messageData['sourceUserId'] as String? ??
'unknown';
final callerName = messageData['callerName'] as String? ??
messageData['callerUserName'] as String? ??
messageData['userName'] as String? ??
'Unknown Caller';
final callerEmployeeNumber = messageData['callerEmployeeNumber'] as String? ?? callerId;
final isVideoCall = () {
final value = messageData['isVideoCall'];
if (value is bool) return value;
if (value is String) return value.toLowerCase() == 'true';
if (messageData['callType'] == 'video') return true;
return false;
}();
final moduleId = messageData['moduleId'] as String? ??
messageData['applicationId']?.toString();
final referenceId = messageData['referenceId'] as String?;
final conversationId = messageData['conversationId'] as String?;
log('📱 [FOREGROUND] Showing CallKit UI...', name: 'FirebaseNotificationManger');
// Show CallKit UI using CallKitService
try {
final callKitParams = CallKitParams(
id: callId,
nameCaller: callerName,
appName: 'ATOMS',
avatar: '',
handle: callerEmployeeNumber,
type: isVideoCall ? 1 : 0,
duration: 30000,
missedCallNotification: const NotificationParams(
showNotification: true,
isShowCallback: true,
subtitle: 'Missed call',
callbackText: 'Call back',
),
extra: {
'callId': callId,
'callerId': callerId,
'callerEmployeeNumber': callerEmployeeNumber,
'callerName': callerName,
'moduleId': moduleId ?? '',
'referenceId': referenceId ?? '',
'conversationId': conversationId ?? '',
'isVideoCall': isVideoCall,
'timestamp': DateTime.now().toIso8601String(),
},
android: const AndroidParams(
isCustomNotification: true,
isShowLogo: false,
ringtonePath: 'system_ringtone_default',
backgroundColor: '#0955fa',
backgroundUrl: '',
actionColor: '#4CAF50',
textColor: '#ffffff',
incomingCallNotificationChannelName: 'Incoming Calls',
missedCallNotificationChannelName: 'Missed Calls',
),
ios: const IOSParams(
iconName: 'CallKitLogo',
handleType: 'generic',
supportsVideo: true,
maximumCallGroups: 1,
maximumCallsPerCallGroup: 1,
audioSessionMode: 'default',
audioSessionActive: true,
audioSessionPreferredSampleRate: 44100.0,
audioSessionPreferredIOBufferDuration: 0.005,
supportsDTMF: true,
supportsHolding: false,
supportsGrouping: false,
supportsUngrouping: false,
ringtonePath: 'system_ringtone_default',
),
);
await FlutterCallkitIncoming.showCallkitIncoming(callKitParams);
log('✅ [FOREGROUND] CallKit UI displayed successfully', name: 'FirebaseNotificationManger');
} catch (e, stackTrace) {
log('❌ [FOREGROUND] Failed to show CallKit UI: $e\nStack: $stackTrace', name: 'FirebaseNotificationManger');
}
log('<EFBFBD><EFBFBD><EFBFBD>══════════════════════════════════════════', name: 'FirebaseNotificationManger');
return;
}
log('📱 [HANDLE_MESSAGE] Request Type: ${messageData["requestType"]}', name: 'FirebaseNotificationManger');
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!));
}
log('═══════════════════════════════════════════', name: 'FirebaseNotificationManger');
}
static initialized(BuildContext context) async {
//TOD0 add platform check here also
if (!(await isGoogleServicesAvailable()) && Platform.isAndroid) {
log('📱 [INIT] Using Huawei Push Services', name: 'FirebaseNotificationManger');
var initialNotification = await h_push.Push.getInitialNotification();
if (initialNotification != null) {
Map<String, dynamic> remoteData = Map<String, dynamic>.from(initialNotification["extras"] as Map);
log('📱 [HUAWEI_INITIAL] Extras data: $remoteData', name: 'FirebaseNotificationManger');
// Check if it's a call notification
final notificationType = remoteData['notificationType'] as String?;
if (notificationType == 'incoming_call') {
log('📞 [HUAWEI_INITIAL] Call notification - will be handled by SignalR', name: 'FirebaseNotificationManger');
// In foreground, SignalR will handle via OnReceiveCall event
} else {
log('📱 [HUAWEI_INITIAL] Processing regular notification', name: 'FirebaseNotificationManger');
handleMessage(context, remoteData);
}
log('═══════════════════════════════════════════', name: 'FirebaseNotificationManger');
} else {
log('📱 [HUAWEI_INITIAL] No initial notification', name: 'FirebaseNotificationManger');
}
h_push.Push.onNotificationOpenedApp.listen((message) {
try {
if (message is Map<String, dynamic>) {
Map<String, dynamic> remoteData = message;
remoteData = remoteData["extras"];
log('📱 [HUAWEI_TAP] Extras data: $remoteData', name: 'FirebaseNotificationManger');
// Check if it's a call notification
final notificationType = remoteData['notificationType'] as String?;
log('📱 [HUAWEI_TAP] Notification Type: $notificationType', name: 'FirebaseNotificationManger');
if (notificationType == 'incoming_call') {
log('📞 [HUAWEI_TAP] Call notification - will be handled by SignalR', name: 'FirebaseNotificationManger');
// In foreground, SignalR will handle via OnReceiveCall event
} else {
log('📱 [HUAWEI_TAP] Processing regular notification', name: 'FirebaseNotificationManger');
handleMessage(context, remoteData);
}
}
log('═══════════════════════════════════════════', name: 'FirebaseNotificationManger');
} catch (ex) {
log('❌ [HUAWEI_TAP] Parsing error: $ex', name: 'FirebaseNotificationManger');
}
}, onError: (e) => log('❌ [HUAWEI_TAP] Error: ${e.toString()}', name: 'FirebaseNotificationManger'));
return;
}
log('📱 [INIT] Using Firebase Cloud Messaging (FCM)', name: 'FirebaseNotificationManger');
NotificationSettings settings;
try {
settings = await messaging.requestPermission(
alert: true,
announcement: false,
badge: true,
carPlay: false,
criticalAlert: false,
provisional: false,
sound: true,
);
log('✅ [INIT] Notification permissions granted: ${settings.authorizationStatus}', name: 'FirebaseNotificationManger');
} catch (error) {
log('❌ [INIT] Permission request failed: $error', name: 'FirebaseNotificationManger');
return;
}
if (settings.authorizationStatus != AuthorizationStatus.authorized) {
log('⚠️ [INIT] Notifications not authorized', name: 'FirebaseNotificationManger');
return;
}
await FirebaseMessaging.instance.setAutoInitEnabled(true);
await FirebaseMessaging.instance.setForegroundNotificationPresentationOptions(alert: true, badge: true, sound: true);
FirebaseMessaging.instance.getInitialMessage().then((initialMessage) {
if (initialMessage != null) {
// Check if it's a call notification
final notificationType = initialMessage.data['notificationType'] as String? ??
initialMessage.data['type'] as String?;
final transactionType = initialMessage.data['transactionType'] as String?;
log('📱 [FCM_INITIAL] Notification Type: $notificationType', name: 'FirebaseNotificationManger');
log('📱 [FCM_INITIAL] Transaction Type: $transactionType', name: 'FirebaseNotificationManger');
if (notificationType == 'incoming_call' || transactionType == 'call') {
log('📞 [FCM_INITIAL] Call notification detected', name: 'FirebaseNotificationManger');
log(' ℹ️ Background handler already saved this call to SharedPreferences', name: 'FirebaseNotificationManger');
log(' ℹ️ AppLifecycleObserver will restore the call when app initializes', name: 'FirebaseNotificationManger');
log(' ℹ️ Skipping duplicate processing', name: 'FirebaseNotificationManger');
} else {
log('📱 [FCM_INITIAL] Processing regular notification', name: 'FirebaseNotificationManger');
handleMessage(context, initialMessage.data);
}
log('═══════════════════════════════════════════', name: 'FirebaseNotificationManger');
} else {
log('📱 [FCM_INITIAL] No initial message', name: 'FirebaseNotificationManger');
}
});
FirebaseMessaging.onMessage.listen((RemoteMessage message) {
log('═══════════════════════════════════════════', name: 'FirebaseNotificationManger');
log('📱 [FCM_FOREGROUND] Message received', name: 'FirebaseNotificationManger');
log('📱 [FCM_FOREGROUND] Message ID: ${message.messageId}', name: 'FirebaseNotificationManger');
log('📱 [FCM_FOREGROUND] Data: ${message.data}', name: 'FirebaseNotificationManger');
log('📱 [FCM_FOREGROUND] Title: ${message.notification?.title}', name: 'FirebaseNotificationManger');
log('📱 [FCM_FOREGROUND] Body: ${message.notification?.body}', name: 'FirebaseNotificationManger');
// FIXED: Check if it's a call notification - CHECK BOTH 'notificationType' AND 'type'
final notificationType = message.data['notificationType'] as String? ??
message.data['type'] as String?; // Backend uses 'type'
log('📱 [FCM_FOREGROUND] Notification Type: $notificationType', name: 'FirebaseNotificationManger');
if (notificationType == 'incoming_call') {
log('📞 [FOREGROUND] Incoming call via FCM', name: 'FirebaseNotificationManger');
log('📞 [FCM_FOREGROUND] Call data:', name: 'FirebaseNotificationManger');
log(' - callId: ${message.data['callId']}', name: 'FirebaseNotificationManger');
log(' - callerId: ${message.data['callerId']}', name: 'FirebaseNotificationManger');
log(' - calleeEmployeeNumber: ${message.data['calleeEmployeeNumber']}', name: 'FirebaseNotificationManger');
log(' - callerName: ${message.data['callerName']}', name: 'FirebaseNotificationManger');
log(' - callerUserName: ${message.data['callerUserName']}', name: 'FirebaseNotificationManger');
log(' - callerEmployeeNumber: ${message.data['callerEmployeeNumber']}', name: 'FirebaseNotificationManger');
log(' - isVideoCall: ${message.data['isVideoCall']}', name: 'FirebaseNotificationManger');
log(' - callType: ${message.data['callType']}', name: 'FirebaseNotificationManger');
log(' - applicationId: ${message.data['applicationId']}', name: 'FirebaseNotificationManger');
// CRITICAL FIX: Show CallKit UI in foreground
final callId = message.data['callId'] as String? ??
message.data['sessionId'] as String? ??
DateTime.now().millisecondsSinceEpoch.toString();
final callerId = message.data['callerId'] as String? ??
message.data['callerEmployeeNumber'] as String? ??
message.data['sourceUserId'] as String? ??
'unknown';
final callerName = message.data['callerName'] as String? ??
message.data['callerUserName'] as String? ??
message.data['userName'] as String? ??
'Unknown Caller';
final callerEmployeeNumber = message.data['callerEmployeeNumber'] as String? ?? callerId;
final isVideoCall = () {
final value = message.data['isVideoCall'];
if (value is bool) return value;
if (value is String) return value.toLowerCase() == 'true';
if (message.data['callType'] == 'video') return true;
return false;
}();
final moduleId = message.data['moduleId'] as String? ??
message.data['applicationId']?.toString();
final referenceId = message.data['referenceId'] as String?;
final conversationId = message.data['conversationId'] as String?;
log('📱 [FOREGROUND] Showing CallKit UI...', name: 'FirebaseNotificationManger');
try {
final callKitParams = CallKitParams(
id: callId,
nameCaller: callerName,
appName: 'ATOMS',
avatar: '',
handle: callerEmployeeNumber,
type: isVideoCall ? 1 : 0,
duration: 30000,
missedCallNotification: const NotificationParams(
showNotification: true,
isShowCallback: true,
subtitle: 'Missed call',
callbackText: 'Call back',
),
extra: {
'callId': callId,
'callerId': callerId,
'callerEmployeeNumber': callerEmployeeNumber,
'callerName': callerName,
'moduleId': moduleId ?? '',
'referenceId': referenceId ?? '',
'conversationId': conversationId ?? '',
'isVideoCall': isVideoCall,
'timestamp': DateTime.now().toIso8601String(),
},
android: const AndroidParams(
isCustomNotification: true,
isShowLogo: false,
ringtonePath: 'system_ringtone_default',
backgroundColor: '#0955fa',
backgroundUrl: '',
actionColor: '#4CAF50',
textColor: '#ffffff',
incomingCallNotificationChannelName: 'Incoming Calls',
missedCallNotificationChannelName: 'Missed Calls',
),
ios: const IOSParams(
iconName: 'CallKitLogo',
handleType: 'generic',
supportsVideo: true,
maximumCallGroups: 1,
maximumCallsPerCallGroup: 1,
audioSessionMode: 'default',
audioSessionActive: true,
audioSessionPreferredSampleRate: 44100.0,
audioSessionPreferredIOBufferDuration: 0.005,
supportsDTMF: true,
supportsHolding: false,
supportsGrouping: false,
supportsUngrouping: false,
ringtonePath: 'system_ringtone_default',
),
);
FlutterCallkitIncoming.showCallkitIncoming(callKitParams);
log('✅ [FOREGROUND] CallKit UI displayed successfully', name: 'FirebaseNotificationManger');
log('ℹ️ [FOREGROUND] SignalR OnIncomingCallAsync will handle call state in CallManager', name: 'FirebaseNotificationManger');
} catch (e, stackTrace) {
log('❌ [FOREGROUND] Failed to show CallKit UI: $e\nStack: $stackTrace', name: 'FirebaseNotificationManger');
}
log('══════════════════════════════════════════', name: 'FirebaseNotificationManger');
return;
}
log('📱 [FCM_FOREGROUND] Non-call notification', name: 'FirebaseNotificationManger');
log('═══════════════════════════════════════════', name: 'FirebaseNotificationManger');
if (Platform.isAndroid) {
if (message.data["notificationType"] != 'NurseConfirmArrive') {
NotificationManger.showNotification(
title: message.notification?.title ?? "",
subtext: message.notification?.body ?? "",
hashcode: int.tryParse("1234") ?? 1,
payload: json.encode(message.data),
context: context);
}
}
return;
});
FirebaseMessaging.onMessageOpenedApp.listen((RemoteMessage message) {
log('═══════════════════════════════════════════', name: 'FirebaseNotificationManger');
log('📱 [FCM_TAP] Notification tapped', name: 'FirebaseNotificationManger');
log('📱 [FCM_TAP] Message ID: ${message.messageId}', name: 'FirebaseNotificationManger');
log('📱 [FCM_TAP] Data: ${message.data}', name: 'FirebaseNotificationManger');
log('📱 [FCM_TAP] Title: ${message.notification?.title}', name: 'FirebaseNotificationManger');
log('📱 [FCM_TAP] Body: ${message.notification?.body}', name: 'FirebaseNotificationManger');
// NEW: Check if it's a call notification
final notificationType = message.data['notificationType'] as String?;
log('📱 [FCM_TAP] Notification Type: $notificationType', name: 'FirebaseNotificationManger');
if (notificationType == 'incoming_call') {
log('📞 [NOTIFICATION TAP] Call notification tapped - will be handled by SignalR', name: 'FirebaseNotificationManger');
// In foreground, SignalR will handle via OnReceiveCall event
} else {
log('📱 [FCM_TAP] Processing regular notification', name: 'FirebaseNotificationManger');
handleMessage(context, message.data);
}
log('═══════════════════════════════════════════', name: 'FirebaseNotificationManger');
});
FirebaseMessaging.onBackgroundMessage(firebaseMessagingBackgroundHandler);
log('✅ [INIT] FirebaseNotificationManger initialized successfully', name: 'FirebaseNotificationManger');
}
}