mirror of
https://github.com/oxen-io/session-android.git
synced 2024-11-23 18:15:22 +00:00
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:
parent
952bafaf1d
commit
4b01fcec5e
@ -145,6 +145,12 @@ class DatabaseAttachmentProvider(context: Context, helper: SQLCipherOpenHelper)
|
|||||||
return smsDatabase.isOutgoingMessage(timestamp) || mmsDatabase.isOutgoingMessage(timestamp)
|
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) {
|
override fun handleSuccessfulAttachmentUpload(attachmentId: Long, attachmentStream: SignalServiceAttachmentStream, attachmentKey: ByteArray, uploadResult: UploadResult) {
|
||||||
val database = DatabaseComponent.get(context).attachmentDatabase()
|
val database = DatabaseComponent.get(context).attachmentDatabase()
|
||||||
val databaseAttachment = getDatabaseAttachment(attachmentId) ?: return
|
val databaseAttachment = getDatabaseAttachment(attachmentId) ?: return
|
||||||
|
@ -851,8 +851,8 @@ class ConversationActivityV2 : PassphraseRequiredActionBarActivity(), InputBarDe
|
|||||||
binding.messageRequestBar.visibility = View.GONE
|
binding.messageRequestBar.visibility = View.GONE
|
||||||
}
|
}
|
||||||
if (!uiState.conversationExists && !isFinishing) {
|
if (!uiState.conversationExists && !isFinishing) {
|
||||||
// Conversation should be deleted now, go to homepage with a cleared stack
|
// Conversation should be deleted now
|
||||||
baseContext.startHomeActivity(isFromOnboarding = false, isNewAccount = false)
|
finish()
|
||||||
}
|
}
|
||||||
|
|
||||||
// show or hide the text input
|
// show or hide the text input
|
||||||
|
@ -42,18 +42,6 @@ class ConversationActionModeCallback(private val adapter: ConversationAdapter, p
|
|||||||
val blindedPublicKey = openGroup?.publicKey?.let { SodiumUtilities.blindedKeyPair(it, edKeyPair)?.publicKey?.asBytes }
|
val blindedPublicKey = openGroup?.publicKey?.let { SodiumUtilities.blindedKeyPair(it, edKeyPair)?.publicKey?.asBytes }
|
||||||
?.let { AccountId(IdPrefix.BLINDED, it) }?.hexString
|
?.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
|
// Embedded function
|
||||||
fun userCanBanSelectedUsers(): Boolean {
|
fun userCanBanSelectedUsers(): Boolean {
|
||||||
if (openGroup == null) { return false }
|
if (openGroup == null) { return false }
|
||||||
@ -67,7 +55,7 @@ class ConversationActionModeCallback(private val adapter: ConversationAdapter, p
|
|||||||
|
|
||||||
|
|
||||||
// Delete message
|
// 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
|
// Ban user
|
||||||
menu.findItem(R.id.menu_context_ban_user).isVisible = userCanBanSelectedUsers()
|
menu.findItem(R.id.menu_context_ban_user).isVisible = userCanBanSelectedUsers()
|
||||||
// Ban and delete all
|
// Ban and delete all
|
||||||
|
@ -106,6 +106,23 @@ class MmsDatabase(context: Context, databaseHelper: SQLCipherOpenHelper) : Messa
|
|||||||
.any { MmsSmsColumns.Types.isOutgoingMessageType(it) }
|
.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(
|
fun incrementReceiptCount(
|
||||||
messageId: SyncMessageId,
|
messageId: SyncMessageId,
|
||||||
timestamp: Long,
|
timestamp: Long,
|
||||||
|
@ -17,6 +17,9 @@
|
|||||||
package org.thoughtcrime.securesms.database;
|
package org.thoughtcrime.securesms.database;
|
||||||
|
|
||||||
import static org.thoughtcrime.securesms.database.MmsDatabase.MESSAGE_BOX;
|
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.content.Context;
|
||||||
import android.database.Cursor;
|
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) {
|
public @Nullable MessageRecord getMessageFor(long timestamp, String serializedAuthor) {
|
||||||
return getMessageFor(timestamp, serializedAuthor, true);
|
return getMessageFor(timestamp, serializedAuthor, true);
|
||||||
}
|
}
|
||||||
@ -323,7 +334,9 @@ public class MmsSmsDatabase extends Database {
|
|||||||
|
|
||||||
public long getLastMessageTimestamp(long threadId) {
|
public long getLastMessageTimestamp(long threadId) {
|
||||||
String order = MmsSmsColumns.NORMALIZED_DATE_SENT + " DESC";
|
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")) {
|
try (Cursor cursor = queryTables(PROJECTION, selection, order, "1")) {
|
||||||
if (cursor.moveToFirst()) {
|
if (cursor.moveToFirst()) {
|
||||||
|
@ -243,6 +243,7 @@ public class SmsDatabase extends MessagingDatabase {
|
|||||||
contentValues.put(READ, 1);
|
contentValues.put(READ, 1);
|
||||||
contentValues.put(BODY, displayedMessage);
|
contentValues.put(BODY, displayedMessage);
|
||||||
contentValues.put(HAS_MENTION, 0);
|
contentValues.put(HAS_MENTION, 0);
|
||||||
|
contentValues.put(STATUS, Status.STATUS_NONE);
|
||||||
database.update(TABLE_NAME, contentValues, ID_WHERE, new String[] {String.valueOf(messageId)});
|
database.update(TABLE_NAME, contentValues, ID_WHERE, new String[] {String.valueOf(messageId)});
|
||||||
|
|
||||||
updateTypeBitmask(messageId, Types.BASE_TYPE_MASK,
|
updateTypeBitmask(messageId, Types.BASE_TYPE_MASK,
|
||||||
@ -299,6 +300,28 @@ public class SmsDatabase extends MessagingDatabase {
|
|||||||
return isOutgoing;
|
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) {
|
public void incrementReceiptCount(SyncMessageId messageId, boolean deliveryReceipt, boolean readReceipt) {
|
||||||
SQLiteDatabase database = databaseHelper.getWritableDatabase();
|
SQLiteDatabase database = databaseHelper.getWritableDatabase();
|
||||||
Cursor cursor = null;
|
Cursor cursor = null;
|
||||||
|
@ -372,6 +372,11 @@ class HomeActivity : PassphraseRequiredActionBarActivity(),
|
|||||||
binding.seedReminderView.isVisible = false
|
binding.seedReminderView.isVisible = false
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// refresh search on resume, in case we a conversation was deleted
|
||||||
|
if (binding.globalSearchRecycler.isVisible){
|
||||||
|
globalSearchViewModel.refresh()
|
||||||
|
}
|
||||||
|
|
||||||
updateLegacyConfigView()
|
updateLegacyConfigView()
|
||||||
|
|
||||||
// Sync config changes if there are any
|
// 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 |
@ -31,7 +31,8 @@
|
|||||||
android:layout_height="wrap_content"
|
android:layout_height="wrap_content"
|
||||||
android:background="?attr/selectableItemBackgroundBorderless"
|
android:background="?attr/selectableItemBackgroundBorderless"
|
||||||
android:padding="8dp"
|
android:padding="8dp"
|
||||||
android:src="@drawable/ic_trash_filled_32" />
|
app:tint="@color/white"
|
||||||
|
android:src="@drawable/ic_delete" />
|
||||||
|
|
||||||
<ImageView
|
<ImageView
|
||||||
android:id="@+id/scribble_undo_button"
|
android:id="@+id/scribble_undo_button"
|
||||||
|
@ -38,6 +38,7 @@ interface MessageDataProvider {
|
|||||||
fun updateAudioAttachmentDuration(attachmentId: AttachmentId, durationMs: Long, threadId: Long)
|
fun updateAudioAttachmentDuration(attachmentId: AttachmentId, durationMs: Long, threadId: Long)
|
||||||
fun isMmsOutgoing(mmsMessageId: Long): Boolean
|
fun isMmsOutgoing(mmsMessageId: Long): Boolean
|
||||||
fun isOutgoingMessage(timestamp: Long): Boolean
|
fun isOutgoingMessage(timestamp: Long): Boolean
|
||||||
|
fun isDeletedMessage(timestamp: Long): Boolean
|
||||||
fun handleSuccessfulAttachmentUpload(attachmentId: Long, attachmentStream: SignalServiceAttachmentStream, attachmentKey: ByteArray, uploadResult: UploadResult)
|
fun handleSuccessfulAttachmentUpload(attachmentId: Long, attachmentStream: SignalServiceAttachmentStream, attachmentKey: ByteArray, uploadResult: UploadResult)
|
||||||
fun handleFailedAttachmentUpload(attachmentId: Long)
|
fun handleFailedAttachmentUpload(attachmentId: Long)
|
||||||
fun getMessageForQuote(timestamp: Long, author: Address): Triple<Long, Boolean, String>?
|
fun getMessageForQuote(timestamp: Long, author: Address): Triple<Long, Boolean, String>?
|
||||||
|
@ -37,6 +37,13 @@ class MessageSendJob(val message: Message, val destination: Destination) : Job {
|
|||||||
val message = message as? VisibleMessage
|
val message = message as? VisibleMessage
|
||||||
val storage = MessagingModuleConfiguration.shared.storage
|
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 sentTimestamp = this.message.sentTimestamp
|
||||||
val sender = storage.getUserPublicKey()
|
val sender = storage.getUserPublicKey()
|
||||||
if (sentTimestamp != null && sender != null) {
|
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.")
|
Log.w(TAG, "Failed to send $message::class.simpleName.")
|
||||||
val message = message as? VisibleMessage
|
val message = message as? VisibleMessage
|
||||||
if (message != null) {
|
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
|
return // The message has been deleted
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -469,9 +469,15 @@ object MessageSender {
|
|||||||
|
|
||||||
fun handleFailedMessageSend(message: Message, error: Exception, isSyncMessage: Boolean = false) {
|
fun handleFailedMessageSend(message: Message, error: Exception, isSyncMessage: Boolean = false) {
|
||||||
val storage = MessagingModuleConfiguration.shared.storage
|
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 userPublicKey = storage.getUserPublicKey()!!
|
||||||
|
|
||||||
val timestamp = message.sentTimestamp!!
|
|
||||||
val author = message.sender ?: userPublicKey
|
val author = message.sender ?: userPublicKey
|
||||||
|
|
||||||
if (isSyncMessage) storage.markAsSyncFailed(timestamp, author, error)
|
if (isSyncMessage) storage.markAsSyncFailed(timestamp, author, error)
|
||||||
|
Loading…
Reference in New Issue
Block a user