import 'dart:developer'; import 'package:cached_network_image/cached_network_image.dart'; 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/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/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/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 ChatMessageCustomWidget extends StatefulWidget { final ChatMessageModel chatMessageModel; final RequestStatusEnum requestStatusEnum; const ChatMessageCustomWidget({super.key, required this.chatMessageModel, required this.requestStatusEnum}); @override State createState() => _ChatMessageCustomWidgetState(); } class _ChatMessageCustomWidgetState extends State { Future buildRejectOrCancelOfferBottomSheet({required ChatMessageModel chatMessageModel, required RequestOfferStatusEnum requestOfferStatusEnum}) { return showModalBottomSheet( context: context, isScrollControlled: true, enableDrag: true, builder: (BuildContext context) { return Consumer(builder: (BuildContext context, ChatVM chatVM, Widget? child) { return InfoBottomSheet( title: LocaleKeys.pleaseSpecify.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) { if (chatVM.offerRejectModelList[index].index == 3 && requestOfferStatusEnum == RequestOfferStatusEnum.rejected) { return const SizedBox(); } return const Divider(thickness: 0.5); }, itemBuilder: (BuildContext context, int index) { if (chatVM.offerRejectModelList[index].index == 3 && requestOfferStatusEnum == RequestOfferStatusEnum.rejected) { return const SizedBox(); } 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 chatVM.onSendMessageForActionOnRequestOffer( receiverId: (chatMessageModel.isMyMessage ?? false) ? chatMessageModel.receiverUserID ?? "" : chatMessageModel.senderUserID ?? "", chatMessageType: ChatMessageTypeEnum.offer, comments: comments, requestId: chatMessageModel.reqOffer!.requestID ?? -1, serviceProviderID: chatMessageModel.serviceProviderID ?? 0, requestOfferID: chatMessageModel.reqOffer!.id ?? -1, offerPrice: chatMessageModel.reqOffer!.price.toString(), serviceItemName: chatMessageModel.reqOffer!.serviceItemName ?? "", manufacturedOn: chatMessageModel.reqOffer!.manufacturedOn ?? "", manufacturedById: chatMessageModel.reqOffer!.manufacturedById ?? 0, requestOfferStatusEnum: requestOfferStatusEnum, context: context, ); if (status) { chatMessageModel.reqOffer!.requestOfferStatusEnum = requestOfferStatusEnum; setState(() {}); // Navigator.pop(context); chatVM.updateRejectOfferDescription(''); Utils.showToast("Offer ${requestOfferStatusEnum == RequestOfferStatusEnum.rejected ? "Rejected" : "Cancelled"}"); // 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 { Navigator.pop(context); bool status = await context.read().onSendMessageForActionOnRequestOffer( receiverId: chatMessageModel.senderUserID ?? "", 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(), serviceItemName: chatMessageModel.reqOffer!.serviceItemName ?? "", manufacturedOn: chatMessageModel.reqOffer!.manufacturedOn ?? "", manufacturedById: chatMessageModel.reqOffer!.manufacturedById ?? 0, requestOfferStatusEnum: RequestOfferStatusEnum.accepted, context: context, ); if (status) { final requestVM = context.read(); chatMessageModel.reqOffer!.requestOfferStatusEnum = RequestOfferStatusEnum.accepted; requestVM.currentSelectedRequest!.requestStatus = RequestStatusEnum.inProgress; requestVM.updateAcceptedReqOffer(chatMessageModel.reqOffer!); requestVM.updateAcceptedRequestOfferProviderName(chatMessageModel.senderName ?? ""); 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 RequestStatusEnum requestStatusEnum, required ChatMessageModel chatMessageModel, required BuildContext context}) { final requestOfferStatusEnum = chatMessageModel.reqOffer!.requestOfferStatusEnum ?? RequestOfferStatusEnum.offer; if (requestStatusEnum != RequestStatusEnum.submitted && requestOfferStatusEnum != RequestOfferStatusEnum.accepted) { return Column( children: [ Row( crossAxisAlignment: CrossAxisAlignment.end, children: [ "${(chatMessageModel.reqOffer!.price ?? 0.0).toInt()}".toText(fontSize: 19, isBold: true, color: chatMessageModel.isMyMessage! ? MyColors.white : MyColors.darkTextColor), 5.width, LocaleKeys.sar.tr().toText(color: MyColors.lightTextColor, height: 2.2, fontSize: 10, isBold: true), ], ), 5.height, Center( child: LocaleKeys.offerNoLongerAvailable.tr().toText( color: chatMessageModel.isMyMessage! ? MyColors.adPendingStatusColor : MyColors.lightTextColor, fontSize: 12, isItalic: true, ), ).toContainer(borderRadius: 40, width: double.infinity, backgroundColor: chatMessageModel.isMyMessage! ? MyColors.adPendingStatusColor.withOpacity(0.16) : MyColors.grey98Color.withOpacity(0.1)), ], ); } switch (requestOfferStatusEnum) { case RequestOfferStatusEnum.offer: return Column( children: [ 5.height, Row( crossAxisAlignment: CrossAxisAlignment.end, children: [ "${(chatMessageModel.reqOffer!.price ?? 0.0).toInt()}".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: () { buildRejectOrCancelOfferBottomSheet(chatMessageModel: chatMessageModel, requestOfferStatusEnum: RequestOfferStatusEnum.rejected); }, ), ) ], ), ] else if (requestStatusEnum == RequestStatusEnum.submitted) ...[ 10.height, Row( children: [ Expanded( child: ShowFillButton( maxHeight: 27, title: LocaleKeys.cancelOffer.tr(), fontSize: 9, borderColor: MyColors.lightTextColor, isFilled: false, onPressed: () { buildRejectOrCancelOfferBottomSheet(chatMessageModel: chatMessageModel, requestOfferStatusEnum: RequestOfferStatusEnum.cancel); }, backgroundColor: MyColors.white, txtColor: MyColors.lightTextColor, ), ), ], ), ], ], ); 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: [ Row( crossAxisAlignment: CrossAxisAlignment.end, children: [ "${(chatMessageModel.reqOffer!.price ?? 0.0).toInt()}".toText(fontSize: 19, isBold: true, color: chatMessageModel.isMyMessage! ? MyColors.white : MyColors.darkTextColor), 5.width, LocaleKeys.sar.tr().toText(color: MyColors.lightTextColor, height: 2.2, fontSize: 10, isBold: true), ], ), 5.height, 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: [ Row( crossAxisAlignment: CrossAxisAlignment.end, children: [ "${(chatMessageModel.reqOffer!.price ?? 0.0).toInt()}".toText(fontSize: 19, isBold: true, color: chatMessageModel.isMyMessage! ? MyColors.white : MyColors.darkTextColor), 5.width, LocaleKeys.sar.tr().toText(color: MyColors.lightTextColor, height: 2.2, fontSize: 10, isBold: true), ], ), 5.height, 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: [ Row( crossAxisAlignment: CrossAxisAlignment.end, children: [ "${(chatMessageModel.reqOffer!.price ?? 0.0).toInt()}".toText(fontSize: 19, isBold: true, color: chatMessageModel.isMyMessage! ? MyColors.white : MyColors.darkTextColor), 5.width, LocaleKeys.sar.tr().toText(color: MyColors.lightTextColor, height: 2.2, fontSize: 10, isBold: true), ], ), 5.height, 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)), ], ); } } Widget getProfilePicture({String profileImageUrl = ""}) { Widget widget = const SizedBox(); if (profileImageUrl.isEmpty && AppState().getUser.data!.userInfo!.userLocalImage != null) { widget = Image.file( AppState().getUser.data!.userInfo!.userLocalImage!, width: 34, height: 34, fit: BoxFit.fill, ); } else if (profileImageUrl.isEmpty && AppState().getUser.data!.userInfo!.userImageUrl != null) { widget = CachedNetworkImage( imageUrl: AppState().getUser.data!.userInfo!.userImageUrl, imageBuilder: (context, imageProvider) => Container( decoration: BoxDecoration( image: DecorationImage( image: imageProvider, fit: BoxFit.cover, ), ), ), placeholder: (context, url) => const Center(child: CircularProgressIndicator()), errorWidget: (context, url, error) => const Icon(Icons.supervised_user_circle_outlined), fadeInCurve: Curves.easeIn, width: 34, height: 34, fit: BoxFit.fill, fadeInDuration: const Duration(milliseconds: 1000), useOldImageOnUrlChange: false); } return widget.toCircle(borderRadius: 100); } Widget buildFreeTextDetailsInMessage() { return 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, ), ), ), ], ); } Widget buildImageGridWidget( {required List messagesImages, double gridItemSize = 50.0, // Default fixed size for each grid item double spacing = 8.0, double borderWidth = 1, Color borderColor = MyColors.textColor}) { int imageCount = messagesImages.length; if (imageCount == 1) { return Container( width: double.infinity, height: (gridItemSize * 4), decoration: BoxDecoration( border: Border.all(width: borderWidth, color: borderColor), borderRadius: BorderRadius.circular(8), ), child: ClipRRect( borderRadius: BorderRadius.circular(8), child: (messagesImages[0].isFromNetwork ?? false) ? messagesImages[0].imageUrl.buildNetworkImage( fit: BoxFit.cover, ) : messagesImages[0].imagePath.buildFileImage(fit: BoxFit.cover), ), ); } return SizedBox( // height: (gridItemSize * 2) + (spacing * 2), // Fixed height for 2 rows including spacing child: GridView.builder( physics: const NeverScrollableScrollPhysics(), // Prevent scrolling inside grid shrinkWrap: true, // Shrink size to fit the content gridDelegate: SliverGridDelegateWithFixedCrossAxisCount( crossAxisCount: 2, // Show 2 images per row crossAxisSpacing: spacing, mainAxisSpacing: spacing, childAspectRatio: 1, // Keep items square ), itemCount: imageCount > 4 ? 4 : imageCount, itemBuilder: (context, index) { if (index == 3 && imageCount > 4) { return Stack( fit: StackFit.expand, children: [ (messagesImages[0].isFromNetwork ?? false) ? messagesImages[index].imageUrl.buildNetworkImage( fit: BoxFit.cover, width: gridItemSize, height: gridItemSize, ) : messagesImages[index].imagePath.buildFileImage( fit: BoxFit.cover, width: gridItemSize, height: gridItemSize, ), Container( decoration: BoxDecoration( color: Colors.black.withOpacity(0.5), border: Border.all(width: borderWidth, color: borderColor), borderRadius: BorderRadius.circular(8), // Optional: Rounded corners ), child: Center( child: '+${imageCount - 4}'.toText(fontSize: 30, color: MyColors.white, fontWeight: MyFonts.Medium), ), ), ], ); } else { return Container( width: gridItemSize, height: gridItemSize, decoration: BoxDecoration( border: Border.all(width: borderWidth, color: borderColor), borderRadius: BorderRadius.circular(8), // Optional: Rounded corners ), child: ClipRRect( borderRadius: BorderRadius.circular(8), // Apply same radius to image child: messagesImages[index].imageUrl.buildNetworkImage( fit: BoxFit.cover, width: gridItemSize, height: gridItemSize, ), ), ); } }, ), ); } Widget messageWidgetBasedOnType({required ChatMessageTypeEnum? chatMessageTypeEnum}) { Widget messageTypeWidget = const SizedBox(); if (chatMessageTypeEnum == null) { return const SizedBox(); } switch (chatMessageTypeEnum) { case ChatMessageTypeEnum.freeText: messageTypeWidget = Column(children: [buildFreeTextDetailsInMessage(), 10.height]); break; case ChatMessageTypeEnum.image: messageTypeWidget = buildImageGridWidget(messagesImages: widget.chatMessageModel.messageImages ?? []); break; case ChatMessageTypeEnum.audio: case ChatMessageTypeEnum.video: case ChatMessageTypeEnum.file: case ChatMessageTypeEnum.offer: messageTypeWidget = Column( children: [ buildFreeTextDetailsInMessage(), 10.height, buildOfferDetailsInChatMessage(requestStatusEnum: widget.requestStatusEnum, chatMessageModel: widget.chatMessageModel, context: context), ], ); break; } return messageTypeWidget.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), ); } @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: (widget.chatMessageModel.isMyMessage ?? false) ? getProfilePicture() : Container( width: 34, height: 34, alignment: Alignment.center, color: MyColors.darkTextColor, child: ((widget.chatMessageModel.senderName ?? "").getInitials()).toText(color: MyColors.white, isBold: true, fontSize: 12), ).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, messageWidgetBasedOnType(chatMessageTypeEnum: widget.chatMessageModel.chatMessageTypeEnum), ], ), ) ], ), ); } }