working on background call handling
parent
b8595cf796
commit
d5a21a05ca
@ -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<CallManager>();
|
||||
SignalRService get _signalRService => getIt<SignalRService>();
|
||||
|
||||
bool _isInitializing = false;
|
||||
|
||||
// Cache for silent login (used for incoming calls in background)
|
||||
static ChatLoginResponse? _cachedLoginResponse;
|
||||
static ChatParticipantModel? _cachedParticipants;
|
||||
static String? _cachedMyEmployeeNumber;
|
||||
|
||||
Future<bool> 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<bool> 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<void> 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');
|
||||
}
|
||||
}
|
||||
Loading…
Reference in New Issue