@@ -262,7 +262,7 @@ class ReaderViewModel @Inject constructor(
|
|||||||
}
|
}
|
||||||
|
|
||||||
@MainThread
|
@MainThread
|
||||||
fun onCurrentPageChanged(position: Int) {
|
fun onCurrentPageChanged(lowerPos: Int, upperPos: Int) {
|
||||||
val prevJob = stateChangeJob
|
val prevJob = stateChangeJob
|
||||||
val pages = content.value.pages // capture immediately
|
val pages = content.value.pages // capture immediately
|
||||||
stateChangeJob = launchJob(Dispatchers.Default) {
|
stateChangeJob = launchJob(Dispatchers.Default) {
|
||||||
@@ -271,7 +271,8 @@ class ReaderViewModel @Inject constructor(
|
|||||||
if (pages.size != content.value.pages.size) {
|
if (pages.size != content.value.pages.size) {
|
||||||
return@launchJob // TODO
|
return@launchJob // TODO
|
||||||
}
|
}
|
||||||
pages.getOrNull(position)?.let { page ->
|
val centerPos = (lowerPos + upperPos) / 2
|
||||||
|
pages.getOrNull(centerPos)?.let { page ->
|
||||||
currentState.update { cs ->
|
currentState.update { cs ->
|
||||||
cs?.copy(chapterId = page.chapterId, page = page.index)
|
cs?.copy(chapterId = page.chapterId, page = page.index)
|
||||||
}
|
}
|
||||||
@@ -281,14 +282,14 @@ class ReaderViewModel @Inject constructor(
|
|||||||
return@launchJob
|
return@launchJob
|
||||||
}
|
}
|
||||||
ensureActive()
|
ensureActive()
|
||||||
if (position >= pages.lastIndex - BOUNDS_PAGE_OFFSET) {
|
if (upperPos >= pages.lastIndex - BOUNDS_PAGE_OFFSET) {
|
||||||
loadPrevNextChapter(pages.last().chapterId, isNext = true)
|
loadPrevNextChapter(pages.last().chapterId, isNext = true)
|
||||||
}
|
}
|
||||||
if (position <= BOUNDS_PAGE_OFFSET) {
|
if (lowerPos <= BOUNDS_PAGE_OFFSET) {
|
||||||
loadPrevNextChapter(pages.first().chapterId, isNext = false)
|
loadPrevNextChapter(pages.first().chapterId, isNext = false)
|
||||||
}
|
}
|
||||||
if (pageLoader.isPrefetchApplicable()) {
|
if (pageLoader.isPrefetchApplicable()) {
|
||||||
pageLoader.prefetch(pages.trySublist(position + 1, position + PREFETCH_LIMIT))
|
pageLoader.prefetch(pages.trySublist(upperPos + 1, upperPos + PREFETCH_LIMIT))
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -172,7 +172,8 @@ class ReversedReaderFragment : BaseReaderFragment<FragmentReaderStandardBinding>
|
|||||||
}
|
}
|
||||||
|
|
||||||
private fun notifyPageChanged(page: Int) {
|
private fun notifyPageChanged(page: Int) {
|
||||||
viewModel.onCurrentPageChanged(reversed(page))
|
val pos = reversed(page)
|
||||||
|
viewModel.onCurrentPageChanged(pos, pos)
|
||||||
}
|
}
|
||||||
|
|
||||||
private fun reversed(position: Int): Int {
|
private fun reversed(position: Int): Int {
|
||||||
|
|||||||
@@ -170,7 +170,7 @@ class PagerReaderFragment : BaseReaderFragment<FragmentReaderStandardBinding>(),
|
|||||||
}
|
}
|
||||||
|
|
||||||
private fun notifyPageChanged(page: Int) {
|
private fun notifyPageChanged(page: Int) {
|
||||||
viewModel.onCurrentPageChanged(page)
|
viewModel.onCurrentPageChanged(page, page)
|
||||||
}
|
}
|
||||||
|
|
||||||
companion object {
|
companion object {
|
||||||
|
|||||||
@@ -24,7 +24,8 @@ import org.koitharu.kotatsu.reader.ui.pager.ReaderPage
|
|||||||
import javax.inject.Inject
|
import javax.inject.Inject
|
||||||
|
|
||||||
@AndroidEntryPoint
|
@AndroidEntryPoint
|
||||||
class WebtoonReaderFragment : BaseReaderFragment<FragmentReaderWebtoonBinding>() {
|
class WebtoonReaderFragment : BaseReaderFragment<FragmentReaderWebtoonBinding>(),
|
||||||
|
WebtoonRecyclerView.OnWebtoonScrollListener {
|
||||||
|
|
||||||
@Inject
|
@Inject
|
||||||
lateinit var networkState: NetworkState
|
lateinit var networkState: NetworkState
|
||||||
@@ -46,7 +47,7 @@ class WebtoonReaderFragment : BaseReaderFragment<FragmentReaderWebtoonBinding>()
|
|||||||
with(binding.recyclerView) {
|
with(binding.recyclerView) {
|
||||||
setHasFixedSize(true)
|
setHasFixedSize(true)
|
||||||
adapter = readerAdapter
|
adapter = readerAdapter
|
||||||
addOnPageScrollListener(PageScrollListener())
|
addOnPageScrollListener(this@WebtoonReaderFragment)
|
||||||
recyclerLifecycleDispatcher = RecyclerViewLifecycleDispatcher().also {
|
recyclerLifecycleDispatcher = RecyclerViewLifecycleDispatcher().also {
|
||||||
addOnScrollListener(it)
|
addOnScrollListener(it)
|
||||||
}
|
}
|
||||||
@@ -70,6 +71,15 @@ class WebtoonReaderFragment : BaseReaderFragment<FragmentReaderWebtoonBinding>()
|
|||||||
exceptionResolver = exceptionResolver,
|
exceptionResolver = exceptionResolver,
|
||||||
)
|
)
|
||||||
|
|
||||||
|
override fun onScrollChanged(
|
||||||
|
recyclerView: WebtoonRecyclerView,
|
||||||
|
dy: Int,
|
||||||
|
firstVisiblePosition: Int,
|
||||||
|
lastVisiblePosition: Int,
|
||||||
|
) {
|
||||||
|
viewModel.onCurrentPageChanged(firstVisiblePosition, lastVisiblePosition)
|
||||||
|
}
|
||||||
|
|
||||||
override suspend fun onPagesChanged(pages: List<ReaderPage>, pendingState: ReaderState?) = coroutineScope {
|
override suspend fun onPagesChanged(pages: List<ReaderPage>, pendingState: ReaderState?) = coroutineScope {
|
||||||
val setItems = launch {
|
val setItems = launch {
|
||||||
requireAdapter().setItems(pages)
|
requireAdapter().setItems(pages)
|
||||||
@@ -91,7 +101,7 @@ class WebtoonReaderFragment : BaseReaderFragment<FragmentReaderWebtoonBinding>()
|
|||||||
?.restoreScroll(pendingState.scroll)
|
?.restoreScroll(pendingState.scroll)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
notifyPageChanged(position)
|
viewModel.onCurrentPageChanged(position, position)
|
||||||
} else {
|
} else {
|
||||||
Snackbar.make(requireView(), R.string.not_found_404, Snackbar.LENGTH_SHORT)
|
Snackbar.make(requireView(), R.string.not_found_404, Snackbar.LENGTH_SHORT)
|
||||||
.show()
|
.show()
|
||||||
@@ -121,10 +131,6 @@ class WebtoonReaderFragment : BaseReaderFragment<FragmentReaderWebtoonBinding>()
|
|||||||
viewBinding?.frame?.onZoomOut()
|
viewBinding?.frame?.onZoomOut()
|
||||||
}
|
}
|
||||||
|
|
||||||
private fun notifyPageChanged(page: Int) {
|
|
||||||
viewModel.onCurrentPageChanged(page)
|
|
||||||
}
|
|
||||||
|
|
||||||
override fun switchPageBy(delta: Int) {
|
override fun switchPageBy(delta: Int) {
|
||||||
with(requireViewBinding().recyclerView) {
|
with(requireViewBinding().recyclerView) {
|
||||||
if (isAnimationEnabled()) {
|
if (isAnimationEnabled()) {
|
||||||
@@ -147,12 +153,4 @@ class WebtoonReaderFragment : BaseReaderFragment<FragmentReaderWebtoonBinding>()
|
|||||||
}
|
}
|
||||||
return true
|
return true
|
||||||
}
|
}
|
||||||
|
|
||||||
private inner class PageScrollListener : WebtoonRecyclerView.OnPageScrollListener() {
|
|
||||||
|
|
||||||
override fun onPageChanged(recyclerView: WebtoonRecyclerView, index: Int) {
|
|
||||||
super.onPageChanged(recyclerView, index)
|
|
||||||
notifyPageChanged(index)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -6,8 +6,8 @@ import android.view.View
|
|||||||
import androidx.core.view.ViewCompat.TYPE_TOUCH
|
import androidx.core.view.ViewCompat.TYPE_TOUCH
|
||||||
import androidx.core.view.forEach
|
import androidx.core.view.forEach
|
||||||
import androidx.core.view.iterator
|
import androidx.core.view.iterator
|
||||||
|
import androidx.recyclerview.widget.LinearLayoutManager
|
||||||
import androidx.recyclerview.widget.RecyclerView
|
import androidx.recyclerview.widget.RecyclerView
|
||||||
import org.koitharu.kotatsu.core.util.ext.findCenterViewPosition
|
|
||||||
import java.util.LinkedList
|
import java.util.LinkedList
|
||||||
import java.util.WeakHashMap
|
import java.util.WeakHashMap
|
||||||
|
|
||||||
@@ -15,7 +15,8 @@ class WebtoonRecyclerView @JvmOverloads constructor(
|
|||||||
context: Context, attrs: AttributeSet? = null, defStyleAttr: Int = 0
|
context: Context, attrs: AttributeSet? = null, defStyleAttr: Int = 0
|
||||||
) : RecyclerView(context, attrs, defStyleAttr) {
|
) : RecyclerView(context, attrs, defStyleAttr) {
|
||||||
|
|
||||||
private var onPageScrollListeners: MutableList<OnPageScrollListener>? = null
|
private var onPageScrollListeners = LinkedList<OnWebtoonScrollListener>()
|
||||||
|
private val scrollDispatcher = WebtoonScrollDispatcher()
|
||||||
private val detachedViews = WeakHashMap<View, Unit>()
|
private val detachedViews = WeakHashMap<View, Unit>()
|
||||||
private var isFixingScroll: Boolean = false
|
private var isFixingScroll: Boolean = false
|
||||||
|
|
||||||
@@ -103,22 +104,20 @@ class WebtoonRecyclerView @JvmOverloads constructor(
|
|||||||
return 0
|
return 0
|
||||||
}
|
}
|
||||||
|
|
||||||
fun addOnPageScrollListener(listener: OnPageScrollListener) {
|
fun addOnPageScrollListener(listener: OnWebtoonScrollListener) {
|
||||||
val list = onPageScrollListeners ?: LinkedList<OnPageScrollListener>().also { onPageScrollListeners = it }
|
onPageScrollListeners.add(listener)
|
||||||
list.add(listener)
|
|
||||||
}
|
}
|
||||||
|
|
||||||
fun removeOnPageScrollListener(listener: OnPageScrollListener) {
|
fun removeOnPageScrollListener(listener: OnWebtoonScrollListener) {
|
||||||
onPageScrollListeners?.remove(listener)
|
onPageScrollListeners.remove(listener)
|
||||||
}
|
}
|
||||||
|
|
||||||
private fun notifyScrollChanged(dy: Int) {
|
private fun notifyScrollChanged(dy: Int) {
|
||||||
val listeners = onPageScrollListeners
|
val listeners = onPageScrollListeners
|
||||||
if (listeners.isNullOrEmpty()) {
|
if (listeners.isEmpty()) {
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
val centerPosition = findCenterViewPosition()
|
scrollDispatcher.dispatchScroll(this, dy)
|
||||||
listeners.forEach { it.dispatchScroll(this, dy, centerPosition) }
|
|
||||||
}
|
}
|
||||||
|
|
||||||
fun relayoutChildren() {
|
fun relayoutChildren() {
|
||||||
@@ -162,20 +161,30 @@ class WebtoonRecyclerView @JvmOverloads constructor(
|
|||||||
else -> false
|
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) {
|
fun dispatchScroll(rv: WebtoonRecyclerView, dy: Int) {
|
||||||
onScroll(recyclerView, dy)
|
val lm = rv.layoutManager as? LinearLayoutManager
|
||||||
if (centerPosition != NO_POSITION && centerPosition != lastPosition) {
|
if (lm == null) {
|
||||||
lastPosition = centerPosition
|
firstPos = NO_POSITION
|
||||||
onPageChanged(recyclerView, centerPosition)
|
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)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
Reference in New Issue
Block a user