mirror of
https://github.com/oxen-io/session-android.git
synced 2024-11-25 11:05:25 +00:00
fix: don't set the read flag in update notifications, some roundabout logic for first loads and scrolling to last known positions
This commit is contained in:
parent
ebdfd2538d
commit
cd66901412
@ -58,6 +58,7 @@ import kotlinx.coroutines.channels.Channel
|
|||||||
import kotlinx.coroutines.flow.collectLatest
|
import kotlinx.coroutines.flow.collectLatest
|
||||||
import kotlinx.coroutines.flow.consumeAsFlow
|
import kotlinx.coroutines.flow.consumeAsFlow
|
||||||
import kotlinx.coroutines.flow.debounce
|
import kotlinx.coroutines.flow.debounce
|
||||||
|
import kotlinx.coroutines.flow.filter
|
||||||
import kotlinx.coroutines.launch
|
import kotlinx.coroutines.launch
|
||||||
import kotlinx.coroutines.withContext
|
import kotlinx.coroutines.withContext
|
||||||
import network.loki.messenger.R
|
import network.loki.messenger.R
|
||||||
@ -174,6 +175,7 @@ import org.thoughtcrime.securesms.util.toPx
|
|||||||
import java.lang.ref.WeakReference
|
import java.lang.ref.WeakReference
|
||||||
import java.util.Locale
|
import java.util.Locale
|
||||||
import java.util.concurrent.ExecutionException
|
import java.util.concurrent.ExecutionException
|
||||||
|
import java.util.concurrent.atomic.AtomicBoolean
|
||||||
import java.util.concurrent.atomic.AtomicLong
|
import java.util.concurrent.atomic.AtomicLong
|
||||||
import java.util.concurrent.atomic.AtomicReference
|
import java.util.concurrent.atomic.AtomicReference
|
||||||
import javax.inject.Inject
|
import javax.inject.Inject
|
||||||
@ -272,7 +274,7 @@ class ConversationActivityV2 : PassphraseRequiredActionBarActivity(), InputBarDe
|
|||||||
val searchViewModel: SearchViewModel by viewModels()
|
val searchViewModel: SearchViewModel by viewModels()
|
||||||
var searchViewItem: MenuItem? = null
|
var searchViewItem: MenuItem? = null
|
||||||
|
|
||||||
private val bufferedLastSeenChannel = Channel<Unit>(capacity = Channel.RENDEZVOUS, onBufferOverflow = BufferOverflow.DROP_OLDEST)
|
private val bufferedLastSeenChannel = Channel<Long>(capacity = 512, onBufferOverflow = BufferOverflow.DROP_OLDEST)
|
||||||
|
|
||||||
private val isScrolledToBottom: Boolean
|
private val isScrolledToBottom: Boolean
|
||||||
get() {
|
get() {
|
||||||
@ -340,6 +342,7 @@ class ConversationActivityV2 : PassphraseRequiredActionBarActivity(), InputBarDe
|
|||||||
private val cameraButton by lazy { InputBarButton(this, R.drawable.ic_baseline_photo_camera_24, hasOpaqueBackground = true) }
|
private val cameraButton by lazy { InputBarButton(this, R.drawable.ic_baseline_photo_camera_24, hasOpaqueBackground = true) }
|
||||||
private val messageToScrollTimestamp = AtomicLong(-1)
|
private val messageToScrollTimestamp = AtomicLong(-1)
|
||||||
private val messageToScrollAuthor = AtomicReference<Address?>(null)
|
private val messageToScrollAuthor = AtomicReference<Address?>(null)
|
||||||
|
private val firstLoad = AtomicBoolean(true)
|
||||||
|
|
||||||
private lateinit var reactionDelegate: ConversationReactionDelegate
|
private lateinit var reactionDelegate: ConversationReactionDelegate
|
||||||
private val reactWithAnyEmojiStartPage = -1
|
private val reactWithAnyEmojiStartPage = -1
|
||||||
@ -440,16 +443,12 @@ class ConversationActivityV2 : PassphraseRequiredActionBarActivity(), InputBarDe
|
|||||||
lifecycle.repeatOnLifecycle(Lifecycle.State.RESUMED) {
|
lifecycle.repeatOnLifecycle(Lifecycle.State.RESUMED) {
|
||||||
// only update the conversation every 3 seconds maximum
|
// only update the conversation every 3 seconds maximum
|
||||||
// channel is rendezvous and shouldn't block on try send calls as often as we want
|
// 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 bufferedFlow = bufferedLastSeenChannel.consumeAsFlow()
|
||||||
val lastItemPos = layoutManager.findLastCompletelyVisibleItemPosition()
|
bufferedFlow.filter {
|
||||||
// adapter.item
|
it > storage.getLastSeen(viewModel.threadId)
|
||||||
withContext(Dispatchers.IO) {
|
}.collectLatest { latestMessageRead ->
|
||||||
storage.markConversationAsRead(viewModel.threadId, SnodeAPI.nowWithOffset)
|
|
||||||
}
|
|
||||||
val bufferedFlow = bufferedLastSeenChannel.consumeAsFlow().debounce(3.seconds)
|
|
||||||
bufferedFlow.collectLatest {
|
|
||||||
withContext(Dispatchers.IO) {
|
withContext(Dispatchers.IO) {
|
||||||
storage.markConversationAsRead(viewModel.threadId, SnodeAPI.nowWithOffset)
|
storage.markConversationAsRead(viewModel.threadId, latestMessageRead)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -500,8 +499,9 @@ class ConversationActivityV2 : PassphraseRequiredActionBarActivity(), InputBarDe
|
|||||||
val author = messageToScrollAuthor.getAndSet(null)
|
val author = messageToScrollAuthor.getAndSet(null)
|
||||||
if (author != null && messageTimestamp >= 0) {
|
if (author != null && messageTimestamp >= 0) {
|
||||||
jumpToMessage(author, messageTimestamp, null)
|
jumpToMessage(author, messageTimestamp, null)
|
||||||
|
} else if (firstLoad.getAndSet(false)) {
|
||||||
|
scrollToFirstUnreadMessageIfNeeded()
|
||||||
}
|
}
|
||||||
bufferedLastSeenChannel.trySend(Unit)
|
|
||||||
}
|
}
|
||||||
updatePlaceholder()
|
updatePlaceholder()
|
||||||
}
|
}
|
||||||
@ -990,7 +990,13 @@ class ConversationActivityV2 : PassphraseRequiredActionBarActivity(), InputBarDe
|
|||||||
binding.typingIndicatorViewContainer.isVisible = wasTypingIndicatorVisibleBefore && isScrolledToBottom
|
binding.typingIndicatorViewContainer.isVisible = wasTypingIndicatorVisibleBefore && isScrolledToBottom
|
||||||
binding.typingIndicatorViewContainer.isVisible
|
binding.typingIndicatorViewContainer.isVisible
|
||||||
showOrHideScrollToBottomButton()
|
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)
|
unreadCount = min(unreadCount, firstVisiblePosition).coerceAtLeast(0)
|
||||||
updateUnreadCountIndicator()
|
updateUnreadCountIndicator()
|
||||||
}
|
}
|
||||||
|
@ -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.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.ThreadDatabase.Reader
|
||||||
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
|
||||||
@ -219,11 +220,14 @@ class ConversationAdapter(
|
|||||||
|
|
||||||
fun findLastSeenItemPosition(lastSeenTimestamp: Long): Int? {
|
fun findLastSeenItemPosition(lastSeenTimestamp: Long): Int? {
|
||||||
val cursor = this.cursor
|
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) {
|
for (i in 0 until itemCount) {
|
||||||
cursor.moveToPosition(i)
|
cursor.moveToPosition(i)
|
||||||
val message = messageDB.readerFor(cursor).current
|
val message = messageDB.readerFor(cursor).current
|
||||||
if (message.isOutgoing || message.dateReceived <= lastSeenTimestamp) { return i }
|
if (message.isOutgoing || message.dateSent <= lastSeenTimestamp) { return i }
|
||||||
}
|
}
|
||||||
return null
|
return null
|
||||||
}
|
}
|
||||||
@ -243,4 +247,11 @@ class ConversationAdapter(
|
|||||||
this.searchQuery = query
|
this.searchQuery = query
|
||||||
notifyDataSetChanged()
|
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
|
||||||
|
}
|
||||||
}
|
}
|
@ -64,6 +64,7 @@ import org.thoughtcrime.securesms.notifications.MarkReadReceiver;
|
|||||||
import org.thoughtcrime.securesms.util.SessionMetaProtocol;
|
import org.thoughtcrime.securesms.util.SessionMetaProtocol;
|
||||||
|
|
||||||
import java.io.Closeable;
|
import java.io.Closeable;
|
||||||
|
import java.util.Collections;
|
||||||
import java.util.HashMap;
|
import java.util.HashMap;
|
||||||
import java.util.LinkedList;
|
import java.util.LinkedList;
|
||||||
import java.util.List;
|
import java.util.List;
|
||||||
@ -313,8 +314,12 @@ public class ThreadDatabase extends Database {
|
|||||||
final List<MarkedMessageInfo> smsRecords = DatabaseComponent.get(context).smsDatabase().setMessagesRead(threadId, lastReadTime);
|
final List<MarkedMessageInfo> smsRecords = DatabaseComponent.get(context).smsDatabase().setMessagesRead(threadId, lastReadTime);
|
||||||
final List<MarkedMessageInfo> mmsRecords = DatabaseComponent.get(context).mmsDatabase().setMessagesRead(threadId, lastReadTime);
|
final List<MarkedMessageInfo> mmsRecords = DatabaseComponent.get(context).mmsDatabase().setMessagesRead(threadId, lastReadTime);
|
||||||
|
|
||||||
ContentValues contentValues = new ContentValues(1);
|
if (smsRecords.isEmpty() && mmsRecords.isEmpty()) {
|
||||||
contentValues.put(READ, 1);
|
return Collections.emptyList();
|
||||||
|
}
|
||||||
|
|
||||||
|
ContentValues contentValues = new ContentValues(2);
|
||||||
|
contentValues.put(READ, smsRecords.isEmpty() && mmsRecords.isEmpty());
|
||||||
contentValues.put(LAST_SEEN, lastReadTime);
|
contentValues.put(LAST_SEEN, lastReadTime);
|
||||||
|
|
||||||
SQLiteDatabase db = databaseHelper.getWritableDatabase();
|
SQLiteDatabase db = databaseHelper.getWritableDatabase();
|
||||||
|
@ -240,10 +240,10 @@ public class DefaultMessageNotifier implements MessageNotifier {
|
|||||||
!(recipient.isApproved() || threads.getLastSeenAndHasSent(threadId).second())) {
|
!(recipient.isApproved() || threads.getLastSeenAndHasSent(threadId).second())) {
|
||||||
TextSecurePreferences.removeHasHiddenMessageRequests(context);
|
TextSecurePreferences.removeHasHiddenMessageRequests(context);
|
||||||
}
|
}
|
||||||
if (isVisible && recipient != null && threads.getMessageCount(threadId) > 0) {
|
// if (isVisible && recipient != null && threads.getMessageCount(threadId) > 0) {
|
||||||
List<MarkedMessageInfo> messageIds = threads.setRead(threadId, true);
|
// List<MarkedMessageInfo> messageIds = threads.setRead(threadId, false);
|
||||||
if (SessionMetaProtocol.shouldSendReadReceipt(recipient)) { MarkReadReceiver.process(context, messageIds); }
|
// if (SessionMetaProtocol.shouldSendReadReceipt(recipient)) { MarkReadReceiver.process(context, messageIds); }
|
||||||
}
|
// }
|
||||||
|
|
||||||
if (!TextSecurePreferences.isNotificationsEnabled(context) ||
|
if (!TextSecurePreferences.isNotificationsEnabled(context) ||
|
||||||
(recipient != null && recipient.isMuted()))
|
(recipient != null && recipient.isMuted()))
|
||||||
|
@ -70,9 +70,8 @@ fun MessageSender.create(name: String, members: Collection<String>): Promise<Str
|
|||||||
storage.addClosedGroupPublicKey(groupPublicKey)
|
storage.addClosedGroupPublicKey(groupPublicKey)
|
||||||
// Store the encryption key pair
|
// Store the encryption key pair
|
||||||
storage.addClosedGroupEncryptionKeyPair(encryptionKeyPair, groupPublicKey, sentTime)
|
storage.addClosedGroupEncryptionKeyPair(encryptionKeyPair, groupPublicKey, sentTime)
|
||||||
// Notify the user
|
// Create the thread
|
||||||
val threadID = storage.getOrCreateThreadIdFor(Address.fromSerialized(groupID))
|
storage.getOrCreateThreadIdFor(Address.fromSerialized(groupID))
|
||||||
storage.insertOutgoingInfoMessage(context, groupID, SignalServiceGroup.Type.CREATION, name, members, admins, threadID, sentTime)
|
|
||||||
storage.createInitialConfigGroup(groupPublicKey, name, GroupUtil.createConfigMemberMap(members, admins), sentTime, encryptionKeyPair)
|
storage.createInitialConfigGroup(groupPublicKey, name, GroupUtil.createConfigMemberMap(members, admins), sentTime, encryptionKeyPair)
|
||||||
// Notify the PN server
|
// Notify the PN server
|
||||||
PushNotificationAPI.performOperation(PushNotificationAPI.ClosedGroupOperation.Subscribe, groupPublicKey, userPublicKey)
|
PushNotificationAPI.performOperation(PushNotificationAPI.ClosedGroupOperation.Subscribe, groupPublicKey, userPublicKey)
|
||||||
|
@ -498,13 +498,8 @@ private fun handleNewClosedGroup(sender: String, sentTimestamp: Long, groupPubli
|
|||||||
storage.setExpirationTimer(groupID, expireTimer)
|
storage.setExpirationTimer(groupID, expireTimer)
|
||||||
// Notify the PN server
|
// Notify the PN server
|
||||||
PushNotificationAPI.performOperation(PushNotificationAPI.ClosedGroupOperation.Subscribe, groupPublicKey, storage.getUserPublicKey()!!)
|
PushNotificationAPI.performOperation(PushNotificationAPI.ClosedGroupOperation.Subscribe, groupPublicKey, storage.getUserPublicKey()!!)
|
||||||
// Notify the user
|
// Create thread
|
||||||
if (userPublicKey == sender && !groupExists) {
|
storage.getOrCreateThreadIdFor(Address.fromSerialized(groupID))
|
||||||
val threadID = storage.getOrCreateThreadIdFor(Address.fromSerialized(groupID))
|
|
||||||
storage.insertOutgoingInfoMessage(context, groupID, SignalServiceGroup.Type.CREATION, name, members, admins, threadID, sentTimestamp)
|
|
||||||
} else if (userPublicKey != sender) {
|
|
||||||
storage.insertIncomingInfoMessage(context, sender, groupID, SignalServiceGroup.Type.CREATION, name, members, admins, sentTimestamp)
|
|
||||||
}
|
|
||||||
// Start polling
|
// Start polling
|
||||||
ClosedGroupPollerV2.shared.startPolling(groupPublicKey)
|
ClosedGroupPollerV2.shared.startPolling(groupPublicKey)
|
||||||
}
|
}
|
||||||
|
Loading…
Reference in New Issue
Block a user