You cannot select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
cloudsolutions-atoms/lib/modules/cx_module/chat/chat_provider.dart

752 lines
25 KiB
Dart

9 months ago
import 'dart:async';
import 'dart:convert';
import 'dart:developer';
9 months ago
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';
9 months ago
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';
8 months ago
import 'package:test_sa/controllers/api_routes/api_manager.dart';
9 months ago
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';
9 months ago
import 'package:uuid/uuid.dart';
import 'package:flutter/material.dart' as Material;
1 month ago
import 'package:flutter/material.dart';
9 months ago
import 'api_client.dart';
import 'chat_api_client.dart';
import 'model/chat_attachment_model.dart';
import 'model/chat_participant_model.dart';
9 months ago
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';
1 month ago
import 'call/services/callkit_service.dart';
import 'package:flutter_webrtc/flutter_webrtc.dart';
import 'call/call_error_handler.dart';
1 month ago
import 'call/services/call_notification_service.dart';
4 weeks ago
import 'services/call_manager.dart';
4 weeks ago
import 'services/signalr_service.dart';
import 'services/call_coordinator.dart';
4 weeks ago
import 'package:test_sa/core/di/service_locator.dart';
9 months ago
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<SingleUserChatModel>? userChatHistory;
bool messageIsSending = false;
List<SingleUserChatModel> chatResponseList = [];
Participants? sender;
Participants? recipient;
late String receiverID;
late int moduleID;
int? referenceID;
4 weeks ago
// SignalR Service - Retrieved from GetIt (Dependency Injection)
// DO NOT instantiate directly - always use getIt<SignalRService>()
SignalRService get _signalRService => getIt<SignalRService>();
// 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<CallManager>();
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;
1 month ago
// 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;
1 month ago
/// 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<void> _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<void> reset() async {
// CRITICAL FIX: DO NOT dispose the SignalR connection
// Just clear ChatProvider's local state
// The SignalRService singleton will manage the connection lifecycle
_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<void> 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<void> 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 {
9 months ago
chatLoginResponse = await ChatApiClient().getChatLoginToken(moduleId, requestId, title, myId, assigneeEmployeeNumber);
chatParticipantModel = await ChatApiClient().loadParticipants(moduleId, requestId, assigneeEmployeeNumber);
log('✅ Got chatParticipantModel: ${chatParticipantModel?.toJson()}');
try {
sender = chatParticipantModel?.participants?.firstWhere(
(participant) => participant.userId?.toLowerCase() == myId.toLowerCase()
);
} 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:need to remove this after testing Cache credentials in CallCoordinator for background calls
if (chatLoginResponse != null && chatParticipantModel != null && sender != null) {
final coordinator = CallCoordinator();
coordinator.cacheCredentials(
loginResponse: chatLoginResponse!,
participants: chatParticipantModel!,
myEmployeeNumber: myId,
);
}
9 months ago
} catch (ex) {
if (kDebugMode) {
print('⚠️ Error in getUserAutoLoginTokenSilent: $ex');
}
log('❌ EXCEPTION in getUserAutoLoginTokenSilent: $ex');
9 months ago
}
chatLoginTokenLoading = false;
if (isMounted) {
notifyListeners();
}
}
9 months ago
// Future<void> getUserLoadChatHistory(int moduleId, int requestId, String myId, String assigneeEmployeeNumber) async {
// await loadChatHistory(moduleId, requestId, myId, assigneeEmployeeNumber);
// }
// Future<void> 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) {}
// }
9 months ago
Future<void> 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
}
}
6 months ago
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();
}
}
9 months ago
// Future<void> 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<bool> invokeSendMessage(Object object) async {
messageIsSending = true;
notifyListeners();
bool returnStatus = false;
try {
await chatHubConnection!.invoke("AddChatUserAsync", args: <Object>[object]);
returnStatus = true;
} catch (ex) {}
messageIsSending = false;
notifyListeners();
return returnStatus;
9 months ago
}
// Future<bool> 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<groups.GroupResponse>? uGroups = [], searchGroups = [];
// Future<void> 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<void> buildHubConnection(String conversationID) async {
try {
log('💬 Conversation ID: $conversationID', name: 'ChatProvider');
4 weeks ago
final signalRService = _signalRService;
if (!signalRService.isConnected) {
final connected = await signalRService.initialize(
userId: chatLoginResponse!.userId.toString(),
authToken: chatLoginResponse!.token ?? '',
conversationId: conversationID,
);
4 weeks ago
if (!connected) {
throw Exception('Failed to initialize SignalR connection');
}
} else {
4 weeks ago
if (conversationID.isNotEmpty) {
try {
await signalRService.invoke("JoinConversation", args: [conversationID]);
} catch (e) {
await signalRService.initialize(
userId: chatLoginResponse!.userId.toString(),
authToken: chatLoginResponse!.token ?? '',
conversationId: conversationID,
);
}
4 weeks ago
}
}
// CRITICAL FIX: Reference the singleton connection, don't create a new one
4 weeks ago
chatHubConnection = signalRService.hubConnection;
_registerChatEventHandlers();
} catch (e, stackTrace) {
4 weeks ago
if (kDebugMode) {
print('⚠️ Error building SignalR connection: $e');
}
rethrow;
}
}
4 weeks ago
/// Register chat event handlers (separate from call handlers)
void _registerChatEventHandlers() {
if (chatHubConnection == null) {
log('⚠️ Cannot register event handlers - connection is null', name: 'ChatProvider');
return;
}
4 weeks ago
_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);
9 months ago
4 weeks ago
log('✅ [EVENT HANDLERS] Chat event handlers registered successfully', name: 'ChatProvider');
}
1 month ago
/// Start a new audio or video call - delegates to CallManager
Future<void> startCall(Participants recipient, CallType callType) async {
1 month ago
log('📞 [ChatProvider] startCall() - delegating to CallManager', name: 'ChatProvider');
if (sender == null) {
1 month ago
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;
}
try {
1 month ago
await CallManager().initialize(
userId: chatLoginResponse!.userId.toString(),
authToken: chatLoginResponse!.token ?? '',
conversationId: chatParticipantModel?.id?.toString(),
moduleId: moduleID.toString(),
referenceId: referenceID?.toString(),
employeeNumber: sender!.employeeNumber,
);
4 weeks ago
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.'
4 weeks ago
);
}
return;
}
9 months ago
1 month ago
// Delegate to CallManager
4 weeks ago
log('📤 [ChatProvider] Delegating to CallManager.startCall()', name: 'ChatProvider');
// Use employeeNumber from ChatParticipantModel for the call
final targetEmployeeNumber = recipient.employeeNumber ?? '';
1 month ago
await CallManager().startCall(
peerId: targetEmployeeNumber,
1 month ago
peerName: recipient.userName ?? 'Unknown',
callType: callType,
peerAvatar: recipient.image,
);
1 month ago
notifyListeners();
}
1 month ago
/// Toggle mute - delegates to CallManager
Future<void> toggleMute() async {
1 month ago
await CallManager().toggleMute();
notifyListeners();
}
1 month ago
/// Toggle speaker - delegates to CallManager
Future<void> toggleSpeaker() async {
1 month ago
await CallManager().toggleSpeaker();
notifyListeners();
9 months ago
}
1 month ago
/// Toggle camera - delegates to CallManager
Future<void> toggleCamera() async {
1 month ago
await CallManager().toggleCamera();
notifyListeners();
}
1 month ago
/// Switch camera - delegates to CallManager
Future<void> switchCamera() async {
1 month ago
await CallManager().switchCamera();
9 months ago
}
1 month ago
/// Hang up - delegates to CallManager
Future<void> hangUp() async {
1 month ago
await CallManager().hangUp();
notifyListeners();
9 months ago
}
1 month ago
/// Accept call - delegates to CallManager
Future<void> acceptCall() async {
1 month ago
await CallManager().acceptCall();
notifyListeners();
}
1 month ago
/// Decline call - delegates to CallManager
Future<void> declineCall(String reason) async {
await CallManager().declineCall(reason);
notifyListeners();
}
1 month ago
/// Handle call history update
void _onCallHistoryUpdated(List<Object?>? args) async {
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');
}
}
4 weeks ago
// ==================== CHAT EVENT HANDLERS ====================
Future<void> onMsgReceived1(List<Object?>? parameters) async {
print("onMsgReceived1:$parameters");
}
Future<void> onMsgReceived(List<Object?>? parameters) async {
try {
if (parameters != null && parameters.isNotEmpty) {
var data = parameters[0] as Map<Object?, Object?>;
SingleUserChatModel chatResponse = SingleUserChatModel.fromJson(Map<String, dynamic>.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<void> onSubmitChatAsync(List<Object?>? parameters) async {
try {
if (parameters != null && parameters.isNotEmpty) {
var data = parameters[0] as Map<Object?, Object?>;
SingleUserChatModel chatResponse = SingleUserChatModel.fromJson(Map<String, dynamic>.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<void> OnTypingAsync(List<Object?>? parameters) async {
try {
isTyping = true;
notifyListeners();
if (kDebugMode) {
print('✍️ User is typing...');
}
} catch (e) {
if (kDebugMode) {
print('⚠️ Error in OnTypingAsync: $e');
}
}
}
Future<void> OnStopTypingAsync(List<Object?>? parameters) async {
try {
isTyping = false;
notifyListeners();
if (kDebugMode) {
print('✅ User stopped typing');
}
} catch (e) {
if (kDebugMode) {
print('⚠️ Error in OnStopTypingAsync: $e');
}
}
}
Future<void> onSeenUserChatAsync(List<Object?>? 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<void> onAckSeenAsync(List<Object?>? 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<bool> resetCount({int? moduleId, int? referenceNo, String? userId}) async {
// Fixed: userId is String? not int?
4 weeks ago
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<List<ChatAttachment>?> uploadAttachments(String username, File file, String conversationId) async {
// Fixed: match actual usage
4 weeks ago
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<List<UnReadMessage>> getUnReadMessages(String employeeId) async {
// Fixed: accept employeeId and return List<UnReadMessage>
4 weeks ago
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 [];
}
}
}