// ignore_for_file: invalid_use_of_protected_member, invalid_use_of_visible_for_testing_member import 'dart:io'; import 'package:any_link_preview/any_link_preview.dart'; import 'package:audioplayers/audioplayers.dart'; import 'package:dio/dio.dart'; import 'package:ebroker/Ui/screens/chat/chat_screen.dart'; import 'package:ebroker/app/app_theme.dart'; import 'package:flutter/foundation.dart'; import 'package:flutter/gestures.dart'; import 'package:flutter/material.dart'; import 'package:flutter_bloc/flutter_bloc.dart'; import 'package:open_filex/open_filex.dart'; import 'package:path_provider/path_provider.dart'; import 'package:permission_handler/permission_handler.dart'; import 'package:url_launcher/url_launcher.dart'; import '../../../../../data/cubits/chatCubits/send_message.dart'; import '../../../../../data/cubits/system/app_theme_cubit.dart'; import '../../../../../utils/Extensions/extensions.dart'; import '../../../../../utils/Notification/chat_message_handler.dart'; import '../../../../../utils/helper_utils.dart'; import '../../../../../utils/hive_utils.dart'; import '../../../../../utils/ui_utils.dart'; part "parts/attachment.part.dart"; part "parts/linkpreview.part.dart"; part "parts/recordmsg.part.dart"; ////Please don't make chaneges without sufficent knowledege in this file. otherwise you will be responsable for it /// //This will store and ensure that msg is already sent so we don't have to send it again Set sentMessages = {}; class ChatMessage extends StatefulWidget { final String message; final String senderId; final bool isSentByMe; final bool? isSentNow; final String propertyId; final String reciverId; final bool isChatAudio; final bool hasAttachment; final dynamic audioFile; final String time; final dynamic attachment; final Function(int id)? onHold; const ChatMessage( {super.key, this.isSentNow, required this.message, required this.isSentByMe, required this.isChatAudio, this.audioFile, this.attachment, required this.senderId, required this.time, required this.hasAttachment, required this.propertyId, required this.reciverId, this.onHold}); @override State createState() => ChatMessageState(); Map toMap() { Map data = {}; data['key'] = key; data['message'] = message; data['isSentNow'] = isSentNow; data['isSentByMe'] = isSentByMe; data['isChatAudio'] = isChatAudio; data['senderId'] = senderId; data['propertyId'] = propertyId; data['reciverId'] = reciverId; data['hasAttachment'] = hasAttachment; data['audioFile'] = audioFile; data['time'] = time; data['attachment'] = attachment; return data; } factory ChatMessage.fromMap(Map json) { var chat = ChatMessage( key: json['key'], message: json['message'], isSentByMe: json['isSentByMe'], isChatAudio: json['isChatAudio'], senderId: json['senderId'], audioFile: json['audioFile'], attachment: json['attachment'], time: json['time'], hasAttachment: json['hasAttachment'], propertyId: json['propertyId'], reciverId: json['reciverId']); return chat; } } class ChatMessageState extends State with AutomaticKeepAliveClientMixin { bool isChatSent = false; bool selectedMessage = false; static bool isMounted = false; String? link; final ValueNotifier _linkAddNotifier = ValueNotifier(""); @override void initState() { ///isSentNow is for check if we are not appending messages multiple time if (widget.isSentByMe && (widget.isSentNow == true) && isChatSent == false) { if (!sentMessages.contains(widget.key)) { context.read().send( senderId: HiveUtils.getUserId().toString(), recieverId: widget.reciverId, attachment: widget.attachment, message: widget.message, proeprtyId: widget.propertyId, audio: widget.audioFile, ); } sentMessages.add(widget.key); isMounted = true; } super.initState(); } String _emptyTextIfAttachmentHasNoText() { if (widget.hasAttachment) { if (widget.message == "[File]") { return ""; } else { return widget.message; } } else { return widget.message; } } bool _isLink(String input) { ///This will check if text contains link final matcher = RegExp( r"(http(s)?:\/\/.)?(www\.)?[-a-zA-Z0-9@:%._\+~#=]{2,256}\.[a-z]{2,6}\b([-a-zA-Z0-9@:%_\+.~#?&//=]*)"); return matcher.hasMatch(input); } List _replaceLink() { //This function will make part of text where link starts. we put invisible charector so we can split it with it final linkPattern = RegExp( r"(http(s)?:\/\/.)?(www\.)?[-a-zA-Z0-9@:%._\+~#=]{2,256}\.[a-z]{2,6}\b([-a-zA-Z0-9@:%_\+.~#?&//=]*)"); ///This is invisible charector [You can replace it with any special chareactor which generally nobody use] const String substringIdentifier = "‎"; ///This will find and add invisible charector in prefix and suffix String splitMapJoin = _emptyTextIfAttachmentHasNoText().splitMapJoin( linkPattern, onMatch: (match) { return substringIdentifier + match.group(0)! + substringIdentifier; }, onNonMatch: (match) { return match; }, ); //finally we split it with invisible charector so it will become list return splitMapJoin.split(substringIdentifier); } List _matchAstric(String data) { var pattern = RegExp(r"\*(.*?)\*"); String mapJoin = data.splitMapJoin( pattern, onMatch: (p0) { return "‎${p0.group(0)!}‎"; }, onNonMatch: (p0) { return p0; }, ); return mapJoin.split("‎"); } @override Widget build(BuildContext context) { super.build(context); bool isDark = context.watch().state.appTheme == AppTheme.dark; return GestureDetector( onLongPress: () { selectedMessageid.value = (widget.key as ValueKey).value; selectedRecieverId.value = int.parse(widget.reciverId); showDeletebutton.value = true; }, onTap: () { selectedMessage = false; }, child: Padding( padding: const EdgeInsets.symmetric(vertical: 7), child: Container( alignment: widget.isSentByMe ? Alignment.centerRight : Alignment.centerLeft, width: MediaQuery.of(context).size.width, margin: EdgeInsets.only( // top: MediaQuery.of(context).size.height * 0.007, right: widget.isSentByMe ? 20 : 0, left: widget.isSentByMe ? 0 : 20, ), child: Column( crossAxisAlignment: widget.isSentByMe ? CrossAxisAlignment.end : CrossAxisAlignment.start, children: [ Container( constraints: BoxConstraints(maxWidth: context.screenWidth * 0.74), decoration: BoxDecoration( color: selectedMessage == true ? (widget.isSentByMe == true ? context.color.tertiaryColor.darken(45) : context.color.secondaryColor.darken(45)) : (widget.isSentByMe ? const Color(0xffEEEEEE) : context.color.secondaryColor), borderRadius: BorderRadius.circular(8) // BorderRadius.only( // topRight: widget.isSentByMe // ? Radius.zero // : const Radius.circular(10), // topLeft: widget.isSentByMe // ? const Radius.circular(10) // : Radius.zero, // bottomLeft: const Radius.circular(10), // bottomRight: const Radius.circular(10), // ), ), child: Wrap( runAlignment: WrapAlignment.end, alignment: WrapAlignment.end, crossAxisAlignment: WrapCrossAlignment.end, children: [ Padding( padding: const EdgeInsets.all(12), child: Container( child: widget.isChatAudio ? RecordMessage( url: widget.audioFile ?? "", isSentByMe: widget.isSentByMe, ) : Column( mainAxisSize: MainAxisSize.min, crossAxisAlignment: CrossAxisAlignment.start, children: [ if (widget.hasAttachment) AttachmentMessage( url: widget.attachment, isSentByMe: widget.isSentByMe), //This is preview builder for image ValueListenableBuilder( valueListenable: _linkAddNotifier, builder: (context, dynamic value, c) { if (value == null) { return const SizedBox.shrink(); } return FutureBuilder( future: AnyLinkPreview.getMetadata( link: value), builder: (context, AsyncSnapshot snapshot) { if (snapshot.connectionState == ConnectionState.done) { if (snapshot.data == null) { return const SizedBox.shrink(); } return LinkPreviw( snapshot: snapshot, link: value, ); } return const SizedBox.shrink(); }, ); }), SelectableText.rich( TextSpan( style: TextStyle( color: (isDark && !widget.isSentByMe) ? context.color.buttonColor : Colors.black), children: _replaceLink().map((data) { //This will add link to msg if (_isLink(data)) { //This will notify priview object that it has link _linkAddNotifier.value = data; _linkAddNotifier.notifyListeners(); return TextSpan( text: data, recognizer: TapGestureRecognizer() ..onTap = () async { await launchUrl( Uri.parse(data)); }, style: TextStyle( decoration: TextDecoration.underline, color: Colors.blue[800])); } //This will make text bold return TextSpan( text: "", children: _matchAstric(data).map((text) { if (text .toString() .startsWith("*") && text.toString().endsWith("*")) { return TextSpan( text: text.replaceAll("*", ""), style: TextStyle( color: (isDark && !widget .isSentByMe) ? context .color.buttonColor : Colors.black, fontWeight: FontWeight.w800)); } return TextSpan( text: text, style: TextStyle( color: (isDark && !widget.isSentByMe) ? context .color.buttonColor : Colors.black)); }).toList(), style: TextStyle( color: widget.isSentByMe ? context.color.secondaryColor : context .color.textColorDark), ); }).toList(), ), style: TextStyle( color: (isDark && !widget.isSentByMe) ? context.color.buttonColor : Colors.black), ), ], ), ), ), if (widget.isSentByMe && widget.isSentNow == true) ...[ BlocConsumer( listener: (context, state) { if (state is SendMessageSuccess) { isChatSent = true; ///Value which we added locally ValueKey? uniqueIdentifier = widget.key as ValueKey; ////We were added local id so whenit completed we will replace it with server message id ChatMessageHandlerOLD.updateMessageId( uniqueIdentifier.value, state.messageId); WidgetsBinding.instance .addPostFrameCallback((timeStamp) { if (mounted) setState(() {}); }); } }, builder: (context, state) { if (state is SendMessageInProgress) { return Padding( padding: const EdgeInsets.only(right: 5.0, bottom: 2), child: Icon( Icons.watch_later_outlined, size: context.font.smaller, color: Colors.black, ), ); } if (state is SendMessageFailed) { return Padding( padding: const EdgeInsets.only(right: 5.0, bottom: 2), child: Icon( Icons.error, size: context.font.smaller, color: Colors.black, ), ); } return const SizedBox.shrink(); }, ) ] ], ), ), const SizedBox( height: 5, ), Padding( padding: const EdgeInsets.only(right: 3.0), child: Text( (DateTime.parse(widget.time)) .toLocal() .toIso8601String() .toString() .formatDate(format: "hh:mm aa"), // style: TextStyle( color: widget.isSentByMe ? context.color.textLightColor : context.color.textLightColor), ).size(context.font.smaller), ), ], ), ), ), ); } @override bool get wantKeepAlive => true; }