From ee1fcd87529ae3db65195134b6689c7c880999f2 Mon Sep 17 00:00:00 2001 From: LittleSheep Date: Sun, 4 May 2025 01:43:27 +0800 Subject: [PATCH] :sparkles: Edit, delete message --- assets/i18n/en-US.json | 3 +- lib/database/message_repository.dart | 1 + lib/models/file.dart | 12 ++++++ lib/screens/chat/room.dart | 64 ++++++++++++++++++++-------- 4 files changed, 62 insertions(+), 18 deletions(-) diff --git a/assets/i18n/en-US.json b/assets/i18n/en-US.json index 87b26b3..9e61276 100644 --- a/assets/i18n/en-US.json +++ b/assets/i18n/en-US.json @@ -89,5 +89,6 @@ "permissionModerator": "Moderator", "permissionMember": "Member", "reply": "Reply", - "forward": "Forward" + "forward": "Forward", + "edited": "Edited" } diff --git a/lib/database/message_repository.dart b/lib/database/message_repository.dart index 3e65440..b3352aa 100644 --- a/lib/database/message_repository.dart +++ b/lib/database/message_repository.dart @@ -441,6 +441,7 @@ class MessageRepository { await _database.saveMessage(_database.messageToCompanion(message)); return message; } catch (e) { + if (e is DioException) return null; // Handle errors rethrow; } diff --git a/lib/models/file.dart b/lib/models/file.dart index 75dc15b..5d2e03a 100644 --- a/lib/models/file.dart +++ b/lib/models/file.dart @@ -16,6 +16,18 @@ abstract class UniversalFile with _$UniversalFile { bool get isOnCloud => data is SnCloudFile; bool get isOnDevice => !isOnCloud; + + factory UniversalFile.fromAttachment(SnCloudFile attachment) { + return UniversalFile( + data: attachment, + type: switch (attachment.mimeType?.split('/').firstOrNull) { + 'image' => UniversalFileType.image, + 'audio' => UniversalFileType.audio, + 'video' => UniversalFileType.video, + _ => UniversalFileType.file, + }, + ); + } } @freezed diff --git a/lib/screens/chat/room.dart b/lib/screens/chat/room.dart index dbea7d1..9158a0c 100644 --- a/lib/screens/chat/room.dart +++ b/lib/screens/chat/room.dart @@ -472,6 +472,22 @@ class ChatRoomScreen extends HookConsumerWidget { case _MessageBubbleAction.edit: messageEditingTo.value = message.toRemoteMessage(); + messageController.text = + messageEditingTo + .value + ?.content ?? + ''; + attachments.value = + messageEditingTo + .value! + .attachments + .map( + (e) => + UniversalFile.fromAttachment( + e, + ), + ) + .toList(); case _MessageBubbleAction.forward: messageForwardingTo.value = message.toRemoteMessage(); @@ -659,11 +675,14 @@ class _MessageBubble extends HookConsumerWidget { @override Widget build(BuildContext context, WidgetRef ref) { - final messagesNotifier = ref.watch( - messagesProvider(message.roomId).notifier, - ); - - final textColor = isCurrentUser ? Colors.white : Colors.black; + final textColor = + isCurrentUser + ? Theme.of(context).colorScheme.onPrimaryContainer + : Theme.of(context).colorScheme.onSurfaceVariant; + final containerColor = + isCurrentUser + ? Theme.of(context).colorScheme.primaryContainer.withOpacity(0.5) + : Theme.of(context).colorScheme.surfaceContainer; return ContextMenuWidget( menuProvider: (_) { @@ -709,6 +728,7 @@ class _MessageBubble extends HookConsumerWidget { child: Padding( padding: const EdgeInsets.symmetric(horizontal: 12, vertical: 4), child: Row( + crossAxisAlignment: CrossAxisAlignment.end, mainAxisAlignment: isCurrentUser ? MainAxisAlignment.end : MainAxisAlignment.start, children: [ @@ -731,12 +751,7 @@ class _MessageBubble extends HookConsumerWidget { vertical: 8, ), decoration: BoxDecoration( - color: - isCurrentUser - ? Theme.of( - context, - ).colorScheme.primary.withOpacity(0.8) - : Colors.grey.shade200, + color: containerColor, borderRadius: BorderRadius.circular(16), ), child: Column( @@ -760,15 +775,24 @@ class _MessageBubble extends HookConsumerWidget { ), const Gap(4), Row( + spacing: 4, mainAxisSize: MainAxisSize.min, children: [ Text( DateFormat.Hm().format(message.createdAt.toLocal()), style: TextStyle(fontSize: 10, color: textColor), ), - const Gap(4), + if (message.toRemoteMessage().editedAt != null) + Text( + 'edited'.tr().toLowerCase(), + style: TextStyle(fontSize: 10, color: textColor), + ), if (isCurrentUser) - _buildStatusIcon(context, message.status), + _buildStatusIcon( + context, + message.status, + textColor, + ), ], ), ], @@ -794,12 +818,16 @@ class _MessageBubble extends HookConsumerWidget { ); } - Widget _buildStatusIcon(BuildContext context, MessageStatus status) { + Widget _buildStatusIcon( + BuildContext context, + MessageStatus status, + Color textColor, + ) { switch (status) { case MessageStatus.pending: - return const Icon(Icons.access_time, size: 12, color: Colors.white70); + return Icon(Icons.access_time, size: 12, color: textColor); case MessageStatus.sent: - return const Icon(Icons.check, size: 12, color: Colors.white70); + return Icon(Icons.check, size: 12, color: textColor); case MessageStatus.failed: return Consumer( builder: @@ -850,7 +878,9 @@ class _MessageQuoteWidget extends HookConsumerWidget { borderRadius: BorderRadius.all(Radius.circular(8)), child: Container( padding: EdgeInsets.symmetric(vertical: 4, horizontal: 6), - color: Theme.of(context).colorScheme.surface.withOpacity(0.2), + color: Theme.of( + context, + ).colorScheme.primaryFixedDim.withOpacity(0.4), child: Column( crossAxisAlignment: CrossAxisAlignment.start, children: [