Fix/message deletion issues (#1697)

* SES-2810 - Removing the screenshot privacy toggle

* SES-2813 - clickable only when there is a 'follow settings'

* SES-2815 - proper icon and spacing for deleted messages

* Simplified deletion dialog to be reused for note to self and the rest as only the labels change

* SES-2819 - Do not show a reaction on a deleted message

* Fixing up deletion details

Message view hides reactions completely if the message is marked as deleted
All  messages can now show the 'Delete' long press option
Community messages should be removed completely not marked as deleted

* Revert "SES-2819 - Do not show a reaction on a deleted message"

This reverts commit 711e31a43a.

* Avoiding adding reactions if the message is marked as deleted

* Removing uneeded icon

* Deletion handled by VM so menu item is always visible

* SES-2811 - Do not attempt to send a failed message marked as deleted

* SES-2818 - Making sure we set the lastMessage in a thread properly, without using 'marked as deleted' messages

* SES-2464 - changed the behaviour to finish the convo activity but instead refresh the sarch on resume

* removing log
This commit is contained in:
ThomasSession 2024-10-21 15:45:02 +11:00 committed by GitHub
parent 952bafaf1d
commit 4b01fcec5e
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
16 changed files with 89 additions and 19 deletions

View File

@ -145,6 +145,12 @@ class DatabaseAttachmentProvider(context: Context, helper: SQLCipherOpenHelper)
return smsDatabase.isOutgoingMessage(timestamp) || mmsDatabase.isOutgoingMessage(timestamp)
}
override fun isDeletedMessage(timestamp: Long): Boolean {
val smsDatabase = DatabaseComponent.get(context).smsDatabase()
val mmsDatabase = DatabaseComponent.get(context).mmsDatabase()
return smsDatabase.isDeletedMessage(timestamp) || mmsDatabase.isDeletedMessage(timestamp)
}
override fun handleSuccessfulAttachmentUpload(attachmentId: Long, attachmentStream: SignalServiceAttachmentStream, attachmentKey: ByteArray, uploadResult: UploadResult) {
val database = DatabaseComponent.get(context).attachmentDatabase()
val databaseAttachment = getDatabaseAttachment(attachmentId) ?: return

View File

@ -851,8 +851,8 @@ class ConversationActivityV2 : PassphraseRequiredActionBarActivity(), InputBarDe
binding.messageRequestBar.visibility = View.GONE
}
if (!uiState.conversationExists && !isFinishing) {
// Conversation should be deleted now, go to homepage with a cleared stack
baseContext.startHomeActivity(isFromOnboarding = false, isNewAccount = false)
// Conversation should be deleted now
finish()
}
// show or hide the text input

View File

@ -42,18 +42,6 @@ class ConversationActionModeCallback(private val adapter: ConversationAdapter, p
val blindedPublicKey = openGroup?.publicKey?.let { SodiumUtilities.blindedKeyPair(it, edKeyPair)?.publicKey?.asBytes }
?.let { AccountId(IdPrefix.BLINDED, it) }?.hexString
// Embedded function
fun userCanDeleteSelectedItems(): Boolean {
// admin can delete all combinations
if(adapter.isAdmin) return true
val allSentByCurrentUser = selectedItems.all { it.isOutgoing }
val allReceivedByCurrentUser = selectedItems.all { !it.isOutgoing }
if (openGroup == null) { return allSentByCurrentUser || allReceivedByCurrentUser }
if (allSentByCurrentUser) { return true }
return OpenGroupManager.isUserModerator(context, openGroup.groupId, userPublicKey, blindedPublicKey)
}
// Embedded function
fun userCanBanSelectedUsers(): Boolean {
if (openGroup == null) { return false }
@ -67,7 +55,7 @@ class ConversationActionModeCallback(private val adapter: ConversationAdapter, p
// Delete message
menu.findItem(R.id.menu_context_delete_message).isVisible = userCanDeleteSelectedItems()
menu.findItem(R.id.menu_context_delete_message).isVisible = true // can always delete since delete logic will be handled by the VM
// Ban user
menu.findItem(R.id.menu_context_ban_user).isVisible = userCanBanSelectedUsers()
// Ban and delete all

View File

@ -106,6 +106,23 @@ class MmsDatabase(context: Context, databaseHelper: SQLCipherOpenHelper) : Messa
.any { MmsSmsColumns.Types.isOutgoingMessageType(it) }
}
fun isDeletedMessage(timestamp: Long): Boolean =
databaseHelper.writableDatabase.query(
TABLE_NAME,
arrayOf(ID, THREAD_ID, MESSAGE_BOX, ADDRESS),
DATE_SENT + " = ?",
arrayOf(timestamp.toString()),
null,
null,
null,
null
).use { cursor ->
cursor.asSequence()
.map { cursor.getColumnIndexOrThrow(MESSAGE_BOX) }
.map(cursor::getLong)
.any { MmsSmsColumns.Types.isDeletedMessage(it) }
}
fun incrementReceiptCount(
messageId: SyncMessageId,
timestamp: Long,

View File

@ -17,6 +17,9 @@
package org.thoughtcrime.securesms.database;
import static org.thoughtcrime.securesms.database.MmsDatabase.MESSAGE_BOX;
import static org.thoughtcrime.securesms.database.MmsSmsColumns.Types.BASE_DELETED_INCOMING_TYPE;
import static org.thoughtcrime.securesms.database.MmsSmsColumns.Types.BASE_DELETED_OUTGOING_TYPE;
import static org.thoughtcrime.securesms.database.MmsSmsColumns.Types.BASE_TYPE_MASK;
import android.content.Context;
import android.database.Cursor;
@ -96,6 +99,14 @@ public class MmsSmsDatabase extends Database {
}
}
public @Nullable MessageRecord getNonDeletedMessageForTimestamp(long timestamp) {
String selection = MmsSmsColumns.NORMALIZED_DATE_SENT + " = " + timestamp;
try (Cursor cursor = queryTables(PROJECTION, selection, null, null)) {
MmsSmsDatabase.Reader reader = readerFor(cursor);
return reader.getNext();
}
}
public @Nullable MessageRecord getMessageFor(long timestamp, String serializedAuthor) {
return getMessageFor(timestamp, serializedAuthor, true);
}
@ -323,7 +334,9 @@ public class MmsSmsDatabase extends Database {
public long getLastMessageTimestamp(long threadId) {
String order = MmsSmsColumns.NORMALIZED_DATE_SENT + " DESC";
String selection = MmsSmsColumns.THREAD_ID + " = " + threadId;
// make sure the last message isn't marked as deleted
String selection = MmsSmsColumns.THREAD_ID + " = " + threadId + " AND " +
"(ifnull("+SmsDatabase.TYPE+", "+MmsDatabase.MESSAGE_BOX+") & "+BASE_TYPE_MASK+") NOT IN ("+BASE_DELETED_OUTGOING_TYPE+", "+BASE_DELETED_INCOMING_TYPE+")"; // this ugly line checks whether the type is deleted (incoming or outgoing) for either the sms table or the mms table
try (Cursor cursor = queryTables(PROJECTION, selection, order, "1")) {
if (cursor.moveToFirst()) {

View File

@ -243,6 +243,7 @@ public class SmsDatabase extends MessagingDatabase {
contentValues.put(READ, 1);
contentValues.put(BODY, displayedMessage);
contentValues.put(HAS_MENTION, 0);
contentValues.put(STATUS, Status.STATUS_NONE);
database.update(TABLE_NAME, contentValues, ID_WHERE, new String[] {String.valueOf(messageId)});
updateTypeBitmask(messageId, Types.BASE_TYPE_MASK,
@ -299,6 +300,28 @@ public class SmsDatabase extends MessagingDatabase {
return isOutgoing;
}
public boolean isDeletedMessage(long timestamp) {
SQLiteDatabase database = databaseHelper.getWritableDatabase();
Cursor cursor = null;
boolean isDeleted = false;
try {
cursor = database.query(TABLE_NAME, new String[] { ID, THREAD_ID, ADDRESS, TYPE },
DATE_SENT + " = ?", new String[] { String.valueOf(timestamp) },
null, null, null, null);
while (cursor.moveToNext()) {
if (Types.isDeletedMessage(cursor.getLong(cursor.getColumnIndexOrThrow(TYPE)))) {
isDeleted = true;
}
}
} finally {
if (cursor != null) cursor.close();
}
return isDeleted;
}
public void incrementReceiptCount(SyncMessageId messageId, boolean deliveryReceipt, boolean readReceipt) {
SQLiteDatabase database = databaseHelper.getWritableDatabase();
Cursor cursor = null;

View File

@ -372,6 +372,11 @@ class HomeActivity : PassphraseRequiredActionBarActivity(),
binding.seedReminderView.isVisible = false
}
// refresh search on resume, in case we a conversation was deleted
if (binding.globalSearchRecycler.isVisible){
globalSearchViewModel.refresh()
}
updateLegacyConfigView()
// Sync config changes if there are any

Binary file not shown.

Before

Width:  |  Height:  |  Size: 1.5 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 835 B

Binary file not shown.

Before

Width:  |  Height:  |  Size: 1.7 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 3.1 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 4.3 KiB

View File

@ -31,7 +31,8 @@
android:layout_height="wrap_content"
android:background="?attr/selectableItemBackgroundBorderless"
android:padding="8dp"
android:src="@drawable/ic_trash_filled_32" />
app:tint="@color/white"
android:src="@drawable/ic_delete" />
<ImageView
android:id="@+id/scribble_undo_button"

View File

@ -38,6 +38,7 @@ interface MessageDataProvider {
fun updateAudioAttachmentDuration(attachmentId: AttachmentId, durationMs: Long, threadId: Long)
fun isMmsOutgoing(mmsMessageId: Long): Boolean
fun isOutgoingMessage(timestamp: Long): Boolean
fun isDeletedMessage(timestamp: Long): Boolean
fun handleSuccessfulAttachmentUpload(attachmentId: Long, attachmentStream: SignalServiceAttachmentStream, attachmentKey: ByteArray, uploadResult: UploadResult)
fun handleFailedAttachmentUpload(attachmentId: Long)
fun getMessageForQuote(timestamp: Long, author: Address): Triple<Long, Boolean, String>?

View File

@ -37,6 +37,13 @@ class MessageSendJob(val message: Message, val destination: Destination) : Job {
val message = message as? VisibleMessage
val storage = MessagingModuleConfiguration.shared.storage
// do not attempt to send if the message is marked as deleted
message?.sentTimestamp?.let{
if(messageDataProvider.isDeletedMessage(it)){
return@execute
}
}
val sentTimestamp = this.message.sentTimestamp
val sender = storage.getUserPublicKey()
if (sentTimestamp != null && sender != null) {
@ -107,7 +114,10 @@ class MessageSendJob(val message: Message, val destination: Destination) : Job {
Log.w(TAG, "Failed to send $message::class.simpleName.")
val message = message as? VisibleMessage
if (message != null) {
if (!MessagingModuleConfiguration.shared.messageDataProvider.isOutgoingMessage(message.sentTimestamp!!)) {
if (
MessagingModuleConfiguration.shared.messageDataProvider.isDeletedMessage(message.sentTimestamp!!) ||
!MessagingModuleConfiguration.shared.messageDataProvider.isOutgoingMessage(message.sentTimestamp!!)
) {
return // The message has been deleted
}
}

View File

@ -469,9 +469,15 @@ object MessageSender {
fun handleFailedMessageSend(message: Message, error: Exception, isSyncMessage: Boolean = false) {
val storage = MessagingModuleConfiguration.shared.storage
val timestamp = message.sentTimestamp!!
// no need to handle if message is marked as deleted
if(MessagingModuleConfiguration.shared.messageDataProvider.isDeletedMessage(message.sentTimestamp!!)){
return
}
val userPublicKey = storage.getUserPublicKey()!!
val timestamp = message.sentTimestamp!!
val author = message.sender ?: userPublicKey
if (isSyncMessage) storage.markAsSyncFailed(timestamp, author, error)