From 5ec2eab6b8dc6f9fb574381183fc8f8c0c5cd6af Mon Sep 17 00:00:00 2001 From: Koitharu Date: Wed, 24 Jan 2024 11:26:29 +0200 Subject: [PATCH] Fix webtoon scroll dispatching (cherry picked from commit a0a72b1192af695c25fe1aab0fc418fd04c17094) --- .../kotatsu/reader/ui/ReaderViewModel.kt | 11 +++-- .../pager/reversed/ReversedReaderFragment.kt | 3 +- .../ui/pager/standard/PagerReaderFragment.kt | 2 +- .../ui/pager/webtoon/WebtoonReaderFragment.kt | 28 +++++------ .../ui/pager/webtoon/WebtoonRecyclerView.kt | 47 +++++++++++-------- 5 files changed, 50 insertions(+), 41 deletions(-) diff --git a/app/src/main/kotlin/org/koitharu/kotatsu/reader/ui/ReaderViewModel.kt b/app/src/main/kotlin/org/koitharu/kotatsu/reader/ui/ReaderViewModel.kt index 03473f94f..899c837ce 100644 --- a/app/src/main/kotlin/org/koitharu/kotatsu/reader/ui/ReaderViewModel.kt +++ b/app/src/main/kotlin/org/koitharu/kotatsu/reader/ui/ReaderViewModel.kt @@ -262,7 +262,7 @@ class ReaderViewModel @Inject constructor( } @MainThread - fun onCurrentPageChanged(position: Int) { + fun onCurrentPageChanged(lowerPos: Int, upperPos: Int) { val prevJob = stateChangeJob val pages = content.value.pages // capture immediately stateChangeJob = launchJob(Dispatchers.Default) { @@ -271,7 +271,8 @@ class ReaderViewModel @Inject constructor( if (pages.size != content.value.pages.size) { return@launchJob // TODO } - pages.getOrNull(position)?.let { page -> + val centerPos = (lowerPos + upperPos) / 2 + pages.getOrNull(centerPos)?.let { page -> currentState.update { cs -> cs?.copy(chapterId = page.chapterId, page = page.index) } @@ -281,14 +282,14 @@ class ReaderViewModel @Inject constructor( return@launchJob } ensureActive() - if (position >= pages.lastIndex - BOUNDS_PAGE_OFFSET) { + if (upperPos >= pages.lastIndex - BOUNDS_PAGE_OFFSET) { loadPrevNextChapter(pages.last().chapterId, isNext = true) } - if (position <= BOUNDS_PAGE_OFFSET) { + if (lowerPos <= BOUNDS_PAGE_OFFSET) { loadPrevNextChapter(pages.first().chapterId, isNext = false) } if (pageLoader.isPrefetchApplicable()) { - pageLoader.prefetch(pages.trySublist(position + 1, position + PREFETCH_LIMIT)) + pageLoader.prefetch(pages.trySublist(upperPos + 1, upperPos + PREFETCH_LIMIT)) } } } diff --git a/app/src/main/kotlin/org/koitharu/kotatsu/reader/ui/pager/reversed/ReversedReaderFragment.kt b/app/src/main/kotlin/org/koitharu/kotatsu/reader/ui/pager/reversed/ReversedReaderFragment.kt index e9ccb68ab..313f2ba16 100644 --- a/app/src/main/kotlin/org/koitharu/kotatsu/reader/ui/pager/reversed/ReversedReaderFragment.kt +++ b/app/src/main/kotlin/org/koitharu/kotatsu/reader/ui/pager/reversed/ReversedReaderFragment.kt @@ -172,7 +172,8 @@ class ReversedReaderFragment : BaseReaderFragment } private fun notifyPageChanged(page: Int) { - viewModel.onCurrentPageChanged(reversed(page)) + val pos = reversed(page) + viewModel.onCurrentPageChanged(pos, pos) } private fun reversed(position: Int): Int { diff --git a/app/src/main/kotlin/org/koitharu/kotatsu/reader/ui/pager/standard/PagerReaderFragment.kt b/app/src/main/kotlin/org/koitharu/kotatsu/reader/ui/pager/standard/PagerReaderFragment.kt index 70a2cbc28..ebf7962a6 100644 --- a/app/src/main/kotlin/org/koitharu/kotatsu/reader/ui/pager/standard/PagerReaderFragment.kt +++ b/app/src/main/kotlin/org/koitharu/kotatsu/reader/ui/pager/standard/PagerReaderFragment.kt @@ -170,7 +170,7 @@ class PagerReaderFragment : BaseReaderFragment(), } private fun notifyPageChanged(page: Int) { - viewModel.onCurrentPageChanged(page) + viewModel.onCurrentPageChanged(page, page) } companion object { diff --git a/app/src/main/kotlin/org/koitharu/kotatsu/reader/ui/pager/webtoon/WebtoonReaderFragment.kt b/app/src/main/kotlin/org/koitharu/kotatsu/reader/ui/pager/webtoon/WebtoonReaderFragment.kt index f3c1d2f89..4531cb8b9 100644 --- a/app/src/main/kotlin/org/koitharu/kotatsu/reader/ui/pager/webtoon/WebtoonReaderFragment.kt +++ b/app/src/main/kotlin/org/koitharu/kotatsu/reader/ui/pager/webtoon/WebtoonReaderFragment.kt @@ -24,7 +24,8 @@ import org.koitharu.kotatsu.reader.ui.pager.ReaderPage import javax.inject.Inject @AndroidEntryPoint -class WebtoonReaderFragment : BaseReaderFragment() { +class WebtoonReaderFragment : BaseReaderFragment(), + WebtoonRecyclerView.OnWebtoonScrollListener { @Inject lateinit var networkState: NetworkState @@ -46,7 +47,7 @@ class WebtoonReaderFragment : BaseReaderFragment() with(binding.recyclerView) { setHasFixedSize(true) adapter = readerAdapter - addOnPageScrollListener(PageScrollListener()) + addOnPageScrollListener(this@WebtoonReaderFragment) recyclerLifecycleDispatcher = RecyclerViewLifecycleDispatcher().also { addOnScrollListener(it) } @@ -70,6 +71,15 @@ class WebtoonReaderFragment : BaseReaderFragment() exceptionResolver = exceptionResolver, ) + override fun onScrollChanged( + recyclerView: WebtoonRecyclerView, + dy: Int, + firstVisiblePosition: Int, + lastVisiblePosition: Int, + ) { + viewModel.onCurrentPageChanged(firstVisiblePosition, lastVisiblePosition) + } + override suspend fun onPagesChanged(pages: List, pendingState: ReaderState?) = coroutineScope { val setItems = launch { requireAdapter().setItems(pages) @@ -91,7 +101,7 @@ class WebtoonReaderFragment : BaseReaderFragment() ?.restoreScroll(pendingState.scroll) } } - notifyPageChanged(position) + viewModel.onCurrentPageChanged(position, position) } else { Snackbar.make(requireView(), R.string.not_found_404, Snackbar.LENGTH_SHORT) .show() @@ -121,10 +131,6 @@ class WebtoonReaderFragment : BaseReaderFragment() viewBinding?.frame?.onZoomOut() } - private fun notifyPageChanged(page: Int) { - viewModel.onCurrentPageChanged(page) - } - override fun switchPageBy(delta: Int) { with(requireViewBinding().recyclerView) { if (isAnimationEnabled()) { @@ -147,12 +153,4 @@ class WebtoonReaderFragment : BaseReaderFragment() } return true } - - private inner class PageScrollListener : WebtoonRecyclerView.OnPageScrollListener() { - - override fun onPageChanged(recyclerView: WebtoonRecyclerView, index: Int) { - super.onPageChanged(recyclerView, index) - notifyPageChanged(index) - } - } } diff --git a/app/src/main/kotlin/org/koitharu/kotatsu/reader/ui/pager/webtoon/WebtoonRecyclerView.kt b/app/src/main/kotlin/org/koitharu/kotatsu/reader/ui/pager/webtoon/WebtoonRecyclerView.kt index 788a11107..346ea2b5f 100644 --- a/app/src/main/kotlin/org/koitharu/kotatsu/reader/ui/pager/webtoon/WebtoonRecyclerView.kt +++ b/app/src/main/kotlin/org/koitharu/kotatsu/reader/ui/pager/webtoon/WebtoonRecyclerView.kt @@ -6,8 +6,8 @@ import android.view.View import androidx.core.view.ViewCompat.TYPE_TOUCH import androidx.core.view.forEach import androidx.core.view.iterator +import androidx.recyclerview.widget.LinearLayoutManager import androidx.recyclerview.widget.RecyclerView -import org.koitharu.kotatsu.core.util.ext.findCenterViewPosition import java.util.LinkedList import java.util.WeakHashMap @@ -15,7 +15,8 @@ class WebtoonRecyclerView @JvmOverloads constructor( context: Context, attrs: AttributeSet? = null, defStyleAttr: Int = 0 ) : RecyclerView(context, attrs, defStyleAttr) { - private var onPageScrollListeners: MutableList? = null + private var onPageScrollListeners = LinkedList() + private val scrollDispatcher = WebtoonScrollDispatcher() private val detachedViews = WeakHashMap() private var isFixingScroll: Boolean = false @@ -103,22 +104,20 @@ class WebtoonRecyclerView @JvmOverloads constructor( return 0 } - fun addOnPageScrollListener(listener: OnPageScrollListener) { - val list = onPageScrollListeners ?: LinkedList().also { onPageScrollListeners = it } - list.add(listener) + fun addOnPageScrollListener(listener: OnWebtoonScrollListener) { + onPageScrollListeners.add(listener) } - fun removeOnPageScrollListener(listener: OnPageScrollListener) { - onPageScrollListeners?.remove(listener) + fun removeOnPageScrollListener(listener: OnWebtoonScrollListener) { + onPageScrollListeners.remove(listener) } private fun notifyScrollChanged(dy: Int) { val listeners = onPageScrollListeners - if (listeners.isNullOrEmpty()) { + if (listeners.isEmpty()) { return } - val centerPosition = findCenterViewPosition() - listeners.forEach { it.dispatchScroll(this, dy, centerPosition) } + scrollDispatcher.dispatchScroll(this, dy) } fun relayoutChildren() { @@ -162,20 +161,30 @@ class WebtoonRecyclerView @JvmOverloads constructor( else -> false } - abstract class OnPageScrollListener { + private class WebtoonScrollDispatcher { - private var lastPosition = NO_POSITION + private var firstPos = NO_POSITION + private var lastPos = NO_POSITION - fun dispatchScroll(recyclerView: WebtoonRecyclerView, dy: Int, centerPosition: Int) { - onScroll(recyclerView, dy) - if (centerPosition != NO_POSITION && centerPosition != lastPosition) { - lastPosition = centerPosition - onPageChanged(recyclerView, centerPosition) + fun dispatchScroll(rv: WebtoonRecyclerView, dy: Int) { + val lm = rv.layoutManager as? LinearLayoutManager + if (lm == null) { + firstPos = NO_POSITION + lastPos = NO_POSITION + return + } + val newFirstPos = lm.findFirstVisibleItemPosition() + val newLastPos = lm.findLastVisibleItemPosition() + if (newFirstPos != firstPos || newLastPos != lastPos) { + firstPos = newFirstPos + lastPos = newLastPos + rv.onPageScrollListeners.forEach { it.onScrollChanged(rv, dy, newFirstPos, newLastPos) } } } + } - open fun onScroll(recyclerView: WebtoonRecyclerView, dy: Int) = Unit + interface OnWebtoonScrollListener { - open fun onPageChanged(recyclerView: WebtoonRecyclerView, index: Int) = Unit + fun onScrollChanged(recyclerView: WebtoonRecyclerView, dy: Int, firstVisiblePosition: Int, lastVisiblePosition: Int) } }