From 44833bb87f423df319da30848d57d85d08abc9db Mon Sep 17 00:00:00 2001 From: LittleSheep Date: Fri, 28 Jun 2024 02:49:28 +0800 Subject: [PATCH] :sparkles: Quote messages --- lib/controllers/chat_events_controller.dart | 10 +++ lib/screens/channel/channel_chat.dart | 2 +- lib/widgets/chat/chat_event.dart | 89 +++++++++++++++++---- lib/widgets/chat/chat_event_action_log.dart | 11 +-- lib/widgets/chat/chat_event_message.dart | 41 +++++----- 5 files changed, 108 insertions(+), 45 deletions(-) diff --git a/lib/controllers/chat_events_controller.dart b/lib/controllers/chat_events_controller.dart index 4d3d1f4..709f55b 100644 --- a/lib/controllers/chat_events_controller.dart +++ b/lib/controllers/chat_events_controller.dart @@ -14,13 +14,23 @@ class ChatEventController { final RxBool isLoading = false.obs; Channel? channel; + String? scope; initialize() async { database = await createHistoryDb(); currentEvents.clear(); } + Future getEvent(int id) async { + if(channel == null || scope == null) return null; + + return await database.getEvent(id, channel!, scope: scope!); + } + Future getEvents(Channel channel, String scope) async { + this.channel = channel; + this.scope = scope; + syncLocal(channel); isLoading.value = true; diff --git a/lib/screens/channel/channel_chat.dart b/lib/screens/channel/channel_chat.dart index cb19cee..9be61ed 100644 --- a/lib/screens/channel/channel_chat.dart +++ b/lib/screens/channel/channel_chat.dart @@ -149,6 +149,7 @@ class _ChannelChatScreenState extends State { key: Key('m${item.uuid}'), item: item, isMerged: isMerged, + chatController: _chatController, ); } @@ -202,7 +203,6 @@ class _ChannelChatScreenState extends State { _chatController.initialize(); getChannel().then((_) { - _chatController.channel = _channel!; _chatController.getEvents(_channel!, widget.realm); listenMessages(); diff --git a/lib/widgets/chat/chat_event.dart b/lib/widgets/chat/chat_event.dart index c6b7707..1ff78e4 100644 --- a/lib/widgets/chat/chat_event.dart +++ b/lib/widgets/chat/chat_event.dart @@ -1,6 +1,9 @@ +import 'dart:math'; + 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'; @@ -15,6 +18,8 @@ class ChatEvent extends StatelessWidget { final bool isMerged; final bool isHasMerged; + final ChatEventController? chatController; + const ChatEvent({ super.key, required this.item, @@ -22,8 +27,28 @@ class ChatEvent extends StatelessWidget { this.isMerged = false, this.isHasMerged = false, this.isQuote = false, + this.chatController, }); + 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': @@ -40,6 +65,7 @@ class ChatEvent extends StatelessWidget { text: 'messageEditDesc'.trParams({'id': '#${item.id}'}), isMerged: isMerged, isHasMerged: isHasMerged, + isQuote: isQuote, ); case 'messages.delete': return ChatEventMessageActionLog( @@ -47,6 +73,7 @@ class ChatEvent extends StatelessWidget { text: 'messageDeleteDesc'.trParams({'id': '#${item.id}'}), isMerged: isMerged, isHasMerged: isHasMerged, + isQuote: isQuote, ); case 'system.changes': return ChatEventMessageActionLog( @@ -54,6 +81,7 @@ class ChatEvent extends StatelessWidget { text: item.body['text'], isMerged: isMerged, isHasMerged: isHasMerged, + isQuote: isQuote, ); default: return ChatEventMessageActionLog( @@ -61,31 +89,55 @@ class ChatEvent extends StatelessWidget { text: 'messageTypeUnsupported'.trParams({'type': item.type}), isMerged: isMerged, isHasMerged: isHasMerged, + isQuote: isQuote, ); } } Widget buildBody(BuildContext context) { - if (isContentPreviewing || isMerged) { - return buildContent(); - } else if (isQuote) { - return Row( - mainAxisAlignment: MainAxisAlignment.center, + if (isContentPreviewing || (isMerged && !isQuote)) { + return Column( children: [ - Transform.scale( - scaleX: -1, - child: const FaIcon(FontAwesomeIcons.reply, size: 14), - ), - const SizedBox(width: 12), - AccountAvatar(content: item.sender.account.avatar, radius: 8), - const SizedBox(width: 4), - Text( - item.sender.account.nick, - style: const TextStyle(fontWeight: FontWeight.bold), - ), - Expanded(child: buildContent()), + if (item.body['quote_event'] != null && chatController != null) + buildQuote(), + buildContent(), ], ); + } else if (isQuote) { + return Row( + crossAxisAlignment: CrossAxisAlignment.end, + children: [ + const Opacity( + opacity: 0.75, + child: FaIcon(FontAwesomeIcons.quoteLeft, size: 14), + ).paddingOnly(bottom: 4), + const SizedBox(width: 4), + Expanded( + child: Card( + 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), + ], + ).paddingSymmetric(horizontal: 12, vertical: 4), + ), + ), + ], + ).paddingOnly(left: isMerged ? 66 : 14, right: 4); } else { return Column( key: Key('m${item.uuid}'), @@ -121,6 +173,9 @@ class ChatEvent extends StatelessWidget { Text(format(item.createdAt, locale: 'en_short')) ], ).paddingSymmetric(horizontal: 12), + if (item.body['quote_event'] != null && + chatController != null) + buildQuote(), buildContent(), ], ), diff --git a/lib/widgets/chat/chat_event_action_log.dart b/lib/widgets/chat/chat_event_action_log.dart index a82b583..913a132 100644 --- a/lib/widgets/chat/chat_event_action_log.dart +++ b/lib/widgets/chat/chat_event_action_log.dart @@ -1,16 +1,10 @@ -import 'dart:math'; - import 'package:flutter/material.dart'; -import 'package:flutter_markdown/flutter_markdown.dart'; import 'package:get/get.dart'; -import 'package:solian/models/event.dart'; -import 'package:solian/widgets/attachments/attachment_list.dart'; -import 'package:timeago/timeago.dart' show format; -import 'package:url_launcher/url_launcher_string.dart'; class ChatEventMessageActionLog extends StatelessWidget { final Widget icon; final String text; + final bool isQuote; final bool isMerged; final bool isHasMerged; @@ -20,6 +14,7 @@ class ChatEventMessageActionLog extends StatelessWidget { required this.text, this.isMerged = false, this.isHasMerged = false, + this.isQuote = false, }); @override @@ -34,7 +29,7 @@ class ChatEventMessageActionLog extends StatelessWidget { Text(text), ], ).paddingOnly( - left: isMerged ? 64 : 12, + left: isQuote ? 0 : (isMerged ? 64 : 12), top: 2, bottom: isHasMerged ? 2 : 0, ), diff --git a/lib/widgets/chat/chat_event_message.dart b/lib/widgets/chat/chat_event_message.dart index 6866fed..4b43762 100644 --- a/lib/widgets/chat/chat_event_message.dart +++ b/lib/widgets/chat/chat_event_message.dart @@ -23,6 +23,21 @@ class ChatEventMessage extends StatelessWidget { this.isQuote = false, }); + Widget buildAttachment(BuildContext context) { + final body = EventMessageBody.fromJson(item.body); + + return SizedBox( + width: min(MediaQuery.of(context).size.width, 640), + child: AttachmentList( + key: Key('m${item.uuid}attachments'), + parentId: item.uuid, + attachmentsId: body.attachments ?? List.empty(), + divided: true, + viewport: 1, + ), + ); + } + Widget buildContent() { final body = EventMessageBody.fromJson(item.body); final hasAttachment = body.attachments?.isNotEmpty ?? false; @@ -41,9 +56,9 @@ class ChatEventMessage extends StatelessWidget { ); }, ).paddingOnly( - left: 12, - right: 12, - top: 2, + left: isQuote ? 0 : 12, + right: isQuote ? 0 : 12, + top: body.quoteEvent == null ? 2 : 0, bottom: hasAttachment ? 4 : (isHasMerged ? 2 : 0), ); } @@ -51,18 +66,15 @@ class ChatEventMessage extends StatelessWidget { Widget buildBody(BuildContext context) { final body = EventMessageBody.fromJson(item.body); - if (isContentPreviewing || isQuote) { + if (isContentPreviewing) { return buildContent(); } else if (isMerged) { return Column( + crossAxisAlignment: CrossAxisAlignment.start, children: [ buildContent().paddingOnly(left: 52), if (body.attachments?.isNotEmpty ?? false) - AttachmentList( - key: Key('m${item.uuid}attachments'), - parentId: item.uuid, - attachmentsId: body.attachments ?? List.empty(), - ).paddingSymmetric(vertical: 4), + buildAttachment(context).paddingOnly(left: 52, bottom: 4), ], ); } else { @@ -71,16 +83,7 @@ class ChatEventMessage extends StatelessWidget { children: [ buildContent(), if (body.attachments?.isNotEmpty ?? false) - SizedBox( - width: min(MediaQuery.of(context).size.width, 640), - child: AttachmentList( - key: Key('m${item.uuid}attachments'), - parentId: item.uuid, - attachmentsId: body.attachments ?? List.empty(), - divided: true, - viewport: 1, - ), - ), + buildAttachment(context).paddingOnly(bottom: 4), ], ); }