From 8f8f2cabe0eed9b8628c17ea70c8bd0ed1ad7b05 Mon Sep 17 00:00:00 2001 From: otsmr Date: Mon, 27 Oct 2025 00:18:05 +0100 Subject: [PATCH] message deletion --- lib/main.dart | 3 +- lib/src/database/daos/messages.dao.dart | 9 ++- lib/src/database/tables/messages.table.dart | 2 +- lib/src/database/twonly.db.g.dart | 4 +- lib/src/localization/app_de.arb | 3 +- lib/src/localization/app_en.arb | 3 +- .../generated/app_localizations.dart | 10 ++- .../generated/app_localizations_de.dart | 5 +- .../generated/app_localizations_en.dart | 5 +- .../chat_list_entry.dart | 79 +++++++++++-------- .../chat_text_entry.dart | 27 +++++-- .../message_context_menu.dart | 26 +++++- 12 files changed, 120 insertions(+), 56 deletions(-) diff --git a/lib/main.dart b/lib/main.dart index cb8c754..a951611 100644 --- a/lib/main.dart +++ b/lib/main.dart @@ -8,6 +8,7 @@ import 'package:flutter/services.dart'; import 'package:path/path.dart'; import 'package:path_provider/path_provider.dart'; import 'package:provider/provider.dart'; +import 'package:twonly/app.dart'; import 'package:twonly/globals.dart'; import 'package:twonly/src/database/twonly.db.dart'; import 'package:twonly/src/providers/connection.provider.dart'; @@ -19,8 +20,6 @@ import 'package:twonly/src/services/fcm.service.dart'; import 'package:twonly/src/utils/log.dart'; import 'package:twonly/src/utils/storage.dart'; -import 'app.dart'; - void main() async { WidgetsFlutterBinding.ensureInitialized(); await initFCMService(); diff --git a/lib/src/database/daos/messages.dao.dart b/lib/src/database/daos/messages.dao.dart index e3e2d43..7425f5c 100644 --- a/lib/src/database/daos/messages.dao.dart +++ b/lib/src/database/daos/messages.dao.dart @@ -155,12 +155,15 @@ class MessagesDao extends DatabaseAccessor with _$MessagesDaoMixin { } Future handleMessageDeletion( - int contactId, + int? contactId, String messageId, DateTime timestamp, ) async { final msg = await getMessageById(messageId).getSingleOrNull(); - if (msg == null || msg.senderId != contactId) return; + if (msg == null || msg.senderId != contactId) { + Log.error('Message does not exists or contact is not owner.'); + return; + } if (msg.mediaId != null) { await (delete(mediaFiles)..where((t) => t.mediaId.equals(msg.mediaId!))) .go(); @@ -176,7 +179,7 @@ class MessagesDao extends DatabaseAccessor with _$MessagesDaoMixin { await (update(messages) ..where( - (t) => t.messageId.equals(messageId) & t.senderId.equals(contactId), + (t) => t.messageId.equals(messageId), )) .write( const MessagesCompanion( diff --git a/lib/src/database/tables/messages.table.dart b/lib/src/database/tables/messages.table.dart index 628a720..5a9aa3f 100644 --- a/lib/src/database/tables/messages.table.dart +++ b/lib/src/database/tables/messages.table.dart @@ -20,7 +20,7 @@ class Messages extends Table { TextColumn get content => text().nullable()(); TextColumn get mediaId => text() .nullable() - .references(MediaFiles, #mediaId, onDelete: KeyAction.cascade)(); + .references(MediaFiles, #mediaId, onDelete: KeyAction.setNull)(); BoolColumn get mediaStored => boolean().withDefault(const Constant(false))(); diff --git a/lib/src/database/twonly.db.g.dart b/lib/src/database/twonly.db.g.dart index 0a501bb..4ec0de5 100644 --- a/lib/src/database/twonly.db.g.dart +++ b/lib/src/database/twonly.db.g.dart @@ -2243,7 +2243,7 @@ class $MessagesTable extends Messages with TableInfo<$MessagesTable, Message> { type: DriftSqlType.string, requiredDuringInsert: false, defaultConstraints: GeneratedColumn.constraintIsAlways( - 'REFERENCES media_files (media_id) ON DELETE CASCADE')); + 'REFERENCES media_files (media_id) ON DELETE SET NULL')); static const VerificationMeta _mediaStoredMeta = const VerificationMeta('mediaStored'); @override @@ -6464,7 +6464,7 @@ abstract class _$TwonlyDB extends GeneratedDatabase { on: TableUpdateQuery.onTableName('media_files', limitUpdateKind: UpdateKind.delete), result: [ - TableUpdate('messages', kind: UpdateKind.delete), + TableUpdate('messages', kind: UpdateKind.update), ], ), WritePropagation( diff --git a/lib/src/localization/app_de.arb b/lib/src/localization/app_de.arb index f721f6a..25776e0 100644 --- a/lib/src/localization/app_de.arb +++ b/lib/src/localization/app_de.arb @@ -291,7 +291,8 @@ "tutorialChatMessagesReopenMessageDesc": "Wenn dein Freund dir ein Bild oder Video mit unendlicher Anzeigezeit gesendet hat, kannst du es bis zum Neustart der App jederzeit erneut öffnen. Um dies zu tun, musst du einfach doppelt auf die Nachricht klicken. Dein Freund erhält dann eine Benachrichtigung, dass du das Bild erneut angesehen hast.", "memoriesEmpty": "Sobald du Bilder oder Videos speicherst, landen sie hier in deinen Erinnerungen.", "deleteTitle": "Bist du dir sicher?", - "deleteOkBtn": "Für mich löschen", + "deleteOkBtnForAll": "Für alle löschen", + "deleteOkBtnForMe": "Für mich löschen", "deleteImageTitle": "Bist du dir sicher?", "deleteImageBody": "Das Bild wird unwiderruflich gelöscht.", "backupNoticeTitle": "Kein Backup konfiguriert", diff --git a/lib/src/localization/app_en.arb b/lib/src/localization/app_en.arb index 9d1c04d..9f2cc07 100644 --- a/lib/src/localization/app_en.arb +++ b/lib/src/localization/app_en.arb @@ -443,7 +443,8 @@ "tutorialChatMessagesReopenMessageDesc": "If your friend has sent you a picture or video with infinite display time, you can open it again at any time until you restart the app. To do this, simply double-click on the message. Your friend will then receive a notification that you have viewed the picture again.", "memoriesEmpty": "As soon as you save pictures or videos, they end up here in your memories.", "deleteTitle": "Are you sure?", - "deleteOkBtn": "Delete for me", + "deleteOkBtnForAll": "Delete for all", + "deleteOkBtnForMe": "Delete for me", "deleteImageTitle": "Are you sure?", "deleteImageBody": "The image will be irrevocably deleted.", "settingsBackup": "Backup", diff --git a/lib/src/localization/generated/app_localizations.dart b/lib/src/localization/generated/app_localizations.dart index 9191830..b8fd2e1 100644 --- a/lib/src/localization/generated/app_localizations.dart +++ b/lib/src/localization/generated/app_localizations.dart @@ -1760,11 +1760,17 @@ abstract class AppLocalizations { /// **'Are you sure?'** String get deleteTitle; - /// No description provided for @deleteOkBtn. + /// No description provided for @deleteOkBtnForAll. + /// + /// In en, this message translates to: + /// **'Delete for all'** + String get deleteOkBtnForAll; + + /// No description provided for @deleteOkBtnForMe. /// /// In en, this message translates to: /// **'Delete for me'** - String get deleteOkBtn; + String get deleteOkBtnForMe; /// No description provided for @deleteImageTitle. /// diff --git a/lib/src/localization/generated/app_localizations_de.dart b/lib/src/localization/generated/app_localizations_de.dart index 614afb5..9aa1c2c 100644 --- a/lib/src/localization/generated/app_localizations_de.dart +++ b/lib/src/localization/generated/app_localizations_de.dart @@ -930,7 +930,10 @@ class AppLocalizationsDe extends AppLocalizations { String get deleteTitle => 'Bist du dir sicher?'; @override - String get deleteOkBtn => 'Für mich löschen'; + String get deleteOkBtnForAll => 'Für alle löschen'; + + @override + String get deleteOkBtnForMe => 'Für mich löschen'; @override String get deleteImageTitle => 'Bist du dir sicher?'; diff --git a/lib/src/localization/generated/app_localizations_en.dart b/lib/src/localization/generated/app_localizations_en.dart index 523ae87..0394caa 100644 --- a/lib/src/localization/generated/app_localizations_en.dart +++ b/lib/src/localization/generated/app_localizations_en.dart @@ -924,7 +924,10 @@ class AppLocalizationsEn extends AppLocalizations { String get deleteTitle => 'Are you sure?'; @override - String get deleteOkBtn => 'Delete for me'; + String get deleteOkBtnForAll => 'Delete for all'; + + @override + String get deleteOkBtnForMe => 'Delete for me'; @override String get deleteImageTitle => 'Are you sure?'; diff --git a/lib/src/views/chats/chat_messages_components/chat_list_entry.dart b/lib/src/views/chats/chat_messages_components/chat_list_entry.dart index 9da67a7..322c049 100644 --- a/lib/src/views/chats/chat_messages_components/chat_list_entry.dart +++ b/lib/src/views/chats/chat_messages_components/chat_list_entry.dart @@ -118,43 +118,52 @@ class _ChatListEntryState extends State { alignment: right ? Alignment.centerRight : Alignment.centerLeft, children: [ - Column( - children: [ - ResponseContainer( - msg: widget.message, - group: widget.group, - mediaService: mediaService, - borderRadius: borderRadius, - scrollToMessage: widget.scrollToMessage, - child: (widget.message.type == MessageType.text) - ? ChatTextEntry( - message: widget.message, - nextMessage: widget.nextMessage, - borderRadius: borderRadius, - minWidth: reactionsForWidth * 43, - ) - : (mediaService == null) - ? null - : ChatMediaEntry( - message: widget.message, - group: widget.group, - mediaService: mediaService!, - galleryItems: widget.galleryItems, - ), - ), - if (reactionsForWidth > 0) - const SizedBox(height: 20, width: 10), - ], - ), - Positioned( - bottom: -20, - left: 5, - right: 5, - child: ReactionRow( + if (widget.message.isDeletedFromSender) + ChatTextEntry( message: widget.message, - reactions: reactions, + nextMessage: widget.nextMessage, + borderRadius: borderRadius, + minWidth: reactionsForWidth * 43, + ) + else + Column( + children: [ + ResponseContainer( + msg: widget.message, + group: widget.group, + mediaService: mediaService, + borderRadius: borderRadius, + scrollToMessage: widget.scrollToMessage, + child: (widget.message.type == MessageType.text) + ? ChatTextEntry( + message: widget.message, + nextMessage: widget.nextMessage, + borderRadius: borderRadius, + minWidth: reactionsForWidth * 43, + ) + : (mediaService == null) + ? null + : ChatMediaEntry( + message: widget.message, + group: widget.group, + mediaService: mediaService!, + galleryItems: widget.galleryItems, + ), + ), + if (reactionsForWidth > 0) + const SizedBox(height: 20, width: 10), + ], + ), + if (!widget.message.isDeletedFromSender) + Positioned( + bottom: -20, + left: 5, + right: 5, + child: ReactionRow( + message: widget.message, + reactions: reactions, + ), ), - ), ], ), ), diff --git a/lib/src/views/chats/chat_messages_components/chat_text_entry.dart b/lib/src/views/chats/chat_messages_components/chat_text_entry.dart index 3154ce2..1369ac3 100644 --- a/lib/src/views/chats/chat_messages_components/chat_text_entry.dart +++ b/lib/src/views/chats/chat_messages_components/chat_text_entry.dart @@ -23,7 +23,12 @@ class ChatTextEntry extends StatelessWidget { @override Widget build(BuildContext context) { - final text = message.content ?? ''; + var text = message.content ?? ''; + + if (message.isDeletedFromSender) { + text = 'Nachricht wurde gelöscht.'; + } + if (EmojiAnimation.supported(text)) { return Container( constraints: const BoxConstraints( @@ -37,11 +42,24 @@ class ChatTextEntry extends StatelessWidget { ); } - final displayTime = !combineTextMessageWithNext(message, nextMessage); + var displayTime = !combineTextMessageWithNext(message, nextMessage); var spacerWidth = minWidth - measureTextWidth(text) - 53; if (spacerWidth < 0) spacerWidth = 0; + Color? color; + var expanded = false; + if (message.quotesMessageId == null) { + color = getMessageColor(message); + } + if (message.isDeletedFromSender) { + color = context.color.surfaceBright; + displayTime = false; + } else if (measureTextWidth(text) > 270 || + message.quotesMessageId != null) { + expanded = true; + } + return Container( constraints: BoxConstraints( maxWidth: MediaQuery.of(context).size.width * 0.8, @@ -49,15 +67,14 @@ class ChatTextEntry extends StatelessWidget { ), padding: const EdgeInsets.only(left: 10, top: 6, bottom: 6, right: 10), decoration: BoxDecoration( - color: - message.quotesMessageId == null ? getMessageColor(message) : null, + color: color, borderRadius: borderRadius, ), child: Row( mainAxisSize: MainAxisSize.min, crossAxisAlignment: CrossAxisAlignment.end, children: [ - if (measureTextWidth(text) > 270 || message.quotesMessageId != null) + if (expanded) Expanded( child: BetterText(text: text), ) diff --git a/lib/src/views/chats/chat_messages_components/message_context_menu.dart b/lib/src/views/chats/chat_messages_components/message_context_menu.dart index 7d6f592..1fab50d 100644 --- a/lib/src/views/chats/chat_messages_components/message_context_menu.dart +++ b/lib/src/views/chats/chat_messages_components/message_context_menu.dart @@ -85,10 +85,32 @@ class MessageContextMenu extends StatelessWidget { context, context.lang.deleteTitle, null, - customOk: context.lang.deleteOkBtn, + customOk: + (message.senderId == null && !message.isDeletedFromSender) + ? context.lang.deleteOkBtnForAll + : context.lang.deleteOkBtnForMe, ); if (delete) { - await twonlyDB.messagesDao.deleteMessagesById(message.messageId); + if (message.senderId == null && !message.isDeletedFromSender) { + await twonlyDB.messagesDao.handleMessageDeletion( + null, + message.messageId, + DateTime.now(), + ); + await sendCipherTextToGroup( + message.groupId, + pb.EncryptedContent( + messageUpdate: pb.EncryptedContent_MessageUpdate( + type: pb.EncryptedContent_MessageUpdate_Type.DELETE, + senderMessageId: message.messageId, + ), + ), + null, + ); + } else { + await twonlyDB.messagesDao + .deleteMessagesById(message.messageId); + } } }, child: const FaIcon(FontAwesomeIcons.trash),