From 518ab82814c4cd79ceebf50e01a6b6b0bc88350f Mon Sep 17 00:00:00 2001
From: WaseemAbbasi22 <50428976+WaseemAbbasi22@users.noreply.github.com>
Date: Sun, 19 Jul 2026 11:44:55 +0300
Subject: [PATCH] inprogess
---
PRODUCTION_READY_CALLING_COMPLETE.md | 0
.../res/values/ic_launcher_background.xml | 1 -
.../firebase_notification_manger.dart | 172 +++++++++++--
lib/main.dart | 2 +
.../services/call_notification_service.dart | 61 ++++-
lib/modules/cx_module/chat/chat_provider.dart | 40 ++-
.../cx_module/chat/services/call_manager.dart | 243 +++++++++++++++---
.../chat/services/signalr_service.dart | 24 +-
8 files changed, 480 insertions(+), 63 deletions(-)
create mode 100644 PRODUCTION_READY_CALLING_COMPLETE.md
diff --git a/PRODUCTION_READY_CALLING_COMPLETE.md b/PRODUCTION_READY_CALLING_COMPLETE.md
new file mode 100644
index 00000000..e69de29b
diff --git a/android/app/src/main/res/values/ic_launcher_background.xml b/android/app/src/main/res/values/ic_launcher_background.xml
index c5d5899f..55344e51 100644
--- a/android/app/src/main/res/values/ic_launcher_background.xml
+++ b/android/app/src/main/res/values/ic_launcher_background.xml
@@ -1,4 +1,3 @@
- #FFFFFF
\ No newline at end of file
diff --git a/lib/controllers/notification/firebase_notification_manger.dart b/lib/controllers/notification/firebase_notification_manger.dart
index ca6bc6b2..536e2368 100644
--- a/lib/controllers/notification/firebase_notification_manger.dart
+++ b/lib/controllers/notification/firebase_notification_manger.dart
@@ -23,20 +23,43 @@ import 'package:test_sa/modules/cx_module/chat/call/services/call_notification_s
@pragma('vm:entry-point')
Future firebaseMessagingBackgroundHandler(RemoteMessage message) async {
try {
+ log('═══════════════════════════════════════════', name: 'FirebaseNotificationManger');
log('📱 [BACKGROUND] Push notification received', name: 'FirebaseNotificationManger');
- log('📱 [BACKGROUND] Data: ${message.data}', name: 'FirebaseNotificationManger');
-
- final notificationType = message.data['notificationType'] as String?;
+ 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?; // Backend uses 'type'
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');
+ 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');
+
await CallNotificationService().handleIncomingCallNotification(
notificationData: message.data,
source: 'push',
);
+ } 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);
@@ -86,11 +109,14 @@ class FirebaseNotificationManger {
}
static void _onMessageReceived(h_push.RemoteMessage remoteMessage) {
- print("onMessageReceivedStream:${remoteMessage.toMap()}");
+ 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) {
- print("onMessageReceivedStream:${error.toString()}");
+ log('❌ [HUAWEI] Message receive error: ${error.toString()}', name: 'FirebaseNotificationManger');
}
static Future isGoogleServicesAvailable() async {
@@ -107,21 +133,48 @@ class FirebaseNotificationManger {
}
static void handleMessage(context, Map messageData) {
- // NEW: Check if this is a call notification first
- final notificationType = messageData['notificationType'] as String?;
+ 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');
+
// Let CallNotificationService handle it
CallNotificationService().handleIncomingCallNotification(
notificationData: messageData,
source: 'push',
);
+ 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;
@@ -230,52 +283,82 @@ class FirebaseNotificationManger {
// }
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] Processing call notification', name: 'FirebaseNotificationManger');
await CallNotificationService().handleIncomingCallNotification(
notificationData: remoteData,
source: 'push',
);
} 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] Processing call notification', name: 'FirebaseNotificationManger');
CallNotificationService().handleIncomingCallNotification(
notificationData: remoteData,
source: 'push',
);
} else {
+ log('📱 [HUAWEI_TAP] Processing regular notification', name: 'FirebaseNotificationManger');
handleMessage(context, remoteData);
}
}
+ log('═══════════════════════════════════════════', name: 'FirebaseNotificationManger');
} catch (ex) {
- print("parsingError:$ex");
+ log('❌ [HUAWEI_TAP] Parsing error: $ex', name: 'FirebaseNotificationManger', error: ex);
}
- }, onError: (e) => print("onNotificationOpenedApp Error${e.toString()}"));
+ }, onError: (e) => log('❌ [HUAWEI_TAP] Error: ${e.toString()}', name: 'FirebaseNotificationManger'));
return;
}
+ log('📱 [INIT] Using Firebase Cloud Messaging (FCM)', name: 'FirebaseNotificationManger');
+
NotificationSettings settings;
try {
@@ -288,11 +371,14 @@ class FirebaseNotificationManger {
provisional: false,
sound: true,
);
+ log('✅ [INIT] Notification permissions granted: ${settings.authorizationStatus}', name: 'FirebaseNotificationManger');
} catch (error) {
+ log('❌ [INIT] Permission request failed: $error', name: 'FirebaseNotificationManger', error: error);
return;
}
if (settings.authorizationStatus != AuthorizationStatus.authorized) {
+ log('⚠️ [INIT] Notifications not authorized', name: 'FirebaseNotificationManger');
return;
}
@@ -301,33 +387,70 @@ class FirebaseNotificationManger {
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');
+
// NEW: Check if it's a call notification
final notificationType = initialMessage.data['notificationType'] as String?;
+ log('📱 [FCM_INITIAL] Notification Type: $notificationType', name: 'FirebaseNotificationManger');
+
if (notificationType == 'incoming_call') {
+ log('📞 [FCM_INITIAL] Processing call notification', name: 'FirebaseNotificationManger');
CallNotificationService().handleIncomingCallNotification(
notificationData: initialMessage.data,
source: 'push',
);
} 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) {
- // NEW: Check if it's a call notification
- final notificationType = message.data['notificationType'] as String?;
-
+ 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');
+
CallNotificationService().handleIncomingCallNotification(
notificationData: message.data,
source: 'push',
);
+ log('═══════════════════════════════════════════', name: 'FirebaseNotificationManger');
return;
}
- // ...existing code...
+ log('📱 [FCM_FOREGROUND] Non-call notification', name: 'FirebaseNotificationManger');
+ log('═══════════════════════════════════════════', name: 'FirebaseNotificationManger');
+
if (Platform.isAndroid) {
if (message.data["notificationType"] != 'NurseConfirmArrive') {
NotificationManger.showNotification(
@@ -342,20 +465,37 @@ class FirebaseNotificationManger {
});
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] Incoming call notification tapped', name: 'FirebaseNotificationManger');
+ log('📞 [FCM_TAP] Call data:', name: 'FirebaseNotificationManger');
+ log(' - callId: ${message.data['callId']}', name: 'FirebaseNotificationManger');
+ log(' - callerId: ${message.data['callerId']}', name: 'FirebaseNotificationManger');
+ log(' - callerName: ${message.data['callerName']}', name: 'FirebaseNotificationManger');
+
CallNotificationService().handleIncomingCallNotification(
notificationData: message.data,
source: 'push',
);
} 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');
}
}
diff --git a/lib/main.dart b/lib/main.dart
index 5c154b24..40190c8f 100644
--- a/lib/main.dart
+++ b/lib/main.dart
@@ -172,6 +172,8 @@ void main() async {
} else {
await Firebase.initializeApp();
}
+
+
SystemChrome.setSystemUIOverlayStyle(const SystemUiOverlayStyle(
statusBarColor: Colors.transparent,
systemNavigationBarColor: Colors.white,
diff --git a/lib/modules/cx_module/chat/call/services/call_notification_service.dart b/lib/modules/cx_module/chat/call/services/call_notification_service.dart
index a782be3b..de4dfa98 100644
--- a/lib/modules/cx_module/chat/call/services/call_notification_service.dart
+++ b/lib/modules/cx_module/chat/call/services/call_notification_service.dart
@@ -25,33 +25,69 @@ class CallNotificationService {
try {
log('═══════════════════════════════════════════', name: 'CallNotificationService');
log('📞 [INCOMING CALL] Notification received from: $source', name: 'CallNotificationService');
- log('📞 [INCOMING CALL] Data: $notificationData', name: 'CallNotificationService');
+ log('📞 [INCOMING CALL] Raw data: $notificationData', name: 'CallNotificationService');
- // Extract call information
- final notificationType = notificationData['notificationType'] as String?;
+ // Extract call information - FIXED: Match actual backend field names
+ final notificationType = notificationData['notificationType'] as String? ??
+ notificationData['type'] as String?; // Backend uses 'type'
final transactionType = notificationData['transactionType'] as String?;
+ log('📞 [INCOMING CALL] Notification Type: $notificationType', name: 'CallNotificationService');
+ log('📞 [INCOMING CALL] Transaction Type: $transactionType', name: 'CallNotificationService');
+
// Verify this is a call notification
if (notificationType != 'incoming_call' && transactionType != 'call') {
log('⚠️ [INCOMING CALL] Not a call notification, ignoring', name: 'CallNotificationService');
+ log(' Expected: notificationType=incoming_call OR transactionType=call', name: 'CallNotificationService');
+ log(' Got: notificationType=$notificationType, transactionType=$transactionType', name: 'CallNotificationService');
return;
}
+ // FIXED: Extract fields matching actual backend format
final callId = notificationData['callId'] as String? ??
- notificationData['sessionId'] as String?;
+ notificationData['sessionId'] as String? ??
+ DateTime.now().millisecondsSinceEpoch.toString(); // Generate if missing
+
final callerId = notificationData['callerId'] as String? ??
+ notificationData['calleeEmployeeNumber'] as String? ?? // Backend field
notificationData['sourceUserId'] as String?;
+
final callerName = notificationData['callerName'] as String? ??
+ notificationData['callerUserName'] as String? ?? // Backend field
notificationData['userName'] as String? ??
'Unknown Caller';
- final isVideoCall = notificationData['isVideoCall'] as bool? ??
- (notificationData['callType'] == 'video');
- final moduleId = notificationData['moduleId'] as String?;
+
+ final callerEmployeeNumber = notificationData['callerEmployeeNumber'] as String?;
+
+ // FIXED: Handle isVideoCall as String from backend
+ final isVideoCall = () {
+ final value = notificationData['isVideoCall'];
+ if (value is bool) {
+ return value;
+ } else if (value is String) {
+ return value.toLowerCase() == 'true';
+ } else if (notificationData['callType'] == 'video') {
+ return true;
+ }
+ return false;
+ }();
+
+ final moduleId = notificationData['moduleId'] as String? ??
+ notificationData['applicationId']?.toString();
final referenceId = notificationData['referenceId'] as String?;
final conversationId = notificationData['conversationId'] as String?;
- if (callId == null || callerId == null) {
- log('❌ [INCOMING CALL] Missing required data (callId or callerId)', name: 'CallNotificationService');
+ log('📞 [INCOMING CALL] Extracted data:', name: 'CallNotificationService');
+ log(' Call ID: $callId', name: 'CallNotificationService');
+ log(' Caller ID: $callerId', name: 'CallNotificationService');
+ log(' Caller Name: $callerName', name: 'CallNotificationService');
+ log(' Caller Employee Number: $callerEmployeeNumber', name: 'CallNotificationService');
+ log(' Is Video: $isVideoCall', name: 'CallNotificationService');
+ log(' Module ID: $moduleId', name: 'CallNotificationService');
+
+ if (callerId == null) {
+ log('❌ [INCOMING CALL] Missing callerId/calleeEmployeeNumber', name: 'CallNotificationService');
+ log(' Available fields: ${notificationData.keys.toList()}', name: 'CallNotificationService');
return;
}
@@ -69,15 +105,13 @@ class CallNotificationService {
}
log('✅ [INCOMING CALL] Valid call notification', name: 'CallNotificationService');
- log(' Call ID: $callId', name: 'CallNotificationService');
- log(' Caller: $callerName ($callerId)', name: 'CallNotificationService');
- log(' Video: $isVideoCall', name: 'CallNotificationService');
// Store pending call data
_pendingCallData = {
'callId': callId,
'callerId': callerId,
'callerName': callerName,
+ 'callerEmployeeNumber': callerEmployeeNumber,
'isVideoCall': isVideoCall,
'moduleId': moduleId,
'referenceId': referenceId,
@@ -86,6 +120,7 @@ class CallNotificationService {
};
// Show CallKit/ConnectionService immediately
+ log('📱 [INCOMING CALL] Showing CallKit UI...', name: 'CallNotificationService');
await _showNativeIncomingCallUI(
callId: callId,
callerName: callerName,
@@ -144,6 +179,8 @@ class CallNotificationService {
backgroundColor: '#0955fa',
backgroundUrl: '',
actionColor: '#4CAF50',
+ textAccept: 'Accept',
+ textDecline: 'Decline',
incomingCallNotificationChannelName: 'Incoming Calls',
missedCallNotificationChannelName: 'Missed Calls',
),
diff --git a/lib/modules/cx_module/chat/chat_provider.dart b/lib/modules/cx_module/chat/chat_provider.dart
index f5b5c1d9..78f0c86d 100644
--- a/lib/modules/cx_module/chat/chat_provider.dart
+++ b/lib/modules/cx_module/chat/chat_provider.dart
@@ -43,7 +43,8 @@ import 'call/services/callkit_service.dart';
import 'package:flutter_webrtc/flutter_webrtc.dart';
import 'call/call_error_handler.dart';
import 'call/services/call_notification_service.dart';
-import 'services/call_manager.dart'; // NEW: Import CallManager
+import 'services/call_manager.dart';
+import 'services/signalr_service.dart'; // ADD: Import SignalRService
HubConnection? chatHubConnection;
@@ -351,6 +352,16 @@ class ChatProvider with ChangeNotifier, DiagnosticableTreeMixin {
await chatHubConnection!.invoke("JoinConversation", args: [conversationID]);
log('✅ Joined conversation: $conversationID', name: 'ChatProvider');
+ // CRITICAL: Share this connection with SignalRService for CallManager
+ log('🔗 [SIGNALR] Sharing connection with CallManager...', name: 'ChatProvider');
+ SignalRService().useExistingConnection(
+ chatHubConnection!,
+ userId: chatLoginResponse!.userId.toString(),
+ authToken: chatLoginResponse!.token ?? '',
+ conversationId: conversationID,
+ );
+ log('✅ [SIGNALR] Connection shared with CallManager', name: 'ChatProvider');
+
// Register ALL event handlers (chat + call)
_registerAllEventHandlers();
@@ -876,7 +887,12 @@ class ChatProvider with ChangeNotifier, DiagnosticableTreeMixin {
return;
}
- // Initialize CallManager if not already initialized
+ // CRITICAL: Ensure CallManager is properly initialized before starting call
+ log('🔧 [ChatProvider] Initializing CallManager before starting call...', name: 'ChatProvider');
+ log(' User ID: ${chatLoginResponse?.userId}', name: 'ChatProvider');
+ log(' Employee Number: ${sender?.employeeNumber}', name: 'ChatProvider');
+ log(' Conversation ID: ${chatParticipantModel?.id}', name: 'ChatProvider');
+
try {
await CallManager().initialize(
userId: chatLoginResponse!.userId.toString(),
@@ -886,11 +902,27 @@ class ChatProvider with ChangeNotifier, DiagnosticableTreeMixin {
referenceId: referenceID?.toString(),
employeeNumber: sender!.employeeNumber,
);
- } catch (e) {
- log('⚠️ [ChatProvider] CallManager already initialized or error: $e', name: 'ChatProvider');
+ log('✅ [ChatProvider] CallManager initialized successfully', name: 'ChatProvider');
+ } catch (e, stackTrace) {
+ log('❌ [ChatProvider] CallManager initialization failed: $e',
+ name: 'ChatProvider', error: e, stackTrace: stackTrace);
+
+ // CRITICAL: Do NOT proceed if initialization failed
+ final context = navigatorKey.currentContext;
+ if (context != null) {
+ CallErrorHandler.showGenericError(
+ context,
+ 'Unable to initialize calling system. Please try again.'
+ );
+ }
+ return;
}
// Delegate to CallManager
+ log('📤 [ChatProvider] Delegating to CallManager.startCall()', name: 'ChatProvider');
+ log(' To: ${recipient.employeeNumber} (${recipient.userName})', name: 'ChatProvider');
+ log(' Type: ${callType.name}', name: 'ChatProvider');
+
await CallManager().startCall(
peerId: recipient.employeeNumber ?? '',
peerName: recipient.userName ?? 'Unknown',
diff --git a/lib/modules/cx_module/chat/services/call_manager.dart b/lib/modules/cx_module/chat/services/call_manager.dart
index 89e4481d..aee7ebae 100644
--- a/lib/modules/cx_module/chat/services/call_manager.dart
+++ b/lib/modules/cx_module/chat/services/call_manager.dart
@@ -3,6 +3,7 @@ import 'dart:convert';
import 'dart:developer';
import 'package:flutter/material.dart';
import 'package:permission_handler/permission_handler.dart';
+import 'package:provider/provider.dart';
import 'package:test_sa/main.dart';
import 'package:test_sa/modules/cx_module/chat/call/audio_call_page.dart';
import 'package:test_sa/modules/cx_module/chat/call/video_call_page.dart';
@@ -11,6 +12,7 @@ import 'package:test_sa/modules/cx_module/chat/call/services/callkit_service.dar
import 'package:test_sa/modules/cx_module/chat/call/call_error_handler.dart';
import 'package:test_sa/modules/cx_module/chat/model/call_session.dart';
import 'package:test_sa/modules/cx_module/chat/services/signalr_service.dart';
+import 'package:test_sa/modules/cx_module/chat/chat_provider.dart';
import 'package:flutter_webrtc/flutter_webrtc.dart';
import 'package:uuid/uuid.dart';
@@ -46,6 +48,8 @@ class CallManager extends ChangeNotifier {
bool _handlersRegistered = false;
// Module context (for SignalR invocations)
+ String? _userId;
+ String? _authToken;
String? _moduleId;
String? _referenceId;
String? _conversationId;
@@ -79,6 +83,8 @@ class CallManager extends ChangeNotifier {
log(' Conversation ID: ${conversationId ?? "none"}', name: 'CallManager');
// Store module context
+ _userId = userId;
+ _authToken = authToken;
_moduleId = moduleId;
_referenceId = referenceId;
_conversationId = conversationId;
@@ -155,6 +161,15 @@ class CallManager extends ChangeNotifier {
_handlersRegistered = true;
log('✅ [HANDLERS] All call handlers registered', name: 'CallManager');
+ log(' - OnIncomingCallAsync', name: 'CallManager');
+ log(' - OnCallAcceptedAsync ⭐', name: 'CallManager');
+ log(' - OnCallDeclinedAsync', name: 'CallManager');
+ log(' - OnHangUpAsync ⭐', name: 'CallManager');
+ log(' - OnOfferAsync ⭐', name: 'CallManager');
+ log(' - OnAnswerOfferAsync ⭐', name: 'CallManager');
+ log(' - OnIceCandidateAsync ⭐', name: 'CallManager');
+ log(' - OnAudioToggle', name: 'CallManager');
+ log(' - OnCameraToggle', name: 'CallManager');
}
/// Start outgoing call
@@ -168,16 +183,31 @@ class CallManager extends ChangeNotifier {
log('═══════════════════════════════════════════', name: 'CallManager');
log('📞 [OUTGOING] Starting ${callType.name} call to $peerName ($peerId)', name: 'CallManager');
- // Check if already in call
- if (_callStatus != CallStatus.idle) {
- log('⚠️ [OUTGOING] Already in a call - CALLER_BUSY', name: 'CallManager');
+ // CRITICAL: Verify CallManager is initialized
+ if (_userId == null || _authToken == null) {
+ log('❌ [OUTGOING] CallManager not initialized!', name: 'CallManager');
+ log(' _userId: ${_userId ?? "NULL"}', name: 'CallManager');
+ log(' _authToken: ${_authToken != null ? "SET" : "NULL"}', name: 'CallManager');
final context = navigatorKey.currentContext;
if (context != null) {
- CallErrorHandler.showCallAlreadyInProgress(context);
+ CallErrorHandler.showGenericError(context, 'Call system not ready. Please try again.');
}
return;
}
+ // CRITICAL: Verify handlers are registered
+ if (!_handlersRegistered) {
+ log('❌ [OUTGOING] Call handlers not registered!', name: 'CallManager');
+ log(' Attempting to register handlers now...', name: 'CallManager');
+ _registerCallHandlers();
+ }
+
+ log('✅ [OUTGOING] CallManager initialized', name: 'CallManager');
+ log(' User ID: $_userId', name: 'CallManager');
+ log(' Employee Number: $_myEmployeeNumber', name: 'CallManager');
+ log(' Handlers registered: $_handlersRegistered', name: 'CallManager');
+ log(' SignalR state: ${_signalRService.connectionState}', name: 'CallManager');
+
// Update status
_updateCallStatus(CallStatus.checkingPermissions);
log('🔍 [OUTGOING] Checking permissions...', name: 'CallManager');
@@ -290,6 +320,51 @@ class CallManager extends ChangeNotifier {
log(' Caller: $callerName ($callerId)', name: 'CallManager');
log(' Video: $isVideoCall', name: 'CallManager');
+ // CRITICAL FIX: Auto-initialize CallManager if not already initialized
+ if (_userId == null || _authToken == null) {
+ log('⚠️ [INCOMING] CallManager not initialized - attempting auto-initialization...', name: 'CallManager');
+
+ // Try to get credentials from ChatProvider
+ try {
+ final chatProvider = Provider.of(navigatorKey.currentContext!, listen: false);
+ final chatLoginResponse = chatProvider.chatLoginResponse;
+ final sender = chatProvider.sender; // Get sender for employee number
+
+ if (chatLoginResponse != null) {
+ log('🔧 [INCOMING] Found credentials in ChatProvider', name: 'CallManager');
+ log(' User ID: ${chatLoginResponse.userId}', name: 'CallManager');
+ log(' Employee Number: ${sender?.employeeNumber ?? "NOT AVAILABLE"}', name: 'CallManager');
+
+ await initialize(
+ userId: chatLoginResponse.userId.toString(),
+ authToken: chatLoginResponse.token ?? '',
+ conversationId: extraData?['conversationId'] as String?,
+ moduleId: extraData?['moduleId'] as String?,
+ referenceId: extraData?['referenceId'] as String?,
+ employeeNumber: sender?.employeeNumber, // Get from sender participant
+ );
+
+ log('✅ [INCOMING] Auto-initialization successful', name: 'CallManager');
+ } else {
+ log('❌ [INCOMING] No credentials available in ChatProvider', name: 'CallManager');
+ // Show error to user
+ final context = navigatorKey.currentContext;
+ if (context != null) {
+ CallErrorHandler.showGenericError(context, 'Unable to receive call. Please login to chat first.');
+ }
+ return;
+ }
+ } catch (e) {
+ log('❌ [INCOMING] Auto-initialization failed: $e', name: 'CallManager');
+ // Show error to user
+ final context = navigatorKey.currentContext;
+ if (context != null) {
+ CallErrorHandler.showGenericError(context, 'Unable to receive call. Please open chat first.');
+ }
+ return;
+ }
+ }
+
// Check if already in call
if (_callStatus != CallStatus.idle) {
log('⚠️ [INCOMING] Already in a call - declining with CALLER_BUSY', name: 'CallManager');
@@ -313,6 +388,10 @@ class CallManager extends ChangeNotifier {
log('🔌 [INCOMING] Ensuring SignalR connection...', name: 'CallManager');
if (!await _signalRService.ensureConnected()) {
log('❌ [INCOMING] SignalR connection failed - cannot handle call', name: 'CallManager');
+ final context = navigatorKey.currentContext;
+ if (context != null) {
+ CallErrorHandler.showSignalRNotConnected(context);
+ }
return;
}
log('✅ [INCOMING] SignalR connected', name: 'CallManager');
@@ -394,12 +473,22 @@ class CallManager extends ChangeNotifier {
try {
log('═══════════════════════════════════════════', name: 'CallManager');
log('✅ [ACCEPT] Accepting call...', name: 'CallManager');
+ log('🔍 [ACCEPT] CODE VERSION: 2026-07-19-v3-DEBUG', name: 'CallManager');
if (_currentCall == null || _callStatus != CallStatus.incomingRinging) {
log('⚠️ [ACCEPT] No incoming call to accept', name: 'CallManager');
+ log(' Current call: ${_currentCall?.callId}', name: 'CallManager');
+ log(' Current status: ${_callStatus.name}', name: 'CallManager');
return;
}
+ log('📊 [ACCEPT] Current call state:', name: 'CallManager');
+ log(' Call ID: ${_currentCall!.callId}', name: 'CallManager');
+ log(' Peer ID: ${_currentCall!.peerId}', name: 'CallManager');
+ log(' Peer Name: ${_currentCall!.peerName}', name: 'CallManager');
+ log(' Call Type: ${_currentCall!.type.name}', name: 'CallManager');
+ log(' Direction: ${_currentCall!.direction.name}', name: 'CallManager');
+
// Check permissions
log('🔍 [ACCEPT] Checking permissions...', name: 'CallManager');
if (!await _checkPermissions(_currentCall!.type)) {
@@ -411,20 +500,31 @@ class CallManager extends ChangeNotifier {
// Ensure SignalR connected
log('🔌 [ACCEPT] Ensuring SignalR connection...', name: 'CallManager');
+ log(' Current SignalR state: ${_signalRService.connectionState}', name: 'CallManager');
+
if (!await _signalRService.ensureConnected()) {
- log('❌ [ACCEPT] SignalR not connected', name: 'CallManager');
+ log('❌ [ACCEPT] SignalR connection failed', name: 'CallManager');
+ log(' Final state: ${_signalRService.connectionState}', name: 'CallManager');
_cleanup();
return;
}
log('✅ [ACCEPT] SignalR connected', name: 'CallManager');
+ log(' SignalR state: ${_signalRService.connectionState}', name: 'CallManager');
_updateCallStatus(CallStatus.connecting);
// Cancel timeout timer
_callTimeoutTimer?.cancel();
+ log('✅ [ACCEPT] Timeout timer cancelled', name: 'CallManager');
// Invoke AnswerCallAsync
log('📤 [ACCEPT] Invoking AnswerCallAsync...', name: 'CallManager');
+ log(' My Employee Number: ${_myEmployeeNumber ?? "NULL"}', name: 'CallManager');
+ log(' Peer ID: ${_currentCall!.peerId}', name: 'CallManager');
+ log(' Module ID: ${_moduleId ?? "0"}', name: 'CallManager');
+ log(' Reference ID: ${_referenceId ?? "0"}', name: 'CallManager');
+ log(' Conversation ID: ${_conversationId ?? ""}', name: 'CallManager');
+
await _signalRService.invoke('AnswerCallAsync', args: [
_myEmployeeNumber ?? '',
_currentCall!.peerId,
@@ -432,18 +532,31 @@ class CallManager extends ChangeNotifier {
_referenceId ?? '0',
_conversationId ?? '',
]);
- log('✅ [ACCEPT] AnswerCallAsync invoked', name: 'CallManager');
+ log('✅ [ACCEPT] AnswerCallAsync invoked successfully', name: 'CallManager');
+ log(' Waiting for backend to process...', name: 'CallManager');
// Initialize WebRTC
log('🔧 [ACCEPT] Initializing WebRTC...', name: 'CallManager');
+ log(' Call type: ${_currentCall!.type.name}', name: 'CallManager');
await _initializeWebRTC();
log('✅ [ACCEPT] WebRTC initialized', name: 'CallManager');
+ log(' WebRTC service: ${_webrtcService != null ? "Created" : "NULL"}', name: 'CallManager');
// Navigate to call screen
+ log('🧭 [ACCEPT] Navigating to call screen...', name: 'CallManager');
_navigateToCallScreen();
log('✅ [ACCEPT] Navigated to call screen', name: 'CallManager');
log('✅ [ACCEPT] Call accepted successfully', name: 'CallManager');
+ log('ℹ️ [ACCEPT] Now waiting for caller to send offer via OnOfferAsync...', name: 'CallManager');
+ log('ℹ️ [ACCEPT] Expected sequence:', name: 'CallManager');
+ log(' 1. Caller receives OnCallAcceptedAsync', name: 'CallManager');
+ log(' 2. Caller creates offer and sends OfferAsync', name: 'CallManager');
+ log(' 3. We receive OnOfferAsync', name: 'CallManager');
+ log(' 4. We create answer and send AnswerOfferAsync', name: 'CallManager');
+ log(' 5. Caller receives OnAnswerOfferAsync', name: 'CallManager');
+ log(' 6. ICE candidates exchange', name: 'CallManager');
+ log(' 7. Connection established', name: 'CallManager');
log('═══════════════════════════════════════════', name: 'CallManager');
} catch (e, stackTrace) {
@@ -762,8 +875,11 @@ class CallManager extends ChangeNotifier {
// ==================== SignalR Event Handlers ====================
void _handleIncomingCall(List