import 'dart:async'; import 'dart:convert'; import 'dart:developer'; import 'dart:io'; import 'dart:typed_data'; import 'package:audio_waveforms/audio_waveforms.dart'; import 'package:flutter/cupertino.dart'; import 'package:flutter/foundation.dart'; import 'package:flutter/services.dart'; import 'package:http/http.dart'; import 'package:intl/intl.dart'; import 'package:just_audio/just_audio.dart' as JustAudio; import 'package:just_audio/just_audio.dart'; import 'package:path_provider/path_provider.dart'; import 'package:permission_handler/permission_handler.dart'; import 'package:signalr_netcore/hub_connection.dart'; import 'package:signalr_netcore/signalr_client.dart'; import 'package:test_sa/controllers/api_routes/api_manager.dart'; import 'package:test_sa/controllers/api_routes/urls.dart'; import 'package:test_sa/extensions/string_extensions.dart'; import 'package:test_sa/main.dart'; import 'package:test_sa/modules/cx_module/chat/call/audio_call_page.dart'; import 'package:test_sa/modules/cx_module/chat/call/video_call_page.dart'; import 'package:test_sa/modules/cx_module/chat/model/chat_login_response_model.dart'; import 'package:uuid/uuid.dart'; import 'package:flutter/material.dart' as Material; import 'package:flutter/material.dart'; import 'api_client.dart'; import 'chat_api_client.dart'; import 'model/chat_attachment_model.dart'; import 'model/chat_participant_model.dart'; import 'model/get_search_user_chat_model.dart'; import 'model/get_single_user_chat_list_model.dart'; import 'model/unread_message_model.dart'; import 'model/user_chat_history_model.dart'; import 'model/call_session.dart'; import 'call/call_debug_helper.dart'; import 'call/incoming_call_dialog.dart'; import 'call/services/webrtc_service.dart'; import 'call/services/callkit_service.dart'; import 'package:flutter_webrtc/flutter_webrtc.dart'; import 'call/call_error_handler.dart'; import 'call/services/call_notification_service.dart'; import 'services/call_manager.dart'; import 'services/signalr_service.dart'; import 'services/call_coordinator.dart'; import 'package:test_sa/core/di/service_locator.dart'; class ChatProvider with ChangeNotifier, DiagnosticableTreeMixin { // ==================== CHAT-ONLY STATE ==================== // ChatProvider now ONLY manages chat-related state // All call state is managed by CallManager bool isTyping = false; bool chatLoginTokenLoading = false; ChatLoginResponse? chatLoginResponse; bool chatParticipantLoading = false; ChatParticipantModel? chatParticipantModel; bool userChatHistoryLoading = false; List? userChatHistory; bool messageIsSending = false; List chatResponseList = []; Participants? sender; Participants? recipient; late String receiverID; late int moduleID; int? referenceID; // SignalR Service - Retrieved from GetIt (Dependency Injection) // DO NOT instantiate directly - always use getIt() SignalRService get _signalRService => getIt(); // FIXED: Make chatHubConnection a regular property that can be set HubConnection? _chatHubConnection; // Getter for backward compatibility HubConnection? get chatHubConnection => _chatHubConnection ?? _signalRService.hubConnection; // Setter for backward compatibility set chatHubConnection(HubConnection? connection) { _chatHubConnection = connection; } // === CALL STATE - DELEGATED TO CallManager (READ-ONLY) === // 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 bool get areCallHandlersRegistered => true; // Always true since CallManager handles it // Private state for legacy ringtone support (chat-related) final JustAudio.AudioPlayer _ringingPlayer = JustAudio.AudioPlayer(); bool _isRingingPlaying = false; /// Update call status with detailed logging void _updateCallStatus(CallStatus newStatus, {String? reason}) { // Call status is now managed by CallManager 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 { // CRITICAL FIX: DO NOT close the SignalR connection here // 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)'); } } /// Reset provider state and properly dispose SignalR connection Future reset() async { // CRITICAL FIX: DO NOT dispose the SignalR connection // Just clear ChatProvider's local state // The SignalRService singleton will manage the connection lifecycle log('🔄 [ChatProvider] Resetting chat state (preserving SignalR connection)', name: 'ChatProvider'); _chatHubConnection = null; chatLoginTokenLoading = false; chatParticipantLoading = false; userChatHistoryLoading = false; chatLoginResponse = null; chatParticipantModel = null; userChatHistory = null; sender = null; recipient = null; ChatApiClient().chatLoginResponse = null; log('✅ [ChatProvider] Chat state reset (SignalR connection preserved)', name: 'ChatProvider'); } /// OPTIMIZATION: Override dispose to ensure SignalR connection cleanup /// This prevents connection leaks when provider is removed from widget tree @override void dispose() { // 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(); } // Future getUserAutoLoginToken(int moduleId, int requestId, String title, String myId, String assigneeEmployeeNumber) async { // reset(); // chatLoginTokenLoading = true; // notifyListeners(); // chatLoginResponse = await ChatApiClient().getChatLoginToken(moduleId, requestId, title, myId); // chatLoginTokenLoading = false; // chatParticipantLoading = true; // notifyListeners(); // // loadParticipants(moduleId, requestId); // if (chatLoginResponse != null) { // await loadChatHistory(moduleId, requestId, myId, assigneeEmployeeNumber); // } // } Future getUserAutoLoginTokenSilent(int moduleId, int requestId, String title, String myId, String assigneeEmployeeNumber, {bool isMounted = true}) async { // OPTIMIZATION: Use async reset for proper cleanup await reset(); receiverID = assigneeEmployeeNumber; chatLoginTokenLoading = true; if (isMounted) { notifyListeners(); } try { log('i am called ...'); chatLoginResponse = await ChatApiClient().getChatLoginToken(moduleId, requestId, title, myId, assigneeEmployeeNumber); log('✅ Got chatLoginResponse'); chatParticipantModel = await ChatApiClient().loadParticipants(moduleId, requestId, assigneeEmployeeNumber); log('✅ Got chatParticipantModel: ${chatParticipantModel?.toJson()}'); // Log available participants for debugging log('🔍 Looking for myId: $myId'); log('🔍 Looking for assigneeEmployeeNumber: $assigneeEmployeeNumber'); // CRITICAL FIX: Match participants by userId, not employeeNumber // The employeeNumber field contains userName, not actual employee number // Use case-insensitive matching and handle not found gracefully try { sender = chatParticipantModel?.participants?.firstWhere( (participant) => participant.userId?.toLowerCase() == myId.toLowerCase() ); log('✅ sender found: userId=${sender?.userId}, userName=${sender?.userName}, employeeNumber=${sender?.employeeNumber}'); } catch (e) { log('âš ī¸ Sender NOT FOUND for myId: $myId. Error: $e'); sender = null; } try { recipient = chatParticipantModel?.participants?.firstWhere( (participant) => participant.userId?.toLowerCase() == assigneeEmployeeNumber.toLowerCase() ); log('✅ recipient found: userId=${recipient?.userId}, userName=${recipient?.userName}, employeeNumber=${recipient?.employeeNumber}'); } catch (e) { log('âš ī¸ Recipient NOT FOUND for assigneeEmployeeNumber: $assigneeEmployeeNumber. Error: $e'); recipient = null; } // CRITICAL: Cache credentials in CallCoordinator for background calls if (chatLoginResponse != null && chatParticipantModel != null && sender != null) { log('💾 [ChatProvider] Caching credentials for future incoming calls...', name: 'ChatProvider'); final coordinator = CallCoordinator(); coordinator.cacheCredentials( loginResponse: chatLoginResponse!, participants: chatParticipantModel!, myEmployeeNumber: myId, ); log('✅ [ChatProvider] Credentials cached successfully', name: 'ChatProvider'); } } catch (ex) { if (kDebugMode) { print('âš ī¸ Error in getUserAutoLoginTokenSilent: $ex'); } log('❌ EXCEPTION in getUserAutoLoginTokenSilent: $ex'); } chatLoginTokenLoading = false; if (isMounted) { notifyListeners(); } } // Future getUserLoadChatHistory(int moduleId, int requestId, String myId, String assigneeEmployeeNumber) async { // await loadChatHistory(moduleId, requestId, myId, assigneeEmployeeNumber); // } // Future loadParticipants(int moduleId, int requestId, String myId, String assigneeEmployeeNumber) async { // userChatHistoryLoading = true; // notifyListeners(); // try { // chatParticipantModel = await ChatApiClient().loadParticipants(moduleId, requestId, assigneeEmployeeNumber); // } catch (ex) { // userChatHistoryLoading = false; // notifyListeners(); // return; // } // // try { // sender = chatParticipantModel?.participants?.singleWhere((participant) => participant.employeeNumber == myId); // recipient = chatParticipantModel?.participants?.singleWhere((participant) => participant.employeeNumber == assigneeEmployeeNumber); // } catch (e) {} // } Future connectToHub(int moduleId, int requestId, String myId, String assigneeEmployeeNumber, bool readOnly, {bool isMounted = true}) async { userChatHistoryLoading = true; if (isMounted) { notifyListeners(); } moduleID = moduleId; referenceID = requestId; // OPTIMIZATION: Wrap connection in try-catch to prevent leaks on error if (!readOnly) { try { await buildHubConnection(chatParticipantModel!.id!.toString()); } catch (e) { if (kDebugMode) { print('âš ī¸ Failed to build hub connection: $e'); } // Continue loading chat history even if hub connection fails } } userChatHistory = null; userChatHistory = await ChatApiClient().loadChatHistory(moduleId, requestId, myId, assigneeEmployeeNumber); chatResponseList = userChatHistory ?? []; chatResponseList.sort((a, b) => b.createdDate!.compareTo(a.createdDate!)); userChatHistoryLoading = false; if (isMounted) { notifyListeners(); } } // Future loadChatHistory(int moduleId, int requestId, String myId, String assigneeEmployeeNumber) async { // userChatHistoryLoading = true; // notifyListeners(); // userChatHistory = await ChatApiClient().loadChatHistory(moduleId, requestId, myId, assigneeEmployeeNumber); // chatResponseList = userChatHistory ?? []; // chatResponseList.sort((a, b) => b.createdDate!.compareTo(a.createdDate!)); // userChatHistoryLoading = false; // notifyListeners(); // } Future invokeSendMessage(Object object) async { messageIsSending = true; notifyListeners(); bool returnStatus = false; try { await chatHubConnection!.invoke("AddChatUserAsync", args: [object]); returnStatus = true; } catch (ex) {} messageIsSending = false; notifyListeners(); return returnStatus; } // Future sendTextMessage(String message) async { // messageIsSending = true; // notifyListeners(); // bool returnStatus = false; // // ChatResponse? chatResponse = await ChatApiClient().sendTextMessage(message, chatParticipantModel!.id!); // if (chatResponse != null) { // returnStatus = true; // // chatResponseList.add(chatResponse); // try { // chatResponseList.sort((a, b) => b.createdDate!.compareTo(a.createdDate!)); // } catch (ex) {} // } // messageIsSending = false; // notifyListeners(); // return returnStatus; // } // List? uGroups = [], searchGroups = []; // Future getUserAutoLoginToken() async { // userLoginToken.UserAutoLoginModel userLoginResponse = await ChatApiClient().getUserLoginToken(); // // if (userLoginResponse.StatusCode == 500) { // disbaleChatForThisUser = true; // notifyListeners(); // } // // if (userLoginResponse.response != null) { // // AppState().setchatUserDetails = userLoginResponse; // } else { // // AppState().setchatUserDetails = userLoginResponse; // userLoginResponse.errorResponses!.first.fieldName.toString() + " Erorr".showToast; // disbaleChatForThisUser = true; // notifyListeners(); // } // } /// OPTIMIZATION: Improved hub connection with error handling /// Prevents connection leaks if initialization fails Future buildHubConnection(String conversationID) async { try { log('═══════════════════════════════════════════', name: 'ChatProvider'); log('🔌 [ChatProvider] buildHubConnection called', name: 'ChatProvider'); log('đŸ’Ŧ Conversation ID: $conversationID', name: 'ChatProvider'); // CRITICAL FIX: Use the SINGLETON SignalR service from DI // DO NOT create a new connection - reuse the existing one final signalRService = _signalRService; log('🔍 [ChatProvider] SignalR service instance: ${signalRService.hashCode}', name: 'ChatProvider'); log(' Is connected: ${signalRService.isConnected}', name: 'ChatProvider'); log(' Connection state: ${signalRService.connectionState}', name: 'ChatProvider'); // CRITICAL FIX: Only initialize if not connected at all // If already connected (from CallManager), just join the new conversation if (!signalRService.isConnected) { log('🔌 [ChatProvider] SignalR not connected, initializing...', name: 'ChatProvider'); final connected = await signalRService.initialize( userId: chatLoginResponse!.userId.toString(), authToken: chatLoginResponse!.token ?? '', conversationId: conversationID, ); if (!connected) { throw Exception('Failed to initialize SignalR connection'); } log('✅ [ChatProvider] SignalR initialized', name: 'ChatProvider'); log(' Connection ID: ${signalRService.connectionId}', name: 'ChatProvider'); } else { log('✅ [ChatProvider] SignalR already connected - reusing existing connection', name: 'ChatProvider'); log(' Connection ID: ${signalRService.connectionId}', name: 'ChatProvider'); // CRITICAL FIX: Just update the conversation context // Do NOT call initialize() again - it will dispose the connection! // The SignalRService.initialize() method already handles conversation switching if (conversationID.isNotEmpty) { try { await signalRService.invoke("JoinConversation", args: [conversationID]); log('✅ [ChatProvider] Joined conversation: $conversationID', name: 'ChatProvider'); } catch (e) { log('âš ī¸ [ChatProvider] Error joining conversation (will retry): $e', name: 'ChatProvider'); // If join fails, try to reconnect with the conversation await signalRService.initialize( userId: chatLoginResponse!.userId.toString(), authToken: chatLoginResponse!.token ?? '', conversationId: conversationID, ); } } } // CRITICAL FIX: Reference the singleton connection, don't create a new one chatHubConnection = signalRService.hubConnection; // Log user details log('👤 User ID: ${chatLoginResponse!.userId}', name: 'ChatProvider'); log('📞 My Employee Number: ${sender?.employeeNumber ?? "NOT SET"}', name: 'ChatProvider'); log('═══════════════════════════════════════════', name: 'ChatProvider'); // Register chat event handlers _registerChatEventHandlers(); } catch (e, stackTrace) { log('❌ [ChatProvider] Error building SignalR connection: $e', name: 'ChatProvider', error: e, stackTrace: stackTrace); if (kDebugMode) { print('âš ī¸ Error building SignalR connection: $e'); } // CRITICAL FIX: Don't dispose on error - just log and continue // The connection might still be usable for calls rethrow; } } /// Register chat event handlers (separate from call handlers) void _registerChatEventHandlers() { if (chatHubConnection == null) { log('âš ī¸ Cannot register event handlers - connection is null', name: 'ChatProvider'); return; } log('🔧 [EVENT HANDLERS] Registering chat event handlers...', name: 'ChatProvider'); // Register chat event handlers via SignalRService _signalRService.on("ReceiveMessage", onMsgReceived1); _signalRService.on("OnMessageReceivedAsync", onMsgReceived); _signalRService.on("OnSubmitChatAsync", onSubmitChatAsync); _signalRService.on("OnTypingAsync", OnTypingAsync); _signalRService.on("OnStopTypingAsync", OnStopTypingAsync); _signalRService.on("OnSeenChatUserAsync", onSeenUserChatAsync); _signalRService.on("OnAckSeenAsync", onAckSeenAsync); _signalRService.on("OnCallHistoryUpdated", _onCallHistoryUpdated); log('✅ [EVENT HANDLERS] Chat event handlers registered successfully', name: 'ChatProvider'); } // ==================== CALL INFRASTRUCTURE ==================== // All call operations now delegate to CallManager /// Start a new audio or video call - delegates to CallManager Future startCall(Participants recipient, CallType callType) async { log('📞 [ChatProvider] startCall() - delegating to CallManager', name: 'ChatProvider'); if (sender == null) { log('❌ [ChatProvider] Cannot start call - sender is null', name: 'ChatProvider'); final context = navigatorKey.currentContext; if (context != null) { CallErrorHandler.showGenericError(context, 'Unable to start call. Please try again.'); } return; } // CRITICAL: Ensure CallManager is properly initialized before starting call log('🔧 [ChatProvider] Initializing CallManager before starting call...', name: 'ChatProvider'); log(' User ID: ${chatLoginResponse?.userId}', name: 'ChatProvider'); log(' Employee Number: ${sender?.employeeNumber}', name: 'ChatProvider'); log(' Conversation ID: ${chatParticipantModel?.id}', name: 'ChatProvider'); try { await CallManager().initialize( userId: chatLoginResponse!.userId.toString(), authToken: chatLoginResponse!.token ?? '', conversationId: chatParticipantModel?.id?.toString(), moduleId: moduleID.toString(), referenceId: referenceID?.toString(), employeeNumber: sender!.employeeNumber, ); log('✅ [ChatProvider] CallManager initialized successfully', name: 'ChatProvider'); } catch (e, stackTrace) { log('❌ [ChatProvider] CallManager initialization failed: $e', name: 'ChatProvider', error: e, stackTrace: stackTrace); // CRITICAL: Do NOT proceed if initialization failed final context = navigatorKey.currentContext; if (context != null) { CallErrorHandler.showGenericError( context, 'Unable to initialize calling system. Please try again.' ); } return; } // Delegate to CallManager log('📤 [ChatProvider] Delegating to CallManager.startCall()', name: 'ChatProvider'); log(' Recipient data:', name: 'ChatProvider'); log(' - userId: ${recipient.userId}', name: 'ChatProvider'); log(' - userName: ${recipient.userName}', name: 'ChatProvider'); log(' - employeeNumber: ${recipient.employeeNumber}', name: 'ChatProvider'); log(' Type: ${callType.name}', name: 'ChatProvider'); // Use employeeNumber from ChatParticipantModel for the call final targetEmployeeNumber = recipient.employeeNumber ?? ''; log(' ✅ Calling with employeeNumber: $targetEmployeeNumber', name: 'ChatProvider'); await CallManager().startCall( peerId: targetEmployeeNumber, peerName: recipient.userName ?? 'Unknown', callType: callType, peerAvatar: recipient.image, ); notifyListeners(); } /// Toggle mute - delegates to CallManager Future toggleMute() async { await CallManager().toggleMute(); notifyListeners(); } /// Toggle speaker - delegates to CallManager Future toggleSpeaker() async { await CallManager().toggleSpeaker(); notifyListeners(); } /// Toggle camera - delegates to CallManager Future toggleCamera() async { await CallManager().toggleCamera(); notifyListeners(); } /// Switch camera - delegates to CallManager Future switchCamera() async { await CallManager().switchCamera(); } /// Hang up - delegates to CallManager Future hangUp() async { await CallManager().hangUp(); notifyListeners(); } /// Accept call - delegates to CallManager Future acceptCall() async { await CallManager().acceptCall(); notifyListeners(); } /// Decline call - delegates to CallManager Future declineCall(String reason) async { await CallManager().declineCall(reason); notifyListeners(); } /// Register call event handlers - kept for backwards compatibility void _registerCallHandlers() { // Call handlers are now managed by CallManager // This method is kept for backwards compatibility but does nothing log('â„šī¸ [ChatProvider] Call handlers are managed by CallManager', name: 'ChatProvider'); } /// Handle call history update void _onCallHistoryUpdated(List? args) async { log('📞 [CALL HISTORY] OnCallHistoryUpdated received', name: 'ChatProvider'); try { if (sender != null && recipient != null) { userChatHistory = await ChatApiClient().loadChatHistory( moduleID, referenceID ?? 0, sender!.employeeNumber ?? '', recipient!.employeeNumber ?? '', ); chatResponseList = userChatHistory ?? []; chatResponseList.sort((a, b) => b.createdDate!.compareTo(a.createdDate!)); notifyListeners(); } } catch (e, stackTrace) { log('❌ [CALL HISTORY] Error reloading chat history: $e\nStack: $stackTrace', name: 'ChatProvider'); } } // ==================== CHAT EVENT HANDLERS ==================== Future onMsgReceived1(List? parameters) async { print("onMsgReceived1:$parameters"); } Future onMsgReceived(List? parameters) async { try { if (parameters != null && parameters.isNotEmpty) { var data = parameters[0] as Map; SingleUserChatModel chatResponse = SingleUserChatModel.fromJson(Map.from(data)); // Add message to chat history chatResponseList.insert(0, chatResponse); chatResponseList.sort((a, b) => b.createdDate!.compareTo(a.createdDate!)); notifyListeners(); if (kDebugMode) { print('✅ Message received: ${chatResponse.contant}'); // Fixed: contant not message } } } catch (e) { if (kDebugMode) { print('âš ī¸ Error in onMsgReceived: $e'); } } } Future onSubmitChatAsync(List? parameters) async { try { if (parameters != null && parameters.isNotEmpty) { var data = parameters[0] as Map; SingleUserChatModel chatResponse = SingleUserChatModel.fromJson(Map.from(data)); // Update existing message or add new one int existingIndex = chatResponseList.indexWhere((msg) => msg.userChatHistoryLineId == chatResponse.userChatHistoryLineId); // Fixed: use correct property if (existingIndex != -1) { chatResponseList[existingIndex] = chatResponse; } else { chatResponseList.insert(0, chatResponse); } chatResponseList.sort((a, b) => b.createdDate!.compareTo(a.createdDate!)); notifyListeners(); if (kDebugMode) { print('✅ Chat submitted: ${chatResponse.contant}'); // Fixed: contant not message } } } catch (e) { if (kDebugMode) { print('âš ī¸ Error in onSubmitChatAsync: $e'); } } } Future OnTypingAsync(List? parameters) async { try { isTyping = true; notifyListeners(); if (kDebugMode) { print('âœī¸ User is typing...'); } } catch (e) { if (kDebugMode) { print('âš ī¸ Error in OnTypingAsync: $e'); } } } Future OnStopTypingAsync(List? parameters) async { try { isTyping = false; notifyListeners(); if (kDebugMode) { print('✅ User stopped typing'); } } catch (e) { if (kDebugMode) { print('âš ī¸ Error in OnStopTypingAsync: $e'); } } } Future onSeenUserChatAsync(List? parameters) async { try { if (parameters != null && parameters.isNotEmpty) { // Handle message seen status update if (kDebugMode) { print('đŸ‘ī¸ Messages marked as seen'); } notifyListeners(); } } catch (e) { if (kDebugMode) { print('âš ī¸ Error in onSeenUserChatAsync: $e'); } } } Future onAckSeenAsync(List? parameters) async { try { if (parameters != null && parameters.isNotEmpty) { // Handle message acknowledgment if (kDebugMode) { print('✅ Message acknowledgment received'); } notifyListeners(); } } catch (e) { if (kDebugMode) { print('âš ī¸ Error in onAckSeenAsync: $e'); } } } // ==================== UTILITY METHODS ==================== /// Reset unread message count 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( moduleId ?? moduleID, // Use parameter or fall back to stored value referenceNo ?? referenceID ?? 0, sender!.employeeNumber ?? '', ); if (kDebugMode) { print('✅ Reset unread count'); } return true; } return false; } catch (e) { if (kDebugMode) { print('âš ī¸ resetCount error: $e'); } log('resetCount error: $e'); return false; } } /// Upload attachments Future?> uploadAttachments(String username, File file, String conversationId) async { // Fixed: match actual usage try { if (chatLoginResponse == null || chatParticipantModel == null) { if (kDebugMode) { print('âš ī¸ Cannot upload - chat not initialized'); } return null; } // Upload single file final files = [file]; // TODO: Implement file upload to chat API // The ChatApiClient doesn't have uploadAttachments method yet // For now, return empty list if (kDebugMode) { print('âš ī¸ uploadAttachments not implemented in ChatApiClient yet'); print(' Username: $username'); print(' File: ${file.path}'); print(' Conversation: $conversationId'); } return []; } catch (e) { if (kDebugMode) { print('âš ī¸ Error uploading attachments: $e'); } log('uploadAttachments error: $e'); return null; } } /// Get unread messages Future> getUnReadMessages(String employeeId) async { // Fixed: accept employeeId and return List try { if (chatLoginResponse == null) { if (kDebugMode) { print('âš ī¸ Cannot get unread messages - not logged in'); } return []; } // TODO: Implement getUnreadMessages API in ChatApiClient // The ChatApiClient doesn't have this method yet // For now, return empty list if (kDebugMode) { print('âš ī¸ getUnreadMessages API not implemented in ChatApiClient yet'); print(' Requested for employeeId: $employeeId'); } return []; } catch (e) { if (kDebugMode) { print('âš ī¸ Error getting unread messages: $e'); } log('getUnReadMessages error: $e'); return []; } } }