diff --git a/app/src/main/java/org/thoughtcrime/securesms/conversation/v2/ConversationActivityV2.kt b/app/src/main/java/org/thoughtcrime/securesms/conversation/v2/ConversationActivityV2.kt index c35855adb3..76ac82666a 100644 --- a/app/src/main/java/org/thoughtcrime/securesms/conversation/v2/ConversationActivityV2.kt +++ b/app/src/main/java/org/thoughtcrime/securesms/conversation/v2/ConversationActivityV2.kt @@ -59,6 +59,7 @@ import kotlinx.coroutines.channels.Channel import kotlinx.coroutines.flow.collectLatest import kotlinx.coroutines.flow.consumeAsFlow import kotlinx.coroutines.flow.debounce +import kotlinx.coroutines.flow.filter import kotlinx.coroutines.launch import kotlinx.coroutines.withContext import network.loki.messenger.R @@ -175,6 +176,7 @@ import org.thoughtcrime.securesms.util.toPx import java.lang.ref.WeakReference import java.util.Locale import java.util.concurrent.ExecutionException +import java.util.concurrent.atomic.AtomicBoolean import java.util.concurrent.atomic.AtomicLong import java.util.concurrent.atomic.AtomicReference import javax.inject.Inject @@ -273,7 +275,7 @@ class ConversationActivityV2 : PassphraseRequiredActionBarActivity(), InputBarDe val searchViewModel: SearchViewModel by viewModels() var searchViewItem: MenuItem? = null - private val bufferedLastSeenChannel = Channel(capacity = Channel.RENDEZVOUS, onBufferOverflow = BufferOverflow.DROP_OLDEST) + private val bufferedLastSeenChannel = Channel(capacity = 512, onBufferOverflow = BufferOverflow.DROP_OLDEST) private val isScrolledToBottom: Boolean get() { @@ -341,6 +343,7 @@ class ConversationActivityV2 : PassphraseRequiredActionBarActivity(), InputBarDe private val cameraButton by lazy { InputBarButton(this, R.drawable.ic_baseline_photo_camera_24, hasOpaqueBackground = true) } private val messageToScrollTimestamp = AtomicLong(-1) private val messageToScrollAuthor = AtomicReference(null) + private val firstLoad = AtomicBoolean(true) private lateinit var reactionDelegate: ConversationReactionDelegate private val reactWithAnyEmojiStartPage = -1 @@ -442,16 +445,12 @@ class ConversationActivityV2 : PassphraseRequiredActionBarActivity(), InputBarDe lifecycle.repeatOnLifecycle(Lifecycle.State.RESUMED) { // only update the conversation every 3 seconds maximum // channel is rendezvous and shouldn't block on try send calls as often as we want - val layoutManager = binding?.conversationRecyclerView?.layoutManager as? LinearLayoutManager ?: return@repeatOnLifecycle - val lastItemPos = layoutManager.findLastCompletelyVisibleItemPosition() -// adapter.item - withContext(Dispatchers.IO) { - storage.markConversationAsRead(viewModel.threadId, SnodeAPI.nowWithOffset) - } - val bufferedFlow = bufferedLastSeenChannel.consumeAsFlow().debounce(3.seconds) - bufferedFlow.collectLatest { + val bufferedFlow = bufferedLastSeenChannel.consumeAsFlow() + bufferedFlow.filter { + it > storage.getLastSeen(viewModel.threadId) + }.collectLatest { latestMessageRead -> withContext(Dispatchers.IO) { - storage.markConversationAsRead(viewModel.threadId, SnodeAPI.nowWithOffset) + storage.markConversationAsRead(viewModel.threadId, latestMessageRead) } } } @@ -502,8 +501,9 @@ class ConversationActivityV2 : PassphraseRequiredActionBarActivity(), InputBarDe val author = messageToScrollAuthor.getAndSet(null) if (author != null && messageTimestamp >= 0) { jumpToMessage(author, messageTimestamp, null) + } else if (firstLoad.getAndSet(false)) { + scrollToFirstUnreadMessageIfNeeded() } - bufferedLastSeenChannel.trySend(Unit) } updatePlaceholder() } @@ -998,7 +998,13 @@ class ConversationActivityV2 : PassphraseRequiredActionBarActivity(), InputBarDe binding.typingIndicatorViewContainer.isVisible = wasTypingIndicatorVisibleBefore && isScrolledToBottom binding.typingIndicatorViewContainer.isVisible showOrHideScrollToBottomButton() - val firstVisiblePosition = layoutManager?.findFirstVisibleItemPosition() ?: -1 + val firstVisiblePosition = layoutManager?.findFirstCompletelyVisibleItemPosition() ?: RecyclerView.NO_POSITION + if (!firstLoad.get() && firstVisiblePosition != RecyclerView.NO_POSITION) { + val visibleItemTimestamp = adapter.getTimestampForItemAt(firstVisiblePosition) + if (visibleItemTimestamp != null) { + bufferedLastSeenChannel.trySend(visibleItemTimestamp) + } + } unreadCount = min(unreadCount, firstVisiblePosition).coerceAtLeast(0) updateUnreadCountIndicator() } 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 85d3c8e6de..6a5a280895 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 @@ -27,6 +27,7 @@ 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.ThreadDatabase.Reader import org.thoughtcrime.securesms.database.model.MessageRecord import org.thoughtcrime.securesms.dependencies.DatabaseComponent import org.thoughtcrime.securesms.mms.GlideRequests @@ -219,11 +220,14 @@ class ConversationAdapter( fun findLastSeenItemPosition(lastSeenTimestamp: Long): Int? { val cursor = this.cursor - if (lastSeenTimestamp <= 0L || cursor == null || !isActiveCursor) return null + if (cursor == null || !isActiveCursor) return null + if (lastSeenTimestamp == 0L && cursor.moveToLast()) { + return cursor.position + } for (i in 0 until itemCount) { cursor.moveToPosition(i) val message = messageDB.readerFor(cursor).current - if (message.isOutgoing || message.dateReceived <= lastSeenTimestamp) { return i } + if (message.isOutgoing || message.dateSent <= lastSeenTimestamp) { return i } } return null } @@ -243,4 +247,11 @@ class ConversationAdapter( this.searchQuery = query notifyDataSetChanged() } + + fun getTimestampForItemAt(firstVisiblePosition: Int): Long? { + val cursor = this.cursor ?: return null + if (!cursor.moveToPosition(firstVisiblePosition)) return null + val message = messageDB.readerFor(cursor).current ?: return null + return message.timestamp + } } \ No newline at end of file diff --git a/app/src/main/java/org/thoughtcrime/securesms/database/MmsDatabase.kt b/app/src/main/java/org/thoughtcrime/securesms/database/MmsDatabase.kt index 07637b4280..ca44bc8d8b 100644 --- a/app/src/main/java/org/thoughtcrime/securesms/database/MmsDatabase.kt +++ b/app/src/main/java/org/thoughtcrime/securesms/database/MmsDatabase.kt @@ -199,25 +199,6 @@ class MmsDatabase(context: Context, databaseHelper: SQLCipherOpenHelper) : Messa } } - @Throws(RecipientFormattingException::class, MmsException::class) - private fun getThreadIdFor(retrieved: IncomingMediaMessage): Long { - return if (retrieved.groupId != null) { - val groupRecipients = Recipient.from( - context, - retrieved.groupId, - true - ) - get(context).threadDatabase().getOrCreateThreadIdFor(groupRecipients) - } else { - val sender = Recipient.from( - context, - retrieved.from, - true - ) - get(context).threadDatabase().getOrCreateThreadIdFor(sender) - } - } - private fun rawQuery(where: String, arguments: Array?): Cursor { val database = databaseHelper.readableDatabase return database.rawQuery( diff --git a/app/src/main/java/org/thoughtcrime/securesms/database/ThreadDatabase.java b/app/src/main/java/org/thoughtcrime/securesms/database/ThreadDatabase.java index ceda7da141..28ae63bc32 100644 --- a/app/src/main/java/org/thoughtcrime/securesms/database/ThreadDatabase.java +++ b/app/src/main/java/org/thoughtcrime/securesms/database/ThreadDatabase.java @@ -64,6 +64,7 @@ import org.thoughtcrime.securesms.notifications.MarkReadReceiver; import org.thoughtcrime.securesms.util.SessionMetaProtocol; import java.io.Closeable; +import java.util.Collections; import java.util.HashMap; import java.util.LinkedList; import java.util.List; @@ -313,8 +314,12 @@ public class ThreadDatabase extends Database { final List smsRecords = DatabaseComponent.get(context).smsDatabase().setMessagesRead(threadId, lastReadTime); final List mmsRecords = DatabaseComponent.get(context).mmsDatabase().setMessagesRead(threadId, lastReadTime); - ContentValues contentValues = new ContentValues(1); - contentValues.put(READ, 1); + if (smsRecords.isEmpty() && mmsRecords.isEmpty()) { + return Collections.emptyList(); + } + + ContentValues contentValues = new ContentValues(2); + contentValues.put(READ, smsRecords.isEmpty() && mmsRecords.isEmpty()); contentValues.put(LAST_SEEN, lastReadTime); SQLiteDatabase db = databaseHelper.getWritableDatabase(); @@ -434,9 +439,12 @@ public class ThreadDatabase extends Database { String query = "SELECT COUNT (*) FROM " + TABLE_NAME + " LEFT OUTER JOIN " + RecipientDatabase.TABLE_NAME + " ON " + TABLE_NAME + "." + ADDRESS + " = " + RecipientDatabase.TABLE_NAME + "." + RecipientDatabase.ADDRESS + + " LEFT OUTER JOIN " + GroupDatabase.TABLE_NAME + + " ON " + TABLE_NAME + "." + ADDRESS + " = " + GroupDatabase.TABLE_NAME + "." + GROUP_ID + " WHERE " + MESSAGE_COUNT + " != 0 AND " + ARCHIVED + " = 0 AND " + HAS_SENT + " = 0 AND " + MESSAGE_COUNT + " = " + UNREAD_COUNT + " AND " + RecipientDatabase.TABLE_NAME + "." + RecipientDatabase.BLOCK + " = 0 AND " + - RecipientDatabase.TABLE_NAME + "." + RecipientDatabase.APPROVED + " = 0"; + RecipientDatabase.TABLE_NAME + "." + RecipientDatabase.APPROVED + " = 0 AND " + + GroupDatabase.TABLE_NAME + "." + GROUP_ID + " IS NULL"; cursor = db.rawQuery(query, null); if (cursor != null && cursor.moveToFirst()) diff --git a/app/src/main/java/org/thoughtcrime/securesms/notifications/DefaultMessageNotifier.java b/app/src/main/java/org/thoughtcrime/securesms/notifications/DefaultMessageNotifier.java index dfa953044b..96593a1a53 100644 --- a/app/src/main/java/org/thoughtcrime/securesms/notifications/DefaultMessageNotifier.java +++ b/app/src/main/java/org/thoughtcrime/securesms/notifications/DefaultMessageNotifier.java @@ -240,10 +240,10 @@ public class DefaultMessageNotifier implements MessageNotifier { !(recipient.isApproved() || threads.getLastSeenAndHasSent(threadId).second())) { TextSecurePreferences.removeHasHiddenMessageRequests(context); } - if (isVisible && recipient != null && threads.getMessageCount(threadId) > 0) { - List messageIds = threads.setRead(threadId, true); - if (SessionMetaProtocol.shouldSendReadReceipt(recipient)) { MarkReadReceiver.process(context, messageIds); } - } +// if (isVisible && recipient != null && threads.getMessageCount(threadId) > 0) { +// List messageIds = threads.setRead(threadId, false); +// if (SessionMetaProtocol.shouldSendReadReceipt(recipient)) { MarkReadReceiver.process(context, messageIds); } +// } if (!TextSecurePreferences.isNotificationsEnabled(context) || (recipient != null && recipient.isMuted())) diff --git a/libsession/src/main/java/org/session/libsession/messaging/sending_receiving/MessageSenderClosedGroupHandler.kt b/libsession/src/main/java/org/session/libsession/messaging/sending_receiving/MessageSenderClosedGroupHandler.kt index 4894565632..2c01f5f452 100644 --- a/libsession/src/main/java/org/session/libsession/messaging/sending_receiving/MessageSenderClosedGroupHandler.kt +++ b/libsession/src/main/java/org/session/libsession/messaging/sending_receiving/MessageSenderClosedGroupHandler.kt @@ -70,9 +70,8 @@ fun MessageSender.create(name: String, members: Collection): Promise