|
|
|
|
@ -17,9 +17,6 @@ import 'package:flutter_webrtc/flutter_webrtc.dart';
|
|
|
|
|
import 'package:uuid/uuid.dart';
|
|
|
|
|
import 'package:test_sa/core/di/service_locator.dart';
|
|
|
|
|
|
|
|
|
|
/// CallManager - Singleton service that manages all call responsibilities
|
|
|
|
|
/// Works independently of ChatProvider
|
|
|
|
|
/// Uses SignalRService via Dependency Injection
|
|
|
|
|
class CallManager extends ChangeNotifier {
|
|
|
|
|
static final CallManager _instance = CallManager._internal();
|
|
|
|
|
factory CallManager() => _instance;
|
|
|
|
|
@ -58,6 +55,12 @@ class CallManager extends ChangeNotifier {
|
|
|
|
|
String? _conversationId;
|
|
|
|
|
String? _myEmployeeNumber;
|
|
|
|
|
|
|
|
|
|
// CRITICAL FIX: Static credential cache for background calls
|
|
|
|
|
// This allows incoming calls to work even when app is in background
|
|
|
|
|
static String? _cachedUserId;
|
|
|
|
|
static String? _cachedAuthToken;
|
|
|
|
|
static String? _cachedEmployeeNumber;
|
|
|
|
|
|
|
|
|
|
// Getters
|
|
|
|
|
CallSession? get currentCall => _currentCall;
|
|
|
|
|
CallStatus get callStatus => _callStatus;
|
|
|
|
|
@ -80,10 +83,61 @@ class CallManager extends ChangeNotifier {
|
|
|
|
|
String? employeeNumber,
|
|
|
|
|
}) async {
|
|
|
|
|
try {
|
|
|
|
|
log('═══════════════════════════════════════════', name: 'CallManager');
|
|
|
|
|
log('🔧 [INIT] Initializing CallManager...', name: 'CallManager');
|
|
|
|
|
log(' User ID: $userId', name: 'CallManager');
|
|
|
|
|
log(' Conversation ID: ${conversationId ?? "none"}', name: 'CallManager');
|
|
|
|
|
log('🔧 [CallManager] Initializing...', name: 'CallManager');
|
|
|
|
|
|
|
|
|
|
// CRITICAL FIX: Don't re-initialize if already initialized with same USER credentials
|
|
|
|
|
// conversationId can change (different chats) - that's OK, just update it
|
|
|
|
|
// Only re-initialize if USER changes (userId, authToken, employeeNumber)
|
|
|
|
|
// Compare employee numbers case-insitively (backend sometimes changes case)
|
|
|
|
|
if (_userId == userId &&
|
|
|
|
|
_authToken == authToken &&
|
|
|
|
|
_myEmployeeNumber?.toLowerCase() == employeeNumber?.toLowerCase() &&
|
|
|
|
|
_handlersRegistered) {
|
|
|
|
|
|
|
|
|
|
// User is the same, just update conversation context without reconnecting
|
|
|
|
|
final conversationChanged = _conversationId != conversationId;
|
|
|
|
|
final moduleChanged = _moduleId != moduleId;
|
|
|
|
|
final referenceChanged = _referenceId != referenceId;
|
|
|
|
|
|
|
|
|
|
if (conversationChanged || moduleChanged || referenceChanged) {
|
|
|
|
|
log('🔄 [CallManager] Updating context (no reconnection needed)', name: 'CallManager');
|
|
|
|
|
log(' Conversation: $_conversationId -> $conversationId', name: 'CallManager');
|
|
|
|
|
log(' Module: $_moduleId -> $moduleId', name: 'CallManager');
|
|
|
|
|
log(' Reference: $_referenceId -> $referenceId', name: 'CallManager');
|
|
|
|
|
|
|
|
|
|
// Update context
|
|
|
|
|
_conversationId = conversationId;
|
|
|
|
|
_moduleId = moduleId;
|
|
|
|
|
_referenceId = referenceId;
|
|
|
|
|
|
|
|
|
|
// Update SignalR conversation only if needed
|
|
|
|
|
if (conversationChanged && conversationId != null) {
|
|
|
|
|
await _signalRService.initialize(
|
|
|
|
|
userId: userId,
|
|
|
|
|
authToken: authToken,
|
|
|
|
|
conversationId: conversationId,
|
|
|
|
|
);
|
|
|
|
|
}
|
|
|
|
|
} else {
|
|
|
|
|
log('✅ [CallManager] Already initialized with same credentials - skipping', name: 'CallManager');
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// Update employee number if case changed (keep latest version)
|
|
|
|
|
if (_myEmployeeNumber != employeeNumber) {
|
|
|
|
|
log('🔄 [CallManager] Updating employee number case: $_myEmployeeNumber -> $employeeNumber', name: 'CallManager');
|
|
|
|
|
_myEmployeeNumber = employeeNumber;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
log(' User ID: $userId', name: 'CallManager');
|
|
|
|
|
log(' Employee Number: $employeeNumber', name: 'CallManager');
|
|
|
|
|
log(' Conversation ID: $conversationId', name: 'CallManager');
|
|
|
|
|
log(' SignalR state: ${_signalRService.connectionState}', name: 'CallManager');
|
|
|
|
|
return;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
log('🔄 [CallManager] User changed - full re-initialization', name: 'CallManager');
|
|
|
|
|
log(' Previous User: $_userId -> New: $userId', name: 'CallManager');
|
|
|
|
|
log(' Previous Employee: $_myEmployeeNumber -> New: $employeeNumber', name: 'CallManager');
|
|
|
|
|
|
|
|
|
|
// Store module context
|
|
|
|
|
_userId = userId;
|
|
|
|
|
@ -94,7 +148,6 @@ class CallManager extends ChangeNotifier {
|
|
|
|
|
_myEmployeeNumber = employeeNumber;
|
|
|
|
|
|
|
|
|
|
// Initialize SignalR connection
|
|
|
|
|
log('🔌 [INIT] Initializing SignalR connection...', name: 'CallManager');
|
|
|
|
|
final connected = await _signalRService.initialize(
|
|
|
|
|
userId: userId,
|
|
|
|
|
authToken: authToken,
|
|
|
|
|
@ -104,7 +157,6 @@ class CallManager extends ChangeNotifier {
|
|
|
|
|
if (!connected) {
|
|
|
|
|
throw Exception('Failed to initialize SignalR connection');
|
|
|
|
|
}
|
|
|
|
|
log('✅ [INIT] SignalR connected', name: 'CallManager');
|
|
|
|
|
|
|
|
|
|
// Initialize CallKit
|
|
|
|
|
if (!_callKitInitialized) {
|
|
|
|
|
@ -116,11 +168,10 @@ class CallManager extends ChangeNotifier {
|
|
|
|
|
_registerCallHandlers();
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
log('✅ [INIT] CallManager initialized successfully', name: 'CallManager');
|
|
|
|
|
log('═══════════════════════════════════════════', name: 'CallManager');
|
|
|
|
|
log('✅ [CallManager] Initialized successfully', name: 'CallManager');
|
|
|
|
|
|
|
|
|
|
} catch (e, stackTrace) {
|
|
|
|
|
log('❌ [INIT] Error initializing CallManager: $e',
|
|
|
|
|
log('❌ [CallManager] Initialization error: $e',
|
|
|
|
|
name: 'CallManager', error: e, stackTrace: stackTrace);
|
|
|
|
|
rethrow;
|
|
|
|
|
}
|
|
|
|
|
@ -129,8 +180,6 @@ class CallManager extends ChangeNotifier {
|
|
|
|
|
/// Initialize CallKit service
|
|
|
|
|
Future<void> _initializeCallKit() async {
|
|
|
|
|
try {
|
|
|
|
|
log('📞 [CALLKIT] Initializing CallKit...', name: 'CallManager');
|
|
|
|
|
|
|
|
|
|
await _callKitService.initialize();
|
|
|
|
|
|
|
|
|
|
// Setup CallKit callbacks
|
|
|
|
|
@ -140,18 +189,15 @@ class CallManager extends ChangeNotifier {
|
|
|
|
|
_callKitService.onCallTimeout = _handleCallKitTimeout;
|
|
|
|
|
|
|
|
|
|
_callKitInitialized = true;
|
|
|
|
|
log('✅ [CALLKIT] CallKit initialized', name: 'CallManager');
|
|
|
|
|
|
|
|
|
|
} catch (e, stackTrace) {
|
|
|
|
|
log('❌ [CALLKIT] Error initializing: $e',
|
|
|
|
|
log('❌ [CallKit] Initialization error: $e',
|
|
|
|
|
name: 'CallManager', error: e, stackTrace: stackTrace);
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
/// Register SignalR call event handlers
|
|
|
|
|
void _registerCallHandlers() {
|
|
|
|
|
log('🔧 [HANDLERS] Registering call event handlers...', name: 'CallManager');
|
|
|
|
|
|
|
|
|
|
_signalRService.on('OnIncomingCallAsync', _handleIncomingCall);
|
|
|
|
|
_signalRService.on('OnCallAcceptedAsync', _handleCallAccepted);
|
|
|
|
|
_signalRService.on('OnCallDeclinedAsync', _handleCallDeclined);
|
|
|
|
|
@ -161,18 +207,13 @@ class CallManager extends ChangeNotifier {
|
|
|
|
|
_signalRService.on('OnIceCandidateAsync', _handleIceCandidate);
|
|
|
|
|
_signalRService.on('OnAudioToggle', _handleAudioToggle);
|
|
|
|
|
_signalRService.on('OnCameraToggle', _handleCameraToggle);
|
|
|
|
|
_signalRService.on('OnUserOnlineAsync', _handleUserOnline);
|
|
|
|
|
_signalRService.on('OnUserOfflineAsync', _handleUserOffline);
|
|
|
|
|
_signalRService.on('OnCallHistoryUpdated', _handleCallHistoryUpdated);
|
|
|
|
|
_signalRService.on('OnError', _handleError);
|
|
|
|
|
|
|
|
|
|
_handlersRegistered = true;
|
|
|
|
|
log('✅ [HANDLERS] All call handlers registered', name: 'CallManager');
|
|
|
|
|
log(' - OnIncomingCallAsync', name: 'CallManager');
|
|
|
|
|
log(' - OnCallAcceptedAsync ⭐', name: 'CallManager');
|
|
|
|
|
log(' - OnCallDeclinedAsync', name: 'CallManager');
|
|
|
|
|
log(' - OnHangUpAsync ⭐', name: 'CallManager');
|
|
|
|
|
log(' - OnOfferAsync ⭐', name: 'CallManager');
|
|
|
|
|
log(' - OnAnswerOfferAsync ⭐', name: 'CallManager');
|
|
|
|
|
log(' - OnIceCandidateAsync ⭐', name: 'CallManager');
|
|
|
|
|
log(' - OnAudioToggle', name: 'CallManager');
|
|
|
|
|
log(' - OnCameraToggle', name: 'CallManager');
|
|
|
|
|
log('✅ [CallManager] Event handlers registered (13 events)', name: 'CallManager');
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
/// Start outgoing call
|
|
|
|
|
@ -269,6 +310,15 @@ class CallManager extends ChangeNotifier {
|
|
|
|
|
);
|
|
|
|
|
log('✅ [OUTGOING] CallUserAsync invoked successfully', name: 'CallManager');
|
|
|
|
|
|
|
|
|
|
// CRITICAL FIX: Check if call was declined/cancelled while we were waiting
|
|
|
|
|
if (_currentCall?.callId != callId || _callStatus == CallStatus.idle) {
|
|
|
|
|
log('⚠️ [OUTGOING] Call was cancelled/declined during setup - aborting', name: 'CallManager');
|
|
|
|
|
log(' Current call ID: ${_currentCall?.callId}', name: 'CallManager');
|
|
|
|
|
log(' Expected call ID: $callId', name: 'CallManager');
|
|
|
|
|
log(' Current status: ${_callStatus.name}', name: 'CallManager');
|
|
|
|
|
return;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
_updateCallStatus(CallStatus.outgoingRinging);
|
|
|
|
|
|
|
|
|
|
// Initialize WebRTC
|
|
|
|
|
@ -276,6 +326,12 @@ class CallManager extends ChangeNotifier {
|
|
|
|
|
await _initializeWebRTC();
|
|
|
|
|
log('✅ [OUTGOING] WebRTC initialized', name: 'CallManager');
|
|
|
|
|
|
|
|
|
|
// CRITICAL FIX: Check again after WebRTC initialization
|
|
|
|
|
if (_currentCall?.callId != callId || _callStatus == CallStatus.idle) {
|
|
|
|
|
log('⚠️ [OUTGOING] Call was cancelled/declined after WebRTC init - aborting', name: 'CallManager');
|
|
|
|
|
return;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// Start timeout timer (60 seconds for outgoing calls)
|
|
|
|
|
_callTimeoutTimer = Timer(const Duration(seconds: 60), () {
|
|
|
|
|
if (_callStatus == CallStatus.outgoingRinging) {
|
|
|
|
|
@ -612,35 +668,106 @@ class CallManager extends ChangeNotifier {
|
|
|
|
|
try {
|
|
|
|
|
log('═══════════════════════════════════════════', name: 'CallManager');
|
|
|
|
|
log('📞 [HANGUP] Hanging up...', name: 'CallManager');
|
|
|
|
|
log(' Current call: ${_currentCall?.callId}', name: 'CallManager');
|
|
|
|
|
log(' Current status: ${_callStatus.name}', name: 'CallManager');
|
|
|
|
|
log(' SignalR state: ${_signalRService.connectionState}', name: 'CallManager');
|
|
|
|
|
|
|
|
|
|
if (_currentCall == null) {
|
|
|
|
|
log('⚠️ [HANGUP] No call to hang up', name: 'CallManager');
|
|
|
|
|
return;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// Ensure SignalR connected
|
|
|
|
|
if (await _signalRService.ensureConnected()) {
|
|
|
|
|
log('📤 [HANGUP] Invoking HangUpAsync...', name: 'CallManager');
|
|
|
|
|
await _signalRService.invoke('HangUpAsync', args: [
|
|
|
|
|
_myEmployeeNumber ?? '',
|
|
|
|
|
_currentCall!.peerId,
|
|
|
|
|
_moduleId ?? '0',
|
|
|
|
|
_referenceId ?? '0',
|
|
|
|
|
_conversationId ?? '',
|
|
|
|
|
]);
|
|
|
|
|
log('✅ [HANGUP] HangUpAsync invoked', name: 'CallManager');
|
|
|
|
|
} else {
|
|
|
|
|
log('⚠️ [HANGUP] SignalR not connected, skipping backend call', name: 'CallManager');
|
|
|
|
|
// Store call info before cleanup
|
|
|
|
|
final peerId = _currentCall!.peerId;
|
|
|
|
|
final callId = _currentCall!.callId;
|
|
|
|
|
|
|
|
|
|
// CRITICAL FIX: Verify SignalR connection state
|
|
|
|
|
log('🔍 [HANGUP] Verifying SignalR connection...', name: 'CallManager');
|
|
|
|
|
|
|
|
|
|
try {
|
|
|
|
|
// Check if connected, if not try to reconnect
|
|
|
|
|
if (!_signalRService.isConnected) {
|
|
|
|
|
log('⚠️ [HANGUP] SignalR not connected, attempting to reconnect...', name: 'CallManager');
|
|
|
|
|
final reconnected = await _signalRService.ensureConnected();
|
|
|
|
|
|
|
|
|
|
if (!reconnected) {
|
|
|
|
|
log('❌ [HANGUP] SignalR reconnection failed', name: 'CallManager');
|
|
|
|
|
} else {
|
|
|
|
|
log('✅ [HANGUP] SignalR reconnected successfully', name: 'CallManager');
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// Try to invoke HangUpAsync if connected
|
|
|
|
|
if (_signalRService.isConnected) {
|
|
|
|
|
log('📤 [HANGUP] Invoking HangUpAsync...', name: 'CallManager');
|
|
|
|
|
log(' From: ${_myEmployeeNumber ?? ""}', name: 'CallManager');
|
|
|
|
|
log(' To: $peerId', name: 'CallManager');
|
|
|
|
|
|
|
|
|
|
// CRITICAL FIX: HangUpAsync only takes 2 parameters (from, to)
|
|
|
|
|
// The old code was sending moduleId as 3rd parameter which caused:
|
|
|
|
|
// "Failed to invoke 'HangUpAsync' due to an error on the server"
|
|
|
|
|
await _signalRService.invoke('HangUpAsync', args: [
|
|
|
|
|
_myEmployeeNumber ?? '',
|
|
|
|
|
peerId,
|
|
|
|
|
]);
|
|
|
|
|
|
|
|
|
|
log('✅ [HANGUP] HangUpAsync invoked successfully', name: 'CallManager');
|
|
|
|
|
} else {
|
|
|
|
|
log('⚠️ [HANGUP] SignalR not connected after retry, skipping backend call', name: 'CallManager');
|
|
|
|
|
}
|
|
|
|
|
} catch (e) {
|
|
|
|
|
log('❌ [HANGUP] Error invoking HangUpAsync: $e', name: 'CallManager');
|
|
|
|
|
// Continue with cleanup even if SignalR call fails
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// End CallKit call
|
|
|
|
|
log('📱 [HANGUP] Ending CallKit call...', name: 'CallManager');
|
|
|
|
|
try {
|
|
|
|
|
await _callKitService.endCall(callId);
|
|
|
|
|
log('✅ [HANGUP] CallKit call ended', name: 'CallManager');
|
|
|
|
|
} catch (e) {
|
|
|
|
|
log('⚠️ [HANGUP] Error ending CallKit: $e', name: 'CallManager');
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// CRITICAL FIX: Don't manually navigate - let the call pages handle navigation
|
|
|
|
|
// Call pages listen to status changes and will pop when status becomes idle
|
|
|
|
|
log('🧭 [HANGUP] Navigation will be handled by call page listeners', name: 'CallManager');
|
|
|
|
|
|
|
|
|
|
// CRITICAL: Always cleanup to reset state for next call
|
|
|
|
|
log('🧹 [HANGUP] Starting cleanup...', name: 'CallManager');
|
|
|
|
|
_cleanup();
|
|
|
|
|
|
|
|
|
|
// CRITICAL FIX: Verify SignalR is still connected after cleanup
|
|
|
|
|
log('🔍 [HANGUP] Post-cleanup SignalR verification...', name: 'CallManager');
|
|
|
|
|
log(' SignalR state: ${_signalRService.connectionState}', name: 'CallManager');
|
|
|
|
|
log(' SignalR connected: ${_signalRService.isConnected}', name: 'CallManager');
|
|
|
|
|
log(' CallManager status: ${_callStatus.name}', name: 'CallManager');
|
|
|
|
|
log(' Current call: ${_currentCall?.callId ?? "null"}', name: 'CallManager');
|
|
|
|
|
|
|
|
|
|
if (!_signalRService.isConnected) {
|
|
|
|
|
log('⚠️ [HANGUP] SignalR disconnected after cleanup, attempting reconnect...', name: 'CallManager');
|
|
|
|
|
await _signalRService.ensureConnected();
|
|
|
|
|
log(' Reconnect result: ${_signalRService.isConnected}', name: 'CallManager');
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
log('✅ [HANGUP] Call ended successfully', name: 'CallManager');
|
|
|
|
|
log(' SignalR ready for next call: ${_signalRService.isConnected}', name: 'CallManager');
|
|
|
|
|
log('═══════════════════════════════════════════', name: 'CallManager');
|
|
|
|
|
|
|
|
|
|
} catch (e, stackTrace) {
|
|
|
|
|
log('❌ [HANGUP] Error hanging up: $e',
|
|
|
|
|
name: 'CallManager', error: e, stackTrace: stackTrace);
|
|
|
|
|
|
|
|
|
|
// CRITICAL FIX: Don't manually navigate in error case either
|
|
|
|
|
log('🧭 [HANGUP-ERROR] Navigation will be handled by call page listeners', name: 'CallManager');
|
|
|
|
|
_cleanup();
|
|
|
|
|
|
|
|
|
|
// Try to ensure SignalR is ready for next call
|
|
|
|
|
try {
|
|
|
|
|
await _signalRService.ensureConnected();
|
|
|
|
|
} catch (reconnectError) {
|
|
|
|
|
log('❌ [HANGUP] Failed to reconnect SignalR: $reconnectError', name: 'CallManager');
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
@ -743,6 +870,12 @@ class CallManager extends ChangeNotifier {
|
|
|
|
|
if (_currentCall!.type == CallType.audio) {
|
|
|
|
|
log('🎵 [WEBRTC] Initializing for audio call', name: 'CallManager');
|
|
|
|
|
await _webrtcService!.initializeForAudioCall();
|
|
|
|
|
|
|
|
|
|
// CRITICAL FIX: Speaker is enabled by default in WebRTC service
|
|
|
|
|
// Update the CallManager state to reflect this
|
|
|
|
|
_isSpeakerOn = true;
|
|
|
|
|
log('✅ [WEBRTC] Speaker state synchronized: ON', name: 'CallManager');
|
|
|
|
|
notifyListeners();
|
|
|
|
|
} else {
|
|
|
|
|
log('📹 [WEBRTC] Initializing for video call', name: 'CallManager');
|
|
|
|
|
await _webrtcService!.initializeForVideoCall();
|
|
|
|
|
@ -990,25 +1123,64 @@ class CallManager extends ChangeNotifier {
|
|
|
|
|
_cleanup();
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
void _handleHangUp(List<Object?>? args) {
|
|
|
|
|
void _handleHangUp(List<Object?>? args) async {
|
|
|
|
|
log('═══════════════════════════════════════════', name: 'CallManager');
|
|
|
|
|
log('📞 [EVENT] 🔴 OnHangUpAsync received', name: 'CallManager');
|
|
|
|
|
log(' Args count: ${args?.length ?? 0}', name: 'CallManager');
|
|
|
|
|
log(' Args: $args', name: 'CallManager');
|
|
|
|
|
log(' Current call status: ${_callStatus.name}', name: 'CallManager');
|
|
|
|
|
log(' Current call ID: ${_currentCall?.callId}', name: 'CallManager');
|
|
|
|
|
log('═══════════════════════════════════════════', name: 'CallManager');
|
|
|
|
|
log(' SignalR state: ${_signalRService.connectionState}', name: 'CallManager');
|
|
|
|
|
|
|
|
|
|
// CRITICAL: Navigate back to close call screen on both devices
|
|
|
|
|
final context = navigatorKey.currentContext;
|
|
|
|
|
if (context != null && Navigator.of(context).canPop()) {
|
|
|
|
|
log('🧭 [HANGUP] Navigating back to close call screen', name: 'CallManager');
|
|
|
|
|
Navigator.of(context).pop();
|
|
|
|
|
// CRITICAL FIX: Don't manually navigate - call pages listen to status and auto-pop when idle
|
|
|
|
|
// Avoid double navigation which causes navigation errors
|
|
|
|
|
log('🧭 [EVENT-HANGUP] Skipping manual navigation - call page will auto-pop on status change', name: 'CallManager');
|
|
|
|
|
|
|
|
|
|
// End CallKit call first
|
|
|
|
|
if (_currentCall != null) {
|
|
|
|
|
log('📱 [EVENT-HANGUP] Ending CallKit call...', name: 'CallManager');
|
|
|
|
|
try {
|
|
|
|
|
await _callKitService.endCall(_currentCall!.callId);
|
|
|
|
|
log('✅ [EVENT-HANGUP] CallKit call ended', name: 'CallManager');
|
|
|
|
|
} catch (e) {
|
|
|
|
|
log('⚠️ [EVENT-HANGUP] Error ending CallKit: $e', name: 'CallManager');
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// Cleanup call resources - this will set status to idle, triggering call page navigation
|
|
|
|
|
log('🧹 [EVENT-HANGUP] Starting cleanup...', name: 'CallManager');
|
|
|
|
|
_cleanup();
|
|
|
|
|
|
|
|
|
|
log('✅ [HANGUP] Call ended and screen closed', name: 'CallManager');
|
|
|
|
|
// CRITICAL FIX: Verify SignalR is still connected after cleanup
|
|
|
|
|
log('🔍 [EVENT-HANGUP] Post-cleanup SignalR verification...', name: 'CallManager');
|
|
|
|
|
log(' SignalR state: ${_signalRService.connectionState}', name: 'CallManager');
|
|
|
|
|
log(' SignalR connected: ${_signalRService.isConnected}', name: 'CallManager');
|
|
|
|
|
log(' CallManager status: ${_callStatus.name}', name: 'CallManager');
|
|
|
|
|
|
|
|
|
|
// CRITICAL: Ensure SignalR is ready for next call
|
|
|
|
|
if (!_signalRService.isConnected) {
|
|
|
|
|
log('⚠️ [EVENT-HANGUP] SignalR disconnected after cleanup, attempting reconnect...', name: 'CallManager');
|
|
|
|
|
try {
|
|
|
|
|
final reconnected = await _signalRService.ensureConnected();
|
|
|
|
|
log(' Reconnect result: $reconnected', name: 'CallManager');
|
|
|
|
|
log(' SignalR state after reconnect: ${_signalRService.connectionState}', name: 'CallManager');
|
|
|
|
|
|
|
|
|
|
if (!reconnected) {
|
|
|
|
|
log('❌ [EVENT-HANGUP] Failed to reconnect SignalR!', name: 'CallManager');
|
|
|
|
|
log(' This will prevent next call from working', name: 'CallManager');
|
|
|
|
|
} else {
|
|
|
|
|
log('✅ [EVENT-HANGUP] SignalR reconnected successfully', name: 'CallManager');
|
|
|
|
|
}
|
|
|
|
|
} catch (e) {
|
|
|
|
|
log('❌ [EVENT-HANGUP] Error reconnecting SignalR: $e', name: 'CallManager');
|
|
|
|
|
}
|
|
|
|
|
} else {
|
|
|
|
|
log('✅ [EVENT-HANGUP] SignalR still connected after cleanup', name: 'CallManager');
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
log('✅ [EVENT-HANGUP] Hangup event processed', name: 'CallManager');
|
|
|
|
|
log(' Call page will auto-navigate when it detects idle status', name: 'CallManager');
|
|
|
|
|
log(' SignalR ready for next call: ${_signalRService.isConnected}', name: 'CallManager');
|
|
|
|
|
log('═══════════════════════════════════════════', name: 'CallManager');
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
@ -1190,4 +1362,60 @@ class CallManager extends ChangeNotifier {
|
|
|
|
|
log('⚠️ [CALLKIT] Call ID mismatch: expected ${_currentCall?.callId}, got $callId', name: 'CallManager');
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// ==================== Additional SignalR Event Handlers ====================
|
|
|
|
|
|
|
|
|
|
void _handleUserOnline(List<Object?>? args) {
|
|
|
|
|
log('📞 [EVENT] 🟢 OnUserOnlineAsync received', name: 'CallManager');
|
|
|
|
|
log(' Args count: ${args?.length ?? 0}', name: 'CallManager');
|
|
|
|
|
if (args != null && args.isNotEmpty) {
|
|
|
|
|
log(' User online: ${args[0]}', name: 'CallManager');
|
|
|
|
|
}
|
|
|
|
|
log('═══════════════════════════════════════════', name: 'CallManager');
|
|
|
|
|
|
|
|
|
|
// Notify listeners in case UI needs to update user status
|
|
|
|
|
notifyListeners();
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
void _handleUserOffline(List<Object?>? args) {
|
|
|
|
|
log('📞 [EVENT] 🔴 OnUserOfflineAsync received', name: 'CallManager');
|
|
|
|
|
log(' Args count: ${args?.length ?? 0}', name: 'CallManager');
|
|
|
|
|
if (args != null && args.isNotEmpty) {
|
|
|
|
|
log(' User offline: ${args[0]}', name: 'CallManager');
|
|
|
|
|
}
|
|
|
|
|
log('═══════════════════════════════════════════', name: 'CallManager');
|
|
|
|
|
|
|
|
|
|
// Notify listeners in case UI needs to update user status
|
|
|
|
|
notifyListeners();
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
void _handleCallHistoryUpdated(List<Object?>? args) {
|
|
|
|
|
log('📞 [EVENT] 📋 OnCallHistoryUpdated received', name: 'CallManager');
|
|
|
|
|
log(' Args count: ${args?.length ?? 0}', name: 'CallManager');
|
|
|
|
|
if (args != null && args.isNotEmpty) {
|
|
|
|
|
log(' Call history update: ${args[0]}', name: 'CallManager');
|
|
|
|
|
}
|
|
|
|
|
log('═══════════════════════════════════════════', name: 'CallManager');
|
|
|
|
|
|
|
|
|
|
// Forward to ChatProvider if needed (call history is typically managed there)
|
|
|
|
|
// This is just logging for now
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
void _handleError(List<Object?>? args) {
|
|
|
|
|
log('═══════════════════════════════════════════', name: 'CallManager');
|
|
|
|
|
log('📞 [EVENT] ❌ OnError received', name: 'CallManager');
|
|
|
|
|
log(' Args count: ${args?.length ?? 0}', name: 'CallManager');
|
|
|
|
|
|
|
|
|
|
if (args != null && args.isNotEmpty) {
|
|
|
|
|
final error = args[0]?.toString() ?? 'Unknown error';
|
|
|
|
|
log(' Error: $error', name: 'CallManager');
|
|
|
|
|
|
|
|
|
|
// Show error to user if we have context
|
|
|
|
|
final context = navigatorKey.currentContext;
|
|
|
|
|
if (context != null) {
|
|
|
|
|
CallErrorHandler.showGenericError(context, 'Call error: $error');
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
log('═══════════════════════════════════════════', name: 'CallManager');
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|