⬆️ 升级支持服务器的 Event Based Messages #2

Merged
LittleSheep merged 5 commits from experimental/event-based-messages into master 2024-06-27 20:37:32 +00:00
5 changed files with 108 additions and 45 deletions
Showing only changes of commit 44833bb87f - Show all commits

View File

@ -14,13 +14,23 @@ class ChatEventController {
final RxBool isLoading = false.obs; final RxBool isLoading = false.obs;
Channel? channel; Channel? channel;
String? scope;
initialize() async { initialize() async {
database = await createHistoryDb(); database = await createHistoryDb();
currentEvents.clear(); currentEvents.clear();
} }
Future<LocalEvent?> getEvent(int id) async {
if(channel == null || scope == null) return null;
return await database.getEvent(id, channel!, scope: scope!);
}
Future<void> getEvents(Channel channel, String scope) async { Future<void> getEvents(Channel channel, String scope) async {
this.channel = channel;
this.scope = scope;
syncLocal(channel); syncLocal(channel);
isLoading.value = true; isLoading.value = true;

View File

@ -149,6 +149,7 @@ class _ChannelChatScreenState extends State<ChannelChatScreen> {
key: Key('m${item.uuid}'), key: Key('m${item.uuid}'),
item: item, item: item,
isMerged: isMerged, isMerged: isMerged,
chatController: _chatController,
); );
} }
@ -202,7 +203,6 @@ class _ChannelChatScreenState extends State<ChannelChatScreen> {
_chatController.initialize(); _chatController.initialize();
getChannel().then((_) { getChannel().then((_) {
_chatController.channel = _channel!;
_chatController.getEvents(_channel!, widget.realm); _chatController.getEvents(_channel!, widget.realm);
listenMessages(); listenMessages();

View File

@ -1,6 +1,9 @@
import 'dart:math';
import 'package:flutter/material.dart'; import 'package:flutter/material.dart';
import 'package:font_awesome_flutter/font_awesome_flutter.dart'; import 'package:font_awesome_flutter/font_awesome_flutter.dart';
import 'package:get/get.dart'; import 'package:get/get.dart';
import 'package:solian/controllers/chat_events_controller.dart';
import 'package:solian/models/event.dart'; import 'package:solian/models/event.dart';
import 'package:solian/widgets/account/account_avatar.dart'; import 'package:solian/widgets/account/account_avatar.dart';
import 'package:solian/widgets/account/account_profile_popup.dart'; import 'package:solian/widgets/account/account_profile_popup.dart';
@ -15,6 +18,8 @@ class ChatEvent extends StatelessWidget {
final bool isMerged; final bool isMerged;
final bool isHasMerged; final bool isHasMerged;
final ChatEventController? chatController;
const ChatEvent({ const ChatEvent({
super.key, super.key,
required this.item, required this.item,
@ -22,8 +27,28 @@ class ChatEvent extends StatelessWidget {
this.isMerged = false, this.isMerged = false,
this.isHasMerged = false, this.isHasMerged = false,
this.isQuote = 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() { Widget buildContent() {
switch (item.type) { switch (item.type) {
case 'messages.new': case 'messages.new':
@ -40,6 +65,7 @@ class ChatEvent extends StatelessWidget {
text: 'messageEditDesc'.trParams({'id': '#${item.id}'}), text: 'messageEditDesc'.trParams({'id': '#${item.id}'}),
isMerged: isMerged, isMerged: isMerged,
isHasMerged: isHasMerged, isHasMerged: isHasMerged,
isQuote: isQuote,
); );
case 'messages.delete': case 'messages.delete':
return ChatEventMessageActionLog( return ChatEventMessageActionLog(
@ -47,6 +73,7 @@ class ChatEvent extends StatelessWidget {
text: 'messageDeleteDesc'.trParams({'id': '#${item.id}'}), text: 'messageDeleteDesc'.trParams({'id': '#${item.id}'}),
isMerged: isMerged, isMerged: isMerged,
isHasMerged: isHasMerged, isHasMerged: isHasMerged,
isQuote: isQuote,
); );
case 'system.changes': case 'system.changes':
return ChatEventMessageActionLog( return ChatEventMessageActionLog(
@ -54,6 +81,7 @@ class ChatEvent extends StatelessWidget {
text: item.body['text'], text: item.body['text'],
isMerged: isMerged, isMerged: isMerged,
isHasMerged: isHasMerged, isHasMerged: isHasMerged,
isQuote: isQuote,
); );
default: default:
return ChatEventMessageActionLog( return ChatEventMessageActionLog(
@ -61,31 +89,55 @@ class ChatEvent extends StatelessWidget {
text: 'messageTypeUnsupported'.trParams({'type': item.type}), text: 'messageTypeUnsupported'.trParams({'type': item.type}),
isMerged: isMerged, isMerged: isMerged,
isHasMerged: isHasMerged, isHasMerged: isHasMerged,
isQuote: isQuote,
); );
} }
} }
Widget buildBody(BuildContext context) { Widget buildBody(BuildContext context) {
if (isContentPreviewing || isMerged) { if (isContentPreviewing || (isMerged && !isQuote)) {
return buildContent(); return Column(
children: [
if (item.body['quote_event'] != null && chatController != null)
buildQuote(),
buildContent(),
],
);
} else if (isQuote) { } else if (isQuote) {
return Row( return Row(
mainAxisAlignment: MainAxisAlignment.center, crossAxisAlignment: CrossAxisAlignment.end,
children: [ children: [
Transform.scale( const Opacity(
scaleX: -1, opacity: 0.75,
child: const FaIcon(FontAwesomeIcons.reply, size: 14), child: FaIcon(FontAwesomeIcons.quoteLeft, size: 14),
), ).paddingOnly(bottom: 4),
const SizedBox(width: 12),
AccountAvatar(content: item.sender.account.avatar, radius: 8),
const SizedBox(width: 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( Text(
item.sender.account.nick, item.sender.account.nick,
style: const TextStyle(fontWeight: FontWeight.bold), style: const TextStyle(fontWeight: FontWeight.bold),
), ),
Expanded(child: buildContent()), 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 { } else {
return Column( return Column(
key: Key('m${item.uuid}'), key: Key('m${item.uuid}'),
@ -121,6 +173,9 @@ class ChatEvent extends StatelessWidget {
Text(format(item.createdAt, locale: 'en_short')) Text(format(item.createdAt, locale: 'en_short'))
], ],
).paddingSymmetric(horizontal: 12), ).paddingSymmetric(horizontal: 12),
if (item.body['quote_event'] != null &&
chatController != null)
buildQuote(),
buildContent(), buildContent(),
], ],
), ),

View File

@ -1,16 +1,10 @@
import 'dart:math';
import 'package:flutter/material.dart'; import 'package:flutter/material.dart';
import 'package:flutter_markdown/flutter_markdown.dart';
import 'package:get/get.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 { class ChatEventMessageActionLog extends StatelessWidget {
final Widget icon; final Widget icon;
final String text; final String text;
final bool isQuote;
final bool isMerged; final bool isMerged;
final bool isHasMerged; final bool isHasMerged;
@ -20,6 +14,7 @@ class ChatEventMessageActionLog extends StatelessWidget {
required this.text, required this.text,
this.isMerged = false, this.isMerged = false,
this.isHasMerged = false, this.isHasMerged = false,
this.isQuote = false,
}); });
@override @override
@ -34,7 +29,7 @@ class ChatEventMessageActionLog extends StatelessWidget {
Text(text), Text(text),
], ],
).paddingOnly( ).paddingOnly(
left: isMerged ? 64 : 12, left: isQuote ? 0 : (isMerged ? 64 : 12),
top: 2, top: 2,
bottom: isHasMerged ? 2 : 0, bottom: isHasMerged ? 2 : 0,
), ),

View File

@ -23,6 +23,21 @@ class ChatEventMessage extends StatelessWidget {
this.isQuote = false, 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() { Widget buildContent() {
final body = EventMessageBody.fromJson(item.body); final body = EventMessageBody.fromJson(item.body);
final hasAttachment = body.attachments?.isNotEmpty ?? false; final hasAttachment = body.attachments?.isNotEmpty ?? false;
@ -41,9 +56,9 @@ class ChatEventMessage extends StatelessWidget {
); );
}, },
).paddingOnly( ).paddingOnly(
left: 12, left: isQuote ? 0 : 12,
right: 12, right: isQuote ? 0 : 12,
top: 2, top: body.quoteEvent == null ? 2 : 0,
bottom: hasAttachment ? 4 : (isHasMerged ? 2 : 0), bottom: hasAttachment ? 4 : (isHasMerged ? 2 : 0),
); );
} }
@ -51,18 +66,15 @@ class ChatEventMessage extends StatelessWidget {
Widget buildBody(BuildContext context) { Widget buildBody(BuildContext context) {
final body = EventMessageBody.fromJson(item.body); final body = EventMessageBody.fromJson(item.body);
if (isContentPreviewing || isQuote) { if (isContentPreviewing) {
return buildContent(); return buildContent();
} else if (isMerged) { } else if (isMerged) {
return Column( return Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: [ children: [
buildContent().paddingOnly(left: 52), buildContent().paddingOnly(left: 52),
if (body.attachments?.isNotEmpty ?? false) if (body.attachments?.isNotEmpty ?? false)
AttachmentList( buildAttachment(context).paddingOnly(left: 52, bottom: 4),
key: Key('m${item.uuid}attachments'),
parentId: item.uuid,
attachmentsId: body.attachments ?? List.empty(),
).paddingSymmetric(vertical: 4),
], ],
); );
} else { } else {
@ -71,16 +83,7 @@ class ChatEventMessage extends StatelessWidget {
children: [ children: [
buildContent(), buildContent(),
if (body.attachments?.isNotEmpty ?? false) if (body.attachments?.isNotEmpty ?? false)
SizedBox( buildAttachment(context).paddingOnly(bottom: 4),
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,
),
),
], ],
); );
} }