Displaying "marked as deleted" messages

Split the `BASE_DELETED_TYPE` into two types:
BASE_DELETED_OUTGOING_TYPE and BASE_DELETED_INCOMING_TYPE
so we can differentiate them visually.
pull/1647/head
ThomasSession 10 months ago
parent 6e315a18f4
commit 7a7d04b6d7

@ -6,6 +6,7 @@ import com.google.protobuf.ByteString
import org.greenrobot.eventbus.EventBus import org.greenrobot.eventbus.EventBus
import org.session.libsession.database.MessageDataProvider import org.session.libsession.database.MessageDataProvider
import org.session.libsession.messaging.MessagingModuleConfiguration import org.session.libsession.messaging.MessagingModuleConfiguration
import org.session.libsession.messaging.messages.MarkAsDeletedMessage
import org.session.libsession.messaging.messages.control.UnsendRequest import org.session.libsession.messaging.messages.control.UnsendRequest
import org.session.libsession.messaging.sending_receiving.attachments.Attachment import org.session.libsession.messaging.sending_receiving.attachments.Attachment
import org.session.libsession.messaging.sending_receiving.attachments.AttachmentId import org.session.libsession.messaging.sending_receiving.attachments.AttachmentId
@ -221,7 +222,8 @@ class DatabaseAttachmentProvider(context: Context, helper: SQLCipherOpenHelper)
val message = database.getMessageFor(timestamp, address) ?: return null val message = database.getMessageFor(timestamp, address) ?: return null
val messagingDatabase: MessagingDatabase = if (message.isMms) DatabaseComponent.get(context).mmsDatabase() val messagingDatabase: MessagingDatabase = if (message.isMms) DatabaseComponent.get(context).mmsDatabase()
else DatabaseComponent.get(context).smsDatabase() else DatabaseComponent.get(context).smsDatabase()
messagingDatabase.markAsDeleted(message.id)
messagingDatabase.markAsDeleted(message.id, message.isOutgoing)
if (message.isOutgoing) { if (message.isOutgoing) {
messagingDatabase.deleteMessage(message.id) messagingDatabase.deleteMessage(message.id)
} }
@ -230,7 +232,7 @@ class DatabaseAttachmentProvider(context: Context, helper: SQLCipherOpenHelper)
} }
override fun markMessagesAsDeleted( override fun markMessagesAsDeleted(
messageIDs: List<Long>, messages: List<MarkAsDeletedMessage>,
threadId: Long, threadId: Long,
isSms: Boolean isSms: Boolean
) { ) {
@ -240,8 +242,8 @@ class DatabaseAttachmentProvider(context: Context, helper: SQLCipherOpenHelper)
//todo DELETION can this be batched? //todo DELETION can this be batched?
//todo DELETION we need to have different types of "marked as deleted": incoming and outgoing //todo DELETION we need to have different types of "marked as deleted": incoming and outgoing
//todo DELETION we need to have customisable text for "marked as deleted" message //todo DELETION we need to have customisable text for "marked as deleted" message
messageIDs.forEach { messageID -> messages.forEach { message ->
messagingDatabase.markAsDeleted(messageID) messagingDatabase.markAsDeleted(message.messageId, message.isOutgoing)
} }
} }

@ -262,13 +262,12 @@ class VisibleMessageView : LinearLayout {
// Get details regarding how we should display the message (it's delivery icon, icon tint colour, and // Get details regarding how we should display the message (it's delivery icon, icon tint colour, and
// the resource string for what text to display (R.string.delivery_status_sent etc.). // the resource string for what text to display (R.string.delivery_status_sent etc.).
val (iconID, iconColor, textId) = getMessageStatusInfo(message)
// If we get any nulls then a message isn't one with a state that we care about (i.e., control messages // If we get a null messageStatus then the message isn't one with a state that we care about (i.e., control messages
// etc.) - so bail. See: `DisplayRecord.is<WHATEVER>` for the full suite of message state methods. // etc.) - so bail. See: `DisplayRecord.is<WHATEVER>` for the full suite of message state methods.
// Also: We set all delivery status elements visibility to false just to make sure we don't display any // Also: We set all delivery status elements visibility to false just to make sure we don't display any
// stale data. // stale data.
if (textId == null) return val messageStatus = getMessageStatusInfo(message) ?: return
binding.messageInnerLayout.modifyLayoutParams<FrameLayout.LayoutParams> { binding.messageInnerLayout.modifyLayoutParams<FrameLayout.LayoutParams> {
gravity = if (message.isOutgoing) Gravity.END else Gravity.START gravity = if (message.isOutgoing) Gravity.END else Gravity.START
@ -277,16 +276,17 @@ class VisibleMessageView : LinearLayout {
horizontalBias = if (message.isOutgoing) 1f else 0f horizontalBias = if (message.isOutgoing) 1f else 0f
} }
// If the message is incoming AND it is not scheduled to disappear then don't show any status or timer details // If the message is incoming AND it is not scheduled to disappear
// OR it is a deleted message then don't show any status or timer details
val scheduledToDisappear = message.expiresIn > 0 val scheduledToDisappear = message.expiresIn > 0
if (message.isIncoming && !scheduledToDisappear) return if (message.isDeleted || message.isIncoming && !scheduledToDisappear) return
// Set text & icons as appropriate for the message state. Note: Possible message states we care // Set text & icons as appropriate for the message state. Note: Possible message states we care
// about are: isFailed, isSyncFailed, isPending, isSyncing, isResyncing, isRead, and isSent. // about are: isFailed, isSyncFailed, isPending, isSyncing, isResyncing, isRead, and isSent.
textId.let(binding.messageStatusTextView::setText) messageStatus.messageText?.let(binding.messageStatusTextView::setText)
iconColor?.let(binding.messageStatusTextView::setTextColor) messageStatus.iconTint?.let(binding.messageStatusTextView::setTextColor)
iconID?.let { ContextCompat.getDrawable(context, it) } messageStatus.iconId?.let { ContextCompat.getDrawable(context, it) }
?.run { iconColor?.let { mutate().apply { setTint(it) } } ?: this } ?.run { messageStatus.iconTint?.let { mutate().apply { setTint(it) } } ?: this }
?.let(binding.messageStatusImageView::setImageDrawable) ?.let(binding.messageStatusImageView::setImageDrawable)
// Potential options at this point are that the message is: // Potential options at this point are that the message is:
@ -363,7 +363,7 @@ class VisibleMessageView : LinearLayout {
@ColorInt val iconTint: Int?, @ColorInt val iconTint: Int?,
@StringRes val messageText: Int?) @StringRes val messageText: Int?)
private fun getMessageStatusInfo(message: MessageRecord): MessageStatusInfo = when { private fun getMessageStatusInfo(message: MessageRecord): MessageStatusInfo? = when {
message.isFailed -> message.isFailed ->
MessageStatusInfo(R.drawable.ic_delivery_status_failed, MessageStatusInfo(R.drawable.ic_delivery_status_failed,
getThemedColor(context, R.attr.danger), getThemedColor(context, R.attr.danger),
@ -399,10 +399,15 @@ class VisibleMessageView : LinearLayout {
context.getColorFromAttr(R.attr.message_status_color), context.getColorFromAttr(R.attr.message_status_color),
R.string.delivery_status_sent R.string.delivery_status_sent
) )
// deleted messages do not have a status but we care about styling them so they need to return something
message.isDeleted ->
MessageStatusInfo(null, null, null)
else -> { else -> {
// The message isn't one we care about for message statuses we display to the user (i.e., // The message isn't one we care about for message statuses we display to the user (i.e.,
// control messages etc. - see the `DisplayRecord.is<WHATEVER>` suite of methods for options). // control messages etc. - see the `DisplayRecord.is<WHATEVER>` suite of methods for options).
MessageStatusInfo(null, null, null) null
} }
} }

@ -46,7 +46,7 @@ public abstract class MessagingDatabase extends Database implements MmsSmsColumn
public abstract void markUnidentified(long messageId, boolean unidentified); public abstract void markUnidentified(long messageId, boolean unidentified);
public abstract void markAsDeleted(long messageId); public abstract void markAsDeleted(long messageId, boolean isOutgoing);
public abstract boolean deleteMessage(long messageId); public abstract boolean deleteMessage(long messageId);
public abstract boolean deleteMessages(long[] messageId, long threadId); public abstract boolean deleteMessages(long[] messageId, long threadId);

@ -290,7 +290,7 @@ class MmsDatabase(context: Context, databaseHelper: SQLCipherOpenHelper) : Messa
db.update(TABLE_NAME, contentValues, ID_WHERE, arrayOf(messageId.toString())) db.update(TABLE_NAME, contentValues, ID_WHERE, arrayOf(messageId.toString()))
} }
override fun markAsDeleted(messageId: Long) { override fun markAsDeleted(messageId: Long, isOutgoing: Boolean) {
val database = databaseHelper.writableDatabase val database = databaseHelper.writableDatabase
val contentValues = ContentValues() val contentValues = ContentValues()
contentValues.put(READ, 1) contentValues.put(READ, 1)
@ -301,7 +301,10 @@ class MmsDatabase(context: Context, databaseHelper: SQLCipherOpenHelper) : Messa
queue(Runnable { attachmentDatabase.deleteAttachmentsForMessage(messageId) }) queue(Runnable { attachmentDatabase.deleteAttachmentsForMessage(messageId) })
val threadId = getThreadIdForMessage(messageId) val threadId = getThreadIdForMessage(messageId)
markAs(messageId, MmsSmsColumns.Types.BASE_DELETED_TYPE, threadId) val deletedType = if (isOutgoing) { MmsSmsColumns.Types.BASE_DELETED_OUTGOING_TYPE} else {
MmsSmsColumns.Types.BASE_DELETED_INCOMING_TYPE
}
markAs(messageId, deletedType, threadId)
} }
override fun markExpireStarted(messageId: Long, startedTimestamp: Long) { override fun markExpireStarted(messageId: Long, startedTimestamp: Long) {

@ -50,10 +50,11 @@ public interface MmsSmsColumns {
protected static final long BASE_PENDING_SECURE_SMS_FALLBACK = 25; protected static final long BASE_PENDING_SECURE_SMS_FALLBACK = 25;
protected static final long BASE_PENDING_INSECURE_SMS_FALLBACK = 26; protected static final long BASE_PENDING_INSECURE_SMS_FALLBACK = 26;
public static final long BASE_DRAFT_TYPE = 27; public static final long BASE_DRAFT_TYPE = 27;
protected static final long BASE_DELETED_TYPE = 28; protected static final long BASE_DELETED_OUTGOING_TYPE = 28;
protected static final long BASE_SYNCING_TYPE = 29; protected static final long BASE_SYNCING_TYPE = 29;
protected static final long BASE_RESYNCING_TYPE = 30; protected static final long BASE_RESYNCING_TYPE = 30;
protected static final long BASE_SYNC_FAILED_TYPE = 31; protected static final long BASE_SYNC_FAILED_TYPE = 31;
protected static final long BASE_DELETED_INCOMING_TYPE = 32;
protected static final long[] OUTGOING_MESSAGE_TYPES = {BASE_OUTBOX_TYPE, BASE_SENT_TYPE, protected static final long[] OUTGOING_MESSAGE_TYPES = {BASE_OUTBOX_TYPE, BASE_SENT_TYPE,
BASE_SYNCING_TYPE, BASE_RESYNCING_TYPE, BASE_SYNCING_TYPE, BASE_RESYNCING_TYPE,
@ -61,6 +62,7 @@ public interface MmsSmsColumns {
BASE_SENDING_TYPE, BASE_SENT_FAILED_TYPE, BASE_SENDING_TYPE, BASE_SENT_FAILED_TYPE,
BASE_PENDING_SECURE_SMS_FALLBACK, BASE_PENDING_SECURE_SMS_FALLBACK,
BASE_PENDING_INSECURE_SMS_FALLBACK, BASE_PENDING_INSECURE_SMS_FALLBACK,
BASE_DELETED_OUTGOING_TYPE,
OUTGOING_CALL_TYPE}; OUTGOING_CALL_TYPE};
@ -182,7 +184,10 @@ public interface MmsSmsColumns {
return (type & BASE_TYPE_MASK) == BASE_INBOX_TYPE; return (type & BASE_TYPE_MASK) == BASE_INBOX_TYPE;
} }
public static boolean isDeletedMessage(long type) { return (type & BASE_TYPE_MASK) == BASE_DELETED_TYPE; } public static boolean isDeletedMessage(long type) {
return (type & BASE_TYPE_MASK) == BASE_DELETED_OUTGOING_TYPE ||
(type & BASE_TYPE_MASK) == BASE_DELETED_INCOMING_TYPE;
}
public static boolean isJoinedType(long type) { public static boolean isJoinedType(long type) {
return (type & BASE_TYPE_MASK) == JOINED_TYPE; return (type & BASE_TYPE_MASK) == JOINED_TYPE;

@ -236,14 +236,17 @@ public class SmsDatabase extends MessagingDatabase {
} }
@Override @Override
public void markAsDeleted(long messageId) { public void markAsDeleted(long messageId, boolean isOutgoing) {
SQLiteDatabase database = databaseHelper.getWritableDatabase(); SQLiteDatabase database = databaseHelper.getWritableDatabase();
ContentValues contentValues = new ContentValues(); ContentValues contentValues = new ContentValues();
contentValues.put(READ, 1); contentValues.put(READ, 1);
contentValues.put(BODY, ""); contentValues.put(BODY, "");
contentValues.put(HAS_MENTION, 0); contentValues.put(HAS_MENTION, 0);
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, Types.BASE_DELETED_TYPE);
updateTypeBitmask(messageId, Types.BASE_TYPE_MASK,
isOutgoing? MmsSmsColumns.Types.BASE_DELETED_OUTGOING_TYPE : MmsSmsColumns.Types.BASE_DELETED_INCOMING_TYPE
);
} }
@Override @Override

@ -14,6 +14,7 @@ import kotlinx.coroutines.flow.Flow
import kotlinx.coroutines.flow.map import kotlinx.coroutines.flow.map
import org.session.libsession.database.MessageDataProvider import org.session.libsession.database.MessageDataProvider
import org.session.libsession.messaging.messages.Destination import org.session.libsession.messaging.messages.Destination
import org.session.libsession.messaging.messages.MarkAsDeletedMessage
import org.session.libsession.messaging.messages.control.MessageRequestResponse import org.session.libsession.messaging.messages.control.MessageRequestResponse
import org.session.libsession.messaging.messages.control.UnsendRequest import org.session.libsession.messaging.messages.control.UnsendRequest
import org.session.libsession.messaging.messages.signal.OutgoingTextMessage import org.session.libsession.messaging.messages.signal.OutgoingTextMessage
@ -186,11 +187,17 @@ class DefaultConversationRepository @Inject constructor(
val (mms, sms) = messages.partition { it.isMms } val (mms, sms) = messages.partition { it.isMms }
if(mms.isNotEmpty()){ if(mms.isNotEmpty()){
messageDataProvider.markMessagesAsDeleted(mms.map { it.id }, threadId, isSms = false) messageDataProvider.markMessagesAsDeleted(mms.map { MarkAsDeletedMessage(
messageId = it.id,
isOutgoing = it.isOutgoing
) }, threadId, isSms = false)
} }
if(sms.isNotEmpty()){ if(sms.isNotEmpty()){
messageDataProvider.markMessagesAsDeleted(sms.map { it.id }, threadId, isSms = true) messageDataProvider.markMessagesAsDeleted(sms.map { MarkAsDeletedMessage(
messageId = it.id,
isOutgoing = it.isOutgoing
) }, threadId, isSms = true)
} }
//todo DELETION delete attachments and links //todo DELETION delete attachments and links

@ -2,12 +2,11 @@
<org.thoughtcrime.securesms.conversation.v2.messages.DeletedMessageView <org.thoughtcrime.securesms.conversation.v2.messages.DeletedMessageView
xmlns:android="http://schemas.android.com/apk/res/android" xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:app="http://schemas.android.com/apk/res-auto" xmlns:app="http://schemas.android.com/apk/res-auto"
android:layout_width="match_parent" android:layout_width="wrap_content"
android:layout_height="wrap_content" android:layout_height="wrap_content"
xmlns:tools="http://schemas.android.com/tools" xmlns:tools="http://schemas.android.com/tools"
android:orientation="horizontal" android:orientation="horizontal"
android:padding="@dimen/small_spacing" android:padding="@dimen/small_spacing">
android:gravity="center">
<ImageView <ImageView
android:id="@+id/deletedMessageViewIconImageView" android:id="@+id/deletedMessageViewIconImageView"

@ -5,6 +5,7 @@
android:layout_height="wrap_content" android:layout_height="wrap_content"
xmlns:tools="http://schemas.android.com/tools" xmlns:tools="http://schemas.android.com/tools"
android:id="@+id/mainContainerConstraint" android:id="@+id/mainContainerConstraint"
android:background="@color/red_400"
xmlns:app="http://schemas.android.com/apk/res-auto"> xmlns:app="http://schemas.android.com/apk/res-auto">
<org.thoughtcrime.securesms.util.MessageBubbleView <org.thoughtcrime.securesms.util.MessageBubbleView
@ -24,6 +25,7 @@
android:id="@+id/deletedMessageView" android:id="@+id/deletedMessageView"
android:layout_width="wrap_content" android:layout_width="wrap_content"
android:layout_height="wrap_content" android:layout_height="wrap_content"
app:layout_constraintHorizontal_bias="0"
/> />
<include layout="@layout/view_untrusted_attachment" <include layout="@layout/view_untrusted_attachment"

@ -1,5 +1,6 @@
package org.session.libsession.database package org.session.libsession.database
import org.session.libsession.messaging.messages.MarkAsDeletedMessage
import org.session.libsession.messaging.sending_receiving.attachments.Attachment import org.session.libsession.messaging.sending_receiving.attachments.Attachment
import org.session.libsession.messaging.sending_receiving.attachments.AttachmentId import org.session.libsession.messaging.sending_receiving.attachments.AttachmentId
import org.session.libsession.messaging.sending_receiving.attachments.AttachmentState import org.session.libsession.messaging.sending_receiving.attachments.AttachmentState
@ -24,7 +25,7 @@ interface MessageDataProvider {
fun deleteMessage(messageID: Long, isSms: Boolean) fun deleteMessage(messageID: Long, isSms: Boolean)
fun deleteMessages(messageIDs: List<Long>, threadId: Long, isSms: Boolean) fun deleteMessages(messageIDs: List<Long>, threadId: Long, isSms: Boolean)
fun updateMessageAsDeleted(timestamp: Long, author: String): Long? fun updateMessageAsDeleted(timestamp: Long, author: String): Long?
fun markMessagesAsDeleted(messageIDs: List<Long>, threadId: Long, isSms: Boolean) fun markMessagesAsDeleted(messages: List<MarkAsDeletedMessage>, threadId: Long, isSms: Boolean)
fun getServerHashForMessage(messageID: Long, mms: Boolean): String? fun getServerHashForMessage(messageID: Long, mms: Boolean): String?
fun getDatabaseAttachment(attachmentId: Long): DatabaseAttachment? fun getDatabaseAttachment(attachmentId: Long): DatabaseAttachment?
fun getAttachmentStream(attachmentId: Long): SessionServiceAttachmentStream? fun getAttachmentStream(attachmentId: Long): SessionServiceAttachmentStream?

@ -0,0 +1,6 @@
package org.session.libsession.messaging.messages
data class MarkAsDeletedMessage(
val messageId: Long,
val isOutgoing: Boolean
)
Loading…
Cancel
Save