Fix incorrect read state causing stale notifications and tweak scroll to bottom behavior.

This commit is contained in:
Cody Henthorne
2023-08-04 08:31:03 -04:00
committed by GitHub
parent 78bdee61ef
commit c3700cf6d9
5 changed files with 58 additions and 30 deletions

View File

@@ -373,6 +373,11 @@ class ConversationFragment :
adapter.unregisterAdapterDataObserver(it)
}
scrollListener?.let {
_binding.conversationItemRecycler.removeOnScrollListener(it)
}
scrollListener = null
_binding.conversationItemRecycler.adapter = null
textDraftSaveDebouncer.clear()
@@ -473,6 +478,7 @@ class ConversationFragment :
private var composeTextEventsListener: ComposeTextEventsListener? = null
private var dataObserver: DataObserver? = null
private var menuProvider: ConversationOptionsMenu.Provider? = null
private var scrollListener: ScrollListener? = null
private val jumpAndPulseScrollStrategy = object : ScrollToPositionDelegate.ScrollStrategy {
override fun performScroll(recyclerView: RecyclerView, layoutManager: LinearLayoutManager, position: Int, smooth: Boolean) {
@@ -830,10 +836,6 @@ class ConversationFragment :
.distinctUntilChanged { r1, r2 -> r1 === r2 || r1.hasSameContent(r2) }
.subscribeBy(onNext = this::onRecipientChanged)
disposables += viewModel.markReadRequests
.observeOn(AndroidSchedulers.mainThread())
.subscribeBy(onNext = markReadHelper::onViewsRevealed)
disposables += viewModel.scrollButtonState
.subscribeBy(onNext = this::presentScrollButtons)
@@ -1448,7 +1450,8 @@ class ConversationFragment :
layoutManager = ConversationLayoutManager(requireContext())
binding.conversationItemRecycler.setHasFixedSize(false)
binding.conversationItemRecycler.layoutManager = layoutManager
binding.conversationItemRecycler.addOnScrollListener(ScrollListener())
scrollListener = ScrollListener()
binding.conversationItemRecycler.addOnScrollListener(scrollListener!!)
adapter = ConversationAdapterV2(
lifecycleOwner = viewLifecycleOwner,
@@ -2248,6 +2251,10 @@ class ConversationFragment :
return layoutManager.findFirstCompletelyVisibleItemPosition() > 4
}
private fun shouldScrollToBottom(): Boolean {
return isScrolledToBottom() || layoutManager.findFirstVisibleItemPosition() <= 0
}
/**
* Controls animation and visibility of the scrollDateHeader.
*/
@@ -2308,9 +2315,11 @@ class ConversationFragment :
override fun onScrolled(recyclerView: RecyclerView, dx: Int, dy: Int) {
if (isScrolledToBottom()) {
viewModel.setShowScrollButtons(false)
viewModel.setShowScrollButtonsForScrollPosition(showScrollButtons = false, willScrollToBottomOnNewMessage = true)
} else if (isScrolledPastButtonThreshold()) {
viewModel.setShowScrollButtons(true)
viewModel.setShowScrollButtonsForScrollPosition(showScrollButtons = true, willScrollToBottomOnNewMessage = false)
} else {
viewModel.setShowScrollButtonsForScrollPosition(showScrollButtons = false, willScrollToBottomOnNewMessage = shouldScrollToBottom())
}
presentComposeDivider()
@@ -2344,10 +2353,9 @@ class ConversationFragment :
private inner class DataObserver : RecyclerView.AdapterDataObserver() {
override fun onItemRangeInserted(positionStart: Int, itemCount: Int) {
Log.d(TAG, "onItemRangeInserted $positionStart $itemCount")
if (positionStart == 0 && itemCount == 1 && !binding.conversationItemRecycler.canScrollVertically(1)) {
Log.d(TAG, "Requesting scroll to bottom.")
if (positionStart == 0 && itemCount == 1 && shouldScrollToBottom()) {
layoutManager.scrollToPositionWithOffset(0, 0)
scrollListener?.onScrolled(binding.conversationItemRecycler, 0, 0)
}
}
@@ -2369,6 +2377,10 @@ class ConversationFragment :
actionMode?.setTitle(calculateSelectedItemCount())
}
}
override fun onItemRangeChanged(positionStart: Int, itemCount: Int) {
scrollListener?.onScrolled(binding.conversationItemRecycler, 0, 0)
}
}
//endregion Scroll Handling
@@ -2820,10 +2832,7 @@ class ConversationFragment :
ViewUtil.hideKeyboard(requireContext(), itemView)
val showScrollButtons = viewModel.showScrollButtonsSnapshot
if (showScrollButtons) {
viewModel.setShowScrollButtons(false)
}
viewModel.setHideScrollButtonsForReactionOverlay(true)
val targetViews: InteractiveConversationElement = target
handleReaction(
@@ -2861,9 +2870,7 @@ class ConversationFragment :
ViewUtil.fadeIn(targetViews.quotedIndicatorView!!, 150)
}
if (showScrollButtons) {
viewModel.setShowScrollButtons(true)
}
viewModel.setHideScrollButtonsForReactionOverlay(false)
}
}
)

View File

@@ -288,15 +288,14 @@ class ConversationRepository(
}
fun getMessageCounts(threadId: Long): Flowable<MessageCounts> {
return RxDatabaseObserver.conversationList
return RxDatabaseObserver.conversation(threadId)
.map { getUnreadCount(threadId) }
.distinctUntilChanged()
.map { MessageCounts(it, getUnreadMentionsCount(threadId)) }
}
private fun getUnreadCount(threadId: Long): Int {
val threadRecord = SignalDatabase.threads.getThreadRecord(threadId)
return threadRecord?.unreadCount ?: 0
return SignalDatabase.messages.getUnreadCount(threadId)
}
private fun getUnreadMentionsCount(threadId: Long): Int {

View File

@@ -1,7 +1,20 @@
/*
* Copyright 2023 Signal Messenger, LLC
* SPDX-License-Identifier: AGPL-3.0-only
*/
package org.thoughtcrime.securesms.conversation.v2
/**
* State information used to display the scroll to next mention and scroll to bottom buttons.
*/
data class ConversationScrollButtonState(
val showScrollButtons: Boolean = false,
val hideScrollButtonsForReactionOverlay: Boolean = false,
val showScrollButtonsForScrollPosition: Boolean = false,
val willScrollToBottomOnNewMessage: Boolean = true,
val unreadCount: Int = 0,
val hasMentions: Boolean = false
)
) {
val showScrollButtons: Boolean
get() = !hideScrollButtonsForReactionOverlay && (showScrollButtonsForScrollPosition || (!willScrollToBottomOnNewMessage && unreadCount > 0))
}

View File

@@ -20,7 +20,6 @@ import io.reactivex.rxjava3.disposables.CompositeDisposable
import io.reactivex.rxjava3.kotlin.addTo
import io.reactivex.rxjava3.kotlin.plusAssign
import io.reactivex.rxjava3.kotlin.subscribeBy
import io.reactivex.rxjava3.processors.PublishProcessor
import io.reactivex.rxjava3.schedulers.Schedulers
import io.reactivex.rxjava3.subjects.BehaviorSubject
import io.reactivex.rxjava3.subjects.PublishSubject
@@ -98,11 +97,6 @@ class ConversationViewModel(
private val _conversationThreadState: Subject<ConversationThreadState> = BehaviorSubject.create()
val conversationThreadState: Single<ConversationThreadState> = _conversationThreadState.firstOrError()
private val _markReadProcessor: PublishProcessor<Long> = PublishProcessor.create()
val markReadRequests: Flowable<Long> = _markReadProcessor
.onBackpressureBuffer()
.distinct()
val pagingController = ProxyPagingController<ConversationElementKey>()
val groupMemberServiceIds: Observable<List<ServiceId>> = recipientRepository
@@ -249,9 +243,18 @@ class ConversationViewModel(
disposables.clear()
}
fun setShowScrollButtons(showScrollButtons: Boolean) {
fun setShowScrollButtonsForScrollPosition(showScrollButtons: Boolean, willScrollToBottomOnNewMessage: Boolean) {
scrollButtonStateStore.update {
it.copy(showScrollButtons = showScrollButtons)
it.copy(
showScrollButtonsForScrollPosition = showScrollButtons,
willScrollToBottomOnNewMessage = willScrollToBottomOnNewMessage
)
}
}
fun setHideScrollButtonsForReactionOverlay(hide: Boolean) {
scrollButtonStateStore.update {
it.copy(hideScrollButtonsForReactionOverlay = hide)
}
}

View File

@@ -22,6 +22,12 @@ object RxDatabaseObserver {
}
}
fun conversation(threadId: Long): Flowable<Unit> {
return databaseFlowable { listener ->
ApplicationDependencies.getDatabaseObserver().registerVerboseConversationObserver(threadId, listener)
}
}
@Suppress("RedundantUnitExpression")
private fun notificationProfilesFlowable(): Flowable<Unit> {
return Flowable.combineLatest(