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'; // NEW: Import CallManager HubConnection? chatHubConnection; class ChatProvider with ChangeNotifier, DiagnosticableTreeMixin { 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; // === CALL STATE - DELEGATED TO CallManager === // These getters delegate to CallManager for backwards compatibility 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 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 { try { if (chatHubConnection != null) { await chatHubConnection!.stop(); if (kDebugMode) { print('🔌 SignalR connection closed successfully'); } } } catch (e) { if (kDebugMode) { print('âš ī¸ Error closing SignalR connection: $e'); } // Don't rethrow - we still want to clean up } finally { chatHubConnection = null; } } /// Reset provider state and properly dispose SignalR connection Future reset() async { // OPTIMIZATION: Use async/await for proper cleanup await _disposeConnection(); chatLoginTokenLoading = false; chatParticipantLoading = false; userChatHistoryLoading = false; chatLoginResponse = null; chatParticipantModel = null; userChatHistory = null; sender = null; recipient = null; ChatApiClient().chatLoginResponse = null; } /// OPTIMIZATION: Override dispose to ensure SignalR connection cleanup /// This prevents connection leaks when provider is removed from widget tree @override void dispose() { _disposeConnection().then((_) { if (kDebugMode) { print('✅ ChatProvider disposed'); } }).catchError((error) { if (kDebugMode) { print('âš ī¸ Error during ChatProvider disposal: $error'); } }); 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('📋 Available participants: ${chatParticipantModel?.participants?.map((p) => '${p.employeeNumber} (${p.userName})').toList()}'); log('🔍 Looking for myId: $myId'); log('🔍 Looking for assigneeEmployeeNumber: $assigneeEmployeeNumber'); // Use case-insensitive matching and handle not found gracefully try { sender = chatParticipantModel?.participants?.firstWhere((participant) => participant.employeeNumber?.toLowerCase() == myId.toLowerCase()); log('✅ sender i got is ${sender?.toJson()}'); } catch (e) { log('âš ī¸ Sender NOT FOUND for myId: $myId. Error: $e'); sender = null; } try { recipient = chatParticipantModel?.participants?.firstWhere((participant) => participant.employeeNumber?.toLowerCase() == assigneeEmployeeNumber.toLowerCase()); log('✅ recipient i got is ${recipient?.toJson()}'); } catch (e) { log('âš ī¸ Recipient NOT FOUND for assigneeEmployeeNumber: $assigneeEmployeeNumber. Error: $e'); recipient = null; } } 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 { // Dispose existing connection if any await _disposeConnection(); chatHubConnection = await getHubConnection(); await chatHubConnection!.start(); // NEW: Log connection details for diagnostics log('═══════════════════════════════════════════', name: 'ChatProvider'); log('🔌 SignalR Hub Connection: Started', name: 'ChatProvider'); log('🔑 Connection ID: ${chatHubConnection!.connectionId ?? "NULL"}', name: 'ChatProvider'); log('👤 User ID: ${chatLoginResponse!.userId}', name: 'ChatProvider'); log('đŸ’Ŧ Conversation ID: $conversationID', name: 'ChatProvider'); log('📞 My Employee Number: ${sender?.employeeNumber ?? "NOT SET"}', name: 'ChatProvider'); log('═══════════════════════════════════════════', name: 'ChatProvider'); if (kDebugMode) { print("🔌 SignalR Hub Connection: Started"); } await chatHubConnection!.invoke("JoinConversation", args: [conversationID]); log('✅ Joined conversation: $conversationID', name: 'ChatProvider'); // Register ALL event handlers (chat + call) _registerAllEventHandlers(); //group On message // chatHubConnection.on("OnDeliveredGroupChatHistoryAsync", onGroupMsgReceived); } catch (e) { log('❌ [CONNECTION] Error building SignalR connection: $e', name: 'ChatProvider'); if (kDebugMode) { print('âš ī¸ Error building SignalR connection: $e'); } // Clean up on error await _disposeConnection(); rethrow; // Rethrow so caller knows about the error } } /// Register all event handlers (chat + call) - called on initial connection and after reconnect void _registerAllEventHandlers() { if (chatHubConnection == null) { log('âš ī¸ Cannot register event handlers - connection is null', name: 'ChatProvider'); return; } log('🔧 [EVENT HANDLERS] Registering all event handlers...', name: 'ChatProvider'); // Register chat event handlers chatHubConnection!.on("ReceiveMessage", onMsgReceived1); chatHubConnection!.on("OnMessageReceivedAsync", onMsgReceived); chatHubConnection!.on("OnSubmitChatAsync", onSubmitChatAsync); chatHubConnection!.on("OnTypingAsync", OnTypingAsync); chatHubConnection!.on("OnStopTypingAsync", OnStopTypingAsync); chatHubConnection!.on("OnSeenChatUserAsync", onSeenUserChatAsync); chatHubConnection!.on("OnAckSeenAsync", onAckSeenAsync); // Register call history event handler chatHubConnection!.on("OnCallHistoryUpdated", _onCallHistoryUpdated); // Register call event handlers _registerCallHandlers(); log('✅ [EVENT HANDLERS] All event handlers registered successfully', name: 'ChatProvider'); } Future getHubConnection() async { if (kDebugMode) { print('🔧 Creating new SignalR hub connection...'); } HubConnection hub; HttpConnectionOptions httpOp = HttpConnectionOptions( skipNegotiation: false, logMessageContent: true, ); hub = HubConnectionBuilder() .withUrl("${URLs.chatHubUrlChat}?UserId=${chatLoginResponse!.userId}&source=Desktop&access_token=${chatLoginResponse!.token}", options: httpOp) .withAutomaticReconnect(retryDelays: [2000, 5000, 10000, 20000]).build(); // NEW: Add global event logging in debug mode AND re-register handlers on reconnect if (kDebugMode) { // Log connection state changes hub.onclose(({Exception? error}) { log('🔴 [SignalR] Connection closed: $error', name: 'ChatProvider'); }); hub.onreconnecting(({Exception? error}) { log('🟡 [SignalR] Reconnecting: $error', name: 'ChatProvider'); }); hub.onreconnected(({String? connectionId}) async { log('đŸŸĸ [SignalR] Reconnected: $connectionId', name: 'ChatProvider'); log('🔄 [SignalR] Re-registering all event handlers after reconnection...', name: 'ChatProvider'); // CRITICAL: Re-register all event handlers after reconnection _registerAllEventHandlers(); log('✅ [SignalR] Event handlers re-registered after reconnection', name: 'ChatProvider'); }); print('✅ SignalR hub connection created with debug logging and auto-reregister'); } else { // Production: still need to re-register handlers on reconnect hub.onreconnected(({String? connectionId}) async { _registerAllEventHandlers(); }); print('✅ SignalR hub connection created'); } return hub; } void registerEvents() { // chatHubConnection.on("OnUpdateUserStatusAsync", changeStatus); // chatHubConnection.on("OnDeliveredChatUserAsync", onMsgReceived); // chatHubConnection.on("OnSubmitChatAsync", OnSubmitChatAsync); // chatHubConnection.on("OnUserTypingAsync", onUserTyping); chatHubConnection?.on("OnUserCountAsync", userCountAsync); // chatHubConnection.on("OnUpdateUserChatHistoryWindowsAsync", updateChatHistoryWindow); // chatHubConnection.on("OnGetUserChatHistoryNotDeliveredAsync", chatNotDelivered); // chatHubConnection.on("OnUpdateUserChatHistoryStatusAsync", updateUserChatStatus); // chatHubConnection.on("OnGetGroupUserStatusAsync", getGroupUserStatus); // // {"type":1,"target":"","arguments":[[{"id":217869,"userName":"Sultan.Khan","email":"Sultan.Khan@cloudsolutions.com.sa","phone":null,"title":"Sultan.Khan","userStatus":1,"image":null,"unreadMessageCount":0,"userAction":3,"isPin":false,"isFav":false,"isAdmin":false,"rKey":null,"totalCount":0,"isHuaweiDevice":false,"deviceToken":null},{"id":15153,"userName":"Tamer.Fanasheh","email":"Tamer.F@cloudsolutions.com.sa","phone":null,"title":"Tamer Fanasheh","userStatus":2,"image":null,"unreadMessageCount":0,"userAction":3,"isPin":false,"isFav":false,"isAdmin":true,"rKey":null,"totalCount":0,"isHuaweiDevice":false,"deviceToken":null}]]} if (kDebugMode) { // logger.i("All listeners registered"); } } // Future getUserRecentChats() async { // ChatUserModel recentChat = await ChatApiClient().getRecentChats(); // ChatUserModel favUList = await ChatApiClient().getFavUsers(); // // userGroups = await ChatApiClient().getGroupsByUserId(); // if (favUList.response != null && recentChat.response != null) { // favUsersList = favUList.response!; // favUsersList.sort((ChatUser a, ChatUser b) => a.userName!.toLowerCase().compareTo(b.userName!.toLowerCase())); // for (dynamic user in recentChat.response!) { // for (dynamic favUser in favUList.response!) { // if (user.id == favUser.id) { // user.isFav = favUser.isFav; // } // } // } // } // pChatHistory = recentChat.response ?? []; // uGroups = userGroups.groupresponse ?? []; // pChatHistory!.sort((ChatUser a, ChatUser b) => a.userName!.toLowerCase().compareTo(b.userName!.toLowerCase())); // searchedChats = pChatHistory; // isLoading = false; // await invokeUserChatHistoryNotDeliveredAsync(userId: int.parse(AppState().chatDetails!.response!.id.toString())); // sort(); // notifyListeners(); // if (searchedChats!.isNotEmpty || favUsersList.isNotEmpty) { // getUserImages(); // } // } Future invokeUserChatHistoryNotDeliveredAsync({required int userId}) async { await chatHubConnection!.invoke("GetUserChatHistoryNotDeliveredAsync", args: [userId]); return ""; } // void getSingleUserChatHistory({required int senderUID, required int receiverUID, required bool loadMore, bool isNewChat = false}) async { // isLoading = true; // if (isNewChat) userChatHistory = []; // if (!loadMore) paginationVal = 0; // isChatScreenActive = true; // receiverID = receiverUID; // Response response = await ChatApiClient().getSingleUserChatHistory(senderUID: senderUID, receiverUID: receiverUID, loadMore: loadMore, paginationVal: paginationVal); // if (response.statusCode == 204) { // if (isNewChat) { // userChatHistory = []; // } else if (loadMore) {} // } else { // if (loadMore) { // List temp = getSingleUserChatModel(response.body).reversed.toList(); // userChatHistory.addAll(temp); // } else { // userChatHistory = getSingleUserChatModel(response.body).reversed.toList(); // } // } // isLoading = false; // notifyListeners(); // // if (isChatScreenActive && receiverUID == receiverID) { // markRead(userChatHistory, receiverUID); // } // // generateConvId(); // } // // void generateConvId() async { // Uuid uuid = const Uuid(); // chatCID = uuid.v4(); // } void markRead(List data, String receiverID) { for (SingleUserChatModel element in data) { // if (AppState().chatDetails!.response!.id! == element.targetUserId) { if (element.isSeen != null) { if (!element.isSeen!) { element.isSeen = true; dynamic data = [ { "userChatHistoryId": element.userChatHistoryId, "TargetUserId": element.currentUserId == receiverID ? element.currentUserId : element.targetUserId, "isDelivered": true, "isSeen": true, } ]; updateUserChatHistoryStatusAsync(data); notifyListeners(); } // } } } } Future resetCount({ required int moduleId, required int referenceNo, String? userId, }) async { try { return await ChatApiClient().resetCountApi(moduleId, referenceNo, userId); } catch (e, stack) { debugPrint('resetCount error: $e'); rethrow; } } void updateUserChatHistoryStatusAsync(List data) { try { chatHubConnection!.invoke("UpdateUserChatHistoryStatusAsync", args: [data]); } catch (e) { throw e; } } void updateUserChatHistoryOnMsg(List data) { try { chatHubConnection!.invoke("UpdateUserChatHistoryStatusAsync", args: [data]); } catch (e) { throw e; } } // List getSingleUserChatModel(String str) => List.from(json.decode(str).map((x) => SingleUserChatModel.fromJson(x))); List getSingleUserChatModel(String str) { final dynamic decodedJson = json.decode(str); // Check if the decoded JSON is already a List if (decodedJson is List) { return List.from(decodedJson.map((x) => SingleUserChatModel.fromJson(x))); } // If it's a Map (a single object), wrap it in a list else if (decodedJson is Map) { return [SingleUserChatModel.fromJson(decodedJson)]; } // Handle unexpected types else { throw const FormatException('Expected a JSON object or a list of JSON objects.'); } } // List getGroupChatHistoryAsync(String str) => // List.from(json.decode(str).map((x) => groupchathistory.GetGroupChatHistoryAsync.fromJson(x))); // Future uploadAttachments(String userId, File file, String fileSource) async { dynamic result; try { Map jsonData = { "IsContextual": true.toString(), "ModuleCode": moduleID.toString(), "ReferenceId": referenceID.toString(), "ReferenceType": "ticket", "ConversationId": chatParticipantModel!.id.toString(), "TargetUserId": receiverID, "SendMessage": true.toString(), }; Object? response = await ChatApiClient().uploadMedia(userId, file, fileSource, jsonData: jsonData); if (response != null) { result = response; } else { result = []; } } catch (e) { throw e; } return result; } Future> getUnReadMessages(String employeeId) async { // employeeId = "FMEngineer"; try { Response response = await ApiClient().getJsonForResponse( "${URLs.unreadMessages}?retrieveAll=true", headers: {'x-api-key': URLs.chatApiKey, 'x-employee-number': employeeId}, ); if (response.statusCode == 200) { List data = jsonDecode(response.body); return data.map((elemet) => UnReadMessage.fromJson(elemet)).toList(); } else { return []; } } catch (error) { return []; } } // void updateUserChatStatus(List? args) { // dynamic items = args!.toList(); // for (var cItem in items[0]) { // for (SingleUserChatModel chat in userChatHistory) { // if (cItem["contantNo"].toString() == chat.contantNo.toString()) { // chat.isSeen = cItem["isSeen"]; // chat.isDelivered = cItem["isDelivered"]; // } // } // } // notifyListeners(); // } void getGroupUserStatus(List? args) { //note: need to implement this function... print(args); } Future markMessageAsRead(int messageId) async { final senderId = sender?.userId; if (senderId == null) return; chatHubConnection?.invoke( "SendMessageReadAsync", args: [messageId, senderId], ); } void onChatSeen(List? args) { dynamic items = args!.toList(); // for (var user in searchedChats!) { // if (user.id == items.first["id"]) { // user.userStatus = items.first["userStatus"]; // } // } // notifyListeners(); } void userCountAsync(List? args) { dynamic items = args!.toList(); // logger.d(items); //logger.d("---------------------------------User Count Async -------------------------------------"); //logger.d(items); // for (var user in searchedChats!) { // if (user.id == items.first["id"]) { // user.userStatus = items.first["userStatus"]; // } // } // notifyListeners(); } // void updateChatHistoryWindow(List? args) { // dynamic items = args!.toList(); // if (kDebugMode) { // logger.i("---------------------------------Update Chat History Windows Async -------------------------------------"); // } // logger.d(items); // // for (var user in searchedChats!) { // // if (user.id == items.first["id"]) { // // user.userStatus = items.first["userStatus"]; // // } // // } // // notifyListeners(); // } // void chatNotDelivered(List? args) { // dynamic items = args!.toList(); // for (dynamic item in items[0]) { // for (ChatUser element in searchedChats!) { // if (element.id == item["currentUserId"]) { // int? val = element.unreadMessageCount ?? 0; // element.unreadMessageCount = val! + 1; // } // } // } // notifyListeners(); // } // // void changeStatus(List? args) { // dynamic items = args!.toList(); // for (ChatUser user in searchedChats!) { // if (user.id == items.first["id"]) { // user.userStatus = items.first["userStatus"]; // } // } // if (teamMembersList.isNotEmpty) { // for (ChatUser user in teamMembersList!) { // if (user.id == items.first["id"]) { // user.userStatus = items.first["userStatus"]; // } // } // } // // notifyListeners(); // } // // void filter(String value) async { // List? tmp = []; // if (value.isEmpty || value == "") { // tmp = pChatHistory; // } else { // for (ChatUser element in pChatHistory!) { // if (element.userName!.toLowerCase().contains(value.toLowerCase())) { // tmp.add(element); // } // } // } // searchedChats = tmp; // notifyListeners(); // } Timer? timer; Future OnTypingAsync(List? parameters) async { // String empId = parameters!.first as String; isTyping = true; notifyListeners(); if (timer?.isActive ?? false) { timer!.cancel(); } timer = Timer(const Duration(milliseconds: 2500), () { isTyping = false; notifyListeners(); }); } Future OnStopTypingAsync(List? parameters) async { if (timer?.isActive ?? false) { timer!.cancel(); } isTyping = false; notifyListeners(); } Future onSeenUserChatAsync(List? parameters) async { try { if (parameters == null || parameters.isEmpty) { log('onSeenUserChatAsync: parameters are null or empty'); return; } final parm = parameters.first; if (parm is! List || parm.isEmpty) { log('onSeenUserChatAsync: parm is not a valid list'); return; } final firstItem = parm.first; if (firstItem is! Map) { log('onSeenUserChatAsync: firstItem is not a Map'); return; } await chatHubConnection!.invoke( "AckSeenAsync", args: [ firstItem['currentUserId'], [firstItem['userChatHistoryId']], ], ); } catch (e, stackTrace) { log('onSeenUserChatAsync error: $e'); log('StackTrace: $stackTrace'); } } Future onAckSeenAsync(List? parameters) async { try { if (parameters == null || parameters.isEmpty) { log('onAckSeenAsync: parameters are null or empty'); return; } final parm = parameters.first; log('parm onAckSeenAsync $parm'); if (chatResponseList.isEmpty) { log('onAckSeenAsync: chatResponseList is empty'); return; } log('last list item id ${chatResponseList.first.toJson()}'); chatResponseList.first.isSeen = true; notifyListeners(); } catch (e, stackTrace) { log('onAckSeenAsync error: $e'); log('StackTrace: $stackTrace'); } } Future onSubmitChatAsync(List? parameters) async { if (kDebugMode) print("OnSubmitChatAsync:$parameters"); if (parameters == null || parameters.isEmpty) return; try { for (dynamic msg in parameters) { var data = getSingleUserChatModel(jsonEncode(msg)); if (kDebugMode) print('Parsed message: $data'); } } catch (e) { if (kDebugMode) print('Error in OnSubmitChatAsync: $e'); } } Future onMsgReceived1(List? parameters) async { print("onMsgReceived1:$parameters"); } Future onMsgReceived(List? parameters) async { List data = []; print("OnMessageReceivedAsync:$parameters"); for (dynamic msg in parameters!) { data = getSingleUserChatModel(jsonEncode(msg)); // ...existing code... } // ...existing code... userChatHistory?.insert(0, data.first); notifyListeners(); // ...existing code... } // ==================== 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; } // Initialize CallManager if not already initialized try { await CallManager().initialize( userId: chatLoginResponse!.userId.toString(), authToken: chatLoginResponse!.token ?? '', conversationId: chatParticipantModel?.id?.toString(), moduleId: moduleID.toString(), referenceId: referenceID?.toString(), employeeNumber: sender!.employeeNumber, ); } catch (e) { log('âš ī¸ [ChatProvider] CallManager already initialized or error: $e', name: 'ChatProvider'); } // Delegate to CallManager await CallManager().startCall( peerId: recipient.employeeNumber ?? '', 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', name: 'ChatProvider', error: e, stackTrace: stackTrace); } } }