audio call in progress

ui_ux_rollout_merge_audio_video_call
WaseemAbbasi22 1 month ago
parent a5d129970b
commit e9df60bd48

@ -5,6 +5,7 @@ import 'dart:io';
import 'package:audio_waveforms/audio_waveforms.dart'; import 'package:audio_waveforms/audio_waveforms.dart';
import 'package:cached_network_image/cached_network_image.dart'; import 'package:cached_network_image/cached_network_image.dart';
import 'package:flutter/cupertino.dart'; import 'package:flutter/cupertino.dart';
import 'package:flutter/foundation.dart';
import 'package:flutter/material.dart'; import 'package:flutter/material.dart';
import 'package:provider/provider.dart'; import 'package:provider/provider.dart';
import 'package:test_sa/extensions/context_extension.dart'; import 'package:test_sa/extensions/context_extension.dart';
@ -204,6 +205,24 @@ class _ChatPageState extends State<ChatPage> {
appBar: DefaultAppBar( appBar: DefaultAppBar(
title: widget.title, title: widget.title,
actions: [ actions: [
// NEW: Call readiness indicator - shows if device can receive calls
if (kDebugMode)
Selector<ChatProvider, bool>(
selector: (_, provider) => provider.areCallHandlersRegistered,
builder: (context, handlersRegistered, _) => Padding(
padding: const EdgeInsets.only(right: 8),
child: Center(
child: Container(
width: 8,
height: 8,
decoration: BoxDecoration(
shape: BoxShape.circle,
color: handlersRegistered ? Colors.green : Colors.red,
),
),
),
),
),
Selector<ChatProvider, Participants?>( Selector<ChatProvider, Participants?>(
selector: (_, provider) => provider.recipient, selector: (_, provider) => provider.recipient,
builder: (context, recipient, _) => IconButton( builder: (context, recipient, _) => IconButton(

@ -44,9 +44,11 @@ import 'package:signalr_netcore/signalr_client.dart';
import 'package:test_sa/controllers/api_routes/api_manager.dart'; import 'package:test_sa/controllers/api_routes/api_manager.dart';
import 'package:test_sa/controllers/api_routes/urls.dart'; import 'package:test_sa/controllers/api_routes/urls.dart';
import 'package:test_sa/extensions/string_extensions.dart'; import 'package:test_sa/extensions/string_extensions.dart';
import 'package:test_sa/main.dart';
import 'package:test_sa/modules/cx_module/chat/model/chat_login_response_model.dart'; import 'package:test_sa/modules/cx_module/chat/model/chat_login_response_model.dart';
import 'package:uuid/uuid.dart'; import 'package:uuid/uuid.dart';
import 'package:flutter/material.dart' as Material; import 'package:flutter/material.dart' as Material;
import 'package:flutter/material.dart'; // NEW - for WidgetsBinding and showDialog
import 'api_client.dart'; import 'api_client.dart';
import 'chat_api_client.dart'; import 'chat_api_client.dart';
@ -56,7 +58,9 @@ import 'model/get_search_user_chat_model.dart';
import 'model/get_single_user_chat_list_model.dart'; import 'model/get_single_user_chat_list_model.dart';
import 'model/unread_message_model.dart'; import 'model/unread_message_model.dart';
import 'model/user_chat_history_model.dart'; import 'model/user_chat_history_model.dart';
import 'model/call_session.dart'; // NEW - Import call session model import 'model/call_session.dart';
import 'call/call_debug_helper.dart';
import 'call/incoming_call_dialog.dart'; // NEW - Import incoming call dialog
//Need to refactor this remove unused code. //Need to refactor this remove unused code.
@ -102,6 +106,10 @@ class ChatProvider with ChangeNotifier, DiagnosticableTreeMixin {
bool isPeerMuted = false; bool isPeerMuted = false;
bool isPeerCameraOn = true; bool isPeerCameraOn = true;
// NEW: Call handlers registration status
bool _callHandlersRegistered = false;
bool get areCallHandlersRegistered => _callHandlersRegistered;
bool get isCallInProgress => callStatus != CallStatus.idle; bool get isCallInProgress => callStatus != CallStatus.idle;
/// OPTIMIZATION: Improved connection disposal to prevent memory leaks /// OPTIMIZATION: Improved connection disposal to prevent memory leaks
@ -345,27 +353,30 @@ class ChatProvider with ChangeNotifier, DiagnosticableTreeMixin {
chatHubConnection = await getHubConnection(); chatHubConnection = await getHubConnection();
await chatHubConnection!.start(); await chatHubConnection!.start();
// NEW: Log connection details for diagnostics
log('═══════════════════════════════════════════', name: 'ChatProvider');
log('🔌 SignalR Hub Connection: Started', name: 'ChatProvider');
log('🔑 Connection ID: ${chatHubConnection!.connectionId ?? "NULL"}', name: 'ChatProvider');
log('👤 User ID: ${chatLoginResponse!.userId}', name: 'ChatProvider');
log('💬 Conversation ID: $conversationID', name: 'ChatProvider');
log('📞 My Employee Number: ${sender?.employeeNumber ?? "NOT SET"}', name: 'ChatProvider');
log('═══════════════════════════════════════════', name: 'ChatProvider');
if (kDebugMode) { if (kDebugMode) {
print("🔌 SignalR Hub Connection: Started"); print("🔌 SignalR Hub Connection: Started");
} }
await chatHubConnection!.invoke("JoinConversation", args: [conversationID]); await chatHubConnection!.invoke("JoinConversation", args: [conversationID]);
log('✅ Joined conversation: $conversationID', name: 'ChatProvider');
// Register chat event handlers // Register ALL event handlers (chat + call)
chatHubConnection!.on("ReceiveMessage", onMsgReceived1); _registerAllEventHandlers();
chatHubConnection!.on("OnMessageReceivedAsync", onMsgReceived);
chatHubConnection!.on("OnSubmitChatAsync", onSubmitChatAsync);
chatHubConnection!.on("OnTypingAsync", OnTypingAsync);
chatHubConnection!.on("OnStopTypingAsync", OnStopTypingAsync);
chatHubConnection!.on("OnSeenChatUserAsync", onSeenUserChatAsync);
chatHubConnection!.on("OnAckSeenAsync", onAckSeenAsync);
// Register call event handlers (PHASE 1)
_registerCallHandlers();
//group On message //group On message
// chatHubConnection.on("OnDeliveredGroupChatHistoryAsync", onGroupMsgReceived); // chatHubConnection.on("OnDeliveredGroupChatHistoryAsync", onGroupMsgReceived);
} catch (e) { } catch (e) {
log('❌ [CONNECTION] Error building SignalR connection: $e', name: 'ChatProvider');
if (kDebugMode) { if (kDebugMode) {
print('⚠️ Error building SignalR connection: $e'); print('⚠️ Error building SignalR connection: $e');
} }
@ -375,18 +386,72 @@ class ChatProvider with ChangeNotifier, DiagnosticableTreeMixin {
} }
} }
/// Register all event handlers (chat + call) - called on initial connection and after reconnect
void _registerAllEventHandlers() {
if (chatHubConnection == null) {
log('⚠️ Cannot register event handlers - connection is null', name: 'ChatProvider');
return;
}
log('🔧 [EVENT HANDLERS] Registering all event handlers...', name: 'ChatProvider');
// Register chat event handlers
chatHubConnection!.on("ReceiveMessage", onMsgReceived1);
chatHubConnection!.on("OnMessageReceivedAsync", onMsgReceived);
chatHubConnection!.on("OnSubmitChatAsync", onSubmitChatAsync);
chatHubConnection!.on("OnTypingAsync", OnTypingAsync);
chatHubConnection!.on("OnStopTypingAsync", OnStopTypingAsync);
chatHubConnection!.on("OnSeenChatUserAsync", onSeenUserChatAsync);
chatHubConnection!.on("OnAckSeenAsync", onAckSeenAsync);
// Register call event handlers
_registerCallHandlers();
log('✅ [EVENT HANDLERS] All event handlers registered successfully', name: 'ChatProvider');
}
Future<HubConnection> getHubConnection() async { Future<HubConnection> getHubConnection() async {
if (kDebugMode) { if (kDebugMode) {
print('🔧 Creating new SignalR hub connection...'); print('🔧 Creating new SignalR hub connection...');
} }
HubConnection hub; HubConnection hub;
HttpConnectionOptions httpOp = HttpConnectionOptions(skipNegotiation: false, logMessageContent: true); HttpConnectionOptions httpOp = HttpConnectionOptions(
skipNegotiation: false,
logMessageContent: true,
);
hub = HubConnectionBuilder() hub = HubConnectionBuilder()
.withUrl("${URLs.chatHubUrlChat}?UserId=${chatLoginResponse!.userId}&source=Desktop&access_token=${chatLoginResponse!.token}", options: httpOp) .withUrl("${URLs.chatHubUrlChat}?UserId=${chatLoginResponse!.userId}&source=Desktop&access_token=${chatLoginResponse!.token}", options: httpOp)
.withAutomaticReconnect(retryDelays: <int>[2000, 5000, 10000, 20000]).build(); .withAutomaticReconnect(retryDelays: <int>[2000, 5000, 10000, 20000]).build();
// NEW: Add global event logging in debug mode AND re-register handlers on reconnect
if (kDebugMode) { if (kDebugMode) {
// Log connection state changes
hub.onclose(({Exception? error}) {
log('🔴 [SignalR] Connection closed: $error', name: 'ChatProvider');
});
hub.onreconnecting(({Exception? error}) {
log('🟡 [SignalR] Reconnecting: $error', name: 'ChatProvider');
});
hub.onreconnected(({String? connectionId}) async {
log('🟢 [SignalR] Reconnected: $connectionId', name: 'ChatProvider');
log('🔄 [SignalR] Re-registering all event handlers after reconnection...', name: 'ChatProvider');
// CRITICAL: Re-register all event handlers after reconnection
_registerAllEventHandlers();
log('✅ [SignalR] Event handlers re-registered after reconnection', name: 'ChatProvider');
});
print('✅ SignalR hub connection created with debug logging and auto-reregister');
} else {
// Production: still need to re-register handlers on reconnect
hub.onreconnected(({String? connectionId}) async {
_registerAllEventHandlers();
});
print('✅ SignalR hub connection created'); print('✅ SignalR hub connection created');
} }
@ -816,7 +881,12 @@ class ChatProvider with ChangeNotifier, DiagnosticableTreeMixin {
/// Start a new audio or video call /// Start a new audio or video call
Future<void> startCall(Participants recipient, CallType callType) async { Future<void> startCall(Participants recipient, CallType callType) async {
log('🔵 [CALL] startCall() invoked', name: 'ChatProvider');
log('🔵 [CALL] Parameters - recipient: ${recipient.employeeNumber}, callType: $callType', name: 'ChatProvider');
log('🔵 [CALL] Current callStatus: $callStatus', name: 'ChatProvider');
if (callStatus != CallStatus.idle) { if (callStatus != CallStatus.idle) {
log('⚠️ [CALL] Cannot start call - already in a call (status: $callStatus)', name: 'ChatProvider');
if (kDebugMode) { if (kDebugMode) {
print('⚠️ Cannot start call - already in a call'); print('⚠️ Cannot start call - already in a call');
} }
@ -824,26 +894,35 @@ class ChatProvider with ChangeNotifier, DiagnosticableTreeMixin {
} }
if (sender == null) { if (sender == null) {
log('❌ [CALL] Cannot start call - sender is null', name: 'ChatProvider');
if (kDebugMode) { if (kDebugMode) {
print('⚠️ Cannot start call - sender is null'); print('⚠️ Cannot start call - sender is null');
} }
return; return;
} }
log('🔵 [CALL] Sender: ${sender!.employeeNumber}', name: 'ChatProvider');
callStatus = CallStatus.checkingPermissions; callStatus = CallStatus.checkingPermissions;
notifyListeners(); notifyListeners();
log('🔵 [CALL] Status changed to: checkingPermissions', name: 'ChatProvider');
try { try {
// Generate unique call ID // Generate unique call ID
final callId = const Uuid().v4(); final callId = const Uuid().v4();
final isVideo = callType == CallType.video; final isVideo = callType == CallType.video;
log('🔵 [CALL] Generated callId: $callId', name: 'ChatProvider');
log('🔵 [CALL] Call type: ${isVideo ? "VIDEO" : "AUDIO"}', name: 'ChatProvider');
// Request permissions // Request permissions
log('🔵 [CALL] Requesting microphone permission...', name: 'ChatProvider');
final micStatus = await Permission.microphone.request(); final micStatus = await Permission.microphone.request();
log('🔵 [CALL] Microphone permission status: ${micStatus.name}', name: 'ChatProvider');
if (!micStatus.isGranted) { if (!micStatus.isGranted) {
callStatus = CallStatus.idle; callStatus = CallStatus.idle;
currentCall = null; currentCall = null;
notifyListeners(); notifyListeners();
log('❌ [CALL] Microphone permission denied - aborting call', name: 'ChatProvider');
if (kDebugMode) { if (kDebugMode) {
print('⚠️ Microphone permission denied'); print('⚠️ Microphone permission denied');
} }
@ -851,11 +930,15 @@ class ChatProvider with ChangeNotifier, DiagnosticableTreeMixin {
} }
if (isVideo) { if (isVideo) {
log('🔵 [CALL] Requesting camera permission...', name: 'ChatProvider');
final cameraStatus = await Permission.camera.request(); final cameraStatus = await Permission.camera.request();
log('🔵 [CALL] Camera permission status: ${cameraStatus.name}', name: 'ChatProvider');
if (!cameraStatus.isGranted) { if (!cameraStatus.isGranted) {
callStatus = CallStatus.idle; callStatus = CallStatus.idle;
currentCall = null; currentCall = null;
notifyListeners(); notifyListeners();
log('❌ [CALL] Camera permission denied - aborting call', name: 'ChatProvider');
if (kDebugMode) { if (kDebugMode) {
print('⚠️ Camera permission denied'); print('⚠️ Camera permission denied');
} }
@ -864,6 +947,7 @@ class ChatProvider with ChangeNotifier, DiagnosticableTreeMixin {
} }
// Create call session // Create call session
log('🔵 [CALL] Creating CallSession...', name: 'ChatProvider');
currentCall = CallSession( currentCall = CallSession(
callId: callId, callId: callId,
type: callType, type: callType,
@ -873,8 +957,21 @@ class ChatProvider with ChangeNotifier, DiagnosticableTreeMixin {
peerAvatar: recipient.image, peerAvatar: recipient.image,
startTime: DateTime.now(), startTime: DateTime.now(),
); );
log('✅ [CALL] CallSession created - peerId: ${currentCall!.peerId}, peerName: ${currentCall!.peerName}', name: 'ChatProvider');
// Invoke SignalR CallUserAsync // Invoke SignalR CallUserAsync
log('🔵 [CALL] Checking SignalR connection status...', name: 'ChatProvider');
if (chatHubConnection == null) {
log('❌ [CALL] SignalR connection is null!', name: 'ChatProvider');
throw Exception('SignalR connection not established');
}
log('🔵 [CALL] SignalR connection state: ${chatHubConnection!.state}', name: 'ChatProvider');
log('🔵 [CALL] Invoking CallUserAsync with args:', name: 'ChatProvider');
log(' - source: ${sender!.employeeNumber}', name: 'ChatProvider');
log(' - target: ${recipient.employeeNumber}', name: 'ChatProvider');
log(' - isVideoCall: $isVideo', name: 'ChatProvider');
await chatHubConnection?.invoke( await chatHubConnection?.invoke(
'CallUserAsync', 'CallUserAsync',
args: [ args: [
@ -883,22 +980,28 @@ class ChatProvider with ChangeNotifier, DiagnosticableTreeMixin {
isVideo, isVideo,
], ],
); );
log('✅ [CALL] CallUserAsync invoked successfully', name: 'ChatProvider');
callStatus = CallStatus.outgoingRinging; callStatus = CallStatus.outgoingRinging;
notifyListeners(); notifyListeners();
log('🔵 [CALL] Status changed to: outgoingRinging', name: 'ChatProvider');
// Start 60s timeout // Start 60s timeout
_callTimeoutTimer?.cancel(); _callTimeoutTimer?.cancel();
log('🔵 [CALL] Starting 60s timeout timer...', name: 'ChatProvider');
_callTimeoutTimer = Timer(const Duration(seconds: 60), () { _callTimeoutTimer = Timer(const Duration(seconds: 60), () {
log('⏱️ [CALL] Timeout timer fired', name: 'ChatProvider');
if (callStatus == CallStatus.outgoingRinging) { if (callStatus == CallStatus.outgoingRinging) {
_handleCallTimeout(); _handleCallTimeout();
} }
}); });
log('✅ [CALL] Call started successfully - callId: $callId', name: 'ChatProvider');
if (kDebugMode) { if (kDebugMode) {
print('✅ Call started: $callId'); print('✅ Call started: $callId');
} }
} catch (e) { } catch (e, stackTrace) {
log('❌ [CALL] Error starting call: $e', name: 'ChatProvider', error: e, stackTrace: stackTrace);
if (kDebugMode) { if (kDebugMode) {
print('❌ Error starting call: $e'); print('❌ Error starting call: $e');
} }
@ -910,15 +1013,27 @@ class ChatProvider with ChangeNotifier, DiagnosticableTreeMixin {
/// Toggle microphone mute /// Toggle microphone mute
void toggleMute() { void toggleMute() {
log('🔵 [CALL] toggleMute() called - current state: $isMuted', name: 'ChatProvider');
isMuted = !isMuted; isMuted = !isMuted;
notifyListeners(); notifyListeners();
log('🔵 [CALL] Microphone ${isMuted ? "muted" : "unmuted"}', name: 'ChatProvider');
// Notify peer via SignalR // Notify peer via SignalR
if (currentCall != null && sender != null) { if (currentCall != null && sender != null) {
log('🔵 [CALL] Invoking AudioToggle to notify peer', name: 'ChatProvider');
log(' - source: ${sender!.employeeNumber}', name: 'ChatProvider');
log(' - target: ${currentCall!.peerId}', name: 'ChatProvider');
chatHubConnection?.invoke( chatHubConnection?.invoke(
'AudioToggle', 'AudioToggle',
args: [sender!.employeeNumber ?? '', currentCall!.peerId], args: [sender!.employeeNumber ?? '', currentCall!.peerId],
); ).then((_) {
log('✅ [CALL] AudioToggle invoked successfully', name: 'ChatProvider');
}).catchError((e) {
log('❌ [CALL] Error invoking AudioToggle: $e', name: 'ChatProvider');
});
} else {
log('⚠️ [CALL] Cannot notify peer - currentCall or sender is null', name: 'ChatProvider');
} }
if (kDebugMode) { if (kDebugMode) {
@ -928,8 +1043,10 @@ class ChatProvider with ChangeNotifier, DiagnosticableTreeMixin {
/// Toggle speakerphone /// Toggle speakerphone
void toggleSpeaker() { void toggleSpeaker() {
log('🔵 [CALL] toggleSpeaker() called - current state: $isSpeakerOn', name: 'ChatProvider');
isSpeakerOn = !isSpeakerOn; isSpeakerOn = !isSpeakerOn;
notifyListeners(); notifyListeners();
log('🔵 [CALL] Speaker ${isSpeakerOn ? "on" : "off"}', name: 'ChatProvider');
if (kDebugMode) { if (kDebugMode) {
print('🔊 Speaker ${isSpeakerOn ? "on" : "off"}'); print('🔊 Speaker ${isSpeakerOn ? "on" : "off"}');
@ -938,15 +1055,27 @@ class ChatProvider with ChangeNotifier, DiagnosticableTreeMixin {
/// Toggle camera (video only) /// Toggle camera (video only)
void toggleCamera() { void toggleCamera() {
log('🔵 [CALL] toggleCamera() called - current state: $isCameraOn', name: 'ChatProvider');
isCameraOn = !isCameraOn; isCameraOn = !isCameraOn;
notifyListeners(); notifyListeners();
log('🔵 [CALL] Camera ${isCameraOn ? "on" : "off"}', name: 'ChatProvider');
// Notify peer via SignalR // Notify peer via SignalR
if (currentCall != null && sender != null) { if (currentCall != null && sender != null) {
log('🔵 [CALL] Invoking CameraToggle to notify peer', name: 'ChatProvider');
log(' - source: ${sender!.employeeNumber}', name: 'ChatProvider');
log(' - target: ${currentCall!.peerId}', name: 'ChatProvider');
chatHubConnection?.invoke( chatHubConnection?.invoke(
'CameraToggle', 'CameraToggle',
args: [sender!.employeeNumber ?? '', currentCall!.peerId], args: [sender!.employeeNumber ?? '', currentCall!.peerId],
); ).then((_) {
log('✅ [CALL] CameraToggle invoked successfully', name: 'ChatProvider');
}).catchError((e) {
log('❌ [CALL] Error invoking CameraToggle: $e', name: 'ChatProvider');
});
} else {
log('⚠️ [CALL] Cannot notify peer - currentCall or sender is null', name: 'ChatProvider');
} }
if (kDebugMode) { if (kDebugMode) {
@ -956,6 +1085,7 @@ class ChatProvider with ChangeNotifier, DiagnosticableTreeMixin {
/// Switch between front and rear camera /// Switch between front and rear camera
void switchCamera() { void switchCamera() {
log('🔵 [CALL] switchCamera() called', name: 'ChatProvider');
if (kDebugMode) { if (kDebugMode) {
print('🔄 Switch camera requested'); print('🔄 Switch camera requested');
} }
@ -965,13 +1095,21 @@ class ChatProvider with ChangeNotifier, DiagnosticableTreeMixin {
/// Switch from audio call to video call /// Switch from audio call to video call
Future<void> switchToVideoCall() async { Future<void> switchToVideoCall() async {
log('🔵 [CALL] switchToVideoCall() called', name: 'ChatProvider');
log('🔵 [CALL] Current call type: ${currentCall?.type}', name: 'ChatProvider');
if (currentCall == null || currentCall!.type == CallType.video) { if (currentCall == null || currentCall!.type == CallType.video) {
log('⚠️ [CALL] Cannot switch - currentCall is null or already video', name: 'ChatProvider');
return; return;
} }
// Request camera permission // Request camera permission
log('🔵 [CALL] Requesting camera permission for upgrade...', name: 'ChatProvider');
final cameraStatus = await Permission.camera.request(); final cameraStatus = await Permission.camera.request();
log('🔵 [CALL] Camera permission status: ${cameraStatus.name}', name: 'ChatProvider');
if (!cameraStatus.isGranted) { if (!cameraStatus.isGranted) {
log('❌ [CALL] Camera permission denied - cannot upgrade to video', name: 'ChatProvider');
if (kDebugMode) { if (kDebugMode) {
print('⚠️ Camera permission denied'); print('⚠️ Camera permission denied');
} }
@ -979,6 +1117,7 @@ class ChatProvider with ChangeNotifier, DiagnosticableTreeMixin {
} }
// Update call type (create new session to maintain immutability) // Update call type (create new session to maintain immutability)
log('🔵 [CALL] Upgrading call to video...', name: 'ChatProvider');
currentCall = CallSession( currentCall = CallSession(
callId: currentCall!.callId, callId: currentCall!.callId,
type: CallType.video, type: CallType.video,
@ -993,6 +1132,7 @@ class ChatProvider with ChangeNotifier, DiagnosticableTreeMixin {
); );
isCameraOn = true; isCameraOn = true;
notifyListeners(); notifyListeners();
log('✅ [CALL] Successfully switched to video call', name: 'ChatProvider');
if (kDebugMode) { if (kDebugMode) {
print('📹 Switched to video call'); print('📹 Switched to video call');
@ -1001,12 +1141,24 @@ class ChatProvider with ChangeNotifier, DiagnosticableTreeMixin {
/// End the current call /// End the current call
Future<void> hangUp() async { Future<void> hangUp() async {
log('🔵 [CALL] hangUp() called', name: 'ChatProvider');
log('🔵 [CALL] Current status: $callStatus', name: 'ChatProvider');
log('🔵 [CALL] Current call: ${currentCall?.callId}', name: 'ChatProvider');
if (currentCall == null || callStatus == CallStatus.idle) { if (currentCall == null || callStatus == CallStatus.idle) {
log('⚠️ [CALL] No active call to hang up', name: 'ChatProvider');
return; return;
} }
try { try {
// Invoke SignalR HangUpAsync // Invoke SignalR HangUpAsync
log('🔵 [CALL] Invoking HangUpAsync with args:', name: 'ChatProvider');
log(' - source: ${sender?.employeeNumber}', name: 'ChatProvider');
log(' - target: ${currentCall!.peerId}', name: 'ChatProvider');
log(' - moduleCode: $moduleID', name: 'ChatProvider');
log(' - referenceId: ${referenceID?.toString() ?? "null"}', name: 'ChatProvider');
log(' - conversationId: ${chatParticipantModel?.id?.toString() ?? "null"}', name: 'ChatProvider');
await chatHubConnection?.invoke( await chatHubConnection?.invoke(
'HangUpAsync', 'HangUpAsync',
args: [ args: [
@ -1017,11 +1169,13 @@ class ChatProvider with ChangeNotifier, DiagnosticableTreeMixin {
chatParticipantModel?.id?.toString() ?? '', chatParticipantModel?.id?.toString() ?? '',
], ],
); );
log('✅ [CALL] HangUpAsync invoked successfully', name: 'ChatProvider');
if (kDebugMode) { if (kDebugMode) {
print('📞 Call ended'); print('📞 Call ended');
} }
} catch (e) { } catch (e, stackTrace) {
log('❌ [CALL] Error invoking HangUpAsync: $e', name: 'ChatProvider', error: e, stackTrace: stackTrace);
if (kDebugMode) { if (kDebugMode) {
print('⚠️ Error ending call: $e'); print('⚠️ Error ending call: $e');
} }
@ -1032,11 +1186,21 @@ class ChatProvider with ChangeNotifier, DiagnosticableTreeMixin {
/// Handle call timeout (60s no answer) /// Handle call timeout (60s no answer)
void _handleCallTimeout() { void _handleCallTimeout() {
log('⏱️ [CALL] _handleCallTimeout() - Call timeout after 60s', name: 'ChatProvider');
log('🔵 [CALL] Current status: $callStatus', name: 'ChatProvider');
if (kDebugMode) { if (kDebugMode) {
print('⏱️ Call timeout - no answer'); print('⏱️ Call timeout - no answer');
} }
// Invoke CallMissedAsync // Invoke CallMissedAsync
log('🔵 [CALL] Invoking CallMissedAsync with args:', name: 'ChatProvider');
log(' - source: ${sender?.employeeNumber}', name: 'ChatProvider');
log(' - target: ${currentCall?.peerId}', name: 'ChatProvider');
log(' - moduleCode: $moduleID', name: 'ChatProvider');
log(' - referenceId: ${referenceID?.toString() ?? "null"}', name: 'ChatProvider');
log(' - conversationId: ${chatParticipantModel?.id?.toString() ?? "null"}', name: 'ChatProvider');
chatHubConnection?.invoke( chatHubConnection?.invoke(
'CallMissedAsync', 'CallMissedAsync',
args: [ args: [
@ -1046,15 +1210,26 @@ class ChatProvider with ChangeNotifier, DiagnosticableTreeMixin {
referenceID?.toString() ?? '', referenceID?.toString() ?? '',
chatParticipantModel?.id?.toString() ?? '', chatParticipantModel?.id?.toString() ?? '',
], ],
); ).then((_) {
log('✅ [CALL] CallMissedAsync invoked successfully', name: 'ChatProvider');
}).catchError((e) {
log('❌ [CALL] Error invoking CallMissedAsync: $e', name: 'ChatProvider');
});
_teardownCall(); _teardownCall();
} }
/// Clean up call resources /// Clean up call resources
void _teardownCall() { void _teardownCall() {
log('🧹 [CALL] _teardownCall() - Cleaning up call resources', name: 'ChatProvider');
log('🔵 [CALL] Previous status: $callStatus', name: 'ChatProvider');
log('🔵 [CALL] Call duration: ${callDuration.inSeconds}s', name: 'ChatProvider');
_callTimeoutTimer?.cancel(); _callTimeoutTimer?.cancel();
log('🔵 [CALL] Timeout timer cancelled', name: 'ChatProvider');
_callDurationTimer?.cancel(); _callDurationTimer?.cancel();
log('🔵 [CALL] Duration timer cancelled', name: 'ChatProvider');
callStatus = CallStatus.idle; callStatus = CallStatus.idle;
currentCall = null; currentCall = null;
@ -1066,6 +1241,7 @@ class ChatProvider with ChangeNotifier, DiagnosticableTreeMixin {
isPeerCameraOn = true; isPeerCameraOn = true;
notifyListeners(); notifyListeners();
log('✅ [CALL] Call resources cleaned up - status reset to idle', name: 'ChatProvider');
if (kDebugMode) { if (kDebugMode) {
print('🧹 Call resources cleaned up'); print('🧹 Call resources cleaned up');
@ -1074,6 +1250,7 @@ class ChatProvider with ChangeNotifier, DiagnosticableTreeMixin {
/// Start call duration timer when connected /// Start call duration timer when connected
void _startCallDurationTimer() { void _startCallDurationTimer() {
log('⏱️ [CALL] _startCallDurationTimer() - Starting call duration counter', name: 'ChatProvider');
_callDurationTimer?.cancel(); _callDurationTimer?.cancel();
callDuration = Duration.zero; callDuration = Duration.zero;
@ -1081,6 +1258,7 @@ class ChatProvider with ChangeNotifier, DiagnosticableTreeMixin {
callDuration = Duration(seconds: callDuration.inSeconds + 1); callDuration = Duration(seconds: callDuration.inSeconds + 1);
notifyListeners(); notifyListeners();
}); });
log('✅ [CALL] Duration timer started', name: 'ChatProvider');
if (kDebugMode) { if (kDebugMode) {
print('⏱️ Call duration timer started'); print('⏱️ Call duration timer started');
@ -1089,20 +1267,33 @@ class ChatProvider with ChangeNotifier, DiagnosticableTreeMixin {
/// Accept incoming call /// Accept incoming call
Future<void> acceptCall() async { Future<void> acceptCall() async {
log('🔵 [CALL] acceptCall() called', name: 'ChatProvider');
log('🔵 [CALL] Current status: $callStatus', name: 'ChatProvider');
log('🔵 [CALL] Current call: ${currentCall?.callId}', name: 'ChatProvider');
if (currentCall == null || callStatus != CallStatus.incomingRinging) { if (currentCall == null || callStatus != CallStatus.incomingRinging) {
log('⚠️ [CALL] Cannot accept - invalid state (call: ${currentCall == null ? "null" : "exists"}, status: $callStatus)', name: 'ChatProvider');
return; return;
} }
// Request permissions // Request permissions
log('🔵 [CALL] Requesting microphone permission...', name: 'ChatProvider');
final micStatus = await Permission.microphone.request(); final micStatus = await Permission.microphone.request();
log('🔵 [CALL] Microphone permission: ${micStatus.name}', name: 'ChatProvider');
if (!micStatus.isGranted) { if (!micStatus.isGranted) {
log('❌ [CALL] Microphone permission denied - declining call', name: 'ChatProvider');
await declineCall('permission_denied'); await declineCall('permission_denied');
return; return;
} }
if (currentCall!.type == CallType.video) { if (currentCall!.type == CallType.video) {
log('🔵 [CALL] Video call - requesting camera permission...', name: 'ChatProvider');
final cameraStatus = await Permission.camera.request(); final cameraStatus = await Permission.camera.request();
log('🔵 [CALL] Camera permission: ${cameraStatus.name}', name: 'ChatProvider');
if (!cameraStatus.isGranted) { if (!cameraStatus.isGranted) {
log('❌ [CALL] Camera permission denied - declining call', name: 'ChatProvider');
await declineCall('permission_denied'); await declineCall('permission_denied');
return; return;
} }
@ -1110,6 +1301,13 @@ class ChatProvider with ChangeNotifier, DiagnosticableTreeMixin {
try { try {
// Invoke SignalR AnswerCallAsync // Invoke SignalR AnswerCallAsync
log('🔵 [CALL] Invoking AnswerCallAsync with args:', name: 'ChatProvider');
log(' - source: ${sender?.employeeNumber}', name: 'ChatProvider');
log(' - target: ${currentCall!.peerId}', name: 'ChatProvider');
log(' - moduleCode: $moduleID', name: 'ChatProvider');
log(' - referenceId: ${referenceID?.toString() ?? "null"}', name: 'ChatProvider');
log(' - conversationId: ${chatParticipantModel?.id?.toString() ?? "null"}', name: 'ChatProvider');
await chatHubConnection?.invoke( await chatHubConnection?.invoke(
'AnswerCallAsync', 'AnswerCallAsync',
args: [ args: [
@ -1120,14 +1318,17 @@ class ChatProvider with ChangeNotifier, DiagnosticableTreeMixin {
chatParticipantModel?.id?.toString() ?? '', chatParticipantModel?.id?.toString() ?? '',
], ],
); );
log('✅ [CALL] AnswerCallAsync invoked successfully', name: 'ChatProvider');
callStatus = CallStatus.connecting; callStatus = CallStatus.connecting;
notifyListeners(); notifyListeners();
log('🔵 [CALL] Status changed to: connecting', name: 'ChatProvider');
if (kDebugMode) { if (kDebugMode) {
print('✅ Call accepted'); print('✅ Call accepted');
} }
} catch (e) { } catch (e, stackTrace) {
log('❌ [CALL] Error accepting call: $e', name: 'ChatProvider', error: e, stackTrace: stackTrace);
if (kDebugMode) { if (kDebugMode) {
print('❌ Error accepting call: $e'); print('❌ Error accepting call: $e');
} }
@ -1137,12 +1338,23 @@ class ChatProvider with ChangeNotifier, DiagnosticableTreeMixin {
/// Decline incoming call /// Decline incoming call
Future<void> declineCall(String reason) async { Future<void> declineCall(String reason) async {
log('🔵 [CALL] declineCall() called with reason: $reason', name: 'ChatProvider');
log('🔵 [CALL] Current call: ${currentCall?.callId}', name: 'ChatProvider');
if (currentCall == null) { if (currentCall == null) {
log('⚠️ [CALL] No call to decline', name: 'ChatProvider');
return; return;
} }
try { try {
// Invoke SignalR CallDeclinedAsync // Invoke SignalR CallDeclinedAsync
log('🔵 [CALL] Invoking CallDeclinedAsync with args:', name: 'ChatProvider');
log(' - source: ${sender?.employeeNumber}', name: 'ChatProvider');
log(' - target: ${currentCall!.peerId}', name: 'ChatProvider');
log(' - moduleCode: $moduleID', name: 'ChatProvider');
log(' - referenceId: ${referenceID?.toString() ?? "null"}', name: 'ChatProvider');
log(' - conversationId: ${chatParticipantModel?.id?.toString() ?? "null"}', name: 'ChatProvider');
await chatHubConnection?.invoke( await chatHubConnection?.invoke(
'CallDeclinedAsync', 'CallDeclinedAsync',
args: [ args: [
@ -1153,11 +1365,13 @@ class ChatProvider with ChangeNotifier, DiagnosticableTreeMixin {
chatParticipantModel?.id?.toString() ?? '', chatParticipantModel?.id?.toString() ?? '',
], ],
); );
log('✅ [CALL] CallDeclinedAsync invoked successfully', name: 'ChatProvider');
if (kDebugMode) { if (kDebugMode) {
print('📞 Call declined: $reason'); print('📞 Call declined: $reason');
} }
} catch (e) { } catch (e, stackTrace) {
log('❌ [CALL] Error declining call: $e', name: 'ChatProvider', error: e, stackTrace: stackTrace);
if (kDebugMode) { if (kDebugMode) {
print('⚠️ Error declining call: $e'); print('⚠️ Error declining call: $e');
} }
@ -1168,44 +1382,128 @@ class ChatProvider with ChangeNotifier, DiagnosticableTreeMixin {
/// Register call-related SignalR event handlers /// Register call-related SignalR event handlers
void _registerCallHandlers() { void _registerCallHandlers() {
if (chatHubConnection == null) return; log('═══════════════════════════════════════════', name: 'ChatProvider');
log('📞 [CALL] Starting call handler registration...', name: 'ChatProvider');
if (chatHubConnection == null) {
log('❌ [CALL] Cannot register handlers - chatHubConnection is null', name: 'ChatProvider');
return;
}
// NEW: Add a GLOBAL catch-all handler to see ALL SignalR events in debug mode
if (kDebugMode) {
log('🔍 [DEBUG] Setting up GLOBAL event interceptor to log ALL incoming SignalR messages', name: 'ChatProvider');
// Store the original method handlers
final originalHandlers = <String, Function>{};
// Register a generic listener for common call event patterns
final allPossibleCallEvents = [
'OnIncomingCallAsync',
'OnIncomingCall',
'IncomingCall',
'OnCallAcceptedAsync',
'OnCallAccepted',
'CallAccepted',
'OnCallDeclinedAsync',
'OnCallDeclined',
'CallDeclined',
'OnHangUpAsync',
'OnHangUp',
'HangUp',
'OnAudioToggle',
'AudioToggle',
'OnCameraToggle',
'CameraToggle',
'CallUserAsync', // Echo back
'OnCallStarted', // Alternative
'OnCallRinging', // Alternative
];
for (final eventName in allPossibleCallEvents) {
chatHubConnection!.on(eventName, (args) {
log('🔔🔔🔔 [GLOBAL DEBUG] Received SignalR event: "$eventName"', name: 'ChatProvider');
log(' Args: $args', name: 'ChatProvider');
log(' Args type: ${args?.runtimeType}', name: 'ChatProvider');
if (args != null && args.isNotEmpty) {
log(' First arg: ${args.first}', name: 'ChatProvider');
log(' First arg type: ${args.first?.runtimeType}', name: 'ChatProvider');
}
});
}
}
// Incoming call // Incoming call
log('🔵 [CALL] Registering: OnIncomingCallAsync', name: 'ChatProvider');
chatHubConnection!.on("OnIncomingCallAsync", _onIncomingCall); chatHubConnection!.on("OnIncomingCallAsync", _onIncomingCall);
// Call accepted // Call accepted
log('🔵 [CALL] Registering: OnCallAcceptedAsync', name: 'ChatProvider');
chatHubConnection!.on("OnCallAcceptedAsync", _onCallAccepted); chatHubConnection!.on("OnCallAcceptedAsync", _onCallAccepted);
// Call declined // Call declined
log('🔵 [CALL] Registering: OnCallDeclinedAsync', name: 'ChatProvider');
chatHubConnection!.on("OnCallDeclinedAsync", _onCallDeclined); chatHubConnection!.on("OnCallDeclinedAsync", _onCallDeclined);
// Call ended // Call ended
log('🔵 [CALL] Registering: OnHangUpAsync', name: 'ChatProvider');
chatHubConnection!.on("OnHangUpAsync", _onCallEnded); chatHubConnection!.on("OnHangUpAsync", _onCallEnded);
// Peer audio toggle // Peer audio toggle
log('🔵 [CALL] Registering: OnAudioToggle', name: 'ChatProvider');
chatHubConnection!.on("OnAudioToggle", _onPeerAudioToggle); chatHubConnection!.on("OnAudioToggle", _onPeerAudioToggle);
// Peer camera toggle // Peer camera toggle
log('🔵 [CALL] Registering: OnCameraToggle', name: 'ChatProvider');
chatHubConnection!.on("OnCameraToggle", _onPeerCameraToggle); chatHubConnection!.on("OnCameraToggle", _onPeerCameraToggle);
log('✅ [CALL] All call handlers registered successfully', name: 'ChatProvider');
log('📞 [CALL] Ready to receive: OnIncomingCallAsync, OnCallAcceptedAsync, OnCallDeclinedAsync, OnHangUpAsync', name: 'ChatProvider');
log('📞 [CALL] My Employee Number: ${sender?.employeeNumber ?? "NOT SET YET"}', name: 'ChatProvider');
log('📞 [CALL] My User ID: ${chatLoginResponse?.userId ?? "NOT SET"}', name: 'ChatProvider');
log('📞 [CALL] SignalR Connection ID: ${chatHubConnection?.connectionId ?? "NULL"}', name: 'ChatProvider');
log('═══════════════════════════════════════════', name: 'ChatProvider');
if (kDebugMode) { if (kDebugMode) {
print('✅ Call handlers registered'); print('✅ Call handlers registered - watching for ALL call events');
} }
_callHandlersRegistered = true;
} }
/// Handle incoming call event /// Handle incoming call event
void _onIncomingCall(List<Object?>? args) { void _onIncomingCall(List<Object?>? args) {
if (args == null || args.isEmpty) return; log('📞 [CALL EVENT] OnIncomingCallAsync received', name: 'ChatProvider');
log('🔵 [CALL EVENT] Raw args: $args', name: 'ChatProvider');
log('🔵 [CALL EVENT] Current status: $callStatus', name: 'ChatProvider');
if (args == null || args.isEmpty) {
log('⚠️ [CALL EVENT] OnIncomingCallAsync - args is null or empty', name: 'ChatProvider');
return;
}
try { try {
final data = args.first as Map<String, dynamic>; final data = args.first as Map<String, dynamic>;
log('🔵 [CALL EVENT] Parsed data: $data', name: 'ChatProvider');
final callerId = data['sourceUserId'] as String?; final callerId = data['sourceUserId'] as String?;
final callerName = data['userName'] as String? ?? 'Unknown'; final callerName = data['userName'] as String? ?? 'Unknown';
final isVideo = data['isVideoCall'] as bool? ?? false; final isVideo = data['isVideoCall'] as bool? ?? false;
log('🔵 [CALL EVENT] Parsed values:', name: 'ChatProvider');
log(' - callerId: $callerId', name: 'ChatProvider');
log(' - callerName: $callerName', name: 'ChatProvider');
log(' - isVideoCall: $isVideo', name: 'ChatProvider');
// Check if already in a call // Check if already in a call
if (callStatus != CallStatus.idle) { if (callStatus != CallStatus.idle) {
log('⚠️ [CALL EVENT] Already in a call (status: $callStatus) - auto-rejecting with busy', name: 'ChatProvider');
// Auto-reject with busy status // Auto-reject with busy status
log('🔵 [CALL EVENT] Invoking CallDeclinedAsync (busy) with args:', name: 'ChatProvider');
log(' - source: ${sender?.employeeNumber}', name: 'ChatProvider');
log(' - target: $callerId', name: 'ChatProvider');
chatHubConnection?.invoke( chatHubConnection?.invoke(
'CallDeclinedAsync', 'CallDeclinedAsync',
args: [ args: [
@ -1215,11 +1513,16 @@ class ChatProvider with ChangeNotifier, DiagnosticableTreeMixin {
referenceID?.toString() ?? '', referenceID?.toString() ?? '',
chatParticipantModel?.id?.toString() ?? '', chatParticipantModel?.id?.toString() ?? '',
], ],
); ).then((_) {
log('✅ [CALL EVENT] Auto-rejection sent successfully', name: 'ChatProvider');
}).catchError((e) {
log('❌ [CALL EVENT] Error sending auto-rejection: $e', name: 'ChatProvider');
});
return; return;
} }
// Create call session // Create call session
log('🔵 [CALL EVENT] Creating incoming CallSession...', name: 'ChatProvider');
currentCall = CallSession( currentCall = CallSession(
callId: const Uuid().v4(), callId: const Uuid().v4(),
type: isVideo ? CallType.video : CallType.audio, type: isVideo ? CallType.video : CallType.audio,
@ -1229,14 +1532,37 @@ class ChatProvider with ChangeNotifier, DiagnosticableTreeMixin {
peerAvatar: null, peerAvatar: null,
startTime: DateTime.now(), startTime: DateTime.now(),
); );
log('✅ [CALL EVENT] CallSession created - callId: ${currentCall!.callId}', name: 'ChatProvider');
callStatus = CallStatus.incomingRinging; callStatus = CallStatus.incomingRinging;
notifyListeners(); notifyListeners();
log('🔵 [CALL EVENT] Status changed to: incomingRinging', name: 'ChatProvider');
log('✅ [CALL EVENT] Incoming ${isVideo ? "video" : "audio"} call from $callerName processed', name: 'ChatProvider');
// NEW: Show incoming call dialog using global navigator
log('🔵 [CALL EVENT] Showing incoming call dialog...', name: 'ChatProvider');
final context = navigatorKey.currentContext;
if (context != null && currentCall != null) {
// Use a post-frame callback to ensure we're not in the middle of a build
WidgetsBinding.instance.addPostFrameCallback((_) {
if (context.mounted) {
showDialog(
context: context,
barrierDismissible: false,
builder: (dialogContext) => IncomingCallDialog(call: currentCall!),
);
log('✅ [CALL EVENT] Incoming call dialog shown', name: 'ChatProvider');
}
});
} else {
log('⚠️ [CALL EVENT] Cannot show dialog - context is null or call is null', name: 'ChatProvider');
}
if (kDebugMode) { if (kDebugMode) {
print('📞 Incoming ${isVideo ? "video" : "audio"} call from $callerName'); print('📞 Incoming ${isVideo ? "video" : "audio"} call from $callerName');
} }
} catch (e) { } catch (e, stackTrace) {
log('❌ [CALL EVENT] Error handling incoming call: $e', name: 'ChatProvider', error: e, stackTrace: stackTrace);
if (kDebugMode) { if (kDebugMode) {
print('❌ Error handling incoming call: $e'); print('❌ Error handling incoming call: $e');
} }
@ -1245,30 +1571,50 @@ class ChatProvider with ChangeNotifier, DiagnosticableTreeMixin {
/// Handle call accepted event /// Handle call accepted event
void _onCallAccepted(List<Object?>? args) { void _onCallAccepted(List<Object?>? args) {
log('✅ [CALL EVENT] OnCallAcceptedAsync received', name: 'ChatProvider');
log('🔵 [CALL EVENT] Raw args: $args', name: 'ChatProvider');
log('🔵 [CALL EVENT] Current status: $callStatus', name: 'ChatProvider');
if (callStatus == CallStatus.outgoingRinging) { if (callStatus == CallStatus.outgoingRinging) {
log('🔵 [CALL EVENT] Call was accepted by peer - stopping timeout timer', name: 'ChatProvider');
_callTimeoutTimer?.cancel(); _callTimeoutTimer?.cancel();
callStatus = CallStatus.connecting; callStatus = CallStatus.connecting;
notifyListeners(); notifyListeners();
log('🔵 [CALL EVENT] Status changed to: connecting', name: 'ChatProvider');
log('✅ [CALL EVENT] Call accepted - proceeding with connection', name: 'ChatProvider');
if (kDebugMode) { if (kDebugMode) {
print('✅ Call was accepted'); print('✅ Call was accepted');
} }
} else {
log('⚠️ [CALL EVENT] Received OnCallAcceptedAsync but status is not outgoingRinging: $callStatus', name: 'ChatProvider');
} }
} }
/// Handle call declined event /// Handle call declined event
void _onCallDeclined(List<Object?>? args) { void _onCallDeclined(List<Object?>? args) {
if (args == null || args.isEmpty) return; log('📞 [CALL EVENT] OnCallDeclinedAsync received', name: 'ChatProvider');
log('🔵 [CALL EVENT] Raw args: $args', name: 'ChatProvider');
log('🔵 [CALL EVENT] Current status: $callStatus', name: 'ChatProvider');
if (args == null || args.isEmpty) {
log('⚠️ [CALL EVENT] OnCallDeclinedAsync - args is null or empty', name: 'ChatProvider');
return;
}
try { try {
final reason = args.first as String? ?? 'declined'; final reason = args.first as String? ?? 'declined';
log('🔵 [CALL EVENT] Decline reason: $reason', name: 'ChatProvider');
log('✅ [CALL EVENT] Call was declined by peer', name: 'ChatProvider');
if (kDebugMode) { if (kDebugMode) {
print('📞 Call declined: $reason'); print('📞 Call declined: $reason');
} }
_teardownCall(); _teardownCall();
} catch (e) { } catch (e, stackTrace) {
log('❌ [CALL EVENT] Error handling call declined: $e', name: 'ChatProvider', error: e, stackTrace: stackTrace);
if (kDebugMode) { if (kDebugMode) {
print('❌ Error handling call declined: $e'); print('❌ Error handling call declined: $e');
} }
@ -1277,6 +1623,11 @@ class ChatProvider with ChangeNotifier, DiagnosticableTreeMixin {
/// Handle call ended event /// Handle call ended event
void _onCallEnded(List<Object?>? args) { void _onCallEnded(List<Object?>? args) {
log('📞 [CALL EVENT] OnHangUpAsync received', name: 'ChatProvider');
log('🔵 [CALL EVENT] Raw args: $args', name: 'ChatProvider');
log('🔵 [CALL EVENT] Current status: $callStatus', name: 'ChatProvider');
log('✅ [CALL EVENT] Call ended by peer - cleaning up', name: 'ChatProvider');
if (kDebugMode) { if (kDebugMode) {
print('📞 Call ended by peer'); print('📞 Call ended by peer');
} }
@ -1286,8 +1637,14 @@ class ChatProvider with ChangeNotifier, DiagnosticableTreeMixin {
/// Handle peer audio toggle event /// Handle peer audio toggle event
void _onPeerAudioToggle(List<Object?>? args) { void _onPeerAudioToggle(List<Object?>? args) {
log('🎤 [CALL EVENT] OnAudioToggle received', name: 'ChatProvider');
log('🔵 [CALL EVENT] Raw args: $args', name: 'ChatProvider');
log('🔵 [CALL EVENT] Previous peer mute state: $isPeerMuted', name: 'ChatProvider');
isPeerMuted = !isPeerMuted; isPeerMuted = !isPeerMuted;
notifyListeners(); notifyListeners();
log('🔵 [CALL EVENT] New peer mute state: $isPeerMuted', name: 'ChatProvider');
log('✅ [CALL EVENT] Peer ${isPeerMuted ? "muted" : "unmuted"} their microphone', name: 'ChatProvider');
if (kDebugMode) { if (kDebugMode) {
print('🎤 Peer ${isPeerMuted ? "muted" : "unmuted"}'); print('🎤 Peer ${isPeerMuted ? "muted" : "unmuted"}');
@ -1296,24 +1653,13 @@ class ChatProvider with ChangeNotifier, DiagnosticableTreeMixin {
/// Handle peer camera toggle event /// Handle peer camera toggle event
void _onPeerCameraToggle(List<Object?>? args) { void _onPeerCameraToggle(List<Object?>? args) {
isPeerCameraOn = !isPeerCameraOn; log('📹 [CALL EVENT] OnCameraToggle received', name: 'ChatProvider');
notifyListeners(); log('🔵 [CALL EVENT] Raw args: $args', name: 'ChatProvider');
log('🔵 [CALL EVENT] Previous peer camera state: $isPeerCameraOn', name: 'ChatProvider');
if (kDebugMode) {
print('📹 Peer camera ${isPeerCameraOn ? "on" : "off"}');
}
}
/// Mark call as connected and start duration timer isPeerCameraOn = !isPeerCameraOn;
void markCallConnected() {
if (callStatus == CallStatus.connecting) {
callStatus = CallStatus.connected;
_startCallDurationTimer();
notifyListeners(); notifyListeners();
log('🔵 [CALL EVENT] New peer camera state: $isPeerCameraOn', name: 'ChatProvider');
if (kDebugMode) { log('✅ [CALL EVENT] Peer turned camera ${isPeerCameraOn ? "on" : "off"}', name: 'ChatProvider');
print('✅ Call connected');
}
}
} }
} }

Loading…
Cancel
Save