import 'dart:async'; import 'dart:io'; import 'package:cached_network_image/cached_network_image.dart'; import 'package:ebroker/Ui/screens/ChatNew/MessageTypes/blueprint.dart'; import 'package:ebroker/Ui/screens/ChatNew/MessageTypes/registerar.dart'; import 'package:ebroker/Ui/screens/ChatNew/model.dart'; import 'package:ebroker/Ui/screens/widgets/blurred_dialoge_box.dart'; import 'package:ebroker/app/app.dart'; import 'package:ebroker/data/cubits/chatCubits/delete_message_cubit.dart'; import 'package:ebroker/utils/customHeroAnimation.dart'; import 'package:file_picker/file_picker.dart'; import 'package:flutter/material.dart'; import 'package:flutter_bloc/flutter_bloc.dart'; import 'package:flutter_svg/svg.dart'; import 'package:permission_handler/permission_handler.dart'; import '../../../app/default_app_setting.dart'; import '../../../app/routes.dart'; import '../../../data/Repositories/property_repository.dart'; import '../../../data/cubits/chatCubits/load_chat_messages.dart'; import '../../../data/cubits/chatCubits/send_message.dart'; import '../../../data/helper/widgets.dart'; import '../../../data/model/data_output.dart'; import '../../../data/model/property_model.dart'; import '../../../utils/AppIcon.dart'; import '../../../utils/Extensions/extensions.dart'; import '../../../utils/Notification/chat_message_handler.dart'; import '../../../utils/Notification/notification_service.dart'; import '../../../utils/constant.dart'; import '../../../utils/helper_utils.dart'; import '../../../utils/hive_utils.dart'; import '../../../utils/responsiveSize.dart'; import '../../../utils/ui_utils.dart'; import '../widgets/AnimatedRoutes/transparant_route.dart'; import 'chatAudio/widgets/chat_widget.dart'; import 'chatAudio/widgets/record_button.dart'; int totalMessageCount = 0; ValueNotifier showDeletebutton = ValueNotifier(false); ValueNotifier selectedMessageid = ValueNotifier(-5); ValueNotifier selectedRecieverId = ValueNotifier(-5); class ChatScreen extends StatefulWidget { final String? from; final String profilePicture; final String userName; final String propertyImage; final String proeprtyTitle; final String userId; //for which we are messageing final String propertyId; const ChatScreen( {super.key, required this.profilePicture, required this.userName, required this.propertyImage, required this.proeprtyTitle, required this.userId, required this.propertyId, this.from}); @override State createState() => _ChatScreenState(); } class _ChatScreenState extends State with SingleTickerProviderStateMixin { late final AnimationController _recordButtonAnimation = AnimationController( vsync: this, duration: const Duration( milliseconds: 500, ), ); TextEditingController controller = TextEditingController(); PlatformFile? messageAttachment; bool isFetchedFirstTime = false; double scrollPositionWhenLoadMore = 0; late Stream notificationStream = notificationPermission(); late StreamSubscription notificationStreamSubsctription; bool isNotificationPermissionGranted = true; ValueNotifier showRecordButton = ValueNotifier(true); late final ScrollController _pageScrollController = ScrollController() ..addListener( () { ContextMenuController.removeAny(); if (_pageScrollController.offset >= _pageScrollController.position.maxScrollExtent) { if (context.read().hasMoreChat()) { // setState(() {}); context.read().loadMore(); } } }, ); @override void initState() { Permission.storage.request(); context.read().load( userId: int.parse( widget.userId, ), propertyId: int.parse( widget.propertyId, ), ); currentlyChatPropertyId = widget.propertyId; currentlyChatingWith = widget.userId; notificationStreamSubsctription = notificationStream.listen((PermissionStatus permissionStatus) { isNotificationPermissionGranted = permissionStatus.isGranted; if (mounted) { // setState(() {}); } }); controller.addListener(() { if (controller.text.isNotEmpty) { showRecordButton.value = false; } else { showRecordButton.value = true; } }); super.initState(); } Stream notificationPermission() async* { while (true) { await Future.delayed(const Duration(seconds: 5)); yield* Permission.notification.request().asStream(); } } @override void dispose() { showRecordButton.dispose(); _recordButtonAnimation.dispose(); notificationStreamSubsctription.cancel(); super.dispose(); } List supportedImageTypes = [ 'jpeg', 'jpg', 'png', 'gif', 'webp', 'animated_webp', ]; String getSendMessageType( String? audio, dynamic attachment, String? message) { if (audio != null) { return "audio"; } else { if (attachment != null && (message != null)) { return "file_and_text"; } else if (attachment != null && message == null) { return "file"; } else { return "text"; } } } @override Widget build(BuildContext context) { var chatBackground = "assets/chat_background/light.svg"; var attachmentMIME = ""; if (messageAttachment != null) { attachmentMIME = (messageAttachment?.path?.split(".").last.toLowerCase()) ?? ""; } // return Container(); return WillPopScope( onWillPop: () async { currentlyChatingWith = ""; showDeletebutton.value = false; ChatMessageHandler.flush(); currentlyChatPropertyId = ""; notificationStreamSubsctription.cancel(); ChatMessageHandlerOLD.flushMessages(); return true; }, child: SafeArea( child: Scaffold( backgroundColor: context.color.backgroundColor, bottomNavigationBar: Padding( padding: EdgeInsets.only( bottom: MediaQuery.of(context).viewInsets.bottom, ), child: GestureDetector( behavior: HitTestBehavior.opaque, onTap: () {}, child: Column( mainAxisSize: MainAxisSize.min, children: [ if (messageAttachment != null) ...[ if (supportedImageTypes.contains(attachmentMIME)) ...[ Container( decoration: BoxDecoration( color: context.color.secondaryColor, border: Border.all( color: context.color.borderColor, width: 1.5)), child: Row( children: [ Padding( padding: const EdgeInsets.all(8.0), child: SizedBox( height: 100, width: 100, child: GestureDetector( onTap: () { UiUtils.showFullScreenImage(context, provider: FileImage(File( messageAttachment?.path ?? "", ))); }, child: Image.file( File( messageAttachment?.path ?? "", ), fit: BoxFit.cover, ), )), ), Column( crossAxisAlignment: CrossAxisAlignment.start, children: [ Text(messageAttachment?.name ?? ""), Text(HelperUtils.getFileSizeString( bytes: messageAttachment!.size, ).toString()), ], ) ], ), ) ] else ...[ Container( color: context.color.secondaryColor, child: Padding( padding: const EdgeInsets.symmetric(horizontal: 8.0), child: AttachmentMessage( url: messageAttachment!.path!, isSentByMe: true), ), ), ], const SizedBox( height: 10, ), ], BottomAppBar( padding: const EdgeInsetsDirectional.all(10), elevation: 5, color: context.color.secondaryColor, child: Directionality( textDirection: TextDirection.ltr, child: Row( children: [ Expanded( child: TextField( controller: controller, cursorColor: context.color.tertiaryColor, onTap: () { showDeletebutton.value = false; }, textInputAction: TextInputAction.newline, minLines: 1, maxLines: null, decoration: InputDecoration( suffixIconColor: context.color.textLightColor, suffixIcon: IconButton( onPressed: () async { if (messageAttachment == null) { FilePickerResult? pickedAttachment = await FilePicker.platform.pickFiles( allowMultiple: false, ); messageAttachment = pickedAttachment?.files.first; showRecordButton.value = false; setState(() {}); } else { messageAttachment = null; showRecordButton.value = true; setState(() {}); } }, icon: messageAttachment != null ? const Icon(Icons.close) : Transform.rotate( angle: -3.14 / 5.0, child: const Icon( Icons.attachment, ), ), ), contentPadding: const EdgeInsets.symmetric( vertical: 6, horizontal: 8), border: OutlineInputBorder( borderRadius: BorderRadius.circular(20), borderSide: BorderSide( color: context.color.tertiaryColor)), focusedBorder: OutlineInputBorder( borderRadius: BorderRadius.circular(20), borderSide: BorderSide( color: context.color.tertiaryColor)), hintText: UiUtils.translate( context, "writeHere", ), ), ), ), SizedBox( width: 9.5, ), ValueListenableBuilder( valueListenable: showRecordButton, builder: (context, bool show, Widget? child) { if (show == true) { return RecordButton( controller: _recordButtonAnimation, callback: (path) { if (Constant.isDemoModeOn) { HelperUtils.showSnackBarMessage( context, UiUtils.translate(context, "thisActionNotValidDemo")); return; } ChatMessageModel chatMessageModel = ChatMessageModel( message: controller.text, isSentByMe: true, audio: path, senderId: HiveUtils.getUserId() .toString(), id: DateTime.now().toString(), propertyId: widget.propertyId, receiverId: widget.userId, chatMessageType: getSendMessageType( path, messageAttachment, controller.text), date: DateTime.now().toString(), isSentNow: true); ChatMessageHandler.add( chatMessageModel, ); _pageScrollController.jumpTo( _pageScrollController.offset - 10); //This is adding Chat widget in stream with BlocProvider , because we will need to do api process to store chat message to server, when it will be added to list it's initState method will be called // ChatMessageHandlerOLD.add(BlocProvider( // create: (context) => SendMessageCubit(), // child: ChatMessage( // key: ValueKey( // DateTime.now().toString().toString()), // message: "[AUDIO]", // senderId: HiveUtils.getUserId().toString(), // propertyId: widget.propertyId, // reciverId: widget.userId, // time: DateTime.now().toString(), // hasAttachment: false, // isSentByMe: true, // isChatAudio: true, // isSentNow: true, // audioFile: path, // ), // )); totalMessageCount++; setState(() {}); }, isSending: false, ); } return GestureDetector( onTap: () { showDeletebutton.value = false; //if file is selected then user can send message without text if (controller.text.trim().isEmpty && messageAttachment == null) return; //This is adding Chat widget in stream with BlocProvider , because we will need to do api process to store chat message to server, when it will be added to list it's initState method will be called if (Constant.isDemoModeOn) { HelperUtils.showSnackBarMessage( context, UiUtils.translate(context, "thisActionNotValidDemo")); return; } ChatMessageModel chatMessageModel = ChatMessageModel( message: controller.text, isSentByMe: true, file: messageAttachment?.path, senderId: HiveUtils.getUserId() .toString(), id: DateTime.now().toString(), propertyId: widget.propertyId, receiverId: widget.userId, chatMessageType: getSendMessageType( null, messageAttachment, controller.text.isEmpty ? null : controller.text), date: DateTime.now().toString(), isSentNow: true); ChatMessageHandler.add(chatMessageModel); controller.text = ""; messageAttachment = null; // ChatMessageHandlerOLD.add( // BlocProvider( // key: ValueKey( // DateTime.now().toString().toString()), // create: (context) => SendMessageCubit(), // child: ChatMessage( // key: ValueKey( // DateTime.now().toString().toString()), // message: controller.text, // hasAttachment: messageAttachment != null, // senderId: // HiveUtils.getUserId().toString(), // propertyId: widget.propertyId, // reciverId: widget.userId, // time: DateTime.now().toString(), // isSentByMe: true, // isChatAudio: false, // isSentNow: true, // attachment: messageAttachment?.path, // ), // ), // ); totalMessageCount++; messageAttachment = null; if (mounted) setState(() {}); }, child: CircleAvatar( radius: 20, backgroundColor: context.color.tertiaryColor, child: Icon( Icons.send, color: context.color.buttonColor, ), ), ); }), ], ), ), ), ], ), ), ), appBar: AppBar( centerTitle: false, automaticallyImplyLeading: false, leading: FittedBox( fit: BoxFit.none, child: GestureDetector( onTap: () { Navigator.pop(context); }, child: Padding( padding: const EdgeInsets.only(left: 20.0), child: SvgPicture.asset( AppIcons.arrowLeft, color: context.color.tertiaryColor, height: 24, ), ), ), ), leadingWidth: 24, backgroundColor: context.color.secondaryColor, elevation: 0, iconTheme: IconThemeData(color: context.color.tertiaryColor), bottom: isNotificationPermissionGranted ? null : PreferredSize( preferredSize: const Size.fromHeight(25), child: FittedBox( fit: BoxFit.cover, child: Container( width: context.screenWidth, color: const Color.fromARGB(255, 151, 151, 151), child: Padding( padding: const EdgeInsets.all(8.0), child: Text("turnOnNotification".translate(context)), ), ), ), ), actions: [ ValueListenableBuilder( valueListenable: showDeletebutton, builder: (context, value, child) { if (value == false) return const SizedBox.shrink(); return IconButton( onPressed: () { UiUtils.showBlurredDialoge( context, dialoge: BlurredDialogBox( onAccept: () async { context.read().delete( (selectedMessageid.value), receiverId: selectedRecieverId.value); showDeletebutton.value = false; }, title: "areYouSure".translate(context), content: Text( "msgWillNotRecover".translate(context), ), ), ); }, icon: SvgPicture.asset( AppIcons.delete, color: context.color.tertiaryColor, )); }, ), if (widget.from != "property") FittedBox( fit: BoxFit.none, child: GestureDetector( onTap: () async { try { Widgets.showLoader(context); PropertyRepository fetch = PropertyRepository(); DataOutput dataOutput = await fetch .fetchPropertyFromPropertyId(widget.propertyId); Future.delayed( Duration.zero, () { Widgets.hideLoder(context); HelperUtils.goToNextPage( Routes.propertyDetails, context, false, args: { 'propertyData': dataOutput.modelList[0], 'propertiesList': dataOutput.modelList, 'fromMyProperty': false, }); }, ); } catch (e) { Widgets.hideLoder(context); } }, child: ClipRRect( borderRadius: BorderRadius.circular(8), child: SizedBox( width: 40, height: 40, child: Image.network( widget.propertyImage, fit: BoxFit.cover, ), ), ), ), ), const SizedBox( width: 18, ), ], title: FittedBox( fit: BoxFit.none, child: Row( children: [ widget.profilePicture == "" ? CircleAvatar( backgroundColor: context.color.tertiaryColor, child: LoadAppSettings().svg( appSettings.placeholderLogo!, color: context.color.buttonColor, ), ) : GestureDetector( onTap: () { Navigator.push( context, TransparantRoute( barrierDismiss: true, builder: (context) { return GestureDetector( onTap: () { Navigator.pop(context); }, child: Container( color: const Color.fromARGB(69, 0, 0, 0), ), ); }, ), ); }, child: CustomImageHeroAnimation( type: CImageType.Network, image: widget.profilePicture, child: CircleAvatar( backgroundImage: CachedNetworkImageProvider( widget.profilePicture, ), ), ), ), const SizedBox( width: 10, ), Column( crossAxisAlignment: CrossAxisAlignment.start, children: [ SizedBox( width: context.screenWidth * 0.35, child: Text(widget.userName) .color(context.color.textColorDark) .size(context.font.normal), ), SizedBox( width: context.screenWidth * 0.35, child: Text(widget.proeprtyTitle) .size(context.font.small) .color(context.color.textColorDark), ), ], ) ], ), ), ), body: Stack( children: [ SvgPicture.asset( chatBackground, height: MediaQuery.of(context).size.height, fit: BoxFit.cover, width: MediaQuery.of(context).size.width, ), BlocListener( listener: (context, state) { if (state is DeleteMessageSuccess) { ChatMessageHandlerOLD.removeMessage(state.id); showDeletebutton.value = false; } }, child: GestureDetector( onTap: () { showDeletebutton.value = false; }, child: BlocConsumer( listener: (context, state) { if (state is LoadChatMessagesSuccess) { ChatMessageHandler.fillMessages(state.messages); // ChatMessageHandlerOLD.loadMessages( // state.messages, context); totalMessageCount = state.messages.length; isFetchedFirstTime = true; setState(() {}); } if (state is LoadChatMessagesFailed) {} }, builder: (context, state) { return Stack( children: [ Column( children: [ if (state is LoadChatMessagesSuccess) ...{ if (state.isLoadingMore) ...{ Center( child: SizedBox( width: 20, height: 20, child: UiUtils.progress()), ), } }, Expanded( child: Padding( padding: const EdgeInsets.symmetric( horizontal: 18), child: StreamBuilder( stream: ChatMessageHandler.listenMessages(), builder: (context, snapshot) { if (snapshot.connectionState == ConnectionState.active) { return SizedBox( height: context.screenHeight, child: ListView.builder( reverse: true, shrinkWrap: true, addAutomaticKeepAlives: true, controller: _pageScrollController, physics: const BouncingScrollPhysics(), itemBuilder: (context, index) { List messageList = snapshot.data!; messageList = messageList.toList(); DateTime? currentDate = messageList[index] .message ?.date ?.parseAsDate(); DateTime? nextDate; if (messageList.length > index + 1) { nextDate = messageList[index + 1] .message ?.date ?.parseAsDate(); } Widget dateChip = getDateChip( currentDate!, nextDate); if (index == messageList.length - 1) { dateChip = Padding( padding: const EdgeInsets.all( 10.0), child: Text(currentDate .toString() .formatDate()), ); } return MultiBlocProvider( providers: [ BlocProvider( create: (context) => SendMessageCubit(), ), BlocProvider( create: (context) => DeleteMessageCubit(), ), ], child: Column( children: [ dateChip, RenderMessage( key: Key( messageList[index] .id), message: messageList[index]), ], ), ); }, // separatorBuilder: (context, index) { // List messageList = // snapshot.data!; // messageList = messageList.toList(); // // String? currentMessageDate = // messageList[index].message!.date; // // String? nextMessageDate = // messageList[index + 1].message!.date; // // DateTime current = // DateTime.parse(currentMessageDate!); // DateTime next = // DateTime.parse(nextMessageDate!); // if (index == 4) { // next = DateTime.parse(nextMessageDate!) // .add(Duration(days: 2)); // } // bool sameDate = isSameDate(current, next); // // if (sameDate == true) { // return Center( // child: Text(messageList[index] // .message! // .date! + // "($sameDate)")); // } // return SizedBox.shrink(); // }, itemCount: snapshot.data!.length, ), ); } return Container(); }, ), ), ), ], ), // StreamBuilder( // stream: ChatMessageHandler.getChatStream(), // builder: (context, AsyncSnapshot snapshot) { // Widget? loadingMoreWidget; // if (state is LoadChatMessagesSuccess) { // if (state.isLoadingMore) { // loadingMoreWidget = Text( // UiUtils.getTranslatedLabel( // context, "loading")); // } // } // // if (snapshot.connectionState == // ConnectionState.active || // snapshot.connectionState == // ConnectionState.done) { // return Column( // children: [ // loadingMoreWidget ?? // const SizedBox.shrink(), // Expanded( // child: ListView.builder( // reverse: true, // shrinkWrap: true, // physics: // const BouncingScrollPhysics(), // addAutomaticKeepAlives: true, // controller: _pageScrollController, // itemCount: snapshot.data.length, // padding: // const EdgeInsets.only(bottom: 10), // itemBuilder: (context, index) { // dynamic chat = // (snapshot.data as List) // .elementAt(index); // // return chat; // }, // ), // ), // ], // ); // } // // return Container(); // }), if ((state is LoadChatMessagesInProgress)) Center( child: UiUtils.progress(), ) ], ); }, ), ), ), ], ), ), ), ); } } Widget getDateChip(DateTime currentDate, DateTime? nextDate) { if (nextDate == null) { return const SizedBox.shrink(); } bool sameDate = currentDate.isSameDate(nextDate); if (sameDate == false) { return Padding( padding: const EdgeInsets.all(10.0), child: Text(currentDate.toString().formatDate()).size(15), ); } return const SizedBox.shrink(); } class ChatInfoWidget extends StatelessWidget { final String propertyTitleImage; final String propertyTitle; final String propertyId; const ChatInfoWidget( {super.key, required this.propertyTitleImage, required this.propertyTitle, required this.propertyId}); @override Widget build(BuildContext context) { return Scaffold( appBar: AppBar( backgroundColor: Colors.transparent, elevation: 0, iconTheme: IconThemeData(color: context.color.tertiaryColor), ), backgroundColor: Colors.transparent, body: Padding( padding: const EdgeInsets.all(16.0), child: Column( mainAxisAlignment: MainAxisAlignment.center, children: [ Container( height: context.screenHeight * 0.46, decoration: BoxDecoration( color: context.color.secondaryColor, borderRadius: BorderRadius.circular(10)), width: context.screenWidth, child: Column( mainAxisSize: MainAxisSize.min, children: [ ClipRRect( borderRadius: BorderRadius.circular(10), child: GestureDetector( onTap: () { UiUtils.showFullScreenImage(context, provider: CachedNetworkImageProvider(propertyTitleImage)); }, child: CachedNetworkImage( imageUrl: propertyTitleImage, width: context.screenWidth, fit: BoxFit.cover, height: context.screenHeight * 0.3, ), ), ), Padding( padding: const EdgeInsets.all(8.0), child: Align( alignment: Alignment.centerLeft, child: Text(propertyTitle) .setMaxLines( lines: 2, ) .size( context.font.larger.rf( context, ), ), ), ), const Spacer(), Padding( padding: const EdgeInsets.all(8.0), child: FittedBox( fit: BoxFit.none, child: UiUtils.buildButton(context, onPressed: () async { try { Widgets.showLoader(context); PropertyRepository fetch = PropertyRepository(); DataOutput dataOutput = await fetch .fetchPropertyFromPropertyId(propertyId); Future.delayed( Duration.zero, () { Widgets.hideLoder(context); HelperUtils.goToNextPage( Routes.propertyDetails, context, false, args: { 'propertyData': dataOutput.modelList[0], 'propertiesList': dataOutput.modelList, 'fromMyProperty': false, }); }, ); } catch (e) { Widgets.hideLoder(context); } }, buttonTitle: UiUtils.translate(context, "viewProperty"), width: context.screenWidth * 0.5, fontSize: context.font.normal, height: 40), ), ) ], ), ) ], ), ), ); } }