// ignore_for_file: use_build_context_synchronously import 'dart:convert'; import 'dart:developer'; import 'dart:io'; import 'package:easy_localization/easy_localization.dart'; import 'package:flutter/cupertino.dart'; import 'package:mc_common_app/classes/app_state.dart'; import 'package:mc_common_app/classes/consts.dart'; import 'package:mc_common_app/extensions/string_extensions.dart'; import 'package:mc_common_app/generated/locale_keys.g.dart'; import 'package:mc_common_app/main.dart'; import 'package:mc_common_app/models/chat_models/chat_message_model.dart'; import 'package:mc_common_app/models/general_models/generic_resp_model.dart'; import 'package:mc_common_app/models/requests_models/provider_offers_model.dart'; import 'package:mc_common_app/repositories/chat_repo.dart'; import 'package:mc_common_app/repositories/request_repo.dart'; import 'package:mc_common_app/services/common_services.dart'; import 'package:mc_common_app/utils/enums.dart'; import 'package:mc_common_app/utils/utils.dart'; import 'package:mc_common_app/view_models/ad_view_model.dart'; import 'package:mc_common_app/view_models/base_view_model.dart'; import 'package:mc_common_app/view_models/requests_view_model.dart'; import 'package:provider/provider.dart'; import 'package:signalr_core/signalr_core.dart'; class ChatVM extends BaseVM { final ChatRepo chatRepo; final RequestRepo requestRepo; final CommonAppServices commonServices; ChatVM({required this.chatRepo, required this.requestRepo, required this.commonServices}); HubConnection? hubConnection; String chatMessageText = ""; updateChatMessageText(String value) { chatMessageText = value; } clearChatMessageText() { chatMessageText = ""; notifyListeners(); } int latestOfferId = 0; updateLatestOfferId(var value) { latestOfferId = value; notifyListeners(); } bool acknowledgePaymentToMowaterStatus = false; updateAcknowledgePaymentToMowaterStatus(var value) { acknowledgePaymentToMowaterStatus = value; notifyListeners(); } bool isUserOnChatScreen = false; final ScrollController scrollController = ScrollController(); void scrollChatDown() { if (!isUserOnChatScreen) { return; } if (scrollController.hasClients) { scrollController.animateTo( scrollController.position.maxScrollExtent + 200, // for the text field duration: const Duration(seconds: 1), curve: Curves.fastOutSlowIn, ); } } List dealOptionsModelList = [ OfferRequestCommentModel( index: 0, isSelected: false, title: LocaleKeys.dealCompleted.tr(), ), OfferRequestCommentModel( index: 1, isSelected: false, title: LocaleKeys.theDealNotCompleted.tr(), ), ]; updateIsSelectedInDealOptionsModelList(int index, bool value) { for (var element in dealOptionsModelList) { element.isSelected = false; } dealOptionsModelList[index].isSelected = true; notifyListeners(); } List indexesForCancelSpecialCarOffer = [0, 1, 5, 6, 10]; List indexesForCancelSparePartOffer = [2, 5, 6, 10]; List indexesForRejectOffer = [3, 4, 10]; List indexesForDealNotCompleted = [7, 4, 8, 9, 10]; List offerRejectModelList = [ OfferRequestCommentModel( index: 0, isSelected: true, title: LocaleKeys.dealOutsideApp.tr(), ), OfferRequestCommentModel( index: 1, isSelected: false, title: LocaleKeys.noAgreementCustomer.tr(), ), OfferRequestCommentModel( index: 2, isSelected: false, title: LocaleKeys.itemNoLongerAvailable.tr(), ), OfferRequestCommentModel( index: 3, isSelected: true, title: LocaleKeys.changedMind.tr(), ), OfferRequestCommentModel( index: 4, isSelected: false, title: LocaleKeys.veryHighPrice.tr(), ), OfferRequestCommentModel( index: 5, isSelected: false, title: LocaleKeys.alreadySold.tr(), ), OfferRequestCommentModel( index: 6, isSelected: false, title: LocaleKeys.customerNotResponding.tr(), ), OfferRequestCommentModel( index: 7, isSelected: false, title: LocaleKeys.cancelRequest.tr(), ), OfferRequestCommentModel( index: 8, isSelected: false, title: LocaleKeys.offerNotMatched.tr(), ), OfferRequestCommentModel( index: 9, isSelected: false, title: LocaleKeys.testTheService.tr(), ), OfferRequestCommentModel( index: 10, isSelected: false, title: LocaleKeys.otherVar.tr(), ), ]; void updateSelectionInOfferRejectModelList(int index) { for (var value in offerRejectModelList) { value.isSelected = false; } selectedOfferRequestCommentModel = offerRejectModelList[index]; offerRejectModelList[index].isSelected = true; notifyListeners(); } OfferRequestCommentModel selectedOfferRequestCommentModel = OfferRequestCommentModel( index: 0, isSelected: true, title: LocaleKeys.changedMind.tr(), ); String rejectOfferDescription = ""; String rejectOfferDescriptionError = ""; void updateRejectOfferDescription(String value) { rejectOfferDescription = value; if (value.isNotEmpty) { rejectOfferDescriptionError = ""; } } //REJECTING OFFER bool isRejectOfferButtonValidated() { if (selectedOfferRequestCommentModel.index != offerRejectModelList.length - 1) { return true; } bool isValidated = true; if (rejectOfferDescription.isEmpty) { rejectOfferDescriptionError = GlobalConsts.descriptionError; isValidated = false; } else { rejectOfferDescriptionError = ""; } notifyListeners(); return isValidated; } Future onNewMessageReceivedForRequestOffer({required List messages, bool isMyOwnOffer = false, required RequestsVM requestsVM}) async { if (AppState().currentAppType == AppType.customer) { for (var currentMessage in messages) { logger.i(currentMessage); int providerIndex = serviceProviderOffersList.indexWhere((element) => element.providerUserId == currentMessage.senderUserID); if (providerIndex != -1) { if (currentMessage.chatMessageTypeEnum == ChatMessageTypeEnum.offer) { for (var chatMessage in serviceProviderOffersList[providerIndex].chatMessages!) { if (chatMessage.chatMessageTypeEnum == ChatMessageTypeEnum.offer && chatMessage.reqOffer!.id == currentMessage.reqOffer!.id) { chatMessage.reqOffer!.requestOfferStatusEnum = currentMessage.reqOffer!.requestOfferStatusEnum; } } } serviceProviderOffersList[providerIndex].chatMessages!.add(currentMessage); scrollChatDown(); } } } else { for (var currentMessage in messages) { logger.i(currentMessage); // Where we need to call this function for saving a message in chat we will use receiverUserID and in those cases where received ID is null , it means it is a signal R call int requestIndex = -1; if (isMyOwnOffer) { requestIndex = requestsVM.myFilteredRequests.indexWhere((element) => (element.customerUserID == currentMessage.receiverUserID && element.id == currentMessage.requestID)); } else { requestIndex = requestsVM.myFilteredRequests.indexWhere((element) => (element.customerUserID == currentMessage.senderUserID && element.id == currentMessage.requestID)); } log("requestIndex: $requestIndex"); if (requestIndex != -1) { if (currentMessage.chatMessageTypeEnum == ChatMessageTypeEnum.offer) { for (var chatMessage in requestsVM.myFilteredRequests[requestIndex].chatMessages) { if (chatMessage.chatMessageTypeEnum == ChatMessageTypeEnum.offer && chatMessage.reqOffer!.id == currentMessage.reqOffer!.id) { logger.i("chatMessage reqOfferID: ${chatMessage.reqOffer!.id.toString()}"); logger.i("currentMessage reqOfferID: ${currentMessage.reqOffer!.id.toString()}"); chatMessage.reqOffer!.requestOfferStatusEnum = currentMessage.reqOffer!.requestOfferStatusEnum; } } } requestsVM.addChatMessagesInRequestsModel(msg: currentMessage, index: requestIndex); scrollChatDown(); } } } notifyListeners(); } void _subscribeToReceiveRequestOfferMessages(BuildContext context) { hubConnection!.on(SignalrConsts.receiveMessageRequestOffer, (List? arguments) { logger.i(arguments); if (arguments == null || arguments.isEmpty) return; List chat = []; for (var message in arguments) { final chatMessage = ChatMessageModel.fromJson(message as Map, isForReqOfferImagesURLs: true); chat.add(chatMessage); } onNewMessageReceivedForRequestOffer(messages: chat, requestsVM: context.read()); // Utils.showToast(arguments.toString()); }); } Future markMessagesAsRead(BuildContext context, List messageIds, ChatTypeEnum chatTypeEnum) async { bool status = false; try { // Utils.showLoading(context); status = await chatRepo.markMessageAsRead(messageIds: messageIds, chatTypeEnum: chatTypeEnum); // Utils.hideLoading(context); } catch (e) { logger.i(e.toString()); Utils.showToast(e.toString()); // Utils.hideLoading(context); status = false; } return status; } int _retryCount = 0; final int _maxRetries = 5; // Maximum number of retries bool _isReconnecting = false; Future _reconnectWithBackoff(BuildContext context) async { if (_retryCount >= _maxRetries) { logger.e("Maximum retry attempts reached. Stopping reconnection."); return; } final delay = Duration(seconds: 2 * (_retryCount + 1)); // Exponential backoff logger.i("Retrying connection in ${delay.inSeconds} seconds..."); await Future.delayed(delay); _retryCount++; await buildHubConnection(context); // Try reconnecting } Future buildHubConnection(BuildContext context) async { if (hubConnection == null || hubConnection!.state != HubConnectionState.connected) { if (_isReconnecting) { logger.i("Reconnection already in progress"); return; } if (_isReconnecting) { logger.i("Reconnection already in progress"); return; } try { _isReconnecting = true; hubConnection = await chatRepo.getHubConnection(); await hubConnection!.start(); // Reset retry count on successful connection _retryCount = 0; _isReconnecting = false; // Subscribe to messages _subscribeToReceiveRequestOfferMessages(context); _subscribeToReceiveAdMessages(context); _subscribeToReceiveGeneralMessages(context); // Configure handlers hubConnection!.onclose((exception) async { logger.i("onClose: ${exception.toString()}"); await _reconnectWithBackoff(context); }); hubConnection!.onreconnecting((exception) { logger.i("onReconnecting: ${exception?.toString()}"); }); hubConnection!.onreconnected((connectionId) { logger.i("onReconnected: ${connectionId.toString()}"); }); } catch (e) { logger.e("Error during hub connection setup: ${e.toString()}"); _isReconnecting = false; // Reset on failure } notifyListeners(); } else { logger.i("Hub Already Connected: ${hubConnection!.connectionId.toString()}"); } } Future closeHubConnection() async { if (hubConnection != null && hubConnection!.state == HubConnectionState.connected) { hubConnection!.stop(); } } Future onOfferSendForRequest({ required String receiverId, required ChatMessageTypeEnum chatMessageType, required String message, required int requestId, required String offerPrice, required bool isDeliveryAvailable, required String serviceItemName, required int manufacturedById, required String manufacturedOn, required List offerImages, required BuildContext context, }) async { if (hubConnection == null || hubConnection!.state != HubConnectionState.connected) { logger.i("Hub Not Connected!!"); await buildHubConnection(context); } if (hubConnection!.state == HubConnectionState.connected) { final providerId = AppState().getUser.data!.userInfo!.providerId; final obj = { "ReceiverUserID": receiverId, "MessageType": chatMessageType.getIdFromChatMessageTypeEnum(), "ChatText": message, "RequestID": requestId, "ReqOfferID": 0, "ReqOffer": { "RequestID": requestId, "Price": double.parse(offerPrice), "IsDeliveryAvailable": isDeliveryAvailable, "ServiceItem": serviceItemName, "ReqOfferImages": offerImages, "OfferedItemCreatedBy": manufacturedById, // TODO: The ID should be used from the manufacturer database // "OfferedItemCreatedOn": manufacturedOn, // TODO: This should be in String on Server, Right now it is in DateTime "ServiceProviderID": providerId, "OfferStatus": RequestOfferStatusEnum.offer.getIdFromRequestOfferStatusEnum(), "Comment": message, }, }; logger.i(obj); hubConnection!.invoke( SignalrConsts.sendMessageRequestOffer, args: [obj], ).catchError((e) { logger.i("error in invoking SendMessageRequestOffer: ${e.toString()}"); Utils.showToast(e.toString()); return false; }); } return true; } MessageImageModel convertFileToMessageImageModel({required File file}) { List imageBytes = file.readAsBytesSync(); String image = base64Encode(imageBytes); MessageImageModel vehiclePostingImages = MessageImageModel(imageStr: image, id: 0, isFromNetwork: false, reqOfferID: latestOfferId, imagePath: file.path); return vehiclePostingImages; } List getMessagesImageList() { List requestImages = []; for (var image in pickedImagesForMessage) { var value = convertFileToMessageImageModel(file: File(image.filePath!)); requestImages.add(value); } return requestImages; } List pickedImagesForMessage = []; void removeImageFromList(String filePath) { int index = pickedImagesForMessage.indexWhere((element) => element.filePath == filePath); if (index == -1) { return; } pickedImagesForMessage.removeAt(index); notifyListeners(); } void pickMultipleImages() async { List imageModels = []; List images = await commonServices.pickMultipleImages(); for (var element in images) { imageModels.add(ImageModel(filePath: element.path, isFromNetwork: false)); } pickedImagesForMessage.addAll(imageModels); if (pickedImagesForMessage.length > GlobalConsts().maxFileCount) { pickedImagesForMessage = pickedImagesForMessage.sublist(0, GlobalConsts().maxFileCount); Utils.showToast(LocaleKeys.maxFileSelection); } notifyListeners(); } void clearPickedImagesForMessage() { pickedImagesForMessage.clear(); notifyListeners(); } Future onMessageSendForRequest({ required String receiverId, required ChatMessageTypeEnum chatMessageType, required String message, required int requestId, required BuildContext context, }) async { if (chatMessageType == ChatMessageTypeEnum.freeText && message.isEmpty) return false; if (hubConnection == null || hubConnection!.state != HubConnectionState.connected) { logger.i("Hub Not Connected!!"); await buildHubConnection(context); } if (hubConnection!.state == HubConnectionState.connected) { final userId = AppState().getUser.data!.userInfo!.userId.toString(); final name = AppState().getUser.data!.userInfo!.firstName.toString(); List> requestImagesMap = []; List messageImages = getMessagesImageList(); if (messageImages.isNotEmpty && chatMessageType == ChatMessageTypeEnum.image) { for (var element in messageImages) { requestImagesMap.add(element.toJson()); } } final obj = { "ReceiverUserID": receiverId, "SenderUserID": userId, "MessageType": chatMessageType.getIdFromChatMessageTypeEnum(), "ChatText": chatMessageType == ChatMessageTypeEnum.image ? "${messageImages.length} Image(s)" : message, "RequestID": requestId, "ReqOfferID": latestOfferId, }; if (chatMessageType == ChatMessageTypeEnum.image) { obj.addAll({"ReqOfferImages": requestImagesMap}); } logger.i(obj); hubConnection!.invoke( SignalrConsts.sendMessageRequestOffer, args: [obj], ).catchError((e) { logger.i("error in invoking SendMessage: ${e.toString()}"); Utils.showToast(e.toString()); return false; }); ChatMessageModel chatMessageModel = ChatMessageModel( messageType: chatMessageType.getIdFromChatMessageTypeEnum(), chatText: message, isMyMessage: true, requestID: requestId, senderName: name, senderUserID: userId, chatMessageTypeEnum: chatMessageType, receiverUserID: receiverId, messageImages: messageImages, ); if (AppState().currentAppType == AppType.customer) { int providerIndex = serviceProviderOffersList.indexWhere((element) => element.providerUserId == receiverId); if (providerIndex != -1) { serviceProviderOffersList[providerIndex].chatMessages!.add(chatMessageModel); } } else { int providerIndex = context.read().myFilteredRequests.indexWhere((element) => (element.customerUserID == receiverId && element.id == requestId)); log("providerIndex2:$providerIndex"); if (providerIndex != -1) { context.read().addChatMessagesInRequestsModel(msg: chatMessageModel, index: providerIndex); } } return true; } return false; } List serviceProviderOffersList = []; Future getOffersFromProvidersByRequest({required int requestId, bool isNeedLoading = true}) async { try { if (isNeedLoading) { setState(ViewState.busy); } ProviderOffersModel providerOffersModel = await requestRepo.getOffersFromProvidersByRequest(requestId: requestId); if (isNeedLoading) { setState(ViewState.idle); } serviceProviderOffersList.clear(); serviceProviderOffersList = providerOffersModel.serviceProviders ?? []; notifyListeners(); } catch (e) { logger.i(e.toString()); Utils.showToast(e.toString()); if (isNeedLoading) { setState(ViewState.idle); } } } Future getRequestsChatMessagesForCustomer({ required BuildContext context, required int providerId, required int requestOfferId, required int requestId, required int providerOfferIndex, }) async { try { int customerId = AppState().getUser.data!.userInfo!.customerId!; Utils.showLoading(context); List chatMessages = await chatRepo.getUsersChatMessagesForRequests(providerId: providerId, customerId: customerId, requestOfferId: requestOfferId, requestId: requestId); serviceProviderOffersList[providerOfferIndex].chatMessages = chatMessages; if (serviceProviderOffersList[providerOfferIndex].chatMessages != null) { for (var message in serviceProviderOffersList[providerOfferIndex].chatMessages!) { if (message.chatMessageTypeEnum == ChatMessageTypeEnum.offer) { updateLatestOfferId(message.reqOfferID ?? 0); if (message.reqOffer!.requestOfferStatusEnum == RequestOfferStatusEnum.accepted) { context.read().updateAcceptedReqOffer(message.reqOffer!); context.read().updateAcceptedRequestOfferProviderName(serviceProviderOffersList[providerOfferIndex].name ?? ""); } log("latestOfferId: $latestOfferId"); } } } List unreadMessageIds = []; for (var msg in chatMessages) { if (!msg.isRead! && !msg.isMyMessage!) { unreadMessageIds.add(msg.id!); } } if (unreadMessageIds.isNotEmpty) { log("unreadMessageIds: ${unreadMessageIds.toString()}"); await markMessagesAsRead(context, unreadMessageIds, ChatTypeEnum.requestOffer); } Utils.hideLoading(context); notifyListeners(); } catch (e) { logger.i(e.toString()); Utils.showToast(e.toString()); Utils.hideLoading(context); } } Future getRequestsChatMessagesForProvider({ required BuildContext context, required int customerId, required int requestOfferId, required int requestId, required int customerRequestIndex, }) async { try { int providerId = AppState().getUser.data!.userInfo!.providerId!; Utils.showLoading(context); List chatMessages = await chatRepo.getUsersChatMessagesForRequests(providerId: providerId, customerId: customerId, requestOfferId: requestOfferId, requestId: requestId); context.read().overwriteChatMessagesInRequestsModel(messages: chatMessages, index: customerRequestIndex, context: context); List unreadMessageIds = []; for (var msg in chatMessages) { if (!msg.isRead! && !msg.isMyMessage!) { unreadMessageIds.add(msg.id!); } } if (unreadMessageIds.isNotEmpty) { await markMessagesAsRead(context, unreadMessageIds, ChatTypeEnum.requestOffer); } Utils.hideLoading(context); } catch (e) { logger.i(e.toString()); Utils.showToast(e.toString()); Utils.hideLoading(context); } } Future onActionOfferTapped({required BuildContext context, required RequestOfferStatusEnum requestOfferStatusEnum, required int reqOfferId, required String comments}) async { Utils.showLoading(context); try { GenericRespModel genericRespModel = await requestRepo.updateRequestOfferStatus(requestOfferStatusEnum: requestOfferStatusEnum, requestOfferId: reqOfferId, comments: comments); Utils.showToast(genericRespModel.message.toString()); Utils.showLoading(context); return genericRespModel.messageStatus == 1 ? reqOfferId : -1; } catch (e) { logger.i(e.toString()); Utils.showToast(e.toString()); Utils.hideLoading(context); return -1; } } Future onSendMessageForActionOnRequestOffer({ required String receiverId, required ChatMessageTypeEnum chatMessageType, required String comments, required int requestId, required int serviceProviderID, required int requestOfferID, required String offerPrice, required String serviceItemName, required int manufacturedById, required String manufacturedOn, required RequestOfferStatusEnum requestOfferStatusEnum, required BuildContext context, }) async { if (hubConnection == null || hubConnection!.state != HubConnectionState.connected) { logger.i("Hub Not Connected!!"); await buildHubConnection(context); } if (hubConnection!.state == HubConnectionState.connected) { var obj = { "ReceiverUserID": receiverId, "MessageType": chatMessageType.getIdFromChatMessageTypeEnum(), "ChatText": comments, "RequestID": requestId, "ReqOfferID": requestOfferID, "ReqOffer": { "ID": requestOfferID, "RequestID": requestId, "Price": double.parse(offerPrice), "ServiceItem": serviceItemName, "OfferedItemCreatedBy": manufacturedById, // TODO: The ID should be used from the manufacturer database // "OfferedItemCreatedOn": manufacturedOn, // TODO: This should be in String on Server, Right now it is in DateTime "ServiceProviderID": serviceProviderID, "OfferStatus": requestOfferStatusEnum.getIdFromRequestOfferStatusEnum(), "Comment": comments, }, }; logger.i(obj); hubConnection!.invoke( SignalrConsts.sendMessageRequestOffer, args: [obj], ).catchError((e) { logger.i("error in invoking SendMessageRequestOffer: ${e.toString()}"); Utils.showToast(e.toString()); return false; }); } return true; } // =================== Ads ====================== List currentMessagesForAds = []; Future getUsersChatMessagesForAd({required BuildContext context, int? adID, int? adsChatBuyerId, String? userID, String? senderName, required bool isForBuyer}) async { try { Utils.showLoading(context); currentMessagesForAds = await chatRepo.getUsersChatMessagesForAds(userID: userID, adID: adID, isForBuyer: isForBuyer, adsChatBuyerId: adsChatBuyerId); Utils.hideLoading(context); List unreadMessageIds = []; for (var msg in currentMessagesForAds) { msg.senderName = senderName ?? ""; if (!msg.isRead! && !msg.isMyMessage!) { unreadMessageIds.add(msg.id!); } } if (unreadMessageIds.isNotEmpty) { await markMessagesAsRead(context, unreadMessageIds, ChatTypeEnum.ads); } notifyListeners(); } catch (e) { logger.i(e.toString()); Utils.showToast(e.toString()); Utils.hideLoading(context); } } void _subscribeToReceiveAdMessages(BuildContext context) { hubConnection!.on(SignalrConsts.receiveMessageAds, (List? arguments) { if (arguments == null || arguments.isEmpty) return; List chat = []; for (var message in arguments) { final chatMessage = ChatMessageModel.fromJson(message as Map); chat.add(chatMessage); } onNewMessageReceivedForAds(messages: chat); logger.i(arguments); // Utils.showToast(arguments.toString()); }); } Future onNewMessageReceivedForAds({required List messages}) async { for (var msg in messages) { currentMessagesForAds.add(msg); } scrollChatDown(); notifyListeners(); } Future onTextMessageSendForAds({ required String receiverId, required ChatMessageTypeEnum chatMessageType, required String message, required int adId, required BuildContext context, }) async { if (message.isEmpty) return false; if (hubConnection == null || hubConnection!.state != HubConnectionState.connected) { logger.i("Hub Not Connected!!"); await buildHubConnection(context); } if (hubConnection!.state == HubConnectionState.connected) { final userId = AppState().getUser.data!.userInfo!.userId.toString(); final name = AppState().getUser.data!.userInfo!.firstName.toString(); final obj = { "ReceiverUserID": receiverId, // "SenderUserID": userId, "MessageType": chatMessageType.getIdFromChatMessageTypeEnum(), "ChatText": message, "AdsID": adId, }; logger.i("$obj"); hubConnection!.invoke( SignalrConsts.sendMessageAds, args: [obj], ).catchError((e) { logger.i("error in invoking SendMessage: ${e.toString()}"); Utils.showToast(e.toString()); return false; }); ChatMessageModel chatMessageModel = ChatMessageModel( messageType: chatMessageType.getIdFromChatMessageTypeEnum(), chatText: message, isMyMessage: true, senderName: name, senderUserID: userId, chatMessageTypeEnum: ChatMessageTypeEnum.freeText, receiverUserID: receiverId, ); currentMessagesForAds.add(chatMessageModel); notifyListeners(); if (AppState().currentAppType == AppType.customer) { } else {} return true; } return false; } // ========================General ================== List currentMessagesForGeneralChat = []; Future getUsersChatMessagesForGeneralChat({required BuildContext context, int? adID, int? adsChatBuyerId, String? userID, String? senderName, required bool isForBuyer}) async { try { Utils.showLoading(context); currentMessagesForGeneralChat = await chatRepo.getUsersChatMessagesForGeneralChat(userID: userID, adID: adID, isForBuyer: isForBuyer, adsChatBuyerId: adsChatBuyerId); Utils.hideLoading(context); List unreadMessageIds = []; for (var msg in currentMessagesForGeneralChat) { msg.senderName = senderName ?? ""; if (!msg.isRead! && !msg.isMyMessage!) { unreadMessageIds.add(msg.id!); } } if (unreadMessageIds.isNotEmpty) { await markMessagesAsRead(context, unreadMessageIds, ChatTypeEnum.general); } notifyListeners(); } catch (e) { logger.i(e.toString()); Utils.showToast(e.toString()); Utils.hideLoading(context); } } Future onNewMessageReceivedForGeneral({required List messages}) async { for (var msg in messages) { currentMessagesForAds.add(msg); } scrollChatDown(); notifyListeners(); } void _subscribeToReceiveGeneralMessages(BuildContext context) { hubConnection!.on(SignalrConsts.receiveMessageGeneral, (List? arguments) { if (arguments == null || arguments.isEmpty) return; List chat = []; for (var message in arguments) { final chatMessage = ChatMessageModel.fromJson(message as Map); chat.add(chatMessage); } onNewMessageReceivedForGeneral(messages: chat); logger.i(arguments); // Utils.showToast(arguments.toString()); }); } Future onTextMessageSendForGeneralChat({ required String receiverId, required ChatMessageTypeEnum chatMessageType, required String message, required int adId, required BuildContext context, }) async { if (message.isEmpty) return false; if (hubConnection == null || hubConnection!.state != HubConnectionState.connected) { logger.i("Hub Not Connected!!"); await buildHubConnection(context); } if (hubConnection!.state == HubConnectionState.connected) { final userId = AppState().getUser.data!.userInfo!.userId.toString(); final name = AppState().getUser.data!.userInfo!.firstName.toString(); final obj = { "ReceiverUserID": receiverId, "MessageType": chatMessageType.getIdFromChatMessageTypeEnum(), "ChatText": message, }; logger.i("$obj"); hubConnection!.invoke( SignalrConsts.sendMessageGeneral, args: [obj], ).catchError((e) { logger.i("error in invoking SendMessage: ${e.toString()}"); Utils.showToast(e.toString()); return false; }); ChatMessageModel chatMessageModel = ChatMessageModel( messageType: chatMessageType.getIdFromChatMessageTypeEnum(), chatText: message, isMyMessage: true, senderName: name, senderUserID: userId, chatMessageTypeEnum: ChatMessageTypeEnum.freeText, receiverUserID: receiverId, ); currentMessagesForGeneralChat.add(chatMessageModel); notifyListeners(); if (AppState().currentAppType == AppType.customer) { } else {} return true; } return false; } }