|
|
|
@ -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');
|
|
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|