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? args) { - log('📞 [EVENT] OnIncomingCallAsync received', name: 'CallManager'); + log('═══════════════════════════════════════════', name: 'CallManager'); + log('📞 [EVENT] ⚠️ OnIncomingCallAsync received', name: 'CallManager'); + log(' Args count: ${args?.length ?? 0}', name: 'CallManager'); log(' Args: $args', name: 'CallManager'); + log('═══════════════════════════════════════════', name: 'CallManager'); // Note: Incoming calls are now handled by CallNotificationService // This event is kept for chat screen integration @@ -771,30 +887,71 @@ class CallManager extends ChangeNotifier { } void _handleCallAccepted(List? args) async { - log('📞 [EVENT] OnCallAcceptedAsync received', name: 'CallManager'); + log('═══════════════════════════════════════════', name: 'CallManager'); + log('📞 [EVENT] ✅ OnCallAcceptedAsync received', name: 'CallManager'); + log(' Args count: ${args?.length ?? 0}', name: 'CallManager'); + log(' Args: $args', name: 'CallManager'); log(' Current status: ${_callStatus.name}', name: 'CallManager'); + log(' Current call: ${_currentCall?.callId}', name: 'CallManager'); + log(' Call direction: ${_currentCall?.direction.name}', name: 'CallManager'); + log(' Peer: ${_currentCall?.peerId}', name: 'CallManager'); + log('═══════════════════════════════════════════', name: 'CallManager'); + + // CRITICAL: Only the CALLER should create an offer + // The RECEIVER should wait for OnOfferAsync + if (_currentCall?.direction != CallDirection.outgoing) { + log('⚠️ [EVENT] This is an INCOMING call - receiver should NOT create offer', name: 'CallManager'); + log(' Current direction: ${_currentCall?.direction.name}', name: 'CallManager'); + log(' ✅ Receiver will wait for OnOfferAsync from caller', name: 'CallManager'); + return; + } - if (_callStatus != CallStatus.outgoingRinging) { - log('⚠️ [EVENT] Not in outgoing ringing state, ignoring', name: 'CallManager'); + // FIXED: Accept event in both connecting and outgoingRinging states + // Backend might send OnCallAcceptedAsync very quickly before status changes to outgoingRinging + if (_callStatus != CallStatus.connecting && _callStatus != CallStatus.outgoingRinging) { + log('⚠️ [EVENT] Not in valid state for creating offer (current: ${_callStatus.name})', name: 'CallManager'); + log(' Expected: connecting OR outgoingRinging', name: 'CallManager'); return; } + // If we're still in connecting, update to outgoingRinging first + if (_callStatus == CallStatus.connecting) { + log('🔄 [EVENT] Status was connecting, updating to outgoingRinging first', name: 'CallManager'); + _updateCallStatus(CallStatus.outgoingRinging); + } + _callTimeoutTimer?.cancel(); _updateCallStatus(CallStatus.connecting); try { - // Create and send offer - log('🔧 [EVENT] Creating SDP offer...', name: 'CallManager'); + // Ensure WebRTC is initialized before creating offer + if (_webrtcService == null) { + log('⚠️ [EVENT] WebRTC not initialized yet, waiting...', name: 'CallManager'); + // Wait a bit for WebRTC to initialize (it should be initializing in parallel) + await Future.delayed(const Duration(milliseconds: 500)); + + if (_webrtcService == null) { + log('❌ [EVENT] WebRTC still not initialized after wait', name: 'CallManager'); + return; + } + } + + // CALLER SIDE: Create and send offer + log('🔧 [EVENT] [CALLER] Creating SDP offer...', name: 'CallManager'); final offer = await _webrtcService!.createOffer(); - log('✅ [EVENT] SDP offer created', name: 'CallManager'); + log('✅ [EVENT] [CALLER] SDP offer created (length: ${offer.sdp?.length ?? 0})', name: 'CallManager'); + log(' Offer SDP type: ${offer.type}', name: 'CallManager'); + + log('📤 [EVENT] [CALLER] Sending offer via OfferAsync...', name: 'CallManager'); + log(' To peer: ${_currentCall!.peerId}', name: 'CallManager'); + log(' Call ID: ${_currentCall!.callId}', name: 'CallManager'); - log('📤 [EVENT] Sending offer via OfferAsync...', name: 'CallManager'); await _signalRService.invoke('OfferAsync', args: [ _currentCall!.peerId, offer.sdp ?? '', _currentCall!.callId, ]); - log('✅ [EVENT] Offer sent successfully', name: 'CallManager'); + log('✅ [EVENT] [CALLER] Offer sent successfully via OfferAsync', name: 'CallManager'); } catch (e, stackTrace) { log('❌ [EVENT] Error handling call accepted: $e', @@ -804,8 +961,11 @@ class CallManager extends ChangeNotifier { } void _handleCallDeclined(List? args) { - log('📞 [EVENT] OnCallDeclinedAsync received', name: 'CallManager'); + log('═══════════════════════════════════════════', name: 'CallManager'); + log('📞 [EVENT] ❌ OnCallDeclinedAsync received', name: 'CallManager'); + log(' Args count: ${args?.length ?? 0}', name: 'CallManager'); log(' Args: $args', name: 'CallManager'); + log('═══════════════════════════════════════════', name: 'CallManager'); // Extract decline reason if available String? reason; @@ -828,13 +988,24 @@ class CallManager extends ChangeNotifier { } void _handleHangUp(List? args) { - log('📞 [EVENT] OnHangUpAsync received', name: 'CallManager'); + log('═══════════════════════════════════════════', name: 'CallManager'); + log('📞 [EVENT] 🔴 OnHangUpAsync received', name: 'CallManager'); + log(' Args count: ${args?.length ?? 0}', name: 'CallManager'); log(' Args: $args', name: 'CallManager'); + log(' Current call status: ${_callStatus.name}', name: 'CallManager'); + log(' Current call ID: ${_currentCall?.callId}', name: 'CallManager'); + log('═══════════════════════════════════════════', name: 'CallManager'); + _cleanup(); } void _handleOffer(List? args) async { - log('📞 [EVENT] OnOfferAsync received', name: 'CallManager'); + log('═══════════════════════════════════════════', name: 'CallManager'); + log('📞 [EVENT] 📥 OnOfferAsync received', name: 'CallManager'); + log(' Args count: ${args?.length ?? 0}', name: 'CallManager'); + log(' Current status: ${_callStatus.name}', name: 'CallManager'); + log(' WebRTC initialized: ${_webrtcService != null}', name: 'CallManager'); + log('═══════════════════════════════════════════', name: 'CallManager'); if (args == null || args.isEmpty) { log('⚠️ [EVENT] No offer data received', name: 'CallManager'); @@ -843,11 +1014,13 @@ class CallManager extends ChangeNotifier { try { final offerSdp = args[0] as String?; - if (offerSdp == null) { - log('⚠️ [EVENT] Offer SDP is null', name: 'CallManager'); + if (offerSdp == null || offerSdp.isEmpty) { + log('⚠️ [EVENT] Offer SDP is null or empty', name: 'CallManager'); return; } + log(' Offer SDP length: ${offerSdp.length}', name: 'CallManager'); + if (_webrtcService == null) { log('⚠️ [EVENT] WebRTC service not initialized', name: 'CallManager'); return; @@ -855,15 +1028,19 @@ class CallManager extends ChangeNotifier { log('🔧 [EVENT] Creating SDP answer...', name: 'CallManager'); final answer = await _webrtcService!.createAnswer(offerSdp); - log('✅ [EVENT] SDP answer created', name: 'CallManager'); + log('✅ [EVENT] SDP answer created (length: ${answer.sdp?.length ?? 0})', name: 'CallManager'); + log(' Answer SDP type: ${answer.type}', name: 'CallManager'); log('📤 [EVENT] Sending answer via AnswerOfferAsync...', name: 'CallManager'); + log(' To peer: ${_currentCall!.peerId}', name: 'CallManager'); + log(' Call ID: ${_currentCall!.callId}', name: 'CallManager'); + await _signalRService.invoke('AnswerOfferAsync', args: [ _currentCall!.peerId, answer.sdp ?? '', _currentCall!.callId, ]); - log('✅ [EVENT] Answer sent successfully', name: 'CallManager'); + log('✅ [EVENT] Answer sent successfully via AnswerOfferAsync', name: 'CallManager'); } catch (e, stackTrace) { log('❌ [EVENT] Error handling offer: $e', @@ -872,7 +1049,12 @@ class CallManager extends ChangeNotifier { } void _handleAnswer(List? args) async { - log('📞 [EVENT] OnAnswerOfferAsync received', name: 'CallManager'); + log('═══════════════════════════════════════════', name: 'CallManager'); + log('📞 [EVENT] 📥 OnAnswerOfferAsync received', name: 'CallManager'); + log(' Args count: ${args?.length ?? 0}', name: 'CallManager'); + log(' Current status: ${_callStatus.name}', name: 'CallManager'); + log(' WebRTC initialized: ${_webrtcService != null}', name: 'CallManager'); + log('═══════════════════════════════════════════', name: 'CallManager'); if (args == null || args.isEmpty) { log('⚠️ [EVENT] No answer data received', name: 'CallManager'); @@ -881,11 +1063,13 @@ class CallManager extends ChangeNotifier { try { final answerSdp = args[0] as String?; - if (answerSdp == null) { - log('⚠️ [EVENT] Answer SDP is null', name: 'CallManager'); + if (answerSdp == null || answerSdp.isEmpty) { + log('⚠️ [EVENT] Answer SDP is null or empty', name: 'CallManager'); return; } + log(' Answer SDP length: ${answerSdp.length}', name: 'CallManager'); + if (_webrtcService == null) { log('⚠️ [EVENT] WebRTC service not initialized', name: 'CallManager'); return; @@ -894,6 +1078,7 @@ class CallManager extends ChangeNotifier { log('🔧 [EVENT] Setting remote answer...', name: 'CallManager'); await _webrtcService!.setRemoteAnswer(answerSdp); log('✅ [EVENT] Remote answer set successfully', name: 'CallManager'); + log(' Now waiting for ICE connection to establish...', name: 'CallManager'); } catch (e, stackTrace) { log('❌ [EVENT] Error handling answer: $e', @@ -902,7 +1087,7 @@ class CallManager extends ChangeNotifier { } void _handleIceCandidate(List? args) async { - log('📞 [EVENT] OnIceCandidateAsync received', name: 'CallManager'); + log('📞 [EVENT] 🧊 OnIceCandidateAsync received', name: 'CallManager'); if (args == null || args.isEmpty) { log('⚠️ [EVENT] No ICE candidate data received', name: 'CallManager'); @@ -929,6 +1114,7 @@ class CallManager extends ChangeNotifier { ); log('🧊 [EVENT] Adding remote ICE candidate...', name: 'CallManager'); + log(' Candidate: ${candidate.candidate?.substring(0, 50)}...', name: 'CallManager'); await _webrtcService!.addIceCandidate(candidate); log('✅ [EVENT] Remote ICE candidate added', name: 'CallManager'); @@ -939,7 +1125,7 @@ class CallManager extends ChangeNotifier { } void _handleAudioToggle(List? args) { - log('📞 [EVENT] OnAudioToggle received', name: 'CallManager'); + log('📞 [EVENT] 🎤 OnAudioToggle received', name: 'CallManager'); log(' Peer muted: $_isPeerMuted -> ${!_isPeerMuted}', name: 'CallManager'); _isPeerMuted = !_isPeerMuted; @@ -947,7 +1133,7 @@ class CallManager extends ChangeNotifier { } void _handleCameraToggle(List? args) { - log('📞 [EVENT] OnCameraToggle received', name: 'CallManager'); + log('📞 [EVENT] 📹 OnCameraToggle received', name: 'CallManager'); log(' Peer camera: $_isPeerCameraOn -> ${!_isPeerCameraOn}', name: 'CallManager'); _isPeerCameraOn = !_isPeerCameraOn; @@ -992,4 +1178,3 @@ class CallManager extends ChangeNotifier { } } } - diff --git a/lib/modules/cx_module/chat/services/signalr_service.dart b/lib/modules/cx_module/chat/services/signalr_service.dart index 64f0eabb..eee0568d 100644 --- a/lib/modules/cx_module/chat/services/signalr_service.dart +++ b/lib/modules/cx_module/chat/services/signalr_service.dart @@ -35,6 +35,29 @@ class SignalRService { /// Get current connection state HubConnectionState? get connectionState => _hubConnection?.state; + /// Use an existing HubConnection (from ChatProvider) + /// This prevents creating duplicate connections + void useExistingConnection(HubConnection existingConnection, { + String? userId, + String? authToken, + String? conversationId, + }) { + log('═══════════════════════════════════════════', name: 'SignalRService'); + log('🔌 [SIGNALR] Using existing HubConnection', name: 'SignalRService'); + log(' Connection ID: ${existingConnection.connectionId}', name: 'SignalRService'); + log(' User ID: ${userId ?? "not set"}', name: 'SignalRService'); + log(' Conversation ID: ${conversationId ?? "not set"}', name: 'SignalRService'); + log('═══════════════════════════════════════════', name: 'SignalRService'); + + _hubConnection = existingConnection; + _userId = userId; + _authToken = authToken; + _currentConversationId = conversationId; + + // Re-register all stored event handlers on this connection + _reregisterAllHandlers(); + } + /// Initialize SignalR connection with authentication Future initialize({ required String userId, @@ -274,4 +297,3 @@ class SignalRService { log('✅ [SIGNALR] Service reset complete', name: 'SignalRService'); } } -