From 0febb0456e8bf0596de30bd33d278821280d0e30 Mon Sep 17 00:00:00 2001 From: Al Lansley Date: Wed, 27 Mar 2024 07:42:05 +1100 Subject: [PATCH] SES-212 - Always show delivery status of last sent message - FINAL! (#1418) * Fixes #1408 * Addressed PR feedback * Cleanup * PR adjustments * Further PR adjustments * Updated libsession-util * Added fix for crash when no messages * Ignoring dirty submodules so they don't show up in git * Re-fixed display of delivery status on last sent message (got broken by disappearing messages) * Removed ignore dirty modules line in .gitmodules as it all seems to be playing nice now --------- Co-authored-by: AL-Session <160798022+AL-Session@users.noreply.github.com> Co-authored-by: Al Lansley --- .../conversation/v2/ConversationAdapter.kt | 26 +++++++++++++++- .../v2/messages/VisibleMessageView.kt | 30 ++++++++++++------- .../securesms/database/MmsSmsDatabase.java | 18 +++++++++++ 3 files changed, 62 insertions(+), 12 deletions(-) diff --git a/app/src/main/java/org/thoughtcrime/securesms/conversation/v2/ConversationAdapter.kt b/app/src/main/java/org/thoughtcrime/securesms/conversation/v2/ConversationAdapter.kt index 6013af5ba4..371df34565 100644 --- a/app/src/main/java/org/thoughtcrime/securesms/conversation/v2/ConversationAdapter.kt +++ b/app/src/main/java/org/thoughtcrime/securesms/conversation/v2/ConversationAdapter.kt @@ -22,10 +22,12 @@ import kotlinx.coroutines.launch import network.loki.messenger.R import network.loki.messenger.databinding.ViewVisibleMessageBinding import org.session.libsession.messaging.contacts.Contact +import org.session.libsession.utilities.TextSecurePreferences import org.thoughtcrime.securesms.conversation.v2.messages.ControlMessageView import org.thoughtcrime.securesms.conversation.v2.messages.VisibleMessageView import org.thoughtcrime.securesms.conversation.v2.messages.VisibleMessageViewDelegate import org.thoughtcrime.securesms.database.CursorRecyclerViewAdapter +import org.thoughtcrime.securesms.database.MmsSmsColumns import org.thoughtcrime.securesms.database.model.MessageRecord import org.thoughtcrime.securesms.dependencies.DatabaseComponent import org.thoughtcrime.securesms.mms.GlideRequests @@ -57,6 +59,7 @@ class ConversationAdapter( private val contactCache = SparseArray(100) private val contactLoadedCache = SparseBooleanArray(100) private val lastSeen = AtomicLong(originalLastSeen) + private var lastSentMessageId: Long = -1L init { lifecycleCoroutineScope.launch(IO) { @@ -136,7 +139,8 @@ class ConversationAdapter( senderId, lastSeen.get(), visibleMessageViewDelegate, - onAttachmentNeedsDownload + onAttachmentNeedsDownload, + lastSentMessageId ) if (!message.isDeleted) { @@ -205,8 +209,23 @@ class ConversationAdapter( return messageDB.readerFor(cursor).current } + private fun getLastSentMessageId(cursor: Cursor): Long { + // If we don't move to first (or at least step backwards) we can step off the end of the + // cursor and any query will return an "Index = -1" error. + val cursorHasContent = cursor.moveToFirst() + if (cursorHasContent) { + val thisThreadId = cursor.getLong(4) // Column index 4 is "thread_id" + if (thisThreadId != -1L) { + val thisUsersSessionId = TextSecurePreferences.getLocalNumber(context) + return messageDB.getLastSentMessageFromSender(thisThreadId, thisUsersSessionId) + } + } + return -1L + } + override fun changeCursor(cursor: Cursor?) { super.changeCursor(cursor) + val toRemove = mutableSetOf() val toDeselect = mutableSetOf>() for (selected in selectedItems) { @@ -224,6 +243,11 @@ class ConversationAdapter( toDeselect.iterator().forEach { (pos, record) -> onDeselect(record, pos) } + + // This value gets updated here ONLY when the cursor changes, and the value is then passed + // through to `VisibleMessageView.bind` each time we bind via `onBindItemViewHolder`, above. + // If there are no messages then lastSentMessageId is assigned the value -1L. + if (cursor != null) { lastSentMessageId = getLastSentMessageId(cursor) } } fun findLastSeenItemPosition(lastSeenTimestamp: Long): Int? { diff --git a/app/src/main/java/org/thoughtcrime/securesms/conversation/v2/messages/VisibleMessageView.kt b/app/src/main/java/org/thoughtcrime/securesms/conversation/v2/messages/VisibleMessageView.kt index df49785deb..9dc3d2ab2d 100644 --- a/app/src/main/java/org/thoughtcrime/securesms/conversation/v2/messages/VisibleMessageView.kt +++ b/app/src/main/java/org/thoughtcrime/securesms/conversation/v2/messages/VisibleMessageView.kt @@ -32,6 +32,7 @@ import org.session.libsession.messaging.contacts.Contact import org.session.libsession.messaging.contacts.Contact.ContactContext import org.session.libsession.messaging.open_groups.OpenGroupApi import org.session.libsession.utilities.Address +import org.session.libsession.utilities.TextSecurePreferences import org.session.libsession.utilities.ViewUtil import org.session.libsession.utilities.getColorFromAttr import org.session.libsession.utilities.modifyLayoutParams @@ -131,7 +132,8 @@ class VisibleMessageView : LinearLayout { senderSessionID: String, lastSeen: Long, delegate: VisibleMessageViewDelegate? = null, - onAttachmentNeedsDownload: (Long, Long) -> Unit + onAttachmentNeedsDownload: (Long, Long) -> Unit, + lastSentMessageId: Long ) { val threadID = message.threadId val thread = threadDb.getRecipientForThreadId(threadID) ?: return @@ -195,14 +197,18 @@ class VisibleMessageView : LinearLayout { val contactContext = if (thread.isOpenGroupRecipient) ContactContext.OPEN_GROUP else ContactContext.REGULAR binding.senderNameTextView.text = contact?.displayName(contactContext) ?: senderSessionID + // Unread marker binding.unreadMarkerContainer.isVisible = lastSeen != -1L && message.timestamp > lastSeen && (previous == null || previous.timestamp <= lastSeen) && !message.isOutgoing + // Date break val showDateBreak = isStartOfMessageCluster || snIsSelected binding.dateBreakTextView.text = if (showDateBreak) DateUtils.getDisplayFormattedTimeSpanString(context, Locale.getDefault(), message.timestamp) else null binding.dateBreakTextView.isVisible = showDateBreak + // Message status indicator showStatusMessage(message) + // Emoji Reactions val emojiLayoutParams = binding.emojiReactionsView.root.layoutParams as ConstraintLayout.LayoutParams emojiLayoutParams.horizontalBias = if (message.isOutgoing) 1f else 0f @@ -238,7 +244,8 @@ class VisibleMessageView : LinearLayout { } private fun showStatusMessage(message: MessageRecord) { - val disappearing = message.expiresIn > 0 + + val scheduledToDisappear = message.expiresIn > 0 binding.messageInnerLayout.modifyLayoutParams { gravity = if (message.isOutgoing) Gravity.END else Gravity.START @@ -250,21 +257,22 @@ class VisibleMessageView : LinearLayout { binding.expirationTimerView.isGone = true - if (message.isOutgoing || disappearing) { + if (message.isOutgoing || scheduledToDisappear) { val (iconID, iconColor, textId) = getMessageStatusImage(message) textId?.let(binding.messageStatusTextView::setText) iconColor?.let(binding.messageStatusTextView::setTextColor) iconID?.let { ContextCompat.getDrawable(context, it) } ?.run { iconColor?.let { mutate().apply { setTint(it) } } ?: this } ?.let(binding.messageStatusImageView::setImageDrawable) - - val lastMessageID = mmsSmsDb.getLastMessageID(message.threadId) - val isLastMessage = message.id == lastMessageID - binding.messageStatusTextView.isVisible = - textId != null && (!message.isSent || isLastMessage || disappearing) - val showTimer = disappearing && !message.isPending - binding.messageStatusImageView.isVisible = - iconID != null && !showTimer && (!message.isSent || isLastMessage) + + // Always show the delivery status of the last sent message + val thisUsersSessionId = TextSecurePreferences.getLocalNumber(context) + val lastSentMessageId = mmsSmsDb.getLastSentMessageFromSender(message.threadId, thisUsersSessionId) + val isLastSentMessage = lastSentMessageId == message.id + + binding.messageStatusTextView.isVisible = textId != null && (isLastSentMessage || scheduledToDisappear) + val showTimer = scheduledToDisappear && !message.isPending + binding.messageStatusImageView.isVisible = iconID != null && !showTimer && (!message.isSent || isLastSentMessage) binding.messageStatusImageView.bringToFront() binding.expirationTimerView.bringToFront() diff --git a/app/src/main/java/org/thoughtcrime/securesms/database/MmsSmsDatabase.java b/app/src/main/java/org/thoughtcrime/securesms/database/MmsSmsDatabase.java index 0db4dd00e5..63a9ec5c29 100644 --- a/app/src/main/java/org/thoughtcrime/securesms/database/MmsSmsDatabase.java +++ b/app/src/main/java/org/thoughtcrime/securesms/database/MmsSmsDatabase.java @@ -209,6 +209,24 @@ public class MmsSmsDatabase extends Database { } } + public long getLastSentMessageFromSender(long threadId, String serializedAuthor) { + String order = MmsSmsColumns.NORMALIZED_DATE_SENT + " DESC"; + String selection = MmsSmsColumns.THREAD_ID + " = " + threadId; + + boolean isOwnNumber = Util.isOwnNumber(context, serializedAuthor); + + // Try everything with resources so that they auto-close on end of scope + try (Cursor cursor = queryTables(PROJECTION, selection, order, null)) { + try (MmsSmsDatabase.Reader reader = readerFor(cursor)) { + MessageRecord messageRecord; + while ((messageRecord = reader.getNext()) != null) { + if (isOwnNumber && messageRecord.isOutgoing()) { return messageRecord.id; } + } + } + } + return -1; + } + public Cursor getUnread() { String order = MmsSmsColumns.NORMALIZED_DATE_SENT + " ASC"; String selection = "(" + MmsSmsColumns.READ + " = 0 OR " + MmsSmsColumns.REACTIONS_UNREAD + " = 1) AND " + MmsSmsColumns.NOTIFIED + " = 0";