diff --git a/lib/modules/cx_module/chat/services/call_coordinator.dart b/lib/modules/cx_module/chat/services/call_coordinator.dart new file mode 100644 index 00000000..15f77fca --- /dev/null +++ b/lib/modules/cx_module/chat/services/call_coordinator.dart @@ -0,0 +1,237 @@ +import 'dart:developer'; +import 'package:test_sa/core/di/service_locator.dart'; +import 'package:test_sa/modules/cx_module/chat/services/call_manager.dart'; +import 'package:test_sa/modules/cx_module/chat/services/signalr_service.dart'; +import 'package:test_sa/modules/cx_module/chat/chat_provider.dart'; +import 'package:test_sa/modules/cx_module/chat/model/chat_login_response_model.dart'; +import 'package:test_sa/modules/cx_module/chat/model/chat_participant_model.dart'; +import 'package:test_sa/core/storage/auth_storage.dart'; + +class CallCoordinator { + static final CallCoordinator _instance = CallCoordinator._internal(); + factory CallCoordinator() => _instance; + CallCoordinator._internal(); + + // Services from DI + CallManager get _callManager => getIt(); + SignalRService get _signalRService => getIt(); + + bool _isInitializing = false; + + // Cache for silent login (used for incoming calls in background) + static ChatLoginResponse? _cachedLoginResponse; + static ChatParticipantModel? _cachedParticipants; + static String? _cachedMyEmployeeNumber; + + Future initializeForOutgoingCall({ + required ChatProvider chatProvider, + required String conversationId, + required String moduleId, + required String referenceId, + }) async { + log('═══════════════════════════════════════════', name: 'CallCoordinator'); + log('📞 [OUTGOING] Initializing communication for outgoing call', name: 'CallCoordinator'); + + try { + // Step 1: Ensure chat login credentials exist + log('🔐 [OUTGOING] Step 1: Checking chat login credentials...', name: 'CallCoordinator'); + if (chatProvider.chatLoginResponse == null) { + log('❌ [OUTGOING] Chat login credentials not found', name: 'CallCoordinator'); + log(' User must open chat screen first to initialize credentials', name: 'CallCoordinator'); + return false; + } + log('✅ [OUTGOING] Chat login credentials available', name: 'CallCoordinator'); + + // Step 2: Ensure participants loaded + log('👥 [OUTGOING] Step 2: Checking participants...', name: 'CallCoordinator'); + if (chatProvider.sender == null || chatProvider.recipient == null) { + log('❌ [OUTGOING] Participants not loaded (sender: ${chatProvider.sender != null}, recipient: ${chatProvider.recipient != null})', name: 'CallCoordinator'); + return false; + } + log('✅ [OUTGOING] Participants loaded', name: 'CallCoordinator'); + + // Step 3: Initialize CallManager with credentials + log('🔧 [OUTGOING] Step 3: Initializing CallManager...', name: 'CallCoordinator'); + final userId = chatProvider.chatLoginResponse!.userId.toString(); + final authToken = chatProvider.chatLoginResponse!.token ?? ''; + + // Use employeeNumber from sender participant in ChatParticipantModel + final employeeNumber = chatProvider.sender?.employeeNumber; + + log(' Sender userId: ${chatProvider.sender?.userId}', name: 'CallCoordinator'); + log(' Sender userName: ${chatProvider.sender?.userName}', name: 'CallCoordinator'); + log(' Sender employeeNumber: ${chatProvider.sender?.employeeNumber}', name: 'CallCoordinator'); + log(' Using employeeNumber for CallUserAsync: $employeeNumber', name: 'CallCoordinator'); + + await _callManager.initialize( + userId: userId, + authToken: authToken, + conversationId: conversationId, + moduleId: moduleId, + referenceId: referenceId, + employeeNumber: employeeNumber, + ); + + log('✅ [OUTGOING] Communication initialized successfully', name: 'CallCoordinator'); + log('═══════════════════════════════════════════', name: 'CallCoordinator'); + return true; + + } catch (e, stackTrace) { + log('❌ [OUTGOING] Initialization failed: $e', + name: 'CallCoordinator', error: e, stackTrace: stackTrace); + log('═══════════════════════════════════════════', name: 'CallCoordinator'); + return false; + } + } + Future initializeForIncomingCall({ + required String callerEmployeeNumber, + String? conversationId, + String? moduleId, + String? referenceId, + }) async { + log('═══════════════════════════════════════════', name: 'CallCoordinator'); + log('📞 [INCOMING] Initializing communication for incoming call', name: 'CallCoordinator'); + log(' Caller: $callerEmployeeNumber', name: 'CallCoordinator'); + log(' Module ID: $moduleId', name: 'CallCoordinator'); + + // Prevent concurrent initialization + if (_isInitializing) { + log('⏳ [INCOMING] Already initializing, waiting...', name: 'CallCoordinator'); + await Future.delayed(const Duration(milliseconds: 500)); + return _signalRService.isConnected; + } + + _isInitializing = true; + + try { + String? userId; + String? authToken; + String? myEmployeeNumber; + + // STEP 1: Try to read credentials from SharedPreferences (CRITICAL for background/terminated) + log('🔐 [INCOMING] Step 1: Reading credentials from SharedPreferences...', name: 'CallCoordinator'); + final storedCredentials = await AuthStorage.getCredentials(); + + if (storedCredentials != null) { + // SUCCESS: Found stored credentials + userId = storedCredentials.userId; + authToken = storedCredentials.accessToken; + myEmployeeNumber = storedCredentials.employeeNumber; + + log('✅ [INCOMING] Using stored credentials from SharedPreferences', name: 'CallCoordinator'); + log(' User ID: $userId', name: 'CallCoordinator'); + log(' Employee Number: ${myEmployeeNumber ?? "none"}', name: 'CallCoordinator'); + log(' Token length: ${authToken.length} chars', name: 'CallCoordinator'); + } else { + // Fall back to memory cache (for foreground calls) + log('⚠️ [INCOMING] No stored credentials in SharedPreferences', name: 'CallCoordinator'); + log(' Checking memory cache...', name: 'CallCoordinator'); + + if (_cachedLoginResponse != null && _cachedMyEmployeeNumber != null) { + userId = _cachedLoginResponse!.userId.toString(); + authToken = _cachedLoginResponse!.token ?? ''; + myEmployeeNumber = _cachedMyEmployeeNumber; + log('✅ [INCOMING] Using cached credentials from memory', name: 'CallCoordinator'); + log(' User ID: $userId', name: 'CallCoordinator'); + log(' Employee Number: $myEmployeeNumber', name: 'CallCoordinator'); + } else { + log('❌ [INCOMING] No credentials available anywhere', name: 'CallCoordinator'); + log(' Cannot initialize SignalR without credentials', name: 'CallCoordinator'); + log(' User must be logged in first', name: 'CallCoordinator'); + log('═══════════════════════════════════════════', name: 'CallCoordinator'); + return false; + } + } + + // STEP 2: Initialize SignalR with stored credentials (if not already connected) + if (!_signalRService.isConnected) { + log('🔌 [INCOMING] Step 2: Initializing SignalR connection...', name: 'CallCoordinator'); + final signalRConnected = await _signalRService.initialize( + userId: userId!, + authToken: authToken!, + conversationId: conversationId, + ); + + if (!signalRConnected) { + log('❌ [INCOMING] SignalR connection failed', name: 'CallCoordinator'); + log('═══════════════════════════════════════════', name: 'CallCoordinator'); + return false; + } + + log('✅ [INCOMING] SignalR connected successfully', name: 'CallCoordinator'); + log(' Connection ID: ${_signalRService.connectionId}', name: 'CallCoordinator'); + } else { + log('✅ [INCOMING] SignalR already connected', name: 'CallCoordinator'); + log(' Connection ID: ${_signalRService.connectionId}', name: 'CallCoordinator'); + } + + // STEP 3: Initialize CallManager + log('🔧 [INCOMING] Step 3: Initializing CallManager...', name: 'CallCoordinator'); + await _callManager.initialize( + userId: userId!, + authToken: authToken!, + conversationId: conversationId, + moduleId: moduleId ?? '1002', // Default to ATOMS + referenceId: referenceId ?? '0', + employeeNumber: myEmployeeNumber, + ); + + log('✅ [INCOMING] CallManager initialized successfully', name: 'CallCoordinator'); + log('✅ [INCOMING] Communication initialization complete', name: 'CallCoordinator'); + log(' ✅ SignalR: Connected', name: 'CallCoordinator'); + log(' ✅ CallManager: Ready', name: 'CallCoordinator'); + log(' ✅ Ready to receive incoming call', name: 'CallCoordinator'); + log('═══════════════════════════════════════════', name: 'CallCoordinator'); + return true; + + } catch (e, stackTrace) { + log('❌ [INCOMING] Initialization failed: $e', + name: 'CallCoordinator', error: e, stackTrace: stackTrace); + log('═══════════════════════════════════════════', name: 'CallCoordinator'); + return false; + } finally { + _isInitializing = false; + } + } + + /// Cache chat credentials for future incoming calls + /// Should be called after successful chat login (from ChatProvider) + void cacheCredentials({ + required ChatLoginResponse loginResponse, + required ChatParticipantModel participants, + required String myEmployeeNumber, + }) { + log('💾 [COORDINATOR] Caching credentials for future incoming calls', name: 'CallCoordinator'); + log(' User ID: ${loginResponse.userId}', name: 'CallCoordinator'); + log(' Employee Number: $myEmployeeNumber', name: 'CallCoordinator'); + + _cachedLoginResponse = loginResponse; + _cachedParticipants = participants; + _cachedMyEmployeeNumber = myEmployeeNumber; + + log('✅ [COORDINATOR] Credentials cached successfully', name: 'CallCoordinator'); + } + + /// Ensure communication is ready + /// Used before any call operation + bool isCommunicationReady() { + final signalRReady = _signalRService.isConnected; + + log('🔍 [COORDINATOR] Communication readiness check:', name: 'CallCoordinator'); + log(' SignalR: ${signalRReady ? "✅ Connected" : "❌ Not connected"}', name: 'CallCoordinator'); + log(' CallManager: ✅ Ready', name: 'CallCoordinator'); + + return signalRReady; + } + + /// Reset communication (for logout) + Future reset() async { + log('🔄 [COORDINATOR] Resetting communication...', name: 'CallCoordinator'); + _isInitializing = false; + _cachedLoginResponse = null; + _cachedParticipants = null; + _cachedMyEmployeeNumber = null; + // Services will be reset via service_locator.resetServices() + log('✅ [COORDINATOR] Reset complete', name: 'CallCoordinator'); + } +}