import 'package:flutter/material.dart'; import 'package:font_awesome_flutter/font_awesome_flutter.dart'; import 'package:get/get.dart'; import 'package:solian/controllers/chat_events_controller.dart'; import 'package:solian/models/event.dart'; import 'package:solian/widgets/account/account_avatar.dart'; import 'package:solian/widgets/account/account_profile_popup.dart'; import 'package:solian/widgets/attachments/attachment_list.dart'; import 'package:solian/widgets/chat/chat_event_action_log.dart'; import 'package:solian/widgets/chat/chat_event_message.dart'; import 'package:timeago/timeago.dart' show format; class ChatEvent extends StatelessWidget { final Event item; final bool isContentPreviewing; final bool isQuote; final bool isMerged; final bool isHasMerged; final ChatEventController? chatController; const ChatEvent({ super.key, required this.item, this.isContentPreviewing = false, this.isMerged = false, this.isHasMerged = false, this.isQuote = false, this.chatController, }); String _formatDuration(Duration duration) { String negativeSign = duration.isNegative ? '-' : ''; String twoDigits(int n) => n.toString().padLeft(2, '0'); String twoDigitMinutes = twoDigits(duration.inMinutes.remainder(60).abs()); String twoDigitSeconds = twoDigits(duration.inSeconds.remainder(60).abs()); return '$negativeSign${twoDigits(duration.inHours)}:$twoDigitMinutes:$twoDigitSeconds'; } Widget _buildAttachment(BuildContext context, {bool isMinimal = false}) { final attachments = item.body['attachments'] != null ? List.from(item.body['attachments'].map((x) => x)) : List.empty(); if (attachments.isEmpty) return const SizedBox(); if (isMinimal) { final unFocusColor = Theme.of(context).colorScheme.onSurface.withOpacity(0.75); return Row( children: [ Icon( Icons.attachment, size: 18, color: unFocusColor, ).paddingOnly(right: 6), Text( 'attachmentHint'.trParams( {'count': attachments.length.toString()}, ), style: TextStyle(color: unFocusColor), ) ], ); } return Container( key: Key('m${item.uuid}attachments-box'), width: MediaQuery.of(context).size.width, padding: EdgeInsets.only(top: isMerged ? 0 : 4), constraints: const BoxConstraints( maxHeight: 720, ), child: AttachmentList( key: Key('m${item.uuid}attachments'), parentId: item.uuid, attachmentsId: attachments, viewport: 1, ), ); } Widget _buildQuote() { return FutureBuilder( future: chatController!.getEvent( item.body['quote_event'], ), builder: (context, snapshot) { if (!snapshot.hasData || snapshot.data == null) { return const SizedBox(); } return ChatEvent( item: snapshot.data!.data, isMerged: false, isQuote: true, ).paddingOnly(left: isMerged ? 52 : 0); }, ); } Widget _buildContent() { switch (item.type) { case 'messages.new': return ChatEventMessage( item: item, isContentPreviewing: isContentPreviewing, isMerged: isMerged, isHasMerged: isHasMerged, isQuote: isQuote, ); case 'messages.edit': return ChatEventMessageActionLog( icon: const Icon(Icons.edit_note, size: 16), text: 'messageEditDesc'.trParams({ 'id': '#${item.body['related_event']}', }), isMerged: isMerged, isHasMerged: isHasMerged, isQuote: isQuote, ); case 'messages.delete': return ChatEventMessageActionLog( icon: const Icon(Icons.cancel_schedule_send, size: 16), text: 'messageDeleteDesc'.trParams({ 'id': '#${item.body['related_event']}', }), isMerged: isMerged, isHasMerged: isHasMerged, isQuote: isQuote, ); case 'calls.start': return ChatEventMessageActionLog( icon: const Icon(Icons.call_made, size: 16), text: 'messageCallStartDesc' .trParams({'user': '@${item.sender.account.name}'}), isMerged: isMerged, isHasMerged: isHasMerged, isQuote: isQuote, ); case 'calls.end': return ChatEventMessageActionLog( icon: const Icon(Icons.call_received, size: 16), text: 'messageCallEndDesc'.trParams({ 'duration': _formatDuration( Duration(seconds: item.body['last']), ), }), isMerged: isMerged, isHasMerged: isHasMerged, isQuote: isQuote, ); case 'system.changes': return ChatEventMessageActionLog( icon: const Icon(Icons.manage_history, size: 16), text: item.body['text'], isMerged: isMerged, isHasMerged: isHasMerged, isQuote: isQuote, ); default: return ChatEventMessageActionLog( icon: const Icon(Icons.error, size: 16), text: 'messageTypeUnsupported'.trParams({'type': item.type}), isMerged: isMerged, isHasMerged: isHasMerged, isQuote: isQuote, ); } } Widget _buildBody(BuildContext context) { if (isContentPreviewing || (isMerged && !isQuote)) { return Column( crossAxisAlignment: CrossAxisAlignment.start, children: [ if (item.body['quote_event'] != null && chatController != null) _buildQuote(), Row( crossAxisAlignment: CrossAxisAlignment.center, children: [ Expanded(child: _buildContent()), if (item.isPending) const SizedBox( width: 12, height: 12, child: CircularProgressIndicator(strokeWidth: 2), ), ], ).paddingOnly(right: 12), _buildAttachment(context, isMinimal: isContentPreviewing) .paddingOnly(left: isContentPreviewing ? 12 : 0), ], ); } else if (isQuote) { return Card( child: Row( crossAxisAlignment: CrossAxisAlignment.end, children: [ const Opacity( opacity: 0.75, child: FaIcon(FontAwesomeIcons.quoteLeft, size: 14), ).paddingOnly(bottom: 2.75), const SizedBox(width: 4), Expanded( child: Column( mainAxisAlignment: MainAxisAlignment.start, crossAxisAlignment: CrossAxisAlignment.start, children: [ Row( children: [ AccountAvatar( content: item.sender.account.avatar, radius: 9), const SizedBox(width: 5), Text( item.sender.account.nick, style: const TextStyle(fontWeight: FontWeight.bold), ), const SizedBox(width: 4), Text(format(item.createdAt, locale: 'en_short')), ], ), _buildContent().paddingOnly(left: 0.5), ], ), ), ], ).paddingOnly(left: 12, right: 12, top: 8, bottom: 4), ).paddingOnly(left: isMerged ? 52 : 0, right: 4); } else { return Column( key: Key('m${item.uuid}'), children: [ Row( crossAxisAlignment: CrossAxisAlignment.start, children: [ GestureDetector( child: AccountAvatar(content: item.sender.account.avatar), onTap: () { showModalBottomSheet( useRootNavigator: true, isScrollControlled: true, backgroundColor: Theme.of(context).colorScheme.surface, context: context, builder: (context) => AccountProfilePopup( account: item.sender.account, ), ); }, ), Expanded( child: Column( crossAxisAlignment: CrossAxisAlignment.start, children: [ Row( children: [ Text( item.sender.account.nick, style: const TextStyle(fontWeight: FontWeight.bold), ), const SizedBox(width: 4), Text(format(item.createdAt, locale: 'en_short')) ], ).paddingSymmetric(horizontal: 12), if (item.body['quote_event'] != null && chatController != null) _buildQuote(), Row( crossAxisAlignment: CrossAxisAlignment.center, children: [ Expanded(child: _buildContent()), if (item.isPending) const SizedBox( width: 12, height: 12, child: CircularProgressIndicator(strokeWidth: 2), ), ], ), ], ), ), ], ).paddingSymmetric(horizontal: 12), _buildAttachment(context), ], ); } } @override Widget build(BuildContext context) { return _buildBody(context); } }