diff --git a/assets/translations/en.json b/assets/translations/en.json index 4ae8a2a..2ab46a6 100644 --- a/assets/translations/en.json +++ b/assets/translations/en.json @@ -163,5 +163,7 @@ "eventResourceTag": "Event {}", "messageDelete": "Delete message {}", "messageDeleteDescription": "Are you sure you want to delete this message? This operation is irreversible. You will leave a record of the deleted message.", - "messageDeleted": "Message {} has been deleted" + "messageDeleted": "Message {} has been deleted", + "messageEdited": "Message {} has been edited", + "messageEditedHint": "Edited" } diff --git a/assets/translations/zh.json b/assets/translations/zh.json index 2ac8996..b0b133e 100644 --- a/assets/translations/zh.json +++ b/assets/translations/zh.json @@ -163,5 +163,7 @@ "eventResourceTag": "消息 {}", "messageDelete": "删除消息 {}", "messageDeleteDescription": "你确定要删除这个消息吗?该操作不可撤销。同时您将留下一条删除消息的记录。", - "messageDeleted": "消息 {} 已被删除" + "messageDeleted": "消息 {} 已被删除", + "messageEdited": "消息 {} 已被编辑", + "messageEditedHint": "已编辑" } diff --git a/lib/controllers/chat_message_controller.dart b/lib/controllers/chat_message_controller.dart index df691fc..65a2359 100644 --- a/lib/controllers/chat_message_controller.dart +++ b/lib/controllers/chat_message_controller.dart @@ -1,6 +1,7 @@ import 'dart:async'; import 'dart:math' as math; +import 'package:collection/collection.dart'; import 'package:dio/dio.dart'; import 'package:flutter/material.dart'; import 'package:hive/hive.dart'; @@ -295,7 +296,7 @@ class ChatMessageController extends ChangeNotifier { final countToFetch = math.min(resp.data['count'] as int, 100); for (int idx = 0; idx < countToFetch; idx += kSingleBatchLoadLimit) { - await getMessages(kSingleBatchLoadLimit, idx); + await getMessages(kSingleBatchLoadLimit, idx, forceRemote: true); } } catch (err) { rethrow; @@ -349,10 +350,20 @@ class ChatMessageController extends ChangeNotifier { int take, int offset, { bool forceLocal = false, + bool forceRemote = false, }) async { late List out; - if (_box != null && (_box!.length >= take + offset || forceLocal)) { - out = _box!.values.skip(offset).take(take).toList().reversed.toList(); + if (_box != null && + (_box!.length >= take + offset || forceLocal) && + !forceRemote) { + out = _box!.keys + .toList() + .cast() + .sorted((a, b) => b.compareTo(a)) + .skip(offset) + .take(take) + .map((key) => _box!.get(key)!) + .toList(); } else { final resp = await _sn.client.get( '/cgi/im/channels/${channel!.keyPath}/events', diff --git a/lib/screens/chat/room.dart b/lib/screens/chat/room.dart index de35843..f3bb3c6 100644 --- a/lib/screens/chat/room.dart +++ b/lib/screens/chat/room.dart @@ -98,6 +98,7 @@ class _ChatRoomScreenState extends State { idx > 0 ? _messageController.messages[idx - 1] : null; final canMerge = nextMessage != null && + nextMessage.updatedAt == nextMessage.createdAt && nextMessage.senderId == message.senderId && message.createdAt .difference(nextMessage.createdAt) @@ -105,6 +106,7 @@ class _ChatRoomScreenState extends State { .abs() <= 3; final canMergePrevious = previousMessage != null && + message.updatedAt == message.createdAt && previousMessage.senderId == message.senderId && message.createdAt .difference(previousMessage.createdAt) diff --git a/lib/widgets/chat/chat_message.dart b/lib/widgets/chat/chat_message.dart index ebe4478..a8045a7 100644 --- a/lib/widgets/chat/chat_message.dart +++ b/lib/widgets/chat/chat_message.dart @@ -111,6 +111,10 @@ class ChatMessage extends StatelessWidget { ? data.sender.nick! : user!.nick, ).bold(), + if (data.updatedAt != data.createdAt) + Text( + 'messageEditedHint'.tr(), + ).fontSize(14).opacity(0.75).padding(left: 6), const Gap(6), Text( dateFormatter.format(data.createdAt.toLocal()), @@ -142,23 +146,33 @@ class ChatMessage extends StatelessWidget { onDelete: onDelete, ), )).padding(bottom: 4, top: isMerged ? 4 : 2), - if (data.body['text'] != null) + if (data.type == 'messages.edit') + Row( + children: [ + const Icon(Symbols.edit, size: 20), + const Gap(4), + Text( + 'messageEdited' + .tr(args: ['#${data.relatedEventId}']), + ), + ], + ).opacity(0.75) + else if (data.body['text'] != null) MarkdownTextContent( content: data.body['text'], isAutoWarp: true, ), - if (data.type == 'messages.delete' && - data.relatedEventId != null) + if (data.type == 'messages.delete') Row( children: [ - const Icon(Symbols.delete), - const Gap(8), + const Icon(Symbols.delete, size: 20), + const Gap(4), Text( 'messageDeleted' .tr(args: ['#${data.relatedEventId}']), ), ], - ), + ).opacity(0.75), ], ), )