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

775 lines
39 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('═══════════════════════════════════════════', name: 'FirebaseNotificationManger');
log('🚀 [BACKGROUND ISOLATE] Handler started', name: 'FirebaseNotificationManger');
log('📱 [BACKGROUND] Push notification received', name: 'FirebaseNotificationManger');
log('📱 [BACKGROUND] Message ID: ${message.messageId}', name: 'FirebaseNotificationManger');
log('📱 [BACKGROUND] Data payload: ${message.data}', name: 'FirebaseNotificationManger');
log('📱 [BACKGROUND] Notification title: ${message.notification?.title}', name: 'FirebaseNotificationManger');
log('📱 [BACKGROUND] Notification body: ${message.notification?.body}', 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');
log('📱 [BACKGROUND] Transaction Type: $transactionType', name: 'FirebaseNotificationManger');
// Handle incoming call notifications
if (notificationType == 'incoming_call' || transactionType == 'call') {
log('📞 [BACKGROUND] Incoming call notification detected', name: 'FirebaseNotificationManger');
log('📞 [BACKGROUND] Call data:', name: 'FirebaseNotificationManger');
// 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?;
log(' - callId: $callId', name: 'FirebaseNotificationManger');
log(' - callerId: $callerId', name: 'FirebaseNotificationManger');
log(' - callerName: $callerName', name: 'FirebaseNotificationManger');
log(' - callerEmployeeNumber: $callerEmployeeNumber', name: 'FirebaseNotificationManger');
log(' - isVideoCall: $isVideoCall', name: 'FirebaseNotificationManger');
log(' - moduleId: $moduleId', name: 'FirebaseNotificationManger');
log(' - referenceId: $referenceId', name: 'FirebaseNotificationManger');
log(' - conversationId: $conversationId', name: 'FirebaseNotificationManger');
// CRITICAL FIX: DO NOT initialize any communication services in background isolate
// Only persist call data and show CallKit
log('💾 [BACKGROUND] Saving call data to SharedPreferences...', name: 'FirebaseNotificationManger');
await PendingCallStorage.savePendingCall(
callId: callId,
callerId: callerId,
callerName: callerName,
callerEmployeeNumber: callerEmployeeNumber,
isVideoCall: isVideoCall,
moduleId: moduleId,
referenceId: referenceId,
conversationId: conversationId,
);
log('✅ [BACKGROUND] Call data saved successfully', name: 'FirebaseNotificationManger');
// 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');
log('ℹ️ [BACKGROUND] Main isolate will initialize communication services when app resumes', 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) {
log('═══════════════════════════════════════════', name: 'FirebaseNotificationManger');
log('📱 [HUAWEI] Message received', name: 'FirebaseNotificationManger');
log('📱 [HUAWEI] Full message data: ${remoteMessage.toMap()}', name: 'FirebaseNotificationManger');
log('═══════════════════════════════════════════', name: 'FirebaseNotificationManger');
}
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');
log('📱 [HANDLE_MESSAGE] Transaction Type: $transactionType', name: 'FirebaseNotificationManger');
if (notificationType == 'incoming_call' || transactionType == 'call') {
log('📞 [FOREGROUND] Incoming call notification detected', name: 'FirebaseNotificationManger');
log('📞 [FOREGROUND] Call data:', name: 'FirebaseNotificationManger');
log(' - callId: ${messageData['callId']}', name: 'FirebaseNotificationManger');
log(' - callerId: ${messageData['callerId']}', name: 'FirebaseNotificationManger');
log(' - calleeEmployeeNumber: ${messageData['calleeEmployeeNumber']}', name: 'FirebaseNotificationManger');
log(' - callerName: ${messageData['callerName']}', name: 'FirebaseNotificationManger');
log(' - callerUserName: ${messageData['callerUserName']}', name: 'FirebaseNotificationManger');
log(' - callerEmployeeNumber: ${messageData['callerEmployeeNumber']}', name: 'FirebaseNotificationManger');
log(' - isVideoCall: ${messageData['isVideoCall']}', name: 'FirebaseNotificationManger');
log(' - callType: ${messageData['callType']}', name: 'FirebaseNotificationManger');
log(' - moduleId: ${messageData['moduleId']}', name: 'FirebaseNotificationManger');
log(' - referenceId: ${messageData['referenceId']}', name: 'FirebaseNotificationManger');
log(' - conversationId: ${messageData['conversationId']}', name: 'FirebaseNotificationManger');
log(' - applicationId: ${messageData['applicationId']}', name: 'FirebaseNotificationManger');
// CRITICAL FIX: Show CallKit UI in foreground just like background
// Extract call data (same as background handler)
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');
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('<EFBFBD><EFBFBD><EFBFBD>══════════════════════════════════════════', name: 'FirebaseNotificationManger');
return;
}
log('📱 [HANDLE_MESSAGE] Non-call notification - processing normally', name: 'FirebaseNotificationManger');
log('📱 [HANDLE_MESSAGE] Request Type: ${messageData["requestType"]}', name: 'FirebaseNotificationManger');
log('📱 [HANDLE_MESSAGE] Request Number: ${messageData["requestNumber"]}', name: 'FirebaseNotificationManger');
log('📱 [HANDLE_MESSAGE] Module ID: ${messageData["moduleId"]}', 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 {
log('🔧 [INIT] Initializing FirebaseNotificationManger', name: 'FirebaseNotificationManger');
//TOD0 add platform check here also
if (!(await isGoogleServicesAvailable()) && Platform.isAndroid) {
log('📱 [INIT] Using Huawei Push Services', name: 'FirebaseNotificationManger');
// NEW: Handle Huawei initial notification
var initialNotification = await h_push.Push.getInitialNotification();
if (initialNotification != null) {
log('═══════════════════════════════════════════', name: 'FirebaseNotificationManger');
log('📱 [HUAWEI_INITIAL] Initial notification found', name: 'FirebaseNotificationManger');
log('📱 [HUAWEI_INITIAL] Full data: $initialNotification', name: 'FirebaseNotificationManger');
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?;
log('📱 [HUAWEI_INITIAL] Notification Type: $notificationType', name: 'FirebaseNotificationManger');
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 {
log('═══════════════════════════════════════════', name: 'FirebaseNotificationManger');
log('📱 [HUAWEI_TAP] Notification tapped', name: 'FirebaseNotificationManger');
log('📱 [HUAWEI_TAP] Message type: ${message.runtimeType}', name: 'FirebaseNotificationManger');
log('📱 [HUAWEI_TAP] Full message: $message', name: 'FirebaseNotificationManger');
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) {
log('═══════════════════════════════════════════', name: 'FirebaseNotificationManger');
log('📱 [FCM_INITIAL] Initial message found', name: 'FirebaseNotificationManger');
log('📱 [FCM_INITIAL] Message ID: ${initialMessage.messageId}', name: 'FirebaseNotificationManger');
log('📱 [FCM_INITIAL] Data: ${initialMessage.data}', name: 'FirebaseNotificationManger');
log('📱 [FCM_INITIAL] Title: ${initialMessage.notification?.title}', name: 'FirebaseNotificationManger');
log('📱 [FCM_INITIAL] Body: ${initialMessage.notification?.body}', name: 'FirebaseNotificationManger');
// 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');
}
}