From 11c6e2411e83c1d7f1c369c4d345b842bca70301 Mon Sep 17 00:00:00 2001 From: Sikander Saleem Date: Thu, 6 Aug 2026 12:27:29 +0300 Subject: [PATCH] structure improvements. --- .../firebase_notification_manger.dart | 86 ++++++------------- lib/main.dart | 5 +- .../cx_module/chat/call/audio_call_page.dart | 4 +- .../cx_module/chat/call/video_call_page.dart | 6 +- lib/modules/cx_module/chat/chat_provider.dart | 45 ++++++---- .../cx_module/chat/services/call_manager.dart | 66 ++++---------- 6 files changed, 79 insertions(+), 133 deletions(-) diff --git a/lib/controllers/notification/firebase_notification_manger.dart b/lib/controllers/notification/firebase_notification_manger.dart index 5a647ac9..8d9a095f 100644 --- a/lib/controllers/notification/firebase_notification_manger.dart +++ b/lib/controllers/notification/firebase_notification_manger.dart @@ -26,28 +26,19 @@ import 'package:flutter_callkit_incoming/entities/entities.dart'; Future firebaseMessagingBackgroundHandler(RemoteMessage message) async { try { log('📱 [BACKGROUND] Data payload: ${message.data}', name: 'FirebaseNotificationManger'); - final notificationType = message.data['notificationType'] as String? ?? - message.data['type'] as String?; + final notificationType = message.data['notificationType'] as String? ?? message.data['type'] as String?; final transactionType = message.data['transactionType'] as String?; - + log('📱 [BACKGROUND] Notification Type: $notificationType', name: 'FirebaseNotificationManger'); // Handle incoming call notifications if (notificationType == 'incoming_call' || transactionType == 'call') { // Extract call data - final callId = message.data['callId'] as String? ?? - message.data['sessionId'] as String? ?? - DateTime.now().millisecondsSinceEpoch.toString(); + final callId = message.data['callId'] as String? ?? message.data['sessionId'] as String? ?? DateTime.now().millisecondsSinceEpoch.toString(); - final callerId = message.data['callerId'] as String? ?? - message.data['callerEmployeeNumber'] as String? ?? - message.data['sourceUserId'] as String? ?? - 'unknown'; + final callerId = message.data['callerId'] as String? ?? message.data['callerEmployeeNumber'] as String? ?? message.data['sourceUserId'] as String? ?? 'unknown'; - final callerName = message.data['callerName'] as String? ?? - message.data['callerUserName'] as String? ?? - message.data['userName'] as String? ?? - 'Unknown Caller'; + final callerName = message.data['callerName'] as String? ?? message.data['callerUserName'] as String? ?? message.data['userName'] as String? ?? 'Unknown Caller'; final callerEmployeeNumber = message.data['callerEmployeeNumber'] as String? ?? callerId; @@ -59,14 +50,13 @@ Future firebaseMessagingBackgroundHandler(RemoteMessage message) async { return false; }(); - final moduleId = message.data['moduleId'] as String? ?? - message.data['applicationId']?.toString(); + final moduleId = message.data['moduleId'] as String? ?? message.data['applicationId']?.toString(); final referenceId = message.data['referenceId'] as String?; final conversationId = message.data['conversationId'] as String?; // CRITICAL FIX: DO NOT initialize any communication services in background isolate // Only persist call data and show CallKit - ///TODO need to find other solution to this after testing + ///TODO need to find other solution to this after testing await PendingCallStorage.savePendingCall( callId: callId, callerId: callerId, @@ -141,8 +131,7 @@ Future firebaseMessagingBackgroundHandler(RemoteMessage message) async { log('═══════════════════════════════════════════', name: 'FirebaseNotificationManger'); } catch (e, stackTrace) { - log('❌ [BACKGROUND] Error handling background message: $e', - name: 'FirebaseNotificationManger', error: e, stackTrace: stackTrace); + log('❌ [BACKGROUND] Error handling background message: $e', name: 'FirebaseNotificationManger', error: e, stackTrace: stackTrace); } } @@ -188,8 +177,7 @@ class FirebaseNotificationManger { //print("pushToken:$token"); } - static void _onMessageReceived(h_push.RemoteMessage remoteMessage) { - } + static void _onMessageReceived(h_push.RemoteMessage remoteMessage) {} static void _onMessageReceiveError(Object error) { log('❌ [HUAWEI] Message receive error: ${error.toString()}', name: 'FirebaseNotificationManger'); @@ -214,27 +202,18 @@ class FirebaseNotificationManger { log('📱 [HANDLE_MESSAGE] Full message data: $messageData', name: 'FirebaseNotificationManger'); // FIXED: Check if this is a call notification first - CHECK BOTH 'notificationType' AND 'type' - final notificationType = messageData['notificationType'] as String? ?? - messageData['type'] as String?; // Backend uses 'type' + final notificationType = messageData['notificationType'] as String? ?? messageData['type'] as String?; // Backend uses 'type' final transactionType = messageData['transactionType'] as String?; - + log('📱 [HANDLE_MESSAGE] Notification Type: $notificationType', name: 'FirebaseNotificationManger'); if (notificationType == 'incoming_call' || transactionType == 'call') { // CRITICAL FIX: Show CallKit UI in foreground just like background - final callId = messageData['callId'] as String? ?? - messageData['sessionId'] as String? ?? - DateTime.now().millisecondsSinceEpoch.toString(); + final callId = messageData['callId'] as String? ?? messageData['sessionId'] as String? ?? DateTime.now().millisecondsSinceEpoch.toString(); - final callerId = messageData['callerId'] as String? ?? - messageData['callerEmployeeNumber'] as String? ?? - messageData['sourceUserId'] as String? ?? - 'unknown'; + final callerId = messageData['callerId'] as String? ?? messageData['callerEmployeeNumber'] as String? ?? messageData['sourceUserId'] as String? ?? 'unknown'; - final callerName = messageData['callerName'] as String? ?? - messageData['callerUserName'] as String? ?? - messageData['userName'] as String? ?? - 'Unknown Caller'; + final callerName = messageData['callerName'] as String? ?? messageData['callerUserName'] as String? ?? messageData['userName'] as String? ?? 'Unknown Caller'; final callerEmployeeNumber = messageData['callerEmployeeNumber'] as String? ?? callerId; @@ -246,8 +225,7 @@ class FirebaseNotificationManger { return false; }(); - final moduleId = messageData['moduleId'] as String? ?? - messageData['applicationId']?.toString(); + final moduleId = messageData['moduleId'] as String? ?? messageData['applicationId']?.toString(); final referenceId = messageData['referenceId'] as String?; final conversationId = messageData['conversationId'] as String?; @@ -432,7 +410,6 @@ class FirebaseNotificationManger { } static initialized(BuildContext context) async { - //TOD0 add platform check here also if (!(await isGoogleServicesAvailable()) && Platform.isAndroid) { log('📱 [INIT] Using Huawei Push Services', name: 'FirebaseNotificationManger'); @@ -515,8 +492,7 @@ class FirebaseNotificationManger { FirebaseMessaging.instance.getInitialMessage().then((initialMessage) { if (initialMessage != null) { // Check if it's a call notification - final notificationType = initialMessage.data['notificationType'] as String? ?? - initialMessage.data['type'] as String?; + final notificationType = initialMessage.data['notificationType'] as String? ?? initialMessage.data['type'] as String?; final transactionType = initialMessage.data['transactionType'] as String?; log('📱 [FCM_INITIAL] Notification Type: $notificationType', name: 'FirebaseNotificationManger'); log('📱 [FCM_INITIAL] Transaction Type: $transactionType', name: 'FirebaseNotificationManger'); @@ -545,8 +521,7 @@ class FirebaseNotificationManger { log('📱 [FCM_FOREGROUND] Body: ${message.notification?.body}', name: 'FirebaseNotificationManger'); // FIXED: Check if it's a call notification - CHECK BOTH 'notificationType' AND 'type' - final notificationType = message.data['notificationType'] as String? ?? - message.data['type'] as String?; // Backend uses 'type' + final notificationType = message.data['notificationType'] as String? ?? message.data['type'] as String?; // Backend uses 'type' log('📱 [FCM_FOREGROUND] Notification Type: $notificationType', name: 'FirebaseNotificationManger'); if (notificationType == 'incoming_call') { @@ -563,19 +538,11 @@ class FirebaseNotificationManger { log(' - applicationId: ${message.data['applicationId']}', name: 'FirebaseNotificationManger'); // CRITICAL FIX: Show CallKit UI in foreground - final callId = message.data['callId'] as String? ?? - message.data['sessionId'] as String? ?? - DateTime.now().millisecondsSinceEpoch.toString(); + final callId = message.data['callId'] as String? ?? message.data['sessionId'] as String? ?? DateTime.now().millisecondsSinceEpoch.toString(); - final callerId = message.data['callerId'] as String? ?? - message.data['callerEmployeeNumber'] as String? ?? - message.data['sourceUserId'] as String? ?? - 'unknown'; + final callerId = message.data['callerId'] as String? ?? message.data['callerEmployeeNumber'] as String? ?? message.data['sourceUserId'] as String? ?? 'unknown'; - final callerName = message.data['callerName'] as String? ?? - message.data['callerUserName'] as String? ?? - message.data['userName'] as String? ?? - 'Unknown Caller'; + final callerName = message.data['callerName'] as String? ?? message.data['callerUserName'] as String? ?? message.data['userName'] as String? ?? 'Unknown Caller'; final callerEmployeeNumber = message.data['callerEmployeeNumber'] as String? ?? callerId; @@ -587,8 +554,7 @@ class FirebaseNotificationManger { return false; }(); - final moduleId = message.data['moduleId'] as String? ?? - message.data['applicationId']?.toString(); + final moduleId = message.data['moduleId'] as String? ?? message.data['applicationId']?.toString(); final referenceId = message.data['referenceId'] as String?; final conversationId = message.data['conversationId'] as String?; @@ -666,16 +632,12 @@ class FirebaseNotificationManger { if (Platform.isAndroid) { if (message.data["notificationType"] != 'NurseConfirmArrive') { NotificationManger.showNotification( - title: message.notification?.title ?? "", - subtext: message.notification?.body ?? "", - hashcode: int.tryParse("1234") ?? 1, - payload: json.encode(message.data), - context: context); + title: message.notification?.title ?? "", subtext: message.notification?.body ?? "", hashcode: int.tryParse("1234") ?? 1, payload: json.encode(message.data), context: context); } } return; }); - + FirebaseMessaging.onMessageOpenedApp.listen((RemoteMessage message) { log('═══════════════════════════════════════════', name: 'FirebaseNotificationManger'); log('📱 [FCM_TAP] Notification tapped', name: 'FirebaseNotificationManger'); @@ -697,7 +659,7 @@ class FirebaseNotificationManger { } log('═══════════════════════════════════════════', name: 'FirebaseNotificationManger'); }); - + FirebaseMessaging.onBackgroundMessage(firebaseMessagingBackgroundHandler); log('✅ [INIT] FirebaseNotificationManger initialized successfully', name: 'FirebaseNotificationManger'); diff --git a/lib/main.dart b/lib/main.dart index 458bc81e..f2ca5b0c 100644 --- a/lib/main.dart +++ b/lib/main.dart @@ -37,6 +37,7 @@ import 'package:test_sa/modules/asset_inventory_module/provider/asset_inventory_ import 'package:test_sa/modules/cm_module/cm_detail_provider.dart'; import 'package:test_sa/modules/cm_module/create_cm_request.dart'; import 'package:test_sa/modules/cx_module/chat/chat_provider.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/demo_module/create_demo_request_page.dart'; import 'package:test_sa/modules/demo_module/provider/demo_period_lookup_provider.dart'; @@ -228,7 +229,7 @@ class MyApp extends StatelessWidget { return MultiProvider( providers: [ // ============================================================ - // CORE PROVIDERS (10) - Always instantiated at app launch + // CORE PROVIDERS (11) - Always instantiated at app launch // These are critical for app functionality // ============================================================ ChangeNotifierProvider(create: (_) => UserProvider()), @@ -241,6 +242,8 @@ class MyApp extends StatelessWidget { ChangeNotifierProvider(create: (_) => DepartmentsProvider()), ChangeNotifierProvider(create: (_) => NullableLoadingProvider()), ChangeNotifierProvider(create: (_) => ChatProvider()), + // HYBRID: CallManager uses Provider for UI, static instance for background calls + ChangeNotifierProvider(create: (_) => CallManager()), // ============================================================ // LAZY LOADED PROVIDERS (107) - Created only when accessed diff --git a/lib/modules/cx_module/chat/call/audio_call_page.dart b/lib/modules/cx_module/chat/call/audio_call_page.dart index 98702636..d30a814e 100644 --- a/lib/modules/cx_module/chat/call/audio_call_page.dart +++ b/lib/modules/cx_module/chat/call/audio_call_page.dart @@ -1,5 +1,6 @@ import 'package:flutter/material.dart'; import 'package:cached_network_image/cached_network_image.dart'; +import 'package:provider/provider.dart'; import 'package:test_sa/extensions/int_extensions.dart'; import 'package:test_sa/extensions/text_extensions.dart'; import 'package:test_sa/extensions/widget_extensions.dart'; @@ -22,7 +23,8 @@ class _AudioCallPageState extends State { @override void initState() { super.initState(); - _callManager = CallManager(); + // HYBRID: Get CallManager from Provider (UI layer) + _callManager = Provider.of(context, listen: false); // Listen for call end and navigate back _callManager.addListener(_checkCallStatus); diff --git a/lib/modules/cx_module/chat/call/video_call_page.dart b/lib/modules/cx_module/chat/call/video_call_page.dart index bbc9c309..8fea29eb 100644 --- a/lib/modules/cx_module/chat/call/video_call_page.dart +++ b/lib/modules/cx_module/chat/call/video_call_page.dart @@ -3,7 +3,7 @@ import 'dart:developer'; import 'package:flutter/material.dart'; import 'package:flutter/foundation.dart'; import 'package:flutter_webrtc/flutter_webrtc.dart'; -import 'package:test_sa/core/di/service_locator.dart'; +import 'package:provider/provider.dart'; import 'package:test_sa/extensions/int_extensions.dart'; import 'package:test_sa/extensions/text_extensions.dart'; import 'package:test_sa/extensions/widget_extensions.dart'; @@ -27,8 +27,8 @@ class _VideoCallPageState extends State { @override void initState() { super.initState(); - _callManager = getIt(); - // _callManager = CallManager(); + // HYBRID: Get CallManager from Provider (UI layer) + _callManager = Provider.of(context, listen: false); // Listen for call end _callManager.addListener(_checkCallStatus); diff --git a/lib/modules/cx_module/chat/chat_provider.dart b/lib/modules/cx_module/chat/chat_provider.dart index f72aaa9f..a7535937 100644 --- a/lib/modules/cx_module/chat/chat_provider.dart +++ b/lib/modules/cx_module/chat/chat_provider.dart @@ -95,16 +95,25 @@ class ChatProvider with ChangeNotifier, DiagnosticableTreeMixin { // These getters provide read-only access to call state for UI // All call operations should go through CallManager directly CallManager get _callManager => getIt(); - + CallStatus get callStatus => _callManager.callStatus; + CallSession? get currentCall => _callManager.currentCall; + Duration get callDuration => _callManager.callDuration; + bool get isMuted => _callManager.isMuted; + bool get isSpeakerOn => _callManager.isSpeakerOn; + bool get isCameraOn => _callManager.isCameraOn; + bool get isPeerMuted => _callManager.isPeerMuted; + bool get isPeerCameraOn => _callManager.isPeerCameraOn; + bool get isCallInProgress => _callManager.isCallInProgress; + WebRTCService? get webrtcService => _callManager.webrtcService; // For backwards compatibility with UI components @@ -120,6 +129,7 @@ class ChatProvider with ChangeNotifier, DiagnosticableTreeMixin { log('â„šī¸ [CALL STATUS] Call status is now managed by CallManager', name: 'ChatProvider'); notifyListeners(); } + /// OPTIMIZATION: Improved connection disposal to prevent memory leaks /// This properly handles errors and ensures connection is always cleaned up Future _disposeConnection() async { @@ -127,10 +137,10 @@ class ChatProvider with ChangeNotifier, DiagnosticableTreeMixin { // SignalR is now a SINGLETON managed by SignalRService // It's shared between ChatProvider and CallManager // Closing it here would break ongoing calls and call setup - + // Just clear the local reference _chatHubConnection = null; - + if (kDebugMode) { print('🔌 [ChatProvider] Cleared local SignalR reference (connection remains active)'); } @@ -141,7 +151,7 @@ class ChatProvider with ChangeNotifier, DiagnosticableTreeMixin { // CRITICAL FIX: DO NOT dispose the SignalR connection // Just clear ChatProvider's local state // The SignalRService singleton will manage the connection lifecycle - + _chatHubConnection = null; @@ -154,7 +164,7 @@ class ChatProvider with ChangeNotifier, DiagnosticableTreeMixin { sender = null; recipient = null; ChatApiClient().chatLoginResponse = null; - + log('✅ [ChatProvider] Chat state reset (SignalR connection preserved)', name: 'ChatProvider'); } @@ -165,13 +175,13 @@ class ChatProvider with ChangeNotifier, DiagnosticableTreeMixin { // CRITICAL FIX: DO NOT close SignalR connection on dispose // The connection is a singleton and may be used by other parts of the app // Only clear local state - + _chatHubConnection = null; - + if (kDebugMode) { print('✅ ChatProvider disposed (SignalR connection preserved)'); } - + super.dispose(); } @@ -204,7 +214,7 @@ class ChatProvider with ChangeNotifier, DiagnosticableTreeMixin { log('✅ Got chatParticipantModel: ${chatParticipantModel?.toJson()}'); try { sender = chatParticipantModel?.participants?.firstWhere( - (participant) => participant.userId?.toLowerCase() == myId.toLowerCase() + (participant) => participant.userId?.toLowerCase() == myId.toLowerCase() ); } catch (e) { log('âš ī¸ Sender NOT FOUND for myId: $myId. Error: $e'); @@ -213,7 +223,7 @@ class ChatProvider with ChangeNotifier, DiagnosticableTreeMixin { try { recipient = chatParticipantModel?.participants?.firstWhere( - (participant) => participant.userId?.toLowerCase() == assigneeEmployeeNumber.toLowerCase() + (participant) => participant.userId?.toLowerCase() == assigneeEmployeeNumber.toLowerCase() ); log('✅ recipient found: userId=${recipient?.userId}, userName=${recipient?.userName}, employeeNumber=${recipient?.employeeNumber}'); } catch (e) { @@ -230,7 +240,6 @@ class ChatProvider with ChangeNotifier, DiagnosticableTreeMixin { myEmployeeNumber: myId, ); } - } catch (ex) { if (kDebugMode) { print('âš ī¸ Error in getUserAutoLoginTokenSilent: $ex'); @@ -389,7 +398,6 @@ class ChatProvider with ChangeNotifier, DiagnosticableTreeMixin { // CRITICAL FIX: Reference the singleton connection, don't create a new one chatHubConnection = signalRService.hubConnection; _registerChatEventHandlers(); - } catch (e, stackTrace) { if (kDebugMode) { print('âš ī¸ Error building SignalR connection: $e'); @@ -447,8 +455,8 @@ class ChatProvider with ChangeNotifier, DiagnosticableTreeMixin { final context = navigatorKey.currentContext; if (context != null) { CallErrorHandler.showGenericError( - context, - 'Unable to initialize calling system. Please try again.' + context, + 'Unable to initialize calling system. Please try again.' ); } return; @@ -653,7 +661,8 @@ class ChatProvider with ChangeNotifier, DiagnosticableTreeMixin { // ==================== UTILITY METHODS ==================== /// Reset unread message count - Future resetCount({int? moduleId, int? referenceNo, String? userId}) async { // Fixed: userId is String? not int? + Future resetCount({int? moduleId, int? referenceNo, String? userId}) async { + // Fixed: userId is String? not int? try { if (chatLoginResponse != null && sender != null && recipient != null) { await ChatApiClient().resetCountApi( @@ -678,7 +687,8 @@ class ChatProvider with ChangeNotifier, DiagnosticableTreeMixin { } /// Upload attachments - Future?> uploadAttachments(String username, File file, String conversationId) async { // Fixed: match actual usage + Future?> uploadAttachments(String username, File file, String conversationId) async { + // Fixed: match actual usage try { if (chatLoginResponse == null || chatParticipantModel == null) { if (kDebugMode) { @@ -711,7 +721,8 @@ class ChatProvider with ChangeNotifier, DiagnosticableTreeMixin { } /// Get unread messages - Future> getUnReadMessages(String employeeId) async { // Fixed: accept employeeId and return List + Future> getUnReadMessages(String employeeId) async { + // Fixed: accept employeeId and return List try { if (chatLoginResponse == null) { if (kDebugMode) { diff --git a/lib/modules/cx_module/chat/services/call_manager.dart b/lib/modules/cx_module/chat/services/call_manager.dart index a9d94582..febf92d9 100644 --- a/lib/modules/cx_module/chat/services/call_manager.dart +++ b/lib/modules/cx_module/chat/services/call_manager.dart @@ -19,40 +19,47 @@ import 'package:test_sa/modules/cx_module/chat/services/pending_call_storage.dar import 'package:test_sa/modules/cx_module/chat/services/call_coordinator.dart'; class CallManager extends ChangeNotifier { + static CallManager? _staticInstance; static final CallManager _instance = CallManager._internal(); - factory CallManager() => _instance; + factory CallManager() { + _staticInstance = _instance; + return _instance; + } CallManager._internal(); - // Services - Retrieved from DI + static CallManager get instance { + if (_staticInstance == null) { + throw StateError('CallManager not initialized. Ensure Provider is registered in main.dart'); + } + return _staticInstance!; + } + + static bool get isInitialized => _staticInstance != null; + SignalRService get _signalRService => getIt(); WebRTCService get _webrtcService => getIt(); final CallKitService _callKitService = CallKitService(); - // Public getter for WebRTC service (used by call pages and ChatProvider) WebRTCService get webrtcService => _webrtcService; - // Call state CallSession? _currentCall; CallStatus _callStatus = CallStatus.idle; Duration _callDuration = Duration.zero; Timer? _callDurationTimer; Timer? _callTimeoutTimer; - // Call controls bool _isMuted = false; bool _isSpeakerOn = false; bool _isCameraOn = true; bool _isPeerMuted = false; bool _isPeerCameraOn = true; - // Initialization state bool _callKitInitialized = false; bool _handlersRegistered = false; - // Module context (for SignalR invocations) String? _userId; String? _authToken; String? _moduleId; @@ -60,26 +67,17 @@ 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; - // CRITICAL: Pending call data for background acceptance - // This preserves the COMPLETE notification payload Map? _pendingCallData; - // CRITICAL FIX: Navigation pending flag for background calls - // When app is in background and call is accepted, we set this flag - // The main UI will check this flag when it resumes and navigate bool _navigationPending = false; - // CRITICAL FIX: WebRTC initialization synchronization Completer? _webrtcInitCompleter; - String? _pendingOfferSdp; // Queue for offer that arrives during initialization + String? _pendingOfferSdp; - // Getters CallSession? get currentCall => _currentCall; CallStatus get callStatus => _callStatus; @@ -98,10 +96,8 @@ class CallManager extends ChangeNotifier { bool get isCallInProgress => _callStatus != CallStatus.idle; - // CRITICAL: Public getter for navigation pending flag bool get isNavigationPending => _navigationPending; - /// Initialize CallManager with user credentials Future initialize({ required String userId, required String authToken, @@ -111,14 +107,11 @@ class CallManager extends ChangeNotifier { String? employeeNumber, }) async { try { - // CRITICAL FIX: Cache credentials for background call handling _cachedUserId = userId; _cachedAuthToken = authToken; _cachedEmployeeNumber = employeeNumber; - // CRITICAL FIX: Don't re-initialize if already initialized with same USER credentials 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; @@ -144,7 +137,6 @@ class CallManager extends ChangeNotifier { return; } - // Store module context _userId = userId; _authToken = authToken; _moduleId = moduleId; @@ -152,23 +144,16 @@ class CallManager extends ChangeNotifier { _conversationId = conversationId; _myEmployeeNumber = employeeNumber; - // Initialize SignalR connection - final connected = await _signalRService.initialize( - userId: userId, - authToken: authToken, - conversationId: conversationId, - ); + final connected = await _signalRService.initialize(userId: userId, authToken: authToken, conversationId: conversationId); if (!connected) { throw Exception('Failed to initialize SignalR connection'); } - // Initialize CallKit if (!_callKitInitialized) { await _initializeCallKit(); } - // Register call event handlers if (!_handlersRegistered) { _registerCallHandlers(); } @@ -178,7 +163,6 @@ class CallManager extends ChangeNotifier { } } - /// Initialize CallKit service Future _initializeCallKit() async { if (_callKitInitialized) { return; @@ -439,17 +423,8 @@ class CallManager extends ChangeNotifier { /// Accept incoming call Future acceptCall() async { try { - log('═══════════════════════════════════════════', name: 'CallManager'); - log('✅ [ACCEPT] â–ļī¸ ACCEPT CALL STARTED', name: 'CallManager'); - log('✅ [ACCEPT] Timestamp: ${DateTime.now().toIso8601String()}', name: 'CallManager'); - - // CRITICAL: Handle background acceptance scenario if (_currentCall == null && _pendingCallData != null) { - log('🔧 [ACCEPT] No current call but pending data exists - restoring from background', name: 'CallManager'); - log(' Pending call ID: ${_pendingCallData!['callId']}', name: 'CallManager'); - log(' Pending caller: ${_pendingCallData!['callerName']}', name: 'CallManager'); - // Restore call session from pending data _currentCall = CallSession( callId: _pendingCallData!['callId'] as String, type: (_pendingCallData!['isVideoCall'] as bool) ? CallType.video : CallType.audio, @@ -459,14 +434,10 @@ class CallManager extends ChangeNotifier { peerAvatar: null, startTime: DateTime.now(), ); - _updateCallStatus(CallStatus.incomingRinging); - log('✅ [ACCEPT] Call session restored from pending data', name: 'CallManager'); // Ensure CallManager is initialized with pending call context if (_userId == null || _authToken == null) { - log('âš ī¸ [ACCEPT] CallManager not initialized, attempting auto-initialization...', name: 'CallManager'); - if (_cachedUserId != null && _cachedAuthToken != null && _cachedEmployeeNumber != null) { await initialize( userId: _cachedUserId!, @@ -476,7 +447,6 @@ class CallManager extends ChangeNotifier { referenceId: _pendingCallData!['referenceId'] as String?, employeeNumber: _cachedEmployeeNumber, ); - log('✅ [ACCEPT] CallManager initialized for background call', name: 'CallManager'); } else { log('❌ [ACCEPT] Cannot initialize - no cached credentials', name: 'CallManager'); await _cleanup(); @@ -765,9 +735,7 @@ class CallManager extends ChangeNotifier { _setupWebRTCCallbacks(); // Initialize with timeout protection - final initFuture = _currentCall!.type == CallType.audio - ? _webrtcService.initializeForAudioCall() - : _webrtcService.initializeForVideoCall(); + final initFuture = _currentCall!.type == CallType.audio ? _webrtcService.initializeForAudioCall() : _webrtcService.initializeForVideoCall(); // Add 10 second timeout for initialization await initFuture.timeout(