// ignore_for_file: use_build_context_synchronously import 'dart:developer'; 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/utils/enums.dart'; import 'package:mc_common_app/utils/utils.dart'; import 'package:mc_common_app/view_models/requests_view_model.dart'; import 'package:provider/provider.dart'; import 'package:signalr_core/signalr_core.dart'; import 'package:easy_localization/easy_localization.dart'; class ChatVM extends ChangeNotifier { final ChatRepo chatRepo; final RequestRepo requestRepo; ChatVM({required this.chatRepo, required this.requestRepo}); HubConnection? hubConnection; String chatMessageText = ""; updateChatMessageText(String value) { chatMessageText = value; } clearChatMessageText() { chatMessageText = ""; notifyListeners(); } int latestOfferId = 0; updateLatestOfferId(var value) { latestOfferId = value; notifyListeners(); } bool isUserOnChatScreen = false; final ScrollController scrollController = ScrollController(); void scrollChatDown() { if (!isUserOnChatScreen) { return; } scrollController.animateTo( scrollController.position.maxScrollExtent + 200, // for the text field duration: const Duration(seconds: 1), curve: Curves.fastOutSlowIn, ); } List offerRejectModelList = [ OfferRequestCommentModel( index: 0, isSelected: false, title: LocaleKeys.changedMind.tr(), ), OfferRequestCommentModel( index: 1, isSelected: false, title: LocaleKeys.veryHighPrice.tr(), ), OfferRequestCommentModel( index: 2, isSelected: false, title: LocaleKeys.alreadySold.tr(), ), OfferRequestCommentModel( index: 3, isSelected: true, title: LocaleKeys.otherVar.tr(), ), ]; OfferRequestCommentModel selectedOfferRequestCommentModel = OfferRequestCommentModel( index: 3, isSelected: true, title: LocaleKeys.otherVar.tr(), ); void updateSelectionInOfferRejectModelList(int index) { for (var value in offerRejectModelList) { value.isSelected = false; } selectedOfferRequestCommentModel = offerRejectModelList[index]; offerRejectModelList[index].isSelected = true; notifyListeners(); } 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 msg in messages) { int providerIndex = serviceProviderOffersList.indexWhere((element) => element.providerUserId == msg.senderUserID); if (providerIndex != -1) { serviceProviderOffersList[providerIndex].chatMessages!.add(msg); scrollChatDown(); } } } else { for (var msg in messages) { logger.i(msg); // 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 providerIndex = -1; if (isMyOwnOffer) { providerIndex = requestsVM.myFilteredRequests.indexWhere((element) => (element.customerUserID == msg.receiverUserID && element.id == msg.requestID)); } else { providerIndex = requestsVM.myFilteredRequests.indexWhere((element) => (element.customerUserID == msg.senderUserID && element.id == msg.requestID)); } log("providerIndex: $providerIndex"); if (providerIndex != -1) { requestsVM.addChatMessagesInRequestsModel(msg: msg, index: providerIndex); scrollChatDown(); } } } notifyListeners(); } Future onNewMessageReceivedForAds({required List messages}) async { for (var msg in messages) { currentMessagesForAds.add(msg); } scrollChatDown(); notifyListeners(); } void subscribeToReceiveRequestOfferMessages(BuildContext context) { hubConnection!.on(SignalrConsts.receiveMessageRequestOffer, (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); } onNewMessageReceivedForRequestOffer(messages: chat, requestsVM: context.read()); logger.i(arguments); // Utils.showToast(arguments.toString()); }); } 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 buildHubConnection(BuildContext context) async { if (hubConnection == null || hubConnection!.state != HubConnectionState.connected) { try { hubConnection = await chatRepo.getHubConnection(); await hubConnection!.start(); subscribeToReceiveRequestOfferMessages(context); subscribeToReceiveAdMessages(context); hubConnection!.onclose((exception) { logger.i("onClose: ${exception.toString()}"); buildHubConnection(context); }); hubConnection!.onreconnecting((exception) => () => logger.i("onReconnecting: ${exception.toString()}")); hubConnection!.onreconnected((connectionId) => () => logger.i("onReconnected: ${connectionId.toString()}")); } catch (e) { logger.i("Error: ${e.toString()}"); } notifyListeners(); } } Future onOfferSendForRequest({ required String receiverId, required ChatMessageTypeEnum chatMessageType, required String message, required int requestId, required String offerPrice, 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), "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; } Future onTextMessageSendForRequest({ required String receiverId, required ChatMessageTypeEnum chatMessageType, required String message, required int requestId, required String offerPrice, 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, "RequestID": requestId, "ReqOfferID": latestOfferId, // "ReqOffer": {} }; 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: ChatMessageTypeEnum.freeText, receiverUserID: receiverId, ); 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, required BuildContext context}) async { try { Utils.showLoading(context); ProviderOffersModel providerOffersModel = await requestRepo.getOffersFromProvidersByRequest(requestId: requestId); Utils.hideLoading(context); serviceProviderOffersList.clear(); serviceProviderOffersList = providerOffersModel.serviceProviders ?? []; notifyListeners(); } catch (e) { logger.i(e.toString()); Utils.showToast(e.toString()); Utils.hideLoading(context); } } Future getUsersChatMessagesForCustomer({ 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); log("latestOfferId: $latestOfferId"); } } } Utils.hideLoading(context); notifyListeners(); } catch (e) { logger.i(e.toString()); Utils.showToast(e.toString()); Utils.hideLoading(context); } } Future getUsersChatMessagesForProvider({ 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); 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 { try { GenericRespModel genericRespModel = await requestRepo.updateOfferRequestStatus(requestOfferStatusEnum: requestOfferStatusEnum, requestOfferId: reqOfferId, comments: comments); Utils.showToast(genericRespModel.message.toString()); 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 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), "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!) { 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); } } 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; } 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; } }