Fix concurrent chapters loading in reader
This commit is contained in:
@@ -15,8 +15,8 @@ android {
|
|||||||
applicationId 'org.koitharu.kotatsu'
|
applicationId 'org.koitharu.kotatsu'
|
||||||
minSdkVersion 21
|
minSdkVersion 21
|
||||||
targetSdkVersion 33
|
targetSdkVersion 33
|
||||||
versionCode 544
|
versionCode 545
|
||||||
versionName '5.1'
|
versionName '5.1.1'
|
||||||
generatedDensities = []
|
generatedDensities = []
|
||||||
testInstrumentationRunner "androidx.test.runner.AndroidJUnitRunner"
|
testInstrumentationRunner "androidx.test.runner.AndroidJUnitRunner"
|
||||||
|
|
||||||
@@ -83,7 +83,7 @@ dependencies {
|
|||||||
}
|
}
|
||||||
|
|
||||||
implementation 'org.jetbrains.kotlin:kotlin-stdlib:1.8.21'
|
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.appcompat:appcompat:1.6.1'
|
||||||
implementation 'androidx.core:core-ktx:1.10.1'
|
implementation 'androidx.core:core-ktx:1.10.1'
|
||||||
@@ -140,17 +140,17 @@ dependencies {
|
|||||||
|
|
||||||
testImplementation 'junit:junit:4.13.2'
|
testImplementation 'junit:junit:4.13.2'
|
||||||
testImplementation 'org.json:json:20230227'
|
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:runner:1.5.2'
|
||||||
androidTestImplementation 'androidx.test:rules:1.5.0'
|
androidTestImplementation 'androidx.test:rules:1.5.0'
|
||||||
androidTestImplementation 'androidx.test:core-ktx:1.5.0'
|
androidTestImplementation 'androidx.test:core-ktx:1.5.0'
|
||||||
androidTestImplementation 'androidx.test.ext:junit-ktx:1.1.5'
|
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 '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'
|
androidTestImplementation 'com.google.dagger:hilt-android-testing:2.46.1'
|
||||||
kaptAndroidTest 'com.google.dagger:hilt-android-compiler: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) {
|
override fun onScrolled(recyclerView: RecyclerView, dx: Int, dy: Int) {
|
||||||
super.onScrolled(recyclerView, dx, dy)
|
super.onScrolled(recyclerView, dx, dy)
|
||||||
|
if (recyclerView.hasPendingAdapterUpdates()) {
|
||||||
|
return
|
||||||
|
}
|
||||||
val layoutManager = (recyclerView.layoutManager as? LinearLayoutManager) ?: return
|
val layoutManager = (recyclerView.layoutManager as? LinearLayoutManager) ?: return
|
||||||
val firstVisibleItemPosition = layoutManager.findFirstVisibleItemPosition()
|
val firstVisibleItemPosition = layoutManager.findFirstVisibleItemPosition()
|
||||||
if (firstVisibleItemPosition == RecyclerView.NO_POSITION) {
|
if (firstVisibleItemPosition == RecyclerView.NO_POSITION) {
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
if (firstVisibleItemPosition <= offsetTop) {
|
|
||||||
onScrolledToStart(recyclerView)
|
|
||||||
}
|
|
||||||
val visibleItemCount = layoutManager.childCount
|
val visibleItemCount = layoutManager.childCount
|
||||||
val totalItemCount = layoutManager.itemCount
|
val totalItemCount = layoutManager.itemCount
|
||||||
if (visibleItemCount + firstVisibleItemPosition >= totalItemCount - offsetBottom) {
|
if (visibleItemCount + firstVisibleItemPosition >= totalItemCount - offsetBottom) {
|
||||||
onScrolledToEnd(recyclerView)
|
onScrolledToEnd(recyclerView)
|
||||||
}
|
}
|
||||||
|
if (firstVisibleItemPosition <= offsetTop) {
|
||||||
|
onScrolledToStart(recyclerView)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
abstract fun onScrolledToStart(recyclerView: 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
|
val prevJob = stateChangeJob
|
||||||
stateChangeJob = launchJob(Dispatchers.Default) {
|
stateChangeJob = launchJob(Dispatchers.Default) {
|
||||||
prevJob?.cancelAndJoin()
|
prevJob?.cancelAndJoin()
|
||||||
|
loadingJob?.join()
|
||||||
val pages = content.value?.pages ?: return@launchJob
|
val pages = content.value?.pages ?: return@launchJob
|
||||||
pages.getOrNull(position)?.let { page ->
|
pages.getOrNull(position)?.let { page ->
|
||||||
currentState.update { cs ->
|
currentState.update { cs ->
|
||||||
@@ -263,12 +264,12 @@ class ReaderViewModel @Inject constructor(
|
|||||||
return@launchJob
|
return@launchJob
|
||||||
}
|
}
|
||||||
ensureActive()
|
ensureActive()
|
||||||
if (position <= BOUNDS_PAGE_OFFSET) {
|
|
||||||
loadPrevNextChapter(pages.first().chapterId, isNext = false)
|
|
||||||
}
|
|
||||||
if (position >= pages.lastIndex - BOUNDS_PAGE_OFFSET) {
|
if (position >= pages.lastIndex - BOUNDS_PAGE_OFFSET) {
|
||||||
loadPrevNextChapter(pages.last().chapterId, isNext = true)
|
loadPrevNextChapter(pages.last().chapterId, isNext = true)
|
||||||
}
|
}
|
||||||
|
if (position <= BOUNDS_PAGE_OFFSET) {
|
||||||
|
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(position + 1, position + PREFETCH_LIMIT))
|
||||||
}
|
}
|
||||||
@@ -348,7 +349,9 @@ class ReaderViewModel @Inject constructor(
|
|||||||
|
|
||||||
@AnyThread
|
@AnyThread
|
||||||
private fun loadPrevNextChapter(currentId: Long, isNext: Boolean) {
|
private fun loadPrevNextChapter(currentId: Long, isNext: Boolean) {
|
||||||
|
val prevJob = loadingJob
|
||||||
loadingJob = launchLoadingJob(Dispatchers.Default) {
|
loadingJob = launchLoadingJob(Dispatchers.Default) {
|
||||||
|
prevJob?.join()
|
||||||
chaptersLoader.loadPrevNextChapter(mangaData.requireValue(), currentId, isNext)
|
chaptersLoader.loadPrevNextChapter(mangaData.requireValue(), currentId, isNext)
|
||||||
content.emitValue(ReaderContent(chaptersLoader.snapshot(), null))
|
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 {
|
fun View.hasGlobalPoint(x: Int, y: Int): Boolean {
|
||||||
if (visibility != View.VISIBLE) {
|
if (visibility != View.VISIBLE) {
|
||||||
return false
|
return false
|
||||||
|
|||||||
Reference in New Issue
Block a user