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 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 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 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 handleMessage(context, Map 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('���══════════════════════════════════════════', 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 remoteData = Map.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) { Map 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'); } }