import 'dart:convert'; import 'dart:io'; import 'package:audio_waveforms/audio_waveforms.dart'; import 'package:cached_network_image/cached_network_image.dart'; import 'package:flutter/cupertino.dart'; import 'package:flutter/material.dart'; import 'package:provider/provider.dart'; import 'package:test_sa/extensions/context_extension.dart'; import 'package:test_sa/extensions/int_extensions.dart'; import 'package:test_sa/extensions/string_extensions.dart'; import 'package:test_sa/extensions/text_extensions.dart'; import 'package:test_sa/extensions/widget_extensions.dart'; import 'package:test_sa/helper/utils.dart'; import 'package:test_sa/models/service_request/service_request.dart'; import 'package:test_sa/modules/cm_module/cm_detail_provider.dart'; import 'package:test_sa/modules/cm_module/views/components/action_button/footer_action_button.dart'; import 'package:test_sa/modules/cx_module/chat/chat_api_client.dart'; import 'package:test_sa/modules/cx_module/chat/chat_provider.dart'; import 'package:test_sa/modules/cx_module/chat/view_all_attachment_page.dart'; import 'package:test_sa/new_views/app_style/app_color.dart'; import 'package:test_sa/new_views/common_widgets/app_filled_button.dart'; import 'package:test_sa/new_views/common_widgets/default_app_bar.dart'; import 'package:test_sa/views/widgets/sound/sound_player.dart'; import 'helper/chat_audio_player.dart'; import 'helper/chat_file_picker.dart'; import 'helper/chat_file_viewer.dart'; import 'model/get_single_user_chat_list_model.dart'; import 'model/user_chat_history_model.dart'; enum ChatState { idle, voiceRecordingStarted, voiceRecordingCompleted } class ChatPage extends StatefulWidget { int moduleId; int requestId; String? assigneeEmployeeNumber; String? myLoginUserID; String? contactEmployeeINumber; String title; bool readOnly; ChatPage({Key? key, required this.moduleId, required this.requestId, this.title = "Chat", this.readOnly = false, this.assigneeEmployeeNumber, this.myLoginUserID, this.contactEmployeeINumber}) : super(key: key); @override _ChatPageState createState() { return _ChatPageState(); } } class _ChatPageState extends State { bool isSender = false; bool isAudioRecording = false; String? recordedFilePath; final RecorderController recorderController = RecorderController(); PlayerController playerController = PlayerController(); TextEditingController textEditingController = TextEditingController(); ChatState chatState = ChatState.idle; late String receiver; @override void initState() { super.initState(); loadChatHistory(); playerController.addListener(() async { // if (playerController.playerState == PlayerState.playing && playerController.maxDuration == await playerController.getDuration()) { // await playerController.stopPlayer(); // setState(() {}); // } }); } void loadChatHistory() { // // String assigneeEmployeeNumber = Provider.of(context, listen: false).currentWorkOrder?.data?.assignedEmployee?.employeeId ?? ""; // // String myEmployeeId = context.userProvider.user!.username!; // // // // receiver = context.userProvider.isNurse ? widget.assigneeEmployeeNumber : widget.myEmployeeID; // // Provider.of(context, listen: false).connectToHub(widget.moduleId, widget.requestId, widget.myEmployeeID, widget.assigneeEmployeeNumber); // // receiver = context.userProvider.isNurse // ? assigneeEmployeeNumber // :myEmployeeId; String assigneeEmployeeNumber = widget.assigneeEmployeeNumber ?? Provider.of(context, listen: false).currentWorkOrder?.data?.assignedEmployee?.employeeId ?? ""; String myEmployeeId = widget.myLoginUserID ?? context.userProvider.user!.username!; receiver = context.userProvider.isNurse ? assigneeEmployeeNumber : (context.userProvider.isEngineer ? (widget.contactEmployeeINumber ?? (Provider.of(context, listen: false).currentWorkOrder!.data!.workOrderContactPerson.isEmpty ? Provider.of(context, listen: false).currentWorkOrder!.data!.workOrderCreatedBy!.employeeId! : Provider.of(context, listen: false).currentWorkOrder!.data!.workOrderContactPerson.first.employeeId!)) : ""); Provider.of(context, listen: false).connectToHub(widget.moduleId, widget.requestId, myEmployeeId, receiver, widget.readOnly, isMounted: mounted); } @override void dispose() { chatHubConnection?.stop(); playerController.dispose(); recorderController.dispose(); super.dispose(); } @override Widget build(BuildContext context) { return Scaffold( backgroundColor: AppColor.white10, appBar: DefaultAppBar(title: widget.title), body: Consumer(builder: (context, chatProvider, child) { if (chatProvider.chatLoginTokenLoading) return const CircularProgressIndicator(color: AppColor.primary10, strokeWidth: 3).center; if (chatProvider.chatLoginResponse == null) { return Column( mainAxisSize: MainAxisSize.min, crossAxisAlignment: CrossAxisAlignment.center, children: [ Text( "Failed to connect chat", overflow: TextOverflow.ellipsis, maxLines: 1, style: AppTextStyles.heading6.copyWith(color: AppColor.neutral50, fontWeight: FontWeight.w500), ), 24.height, AppFilledButton( label: "Go Back", maxWidth: true, buttonColor: AppColor.primary10, onPressed: () => Navigator.pop(context), ).paddingOnly(start: 48, end: 48) ], ).center; } return Column( children: [ Container( color: AppColor.neutral50, constraints: const BoxConstraints(maxHeight: 56), padding: const EdgeInsets.only(left: 16, right: 16, top: 8, bottom: 8), alignment: Alignment.center, child: Row( children: [ Column( mainAxisSize: MainAxisSize.min, crossAxisAlignment: CrossAxisAlignment.start, children: [ Text( chatProvider.recipient?.userName ?? "", overflow: TextOverflow.ellipsis, maxLines: 1, style: AppTextStyles.bodyText2.copyWith(color: AppColor.white10), ), AnimatedSize( duration: const Duration(milliseconds: 250), child: SizedBox( height: chatProvider.isTyping ? null : 0, child: Text( "Typing...", maxLines: 1, style: AppTextStyles.overline.copyWith(color: AppColor.white10), ), )), // if (chatProvider.isTyping) // Text( // "Typing...", // maxLines: 1, // style: AppTextStyles.tinyFont2.copyWith(color: AppColor.white10), // ), ], ).expanded, 4.width, Text( "View All Documents", style: AppTextStyles.bodyText.copyWith( color: AppColor.white10, decoration: TextDecoration.underline, decorationColor: AppColor.white10, ), ).onPress(() { Navigator.push(context, CupertinoPageRoute(builder: (context) => ViewAllAttachmentPage(moduleId: widget.moduleId, requestId: widget.requestId))); }), ], ), ), Container( color: AppColor.neutral100, child: chatProvider.userChatHistoryLoading ? ListView( padding: const EdgeInsets.all(16), children: [ recipientMsgCard(true, null, msg: "Please let me know what is the issue? Please let me know what is the issue?", loading: true), recipientMsgCard(false, null, msg: "testing", loading: true), recipientMsgCard(false, null, msg: "testing testing testing", loading: true), senderMsgCard(true, null, msg: "Please let me know what is the issue? Please let me know what is the issue?", loading: true), senderMsgCard(false, null, msg: "Please let me know what is the issue?", loading: true), ], ) : chatProvider.chatResponseList.isEmpty ? Text( "Send a message to start conversation", overflow: TextOverflow.ellipsis, maxLines: 1, style: AppTextStyles.heading6.copyWith(color: AppColor.neutral50.withOpacity(.5), fontWeight: FontWeight.w500), ).center : ListView.builder( padding: const EdgeInsets.all(16), reverse: true, itemBuilder: (cxt, index) { final currentMessage = chatProvider.chatResponseList[index]; final bool showSenderName = (index == chatProvider.chatResponseList.length - 1) || (currentMessage.currentUserId != chatProvider.chatResponseList[index + 1].currentUserId); bool isSender = chatProvider.chatResponseList[index].currentUserId == chatProvider.sender?.userId!; bool showDateHeader = false; if (index == chatProvider.chatResponseList.length - 1) { showDateHeader = true; } else { final nextMessage = chatProvider.chatResponseList[index + 1]; final currentDate = DateUtils.dateOnly(currentMessage.createdDate!); final nextDate = DateUtils.dateOnly(nextMessage.createdDate!); if (!currentDate.isAtSameMomentAs(nextDate)) { showDateHeader = true; } } return Column(mainAxisSize: MainAxisSize.min, children: [ if (showDateHeader) dateCard(currentMessage.createdDate?.toString().chatMsgDateWithYear ?? ""), isSender ? senderMsgCard(showSenderName, chatProvider.chatResponseList[index]) : recipientMsgCard(showSenderName, chatProvider.chatResponseList[index]) ]); }, itemCount: chatProvider.chatResponseList.length)) .expanded, if (!widget.readOnly) ...[ Divider(height: 1, thickness: 1, color: const Color(0xff767676).withOpacity(.11)), SafeArea( child: ConstrainedBox( constraints: const BoxConstraints(minHeight: 56), child: Row( children: [ if (chatState == ChatState.idle) ...[ TextFormField( controller: textEditingController, cursorColor: AppColor.neutral50, style: AppTextStyles.bodyText.copyWith(color: AppColor.neutral50), minLines: 1, maxLines: 3, textInputAction: TextInputAction.none, keyboardType: TextInputType.multiline, onTap: () { chatHubConnection!.invoke("SendTypingAsync", args: [receiver]); }, onTapOutside: (PointerDownEvent event) { chatHubConnection!.invoke("SendStopTypingAsync", args: [receiver]); }, onChanged: (text) { chatHubConnection!.invoke("SendTypingAsync", args: [receiver]); }, decoration: InputDecoration( enabledBorder: InputBorder.none, focusedBorder: InputBorder.none, border: InputBorder.none, errorBorder: InputBorder.none, contentPadding: const EdgeInsets.only(left: 16, top: 8, bottom: 8), alignLabelWithHint: true, filled: true, constraints: const BoxConstraints(), suffixIconConstraints: const BoxConstraints(), hintText: "Type your message here...", hintStyle: AppTextStyles.bodyText.copyWith(color: const Color(0xffCCCCCC)), ), ).expanded, IconButton( onPressed: () async { FocusScope.of(context).unfocus(); File? file = (await showModalBottomSheet( context: context, shape: const RoundedRectangleBorder( borderRadius: BorderRadius.vertical( top: Radius.circular(20), ), ), clipBehavior: Clip.antiAliasWithSaveLayer, builder: (BuildContext context) => ChatFilePicker())) as File?; if (file != null) { Utils.showUploadingDialog(context); await chatProvider.uploadAttachments(context.userProvider.user!.username!, file, "1"); Utils.hideLoading(context); } }, style: const ButtonStyle( tapTargetSize: MaterialTapTargetSize.shrinkWrap, // or .padded ), icon: "chat_attachment".toSvgAsset(width: 24, height: 24), constraints: const BoxConstraints(), ), IconButton( onPressed: () async { await recorderController.checkPermission(); if (recorderController.hasPermission) { chatState = ChatState.voiceRecordingStarted; recorderController.record(); setState(() {}); } else { "Audio permission denied. Please enable from setting".showToast; } // if (!isPermissionGranted) { // "Audio permission denied. Please enable from setting".showToast; // return; // } }, style: const ButtonStyle( tapTargetSize: MaterialTapTargetSize.shrinkWrap, // or .padded ), icon: "chat_mic".toSvgAsset(width: 24, height: 24), constraints: const BoxConstraints(), ), ] else if (chatState == ChatState.voiceRecordingStarted) ...[ AudioWaveforms( size: Size(MediaQuery.of(context).size.width, 56.0), waveStyle: const WaveStyle(waveColor: AppColor.neutral50, extendWaveform: true, showMiddleLine: false), padding: const EdgeInsets.only(left: 16), recorderController: recorderController, // Customize how waveforms looks. ).expanded, IconButton( onPressed: () async { isAudioRecording = false; await recorderController.pause(); recordedFilePath = await recorderController.stop(); chatState = ChatState.voiceRecordingCompleted; setState(() {}); }, style: const ButtonStyle( tapTargetSize: MaterialTapTargetSize.shrinkWrap, // or .padded ), icon: Icon(Icons.stop_circle_rounded), constraints: const BoxConstraints(), ) ] else if (chatState == ChatState.voiceRecordingCompleted) ...[ if (playerController.playerState == PlayerState.playing) IconButton( onPressed: () async { await playerController.pausePlayer(); await playerController.stopPlayer(); setState(() {}); }, style: const ButtonStyle( tapTargetSize: MaterialTapTargetSize.shrinkWrap, // or .padded ), icon: const Icon(Icons.stop_circle_outlined, size: 20), constraints: const BoxConstraints(), ) else IconButton( onPressed: () async { await playerController.preparePlayer(path: recordedFilePath!); await playerController.startPlayer(); setState(() {}); }, style: const ButtonStyle( tapTargetSize: MaterialTapTargetSize.shrinkWrap, // or .padded ), icon: const Icon(Icons.play_circle_fill_rounded, size: 20), constraints: const BoxConstraints(), ), AudioFileWaveforms( playerController: playerController, waveformData: recorderController.waveData, enableSeekGesture: false, continuousWaveform: false, waveformType: WaveformType.long, playerWaveStyle: const PlayerWaveStyle( fixedWaveColor: AppColor.neutral50, liveWaveColor: AppColor.primary10, showSeekLine: true, ), size: Size(MediaQuery.of(context).size.width, 56.0), ).expanded, IconButton( onPressed: () async { await playerController.stopPlayer(); recorderController.reset(); recordedFilePath = null; chatState = ChatState.idle; setState(() {}); }, style: const ButtonStyle( tapTargetSize: MaterialTapTargetSize.shrinkWrap, // or .padded ), icon: "delete_icon".toSvgAsset(width: 24, height: 24), constraints: const BoxConstraints(), ), ], // if (recordedFilePath == null) ...[ // isAudioRecording // ? AudioWaveforms( // size: Size(MediaQuery.of(context).size.width, 56.0), // // // enableGesture: true, // // waveStyle: const WaveStyle(waveColor: AppColor.neutral50, extendWaveform: true, showMiddleLine: false), // padding: const EdgeInsets.only(left: 16), // recorderController: recorderController, // Customize how waveforms looks. // ).expanded // : TextFormField( // cursorColor: AppColor.neutral50, // style: AppTextStyles.bodyText.copyWith(color: AppColor.neutral50), // minLines: 1, // maxLines: 3, // textInputAction: TextInputAction.none, // keyboardType: TextInputType.multiline, // decoration: InputDecoration( // enabledBorder: InputBorder.none, // focusedBorder: InputBorder.none, // border: InputBorder.none, // errorBorder: InputBorder.none, // contentPadding: const EdgeInsets.only(left: 16, top: 8, bottom: 8), // alignLabelWithHint: true, // filled: true, // constraints: const BoxConstraints(), // suffixIconConstraints: const BoxConstraints(), // hintText: "Type your message here...", // hintStyle: AppTextStyles.bodyText.copyWith(color: const Color(0xffCCCCCC)), // // suffixIcon: Row( // // mainAxisSize: MainAxisSize.min, // // crossAxisAlignment: CrossAxisAlignment.end, // // mainAxisAlignment: MainAxisAlignment.end, // // children: [ // // // // 8.width, // // ], // // ) // ), // ).expanded, // IconButton( // onPressed: () {}, // style: const ButtonStyle( // tapTargetSize: MaterialTapTargetSize.shrinkWrap, // or .padded // ), // icon: "chat_attachment".toSvgAsset(width: 24, height: 24), // constraints: const BoxConstraints(), // ), // ], // if (recordedFilePath == null) // ...[] // else ...[ // IconButton( // onPressed: () async { // await playerController.preparePlayer(path: recordedFilePath!); // playerController.startPlayer(); // }, // style: const ButtonStyle( // tapTargetSize: MaterialTapTargetSize.shrinkWrap, // or .padded // ), // icon: const Icon(Icons.play_circle_fill_rounded, size: 20), // constraints: const BoxConstraints(), // ), // AudioFileWaveforms( // playerController: playerController, // size: Size(300, 50), // ).expanded, // IconButton( // onPressed: () async { // playerController.pausePlayer(); // }, // style: const ButtonStyle( // tapTargetSize: MaterialTapTargetSize.shrinkWrap, // or .padded // ), // icon: const Icon(Icons.pause_circle_filled_outlined, size: 20), // constraints: const BoxConstraints(), // ), // IconButton( // onPressed: () {}, // style: const ButtonStyle( // tapTargetSize: MaterialTapTargetSize.shrinkWrap, // or .padded // ), // icon: "delete".toSvgAsset(width: 24, height: 24), // constraints: const BoxConstraints(), // ), // ], // if (isAudioRecording && recorderController.isRecording) // IconButton( // onPressed: () {}, // style: const ButtonStyle( // tapTargetSize: MaterialTapTargetSize.shrinkWrap, // or .padded // ), // icon: "chat_msg_send".toSvgAsset(width: 24, height: 24), // constraints: const BoxConstraints(), // ), // if (isAudioRecording) // IconButton( // onPressed: () async { // isAudioRecording = false; // await recorderController.pause(); // recordedFilePath = await recorderController.stop(); // chatState = ChatState.voiceRecordingCompleted; // setState(() {}); // }, // style: const ButtonStyle( // tapTargetSize: MaterialTapTargetSize.shrinkWrap, // or .padded // ), // icon: Icon(Icons.stop_circle_rounded), // constraints: const BoxConstraints(), // ) // else // IconButton( // onPressed: () async { // await recorderController.checkPermission(); // if (recorderController.hasPermission) { // setState(() { // isAudioRecording = true; // }); // recorderController.record(); // } else { // "Audio permission denied. Please enable from setting".showToast; // } // // if (!isPermissionGranted) { // // "Audio permission denied. Please enable from setting".showToast; // // return; // // } // }, // style: const ButtonStyle( // tapTargetSize: MaterialTapTargetSize.shrinkWrap, // or .padded // ), // icon: "chat_mic".toSvgAsset(width: 24, height: 24), // constraints: const BoxConstraints(), // ), IconButton( splashColor: Colors.transparent, highlightColor: Colors.transparent, hoverColor: Colors.transparent, onPressed: () async { if (chatState == ChatState.voiceRecordingCompleted) { Utils.showUploadingDialog(context); try { await chatProvider.uploadAttachments(context.userProvider.user!.username!, File(recordedFilePath!), "1"); Utils.hideLoading(context); await playerController.stopPlayer(); recorderController.reset(); recordedFilePath = null; chatState = ChatState.idle; setState(() {}); } catch (ex) { Utils.hideLoading(context); } } else { if (textEditingController.text.isEmpty) return; chatProvider.invokeSendMessage({ "Contant": textEditingController.text, // "ContantNo": "0cc8b126-6180-4f91-a64d-2f62443b3f3f", // "CreatedDate": "2025-11-09T18:58:12.502Z", "CurrentEmployeeNumber": context.userProvider.user!.username!, "ChatEventId": 1, "ConversationId": chatProvider.chatParticipantModel!.id!.toString(), "ModuleCode": widget.moduleId.toString(), "ReferenceNumber": widget.requestId.toString(), "UserChatHistoryLineRequestList": [ {"TargetEmployeeNumber": receiver, "TargetUserStatus": 1, "IsSeen": false, "IsDelivered": true, "SeenOn": null, "DeliveredOn": null} ] }).then((success) { if (success) { textEditingController.clear(); } }); } }, style: const ButtonStyle( tapTargetSize: MaterialTapTargetSize.shrinkWrap, // or .padded ), icon: chatProvider.messageIsSending ? const SizedBox( height: 24, width: 24, child: CircularProgressIndicator(color: AppColor.primary10, strokeWidth: 2), ) : "chat_msg_send".toSvgAsset(width: 24, height: 24), constraints: const BoxConstraints(), ), 8.width, ], ), ), ) ] ], ); })); } Widget dateCard(String date) { return Container( padding: const EdgeInsets.symmetric(vertical: 4, horizontal: 8), margin: const EdgeInsets.only(top: 16, bottom: 8), decoration: BoxDecoration( color: AppColor.neutral50, borderRadius: BorderRadius.circular(6), ), child: Text(date, style: AppTextStyles.bodyText2.copyWith(color: AppColor.white10))) .center; } Widget senderMsgCard(bool showHeader, SingleUserChatModel? chatResponse, {bool loading = false, String msg = ""}) { Widget senderHeader = Row( mainAxisSize: MainAxisSize.min, children: [ Text( "${chatResponse?.currentUserName ?? "User"}(Me)", overflow: TextOverflow.ellipsis, maxLines: 1, style: AppTextStyles.bodyText.copyWith(color: AppColor.neutral50, fontWeight: FontWeight.w600), ).toShimmer(context: context, isShow: loading), 8.width, Container( height: 26, width: 26, decoration: const BoxDecoration(shape: BoxShape.circle, color: Colors.grey), ).toShimmer(context: context, isShow: loading), ], ); return Align( alignment: Alignment.centerRight, child: Column( mainAxisSize: MainAxisSize.min, crossAxisAlignment: CrossAxisAlignment.end, children: [ if (showHeader) ...[senderHeader, 4.height] else 8.height, Row( mainAxisSize: MainAxisSize.min, crossAxisAlignment: CrossAxisAlignment.end, children: [ Flexible( fit: FlexFit.loose, child: Container( padding: const EdgeInsets.all(8), margin: const EdgeInsets.only(right: 8, left: 26 + 8), decoration: BoxDecoration( color: loading ? Colors.transparent : AppColor.white10, borderRadius: BorderRadius.circular(6), ), child: Column( mainAxisSize: MainAxisSize.min, crossAxisAlignment: CrossAxisAlignment.end, children: [ if (chatResponse?.downloadUrl != null) ...[ // if (chatResponse!.fileTypeResponse!.fileKind.toString().toLowerCase() == "audio") // // ASoundPlayer(audio: chatResponse.downloadUrl!) // // ChatAudioPlayer(chatResponse!.fileTypeResponse!, chatResponse.downloadUrl!) // // if ( // // // // chatResponse!.fileTypeResponse!.fileKind.toString().toLowerCase() == "image" // // // // ) // // else ChatFileViewer(chatResponse!.downloadUrl!, chatResponse.fileTypeResponse!), // Container( // width: 250, // height: 250, // decoration: BoxDecoration( // borderRadius: BorderRadius.circular(12), // border: Border.all(color: Colors.grey.withOpacity(.5), width: 1), // image: DecorationImage( // image: NetworkImage(chatResponse.downloadUrl!, headers: {'Authorization': "Bearer ${Provider.of(context, listen: false).chatLoginResponse!.token!}"}), // ), // ), // // child: CachedNetworkImage( // // imageUrl: chatResponse.downloadUrl! ?? "", // // // fit: boxFit ?? BoxFit.cover, // // // alignment: Alignment.center, // // // width: width, // // // height: height, // // httpHeaders: {'Authorization': "Bearer ${Provider.of(context, listen: false).chatLoginResponse!.token!}"}, // // placeholder: (context, url) => const Center(child: CircularProgressIndicator()), // // // errorWidget: (context, url, error) => Icon(showDefaultIcon ? Icons.image_outlined : Icons.broken_image_rounded), // // ), // // // Image.network( // // chatResponse.downloadUrl!, // // headers: {'Authorization': "Bearer ${Provider.of(context, listen: false).chatLoginResponse!.token!}"}, // // ), // ) ] else Text( chatResponse?.contant ?? msg, style: AppTextStyles.bodyText2.copyWith(color: AppColor.neutral120), ).toShimmer(context: context, isShow: loading), if (loading) 4.height, Text( chatResponse?.createdDate?.toString().chatMsgTime ?? "2:00 PM", style: AppTextStyles.textFieldLabelStyle.copyWith(color: AppColor.neutral50.withOpacity(0.5)), ).toShimmer(context: context, isShow: loading), ], )), ), if (chatResponse != null) if (chatResponse.isSeen!) "chat_seen".toSvgAsset(width: 16, height: 16) else if (chatResponse.isDelivered!) "chat_delivered".toSvgAsset(width: 16, height: 16) else "chat_sent".toSvgAsset(width: 16, height: 16), (26 + 8).width, ], ), ], ), ); } Widget recipientMsgCard(bool showHeader, SingleUserChatModel? chatResponse, {bool loading = false, String msg = ""}) { String extraSpaces = ""; int length = 0; if ((chatResponse?.contant ?? "").isNotEmpty) { if (chatResponse!.contant!.length < 8) { length = 8 - chatResponse.contant!.length; } } String contentMsg = chatResponse?.contant == null ? msg : chatResponse!.contant! + extraSpaces; Widget recipientHeader = Row( mainAxisSize: MainAxisSize.min, children: [ Container( height: 26, width: 26, decoration: const BoxDecoration(shape: BoxShape.circle, color: Colors.grey), ).toShimmer(context: context, isShow: loading), 8.width, Text( chatResponse?.currentUserName ?? "User", overflow: TextOverflow.ellipsis, maxLines: 1, style: AppTextStyles.bodyText.copyWith(color: AppColor.neutral50, fontWeight: FontWeight.w600), ).toShimmer(context: context, isShow: loading) ], ); return Align( alignment: Alignment.centerLeft, child: Column( mainAxisSize: MainAxisSize.min, crossAxisAlignment: CrossAxisAlignment.start, children: [ if (showHeader) ...[recipientHeader, 4.height] else 8.height, Container( padding: const EdgeInsets.all(8), margin: const EdgeInsets.only(left: 26 + 8, right: 26 + 8), decoration: BoxDecoration( color: loading ? Colors.transparent : AppColor.primary10, borderRadius: BorderRadius.circular(6), ), child: Column( mainAxisSize: MainAxisSize.min, crossAxisAlignment: CrossAxisAlignment.end, children: [ if (chatResponse?.downloadUrl != null) ...[ ChatFileViewer(chatResponse!.downloadUrl!, chatResponse.fileTypeResponse!), ] else Text( contentMsg, style: AppTextStyles.bodyText2.copyWith(color: AppColor.white10), ).paddingOnly(end: 6 * length).toShimmer(context: context, isShow: loading), if (loading) 4.height, Align( alignment: Alignment.centerRight, widthFactor: 1, child: Text( chatResponse?.createdDate?.toString().chatMsgTime ?? "2:00 PM", style: AppTextStyles.textFieldLabelStyle.copyWith(color: AppColor.white10), ), ).toShimmer(context: context, isShow: loading), ], )), ], ), ); } }