Fix concurrent chapters loading in reader
This commit is contained in:
@@ -15,8 +15,8 @@ android {
|
||||
applicationId 'org.koitharu.kotatsu'
|
||||
minSdkVersion 21
|
||||
targetSdkVersion 33
|
||||
versionCode 544
|
||||
versionName '5.1'
|
||||
versionCode 545
|
||||
versionName '5.1.1'
|
||||
generatedDensities = []
|
||||
testInstrumentationRunner "androidx.test.runner.AndroidJUnitRunner"
|
||||
|
||||
@@ -83,7 +83,7 @@ dependencies {
|
||||
}
|
||||
|
||||
implementation 'org.jetbrains.kotlin:kotlin-stdlib:1.8.21'
|
||||
implementation 'org.jetbrains.kotlinx:kotlinx-coroutines-android:1.7.0'
|
||||
implementation 'org.jetbrains.kotlinx:kotlinx-coroutines-android:1.7.1'
|
||||
|
||||
implementation 'androidx.appcompat:appcompat:1.6.1'
|
||||
implementation 'androidx.core:core-ktx:1.10.1'
|
||||
@@ -140,17 +140,17 @@ dependencies {
|
||||
|
||||
testImplementation 'junit:junit:4.13.2'
|
||||
testImplementation 'org.json:json:20230227'
|
||||
testImplementation 'org.jetbrains.kotlinx:kotlinx-coroutines-test:1.7.0'
|
||||
testImplementation 'org.jetbrains.kotlinx:kotlinx-coroutines-test:1.7.1'
|
||||
|
||||
androidTestImplementation 'androidx.test:runner:1.5.2'
|
||||
androidTestImplementation 'androidx.test:rules:1.5.0'
|
||||
androidTestImplementation 'androidx.test:core-ktx:1.5.0'
|
||||
androidTestImplementation 'androidx.test.ext:junit-ktx:1.1.5'
|
||||
|
||||
androidTestImplementation 'org.jetbrains.kotlinx:kotlinx-coroutines-test:1.7.0'
|
||||
androidTestImplementation 'org.jetbrains.kotlinx:kotlinx-coroutines-test:1.7.1'
|
||||
|
||||
androidTestImplementation 'androidx.room:room-testing:2.5.1'
|
||||
androidTestImplementation 'com.squareup.moshi:moshi-kotlin:1.14.0'
|
||||
androidTestImplementation 'com.squareup.moshi:moshi-kotlin:1.15.0'
|
||||
|
||||
androidTestImplementation 'com.google.dagger:hilt-android-testing:2.46.1'
|
||||
kaptAndroidTest 'com.google.dagger:hilt-android-compiler:2.46.1'
|
||||
|
||||
@@ -8,19 +8,22 @@ abstract class BoundsScrollListener(private val offsetTop: Int, private val offs
|
||||
|
||||
override fun onScrolled(recyclerView: RecyclerView, dx: Int, dy: Int) {
|
||||
super.onScrolled(recyclerView, dx, dy)
|
||||
if (recyclerView.hasPendingAdapterUpdates()) {
|
||||
return
|
||||
}
|
||||
val layoutManager = (recyclerView.layoutManager as? LinearLayoutManager) ?: return
|
||||
val firstVisibleItemPosition = layoutManager.findFirstVisibleItemPosition()
|
||||
if (firstVisibleItemPosition == RecyclerView.NO_POSITION) {
|
||||
return
|
||||
}
|
||||
if (firstVisibleItemPosition <= offsetTop) {
|
||||
onScrolledToStart(recyclerView)
|
||||
}
|
||||
val visibleItemCount = layoutManager.childCount
|
||||
val totalItemCount = layoutManager.itemCount
|
||||
if (visibleItemCount + firstVisibleItemPosition >= totalItemCount - offsetBottom) {
|
||||
onScrolledToEnd(recyclerView)
|
||||
}
|
||||
if (firstVisibleItemPosition <= offsetTop) {
|
||||
onScrolledToStart(recyclerView)
|
||||
}
|
||||
}
|
||||
|
||||
abstract fun onScrolledToStart(recyclerView: RecyclerView)
|
||||
|
||||
@@ -0,0 +1,30 @@
|
||||
package org.koitharu.kotatsu.base.ui.list
|
||||
|
||||
import androidx.recyclerview.widget.RecyclerView
|
||||
|
||||
class ScrollListenerInvalidationObserver(
|
||||
private val recyclerView: RecyclerView,
|
||||
private val scrollListener: RecyclerView.OnScrollListener,
|
||||
) : RecyclerView.AdapterDataObserver() {
|
||||
|
||||
override fun onChanged() {
|
||||
super.onChanged()
|
||||
invalidateScroll()
|
||||
}
|
||||
|
||||
override fun onItemRangeInserted(positionStart: Int, itemCount: Int) {
|
||||
super.onItemRangeInserted(positionStart, itemCount)
|
||||
invalidateScroll()
|
||||
}
|
||||
|
||||
override fun onItemRangeRemoved(positionStart: Int, itemCount: Int) {
|
||||
super.onItemRangeRemoved(positionStart, itemCount)
|
||||
invalidateScroll()
|
||||
}
|
||||
|
||||
private fun invalidateScroll() {
|
||||
recyclerView.post {
|
||||
scrollListener.onScrolled(recyclerView, 0, 0)
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -252,6 +252,7 @@ class ReaderViewModel @Inject constructor(
|
||||
val prevJob = stateChangeJob
|
||||
stateChangeJob = launchJob(Dispatchers.Default) {
|
||||
prevJob?.cancelAndJoin()
|
||||
loadingJob?.join()
|
||||
val pages = content.value?.pages ?: return@launchJob
|
||||
pages.getOrNull(position)?.let { page ->
|
||||
currentState.update { cs ->
|
||||
@@ -263,12 +264,12 @@ class ReaderViewModel @Inject constructor(
|
||||
return@launchJob
|
||||
}
|
||||
ensureActive()
|
||||
if (position <= BOUNDS_PAGE_OFFSET) {
|
||||
loadPrevNextChapter(pages.first().chapterId, isNext = false)
|
||||
}
|
||||
if (position >= pages.lastIndex - BOUNDS_PAGE_OFFSET) {
|
||||
loadPrevNextChapter(pages.last().chapterId, isNext = true)
|
||||
}
|
||||
if (position <= BOUNDS_PAGE_OFFSET) {
|
||||
loadPrevNextChapter(pages.first().chapterId, isNext = false)
|
||||
}
|
||||
if (pageLoader.isPrefetchApplicable()) {
|
||||
pageLoader.prefetch(pages.trySublist(position + 1, position + PREFETCH_LIMIT))
|
||||
}
|
||||
@@ -348,7 +349,9 @@ class ReaderViewModel @Inject constructor(
|
||||
|
||||
@AnyThread
|
||||
private fun loadPrevNextChapter(currentId: Long, isNext: Boolean) {
|
||||
val prevJob = loadingJob
|
||||
loadingJob = launchLoadingJob(Dispatchers.Default) {
|
||||
prevJob?.join()
|
||||
chaptersLoader.loadPrevNextChapter(mangaData.requireValue(), currentId, isNext)
|
||||
content.emitValue(ReaderContent(chaptersLoader.snapshot(), null))
|
||||
}
|
||||
|
||||
@@ -54,6 +54,11 @@ var RecyclerView.firstVisibleItemPosition: Int
|
||||
}
|
||||
}
|
||||
|
||||
val RecyclerView.visibleItemCount: Int
|
||||
get() = (layoutManager as? LinearLayoutManager)?.run {
|
||||
findLastVisibleItemPosition() - findFirstVisibleItemPosition()
|
||||
} ?: 0
|
||||
|
||||
fun View.hasGlobalPoint(x: Int, y: Int): Boolean {
|
||||
if (visibility != View.VISIBLE) {
|
||||
return false
|
||||
|
||||
Reference in New Issue
Block a user