diff --git a/app/build.gradle b/app/build.gradle index cbf886b85..aa5621fc1 100644 --- a/app/build.gradle +++ b/app/build.gradle @@ -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' diff --git a/app/src/main/java/org/koitharu/kotatsu/base/ui/list/BoundsScrollListener.kt b/app/src/main/java/org/koitharu/kotatsu/base/ui/list/BoundsScrollListener.kt index f5019c152..11d65d7b3 100644 --- a/app/src/main/java/org/koitharu/kotatsu/base/ui/list/BoundsScrollListener.kt +++ b/app/src/main/java/org/koitharu/kotatsu/base/ui/list/BoundsScrollListener.kt @@ -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) diff --git a/app/src/main/java/org/koitharu/kotatsu/base/ui/list/ScrollListenerInvalidationObserver.kt b/app/src/main/java/org/koitharu/kotatsu/base/ui/list/ScrollListenerInvalidationObserver.kt new file mode 100644 index 000000000..b3e30c910 --- /dev/null +++ b/app/src/main/java/org/koitharu/kotatsu/base/ui/list/ScrollListenerInvalidationObserver.kt @@ -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) + } + } +} diff --git a/app/src/main/java/org/koitharu/kotatsu/reader/ui/ReaderViewModel.kt b/app/src/main/java/org/koitharu/kotatsu/reader/ui/ReaderViewModel.kt index 5595387e9..da7f04e5b 100644 --- a/app/src/main/java/org/koitharu/kotatsu/reader/ui/ReaderViewModel.kt +++ b/app/src/main/java/org/koitharu/kotatsu/reader/ui/ReaderViewModel.kt @@ -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)) } diff --git a/app/src/main/java/org/koitharu/kotatsu/utils/ext/ViewExt.kt b/app/src/main/java/org/koitharu/kotatsu/utils/ext/ViewExt.kt index 8d08cabd9..599aff2e7 100644 --- a/app/src/main/java/org/koitharu/kotatsu/utils/ext/ViewExt.kt +++ b/app/src/main/java/org/koitharu/kotatsu/utils/ext/ViewExt.kt @@ -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