mirror of
https://github.com/oxen-io/session-android.git
synced 2025-01-03 13:47:45 +00:00
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 <al@oxen.io>
This commit is contained in:
parent
54d6c025b1
commit
0febb0456e
@ -22,10 +22,12 @@ import kotlinx.coroutines.launch
|
|||||||
import network.loki.messenger.R
|
import network.loki.messenger.R
|
||||||
import network.loki.messenger.databinding.ViewVisibleMessageBinding
|
import network.loki.messenger.databinding.ViewVisibleMessageBinding
|
||||||
import org.session.libsession.messaging.contacts.Contact
|
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.ControlMessageView
|
||||||
import org.thoughtcrime.securesms.conversation.v2.messages.VisibleMessageView
|
import org.thoughtcrime.securesms.conversation.v2.messages.VisibleMessageView
|
||||||
import org.thoughtcrime.securesms.conversation.v2.messages.VisibleMessageViewDelegate
|
import org.thoughtcrime.securesms.conversation.v2.messages.VisibleMessageViewDelegate
|
||||||
import org.thoughtcrime.securesms.database.CursorRecyclerViewAdapter
|
import org.thoughtcrime.securesms.database.CursorRecyclerViewAdapter
|
||||||
|
import org.thoughtcrime.securesms.database.MmsSmsColumns
|
||||||
import org.thoughtcrime.securesms.database.model.MessageRecord
|
import org.thoughtcrime.securesms.database.model.MessageRecord
|
||||||
import org.thoughtcrime.securesms.dependencies.DatabaseComponent
|
import org.thoughtcrime.securesms.dependencies.DatabaseComponent
|
||||||
import org.thoughtcrime.securesms.mms.GlideRequests
|
import org.thoughtcrime.securesms.mms.GlideRequests
|
||||||
@ -57,6 +59,7 @@ class ConversationAdapter(
|
|||||||
private val contactCache = SparseArray<Contact>(100)
|
private val contactCache = SparseArray<Contact>(100)
|
||||||
private val contactLoadedCache = SparseBooleanArray(100)
|
private val contactLoadedCache = SparseBooleanArray(100)
|
||||||
private val lastSeen = AtomicLong(originalLastSeen)
|
private val lastSeen = AtomicLong(originalLastSeen)
|
||||||
|
private var lastSentMessageId: Long = -1L
|
||||||
|
|
||||||
init {
|
init {
|
||||||
lifecycleCoroutineScope.launch(IO) {
|
lifecycleCoroutineScope.launch(IO) {
|
||||||
@ -136,7 +139,8 @@ class ConversationAdapter(
|
|||||||
senderId,
|
senderId,
|
||||||
lastSeen.get(),
|
lastSeen.get(),
|
||||||
visibleMessageViewDelegate,
|
visibleMessageViewDelegate,
|
||||||
onAttachmentNeedsDownload
|
onAttachmentNeedsDownload,
|
||||||
|
lastSentMessageId
|
||||||
)
|
)
|
||||||
|
|
||||||
if (!message.isDeleted) {
|
if (!message.isDeleted) {
|
||||||
@ -205,8 +209,23 @@ class ConversationAdapter(
|
|||||||
return messageDB.readerFor(cursor).current
|
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?) {
|
override fun changeCursor(cursor: Cursor?) {
|
||||||
super.changeCursor(cursor)
|
super.changeCursor(cursor)
|
||||||
|
|
||||||
val toRemove = mutableSetOf<MessageRecord>()
|
val toRemove = mutableSetOf<MessageRecord>()
|
||||||
val toDeselect = mutableSetOf<Pair<Int, MessageRecord>>()
|
val toDeselect = mutableSetOf<Pair<Int, MessageRecord>>()
|
||||||
for (selected in selectedItems) {
|
for (selected in selectedItems) {
|
||||||
@ -224,6 +243,11 @@ class ConversationAdapter(
|
|||||||
toDeselect.iterator().forEach { (pos, record) ->
|
toDeselect.iterator().forEach { (pos, record) ->
|
||||||
onDeselect(record, pos)
|
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? {
|
fun findLastSeenItemPosition(lastSeenTimestamp: Long): Int? {
|
||||||
|
@ -32,6 +32,7 @@ import org.session.libsession.messaging.contacts.Contact
|
|||||||
import org.session.libsession.messaging.contacts.Contact.ContactContext
|
import org.session.libsession.messaging.contacts.Contact.ContactContext
|
||||||
import org.session.libsession.messaging.open_groups.OpenGroupApi
|
import org.session.libsession.messaging.open_groups.OpenGroupApi
|
||||||
import org.session.libsession.utilities.Address
|
import org.session.libsession.utilities.Address
|
||||||
|
import org.session.libsession.utilities.TextSecurePreferences
|
||||||
import org.session.libsession.utilities.ViewUtil
|
import org.session.libsession.utilities.ViewUtil
|
||||||
import org.session.libsession.utilities.getColorFromAttr
|
import org.session.libsession.utilities.getColorFromAttr
|
||||||
import org.session.libsession.utilities.modifyLayoutParams
|
import org.session.libsession.utilities.modifyLayoutParams
|
||||||
@ -131,7 +132,8 @@ class VisibleMessageView : LinearLayout {
|
|||||||
senderSessionID: String,
|
senderSessionID: String,
|
||||||
lastSeen: Long,
|
lastSeen: Long,
|
||||||
delegate: VisibleMessageViewDelegate? = null,
|
delegate: VisibleMessageViewDelegate? = null,
|
||||||
onAttachmentNeedsDownload: (Long, Long) -> Unit
|
onAttachmentNeedsDownload: (Long, Long) -> Unit,
|
||||||
|
lastSentMessageId: Long
|
||||||
) {
|
) {
|
||||||
val threadID = message.threadId
|
val threadID = message.threadId
|
||||||
val thread = threadDb.getRecipientForThreadId(threadID) ?: return
|
val thread = threadDb.getRecipientForThreadId(threadID) ?: return
|
||||||
@ -195,14 +197,18 @@ class VisibleMessageView : LinearLayout {
|
|||||||
val contactContext =
|
val contactContext =
|
||||||
if (thread.isOpenGroupRecipient) ContactContext.OPEN_GROUP else ContactContext.REGULAR
|
if (thread.isOpenGroupRecipient) ContactContext.OPEN_GROUP else ContactContext.REGULAR
|
||||||
binding.senderNameTextView.text = contact?.displayName(contactContext) ?: senderSessionID
|
binding.senderNameTextView.text = contact?.displayName(contactContext) ?: senderSessionID
|
||||||
|
|
||||||
// Unread marker
|
// Unread marker
|
||||||
binding.unreadMarkerContainer.isVisible = lastSeen != -1L && message.timestamp > lastSeen && (previous == null || previous.timestamp <= lastSeen) && !message.isOutgoing
|
binding.unreadMarkerContainer.isVisible = lastSeen != -1L && message.timestamp > lastSeen && (previous == null || previous.timestamp <= lastSeen) && !message.isOutgoing
|
||||||
|
|
||||||
// Date break
|
// Date break
|
||||||
val showDateBreak = isStartOfMessageCluster || snIsSelected
|
val showDateBreak = isStartOfMessageCluster || snIsSelected
|
||||||
binding.dateBreakTextView.text = if (showDateBreak) DateUtils.getDisplayFormattedTimeSpanString(context, Locale.getDefault(), message.timestamp) else null
|
binding.dateBreakTextView.text = if (showDateBreak) DateUtils.getDisplayFormattedTimeSpanString(context, Locale.getDefault(), message.timestamp) else null
|
||||||
binding.dateBreakTextView.isVisible = showDateBreak
|
binding.dateBreakTextView.isVisible = showDateBreak
|
||||||
|
|
||||||
// Message status indicator
|
// Message status indicator
|
||||||
showStatusMessage(message)
|
showStatusMessage(message)
|
||||||
|
|
||||||
// Emoji Reactions
|
// Emoji Reactions
|
||||||
val emojiLayoutParams = binding.emojiReactionsView.root.layoutParams as ConstraintLayout.LayoutParams
|
val emojiLayoutParams = binding.emojiReactionsView.root.layoutParams as ConstraintLayout.LayoutParams
|
||||||
emojiLayoutParams.horizontalBias = if (message.isOutgoing) 1f else 0f
|
emojiLayoutParams.horizontalBias = if (message.isOutgoing) 1f else 0f
|
||||||
@ -238,7 +244,8 @@ class VisibleMessageView : LinearLayout {
|
|||||||
}
|
}
|
||||||
|
|
||||||
private fun showStatusMessage(message: MessageRecord) {
|
private fun showStatusMessage(message: MessageRecord) {
|
||||||
val disappearing = message.expiresIn > 0
|
|
||||||
|
val scheduledToDisappear = message.expiresIn > 0
|
||||||
|
|
||||||
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
|
||||||
@ -250,21 +257,22 @@ class VisibleMessageView : LinearLayout {
|
|||||||
|
|
||||||
binding.expirationTimerView.isGone = true
|
binding.expirationTimerView.isGone = true
|
||||||
|
|
||||||
if (message.isOutgoing || disappearing) {
|
if (message.isOutgoing || scheduledToDisappear) {
|
||||||
val (iconID, iconColor, textId) = getMessageStatusImage(message)
|
val (iconID, iconColor, textId) = getMessageStatusImage(message)
|
||||||
textId?.let(binding.messageStatusTextView::setText)
|
textId?.let(binding.messageStatusTextView::setText)
|
||||||
iconColor?.let(binding.messageStatusTextView::setTextColor)
|
iconColor?.let(binding.messageStatusTextView::setTextColor)
|
||||||
iconID?.let { ContextCompat.getDrawable(context, it) }
|
iconID?.let { ContextCompat.getDrawable(context, it) }
|
||||||
?.run { iconColor?.let { mutate().apply { setTint(it) } } ?: this }
|
?.run { iconColor?.let { mutate().apply { setTint(it) } } ?: this }
|
||||||
?.let(binding.messageStatusImageView::setImageDrawable)
|
?.let(binding.messageStatusImageView::setImageDrawable)
|
||||||
|
|
||||||
val lastMessageID = mmsSmsDb.getLastMessageID(message.threadId)
|
// Always show the delivery status of the last sent message
|
||||||
val isLastMessage = message.id == lastMessageID
|
val thisUsersSessionId = TextSecurePreferences.getLocalNumber(context)
|
||||||
binding.messageStatusTextView.isVisible =
|
val lastSentMessageId = mmsSmsDb.getLastSentMessageFromSender(message.threadId, thisUsersSessionId)
|
||||||
textId != null && (!message.isSent || isLastMessage || disappearing)
|
val isLastSentMessage = lastSentMessageId == message.id
|
||||||
val showTimer = disappearing && !message.isPending
|
|
||||||
binding.messageStatusImageView.isVisible =
|
binding.messageStatusTextView.isVisible = textId != null && (isLastSentMessage || scheduledToDisappear)
|
||||||
iconID != null && !showTimer && (!message.isSent || isLastMessage)
|
val showTimer = scheduledToDisappear && !message.isPending
|
||||||
|
binding.messageStatusImageView.isVisible = iconID != null && !showTimer && (!message.isSent || isLastSentMessage)
|
||||||
|
|
||||||
binding.messageStatusImageView.bringToFront()
|
binding.messageStatusImageView.bringToFront()
|
||||||
binding.expirationTimerView.bringToFront()
|
binding.expirationTimerView.bringToFront()
|
||||||
|
@ -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() {
|
public Cursor getUnread() {
|
||||||
String order = MmsSmsColumns.NORMALIZED_DATE_SENT + " ASC";
|
String order = MmsSmsColumns.NORMALIZED_DATE_SENT + " ASC";
|
||||||
String selection = "(" + MmsSmsColumns.READ + " = 0 OR " + MmsSmsColumns.REACTIONS_UNREAD + " = 1) AND " + MmsSmsColumns.NOTIFIED + " = 0";
|
String selection = "(" + MmsSmsColumns.READ + " = 0 OR " + MmsSmsColumns.REACTIONS_UNREAD + " = 1) AND " + MmsSmsColumns.NOTIFIED + " = 0";
|
||||||
|
Loading…
x
Reference in New Issue
Block a user