import 'dart:developer'; import 'package:flutter/material.dart'; import 'package:mc_common_app/classes/app_state.dart'; import 'package:mc_common_app/classes/consts.dart'; import 'package:mc_common_app/config/routes.dart'; import 'package:mc_common_app/extensions/int_extensions.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/models/chat_models/chat_message_model.dart'; import 'package:mc_common_app/models/requests_models/request_model.dart'; import 'package:mc_common_app/theme/colors.dart'; import 'package:mc_common_app/utils/dialogs_and_bottomsheets.dart'; import 'package:mc_common_app/utils/enums.dart'; import 'package:mc_common_app/utils/utils.dart'; import 'package:mc_common_app/view_models/chat_view_model.dart'; import 'package:mc_common_app/view_models/requests_view_model.dart'; import 'package:mc_common_app/widgets/button/show_fill_button.dart'; import 'package:mc_common_app/widgets/checkbox_with_title_desc.dart'; import 'package:mc_common_app/widgets/common_widgets/app_bar.dart'; import 'package:mc_common_app/widgets/common_widgets/info_bottom_sheet.dart'; import 'package:mc_common_app/widgets/extensions/extensions_widget.dart'; import 'package:mc_common_app/widgets/txt_field.dart'; import 'package:provider/provider.dart'; import 'package:easy_localization/easy_localization.dart' as lcl; class ChatView extends StatefulWidget { final ChatViewArguments chatViewArguments; const ChatView({super.key, required this.chatViewArguments}); @override State createState() => _ChatViewState(); } class _ChatViewState extends State { late ChatVM chatVM; late ChatTypeEnum chatTypeEnum; ChatViewArgumentsForRequest? chatViewArgumentsForRequest; ChatViewArgumentsForAd? chatViewArgumentsForAd; @override void initState() { chatTypeEnum = widget.chatViewArguments.chatTypeEnum; if (chatTypeEnum == ChatTypeEnum.ads) { chatViewArgumentsForAd = widget.chatViewArguments.chatViewArgumentsForAd; } else if (chatTypeEnum == ChatTypeEnum.requestOffer) { chatViewArgumentsForRequest = widget.chatViewArguments.chatViewArgumentsForRequest; } chatVM = context.read(); chatVM.isUserOnChatScreen = true; WidgetsBinding.instance.addPostFrameCallback((_) { if (chatVM.scrollController.hasClients) { chatVM.scrollChatDown(); } }); super.initState(); } @override void dispose() { chatVM.isUserOnChatScreen = false; super.dispose(); } Future buildSendOfferBottomSheet(BuildContext context) { RequestModel requestDetail = chatViewArgumentsForRequest!.requestModel!; return showModalBottomSheet( context: context, isScrollControlled: true, enableDrag: true, builder: (BuildContext context) { return Consumer(builder: (BuildContext context, RequestsVM requestsVM, Widget? child) { return InfoBottomSheet( title: LocaleKeys.makeAnOffer.tr().toText(fontSize: 28, isBold: true, letterSpacing: -1.44), description: Padding( padding: EdgeInsets.only(bottom: MediaQuery.of(context).viewInsets.bottom), child: Column( crossAxisAlignment: CrossAxisAlignment.start, children: [ Column( crossAxisAlignment: CrossAxisAlignment.start, children: [ 12.height, TxtField( value: requestsVM.offerPrice, errorValue: requestsVM.offerPriceError, keyboardType: TextInputType.number, hint: LocaleKeys.enterAmount.tr(), numbersOnly: true, onChanged: (v) => requestsVM.updateOfferPrice(v), ), 12.height, TxtField( maxLines: 5, value: requestsVM.offerDescription, errorValue: requestsVM.offerDescriptionError, keyboardType: TextInputType.text, hint: LocaleKeys.description.tr(), onChanged: (v) => requestsVM.updateOfferDescription(v), ), ], ), 25.height, ShowFillButton( title: LocaleKeys.submit.tr(), onPressed: () { requestsVM.onSendOfferPressed( context: context, receiverId: requestDetail.customerID, message: requestsVM.offerDescription, requestId: requestDetail.id, offerPrice: requestsVM.offerPrice, requestModel: requestDetail, requestIndex: chatViewArgumentsForRequest!.requestIndex, isFromChatScreen: true, ); }, maxWidth: double.infinity, ), 19.height, ], ), )); }); }, ); } Future onTextMessageSend() async { bool status = false; if (chatTypeEnum == ChatTypeEnum.requestOffer) { status = await chatVM.onTextMessageSendForRequest( context: context, receiverId: chatViewArgumentsForRequest!.receiverId, message: chatVM.chatMessageText, requestId: chatViewArgumentsForRequest!.requestId ?? 0, offerPrice: "0.0", chatMessageType: ChatMessageTypeEnum.freeText, ); } else if (chatTypeEnum == ChatTypeEnum.ads) { } else {} return status; } @override Widget build(BuildContext context) { return Scaffold( appBar: CustomAppBar(title: LocaleKeys.chat.tr()), body: Consumer2(builder: (BuildContext context, ChatVM chatVM, RequestsVM requestVM, Widget? child) { List chatMessages = []; if (chatTypeEnum == ChatTypeEnum.ads) { chatMessages = chatViewArgumentsForAd!.adDetailsModel!.adMessages ?? []; } else if (chatTypeEnum == ChatTypeEnum.requestOffer) { if (AppState().currentAppType == AppType.customer) { chatMessages = chatVM.serviceProviderOffersList[chatViewArgumentsForRequest!.providerIndex].chatMessages ?? []; } else { chatMessages = requestVM.myFilteredRequests[chatViewArgumentsForRequest!.requestIndex].chatMessages; } } return Column( children: [ Expanded( child: chatMessages.isEmpty ? Center(child: LocaleKeys.noRequestsShow.tr().toText(fontSize: 16, color: MyColors.lightTextColor)) : ListView.separated( controller: chatVM.scrollController, itemCount: chatMessages.length, separatorBuilder: (BuildContext context, int index) => 20.height, itemBuilder: (BuildContext context, int index) { ChatMessageModel chatMessageModel = chatMessages[index]; return ChatMessageCustomWidget(chatMessageModel: chatMessageModel); }, ).horPaddingMain(), ), 10.height, Row( crossAxisAlignment: CrossAxisAlignment.center, children: [ if (chatTypeEnum == ChatTypeEnum.requestOffer && requestVM.currentSelectedRequest!.requestStatus == RequestStatus.inProgress) ...[ Center( child: LocaleKeys.requestAlreadyInProgress.tr().toText( textAlign: TextAlign.center, color: MyColors.lightTextColor, fontSize: 14, ), ).paddingAll(15) ] else ...[ if (AppState().currentAppType == AppType.provider && chatTypeEnum == ChatTypeEnum.requestOffer) ...[ Expanded( flex: 1, child: const Icon( Icons.local_offer_rounded, color: MyColors.darkPrimaryColor, size: 30, ).onPress( () async { buildSendOfferBottomSheet(context); }, ), ), ], Expanded( flex: 8, child: TxtField( value: chatVM.chatMessageText, hint: LocaleKeys.typeMessageHere.tr(), keyboardType: TextInputType.text, isNeedBorder: false, onChanged: (v) => chatVM.updateChatMessageText(v), ), ), Expanded( flex: 1, child: const Icon( Icons.send_rounded, color: MyColors.darkPrimaryColor, size: 30, ).onPress( () async { final status = await chatVM.onTextMessageSendForRequest( context: context, receiverId: chatViewArgumentsForRequest!.receiverId, message: chatVM.chatMessageText, requestId: chatViewArgumentsForRequest!.requestId ?? 0, offerPrice: "0.0", chatMessageType: ChatMessageTypeEnum.freeText, ); if (status) { chatVM.scrollChatDown(); chatVM.clearChatMessageText(); } }, ), ), ], ], ).toContainer(isShadowEnabled: true), ], ); }), // body: ); } } class ChatMessageCustomWidget extends StatefulWidget { final ChatMessageModel chatMessageModel; const ChatMessageCustomWidget({super.key, required this.chatMessageModel}); @override State createState() => _ChatMessageCustomWidgetState(); } class _ChatMessageCustomWidgetState extends State { Future buildRejectOfferBottomSheet({required ChatMessageModel chatMessageModel}) { return showModalBottomSheet( context: context, isScrollControlled: true, enableDrag: true, builder: (BuildContext context) { return Consumer(builder: (BuildContext context, ChatVM chatVM, Widget? child) { return InfoBottomSheet( title: LocaleKeys.makeAnOffer.tr().toText(fontSize: 28, isBold: true, letterSpacing: -1.44), description: Padding( padding: EdgeInsets.only(bottom: MediaQuery.of(context).viewInsets.bottom), child: Column( crossAxisAlignment: CrossAxisAlignment.start, children: [ Column( crossAxisAlignment: CrossAxisAlignment.start, children: [ 12.height, ListView.separated( shrinkWrap: true, itemCount: chatVM.offerRejectModelList.length, separatorBuilder: (BuildContext context, int index) => const Divider(thickness: 0.5), itemBuilder: (BuildContext context, int index) { OfferRequestCommentModel offerRequestCommentModel = chatVM.offerRejectModelList[index]; return CircleCheckBoxWithTitle( isChecked: offerRequestCommentModel.isSelected ?? false, title: '${offerRequestCommentModel.title}', onSelected: () { chatVM.updateSelectionInOfferRejectModelList(index); }, selectedColor: MyColors.darkPrimaryColor, ); }, ), if (chatVM.selectedOfferRequestCommentModel.index == chatVM.offerRejectModelList.length - 1) ...[ // comparing if the "other" is selected 12.height, TxtField( maxLines: 5, value: chatVM.rejectOfferDescription, errorValue: chatVM.rejectOfferDescriptionError, keyboardType: TextInputType.text, hint: LocaleKeys.description.tr(), onChanged: (v) => chatVM.updateRejectOfferDescription(v), ), ], ], ), 25.height, ShowFillButton( title: LocaleKeys.submit.tr(), onPressed: () async { String comments = ""; if (chatVM.selectedOfferRequestCommentModel.index == chatVM.offerRejectModelList.length - 1) //Other { comments = chatVM.rejectOfferDescription; } else { comments = chatVM.selectedOfferRequestCommentModel.title ?? ""; } if (!chatVM.isRejectOfferButtonValidated()) { return; } Navigator.pop(context); bool status = await context.read().onSendMessageForActionOnRequestOffer( receiverId: chatMessageModel.receiverUserID ?? "", chatMessageType: ChatMessageTypeEnum.offer, comments: comments, requestId: chatMessageModel.reqOffer!.requestID ?? -1, serviceProviderID: chatMessageModel.serviceProviderID ?? 0, requestOfferID: chatMessageModel.reqOffer!.id ?? -1, offerPrice: chatMessageModel.reqOffer!.price.toString(), requestOfferStatusEnum: RequestOfferStatusEnum.rejected, context: context, ); if (status) { chatMessageModel.reqOffer!.requestOfferStatusEnum = RequestOfferStatusEnum.rejected; setState(() {}); // Navigator.pop(context); Utils.showToast("Offer Rejected"); // navigateReplaceWithName(context, AppRoutes.dashboard); } }, maxWidth: double.infinity, ), 19.height, ], ), )); }); }, ); } void offerAcceptConfirmationBottomSheet({required ChatMessageModel chatMessageModel}) { return actionConfirmationBottomSheet( context: context, title: LocaleKeys.acceptOfferConfirmation.tr().toText(fontSize: 28, isBold: true, letterSpacing: -1.44), subtitle: LocaleKeys.acceptOfferConfirmationMessage.tr(), actionButtonYes: Expanded( child: ShowFillButton( maxHeight: 55, title: LocaleKeys.yes.tr(), fontSize: 15, onPressed: () async { log("ServiceProviderID: ${chatMessageModel.toString()}"); Navigator.pop(context); // int status = await context.read().onActionOfferTapped( // context: context, // requestOfferStatusEnum: RequestOfferStatusEnum.accepted, // reqOfferId: chatMessageModel.reqOfferID ?? -1, // comments: GlobalConsts.acceptingThisOffer, // ); bool status = await context.read().onSendMessageForActionOnRequestOffer( receiverId: chatMessageModel.receiverUserID ?? "", chatMessageType: ChatMessageTypeEnum.offer, comments: GlobalConsts.acceptingThisOffer, requestId: chatMessageModel.reqOffer!.requestID ?? -1, serviceProviderID: chatMessageModel.serviceProviderID ?? 0, requestOfferID: chatMessageModel.reqOffer!.id ?? -1, offerPrice: chatMessageModel.reqOffer!.price.toString(), requestOfferStatusEnum: RequestOfferStatusEnum.accepted, context: context, ); if (status) { chatMessageModel.reqOffer!.requestOfferStatusEnum = RequestOfferStatusEnum.accepted; setState(() {}); // Navigator.pop(context); Utils.showToast("Offer Accepted"); // navigateReplaceWithName(context, AppRoutes.dashboard); } }, ), ), actionButtonNo: Expanded( child: ShowFillButton( maxHeight: 55, isFilled: false, borderColor: MyColors.darkPrimaryColor, title: LocaleKeys.no.tr(), txtColor: MyColors.darkPrimaryColor, fontSize: 15, onPressed: () { Navigator.pop(context); }, ), ), ); } Widget buildOfferDetailsInChatMessage({required ChatMessageModel chatMessageModel, required BuildContext context}) { final requestOfferStatusEnum = chatMessageModel.reqOffer!.requestOfferStatusEnum ?? RequestOfferStatusEnum.offer; switch (requestOfferStatusEnum) { case RequestOfferStatusEnum.offer: return Column( children: [ 5.height, Row( crossAxisAlignment: CrossAxisAlignment.end, children: [ "${chatMessageModel.reqOffer!.price}".toText(fontSize: 19, isBold: true, color: AppState().currentAppType == AppType.provider ? MyColors.white : MyColors.darkTextColor), 5.width, LocaleKeys.sar.tr().toText(color: MyColors.lightTextColor, height: 2.2, fontSize: 10, isBold: true), ], ), if (widget.chatMessageModel.isMyMessage == false) ...[ 10.height, Row( children: [ Expanded( child: ShowFillButton( maxHeight: 27, title: LocaleKeys.accept.tr(), fontSize: 9, borderColor: MyColors.greenColor, isFilled: false, onPressed: () { offerAcceptConfirmationBottomSheet(chatMessageModel: chatMessageModel); }, backgroundColor: MyColors.white, txtColor: MyColors.greenColor, ), ), 20.width, Expanded( child: ShowFillButton( maxHeight: 27, title: LocaleKeys.reject.tr(), borderColor: MyColors.redColor, isFilled: false, backgroundColor: MyColors.white, txtColor: MyColors.redColor, fontSize: 9, onPressed: () async { buildRejectOfferBottomSheet(chatMessageModel: chatMessageModel); }, ), ) ], ), ], ], ); case RequestOfferStatusEnum.negotiate: return Column( children: [ Center( child: LocaleKeys.newOfferRequired.tr().toText( color: MyColors.adPendingStatusColor, fontSize: 12, isItalic: true, ), ).toContainer(borderRadius: 40, width: double.infinity, backgroundColor: MyColors.adPendingStatusColor.withOpacity(0.16)), ], ); case RequestOfferStatusEnum.accepted: return Column( children: [ Center( child: LocaleKeys.offerHasBeenAccepted.tr().toText( color: MyColors.adPendingStatusColor, fontSize: 12, isItalic: true, ), ).toContainer(borderRadius: 40, width: double.infinity, backgroundColor: MyColors.adPendingStatusColor.withOpacity(0.16)), ], ); case RequestOfferStatusEnum.rejected: return Column( children: [ Center( child: LocaleKeys.offerHasBeenRejected.tr().toText( color: MyColors.adPendingStatusColor, fontSize: 12, isItalic: true, ), ).toContainer(borderRadius: 40, width: double.infinity, backgroundColor: MyColors.adPendingStatusColor.withOpacity(0.16)), ], ); case RequestOfferStatusEnum.cancel: return Column( children: [ Center( child: LocaleKeys.offerHasBeenCancelled.tr().toText( color: MyColors.adPendingStatusColor, fontSize: 12, isItalic: true, ), ).toContainer(borderRadius: 40, width: double.infinity, backgroundColor: MyColors.adPendingStatusColor.withOpacity(0.16)), ], ); } } @override Widget build(BuildContext context) { return Directionality( textDirection: (widget.chatMessageModel.isMyMessage ?? false) ? TextDirection.rtl : TextDirection.ltr, child: Row( crossAxisAlignment: CrossAxisAlignment.start, children: [ Expanded( flex: 1, child: Image.asset( MyAssets.bnCar, width: 34, height: 34, fit: BoxFit.fill, ).toCircle(borderRadius: 100), ), 10.width, Expanded( flex: 10, child: Column( children: [ Row( mainAxisAlignment: MainAxisAlignment.spaceBetween, crossAxisAlignment: CrossAxisAlignment.start, children: [ ((widget.chatMessageModel.isMyMessage ?? false) ? "You" : widget.chatMessageModel.senderName ?? "").toText(fontSize: 16, isBold: true), ], ), 5.height, Column( children: [ Row( crossAxisAlignment: CrossAxisAlignment.start, children: [ Expanded( child: Directionality( textDirection: TextDirection.ltr, child: (widget.chatMessageModel.chatText ?? "").toText( color: (widget.chatMessageModel.isMyMessage ?? false) ? MyColors.white : MyColors.lightTextColor, fontSize: 12, // isBold: true, ), ), ), ], ), if (widget.chatMessageModel.chatMessageTypeEnum == ChatMessageTypeEnum.offer) ...[ buildOfferDetailsInChatMessage(chatMessageModel: widget.chatMessageModel, context: context), ], ], ).toContainer( isShadowEnabled: !(widget.chatMessageModel.isMyMessage ?? false), backgroundColor: (widget.chatMessageModel.isMyMessage ?? false) ? MyColors.darkIconColor : MyColors.white, borderRadius: 0, margin: EdgeInsets.fromLTRB((widget.chatMessageModel.isMyMessage ?? false) ? 25 : 0, 0, !(widget.chatMessageModel.isMyMessage ?? false) ? 25 : 0, 0), ), ], ), ) ], ), ); } }