From 593172f891667c1510d1812917cad93bfa362363 Mon Sep 17 00:00:00 2001 From: Two-Ai <81279822+Two-Ai@users.noreply.github.com> Date: Fri, 16 Dec 2022 22:18:50 -0500 Subject: [PATCH] Track Page progress with StateFlow (#8749) * Update ReaderProgressIndicator documentation ReaderProgressIndicator is not always determinate (cc554530, #5605). * Track Page progress with StateFlow --- .../reader/viewer/ReaderProgressIndicator.kt | 5 +- .../ui/reader/viewer/pager/PagerPageHolder.kt | 46 +++++++++---------- .../viewer/webtoon/WebtoonPageHolder.kt | 41 +++++++++-------- .../eu/kanade/tachiyomi/source/model/Page.kt | 12 ++++- 4 files changed, 54 insertions(+), 50 deletions(-) diff --git a/app/src/main/java/eu/kanade/tachiyomi/ui/reader/viewer/ReaderProgressIndicator.kt b/app/src/main/java/eu/kanade/tachiyomi/ui/reader/viewer/ReaderProgressIndicator.kt index e565ee878d..75f710686a 100644 --- a/app/src/main/java/eu/kanade/tachiyomi/ui/reader/viewer/ReaderProgressIndicator.kt +++ b/app/src/main/java/eu/kanade/tachiyomi/ui/reader/viewer/ReaderProgressIndicator.kt @@ -11,10 +11,9 @@ import androidx.annotation.IntRange import com.google.android.material.progressindicator.CircularProgressIndicator /** - * A wrapper for [CircularProgressIndicator] that always rotates while being determinate. + * A wrapper for [CircularProgressIndicator] that always rotates. * - * By always rotating we give the feedback to the user that the application isn't 'stuck', - * and by making it determinate the user also approximately knows how much the operation will take. + * By always rotating we give the feedback to the user that the application isn't 'stuck'. */ class ReaderProgressIndicator @JvmOverloads constructor( context: Context, diff --git a/app/src/main/java/eu/kanade/tachiyomi/ui/reader/viewer/pager/PagerPageHolder.kt b/app/src/main/java/eu/kanade/tachiyomi/ui/reader/viewer/pager/PagerPageHolder.kt index 04a45defde..5d3da4d5e5 100644 --- a/app/src/main/java/eu/kanade/tachiyomi/ui/reader/viewer/pager/PagerPageHolder.kt +++ b/app/src/main/java/eu/kanade/tachiyomi/ui/reader/viewer/pager/PagerPageHolder.kt @@ -13,8 +13,13 @@ import eu.kanade.tachiyomi.ui.reader.model.ReaderPage import eu.kanade.tachiyomi.ui.reader.viewer.ReaderPageImageView import eu.kanade.tachiyomi.ui.reader.viewer.ReaderProgressIndicator import eu.kanade.tachiyomi.ui.webview.WebViewActivity +import eu.kanade.tachiyomi.util.lang.launchUI import eu.kanade.tachiyomi.util.system.ImageUtil import eu.kanade.tachiyomi.widget.ViewPagerAdapter +import kotlinx.coroutines.CoroutineScope +import kotlinx.coroutines.Dispatchers +import kotlinx.coroutines.Job +import kotlinx.coroutines.flow.collectLatest import rx.Observable import rx.Subscription import rx.android.schedulers.AndroidSchedulers @@ -22,7 +27,6 @@ import rx.schedulers.Schedulers import java.io.BufferedInputStream import java.io.ByteArrayInputStream import java.io.InputStream -import java.util.concurrent.TimeUnit /** * View of the ViewPager that contains a page of a chapter. @@ -54,15 +58,17 @@ class PagerPageHolder( */ private var errorLayout: ReaderErrorBinding? = null + private val scope = CoroutineScope(Dispatchers.IO) + /** * Subscription for status changes of the page. */ private var statusSubscription: Subscription? = null /** - * Subscription for progress changes of the page. + * Job for progress changes of the page. */ - private var progressSubscription: Subscription? = null + private var progressJob: Job? = null /** * Subscription used to read the header of the image. This is needed in order to instantiate @@ -81,7 +87,7 @@ class PagerPageHolder( @SuppressLint("ClickableViewAccessibility") override fun onDetachedFromWindow() { super.onDetachedFromWindow() - unsubscribeProgress() + cancelProgressJob() unsubscribeStatus() unsubscribeReadImageHeader() } @@ -100,18 +106,11 @@ class PagerPageHolder( .subscribe { processStatus(it) } } - /** - * Observes the progress of the page and updates view. - */ - private fun observeProgress() { - progressSubscription?.unsubscribe() - - progressSubscription = Observable.interval(100, TimeUnit.MILLISECONDS) - .map { page.progress } - .distinctUntilChanged() - .onBackpressureLatest() - .observeOn(AndroidSchedulers.mainThread()) - .subscribe { value -> progressIndicator.setProgress(value) } + private fun launchProgressJob() { + progressJob?.cancel() + progressJob = scope.launchUI { + page.progressFlow.collectLatest { value -> progressIndicator.setProgress(value) } + } } /** @@ -124,16 +123,16 @@ class PagerPageHolder( Page.State.QUEUE -> setQueued() Page.State.LOAD_PAGE -> setLoading() Page.State.DOWNLOAD_IMAGE -> { - observeProgress() + launchProgressJob() setDownloading() } Page.State.READY -> { setImage() - unsubscribeProgress() + cancelProgressJob() } Page.State.ERROR -> { setError() - unsubscribeProgress() + cancelProgressJob() } } } @@ -146,12 +145,9 @@ class PagerPageHolder( statusSubscription = null } - /** - * Unsubscribes from the progress subscription. - */ - private fun unsubscribeProgress() { - progressSubscription?.unsubscribe() - progressSubscription = null + private fun cancelProgressJob() { + progressJob?.cancel() + progressJob = null } /** diff --git a/app/src/main/java/eu/kanade/tachiyomi/ui/reader/viewer/webtoon/WebtoonPageHolder.kt b/app/src/main/java/eu/kanade/tachiyomi/ui/reader/viewer/webtoon/WebtoonPageHolder.kt index a2d0c5baee..8927479b7d 100644 --- a/app/src/main/java/eu/kanade/tachiyomi/ui/reader/viewer/webtoon/WebtoonPageHolder.kt +++ b/app/src/main/java/eu/kanade/tachiyomi/ui/reader/viewer/webtoon/WebtoonPageHolder.kt @@ -18,15 +18,19 @@ import eu.kanade.tachiyomi.ui.reader.model.StencilPage import eu.kanade.tachiyomi.ui.reader.viewer.ReaderPageImageView import eu.kanade.tachiyomi.ui.reader.viewer.ReaderProgressIndicator import eu.kanade.tachiyomi.ui.webview.WebViewActivity +import eu.kanade.tachiyomi.util.lang.launchUI import eu.kanade.tachiyomi.util.system.ImageUtil import eu.kanade.tachiyomi.util.system.dpToPx +import kotlinx.coroutines.CoroutineScope +import kotlinx.coroutines.Dispatchers +import kotlinx.coroutines.Job +import kotlinx.coroutines.flow.collectLatest import rx.Observable import rx.Subscription import rx.android.schedulers.AndroidSchedulers import rx.schedulers.Schedulers import java.io.BufferedInputStream import java.io.InputStream -import java.util.concurrent.TimeUnit /** * Holder of the webtoon reader for a single page of a chapter. @@ -67,15 +71,17 @@ class WebtoonPageHolder( */ private var page: ReaderPage? = null + private val scope = CoroutineScope(Dispatchers.IO) + /** * Subscription for status changes of the page. */ private var statusSubscription: Subscription? = null /** - * Subscription for progress changes of the page. + * Job for progress changes of the page. */ - private var progressSubscription: Subscription? = null + private var progressJob: Job? = null /** * Subscription used to read the header of the image. This is needed in order to instantiate @@ -117,7 +123,7 @@ class WebtoonPageHolder( */ override fun recycle() { unsubscribeStatus() - unsubscribeProgress() + cancelProgressJob() unsubscribeReadImageHeader() removeErrorLayout() @@ -145,19 +151,14 @@ class WebtoonPageHolder( /** * Observes the progress of the page and updates view. */ - private fun observeProgress() { - unsubscribeProgress() + private fun launchProgressJob() { + cancelProgressJob() val page = page ?: return - progressSubscription = Observable.interval(100, TimeUnit.MILLISECONDS) - .map { page.progress } - .distinctUntilChanged() - .onBackpressureLatest() - .observeOn(AndroidSchedulers.mainThread()) - .subscribe { value -> progressIndicator.setProgress(value) } - - addSubscription(progressSubscription) + progressJob = scope.launchUI { + page.progressFlow.collectLatest { value -> progressIndicator.setProgress(value) } + } } /** @@ -170,16 +171,16 @@ class WebtoonPageHolder( Page.State.QUEUE -> setQueued() Page.State.LOAD_PAGE -> setLoading() Page.State.DOWNLOAD_IMAGE -> { - observeProgress() + launchProgressJob() setDownloading() } Page.State.READY -> { setImage() - unsubscribeProgress() + cancelProgressJob() } Page.State.ERROR -> { setError() - unsubscribeProgress() + cancelProgressJob() } } } @@ -195,9 +196,9 @@ class WebtoonPageHolder( /** * Unsubscribes from the progress subscription. */ - private fun unsubscribeProgress() { - removeSubscription(progressSubscription) - progressSubscription = null + private fun cancelProgressJob() { + progressJob?.cancel() + progressJob = null } /** diff --git a/source-api/src/main/java/eu/kanade/tachiyomi/source/model/Page.kt b/source-api/src/main/java/eu/kanade/tachiyomi/source/model/Page.kt index 0651ce89d3..44180c6a32 100644 --- a/source-api/src/main/java/eu/kanade/tachiyomi/source/model/Page.kt +++ b/source-api/src/main/java/eu/kanade/tachiyomi/source/model/Page.kt @@ -2,6 +2,8 @@ package eu.kanade.tachiyomi.source.model import android.net.Uri import eu.kanade.tachiyomi.network.ProgressListener +import kotlinx.coroutines.flow.MutableStateFlow +import kotlinx.coroutines.flow.asStateFlow import kotlinx.serialization.Serializable import kotlinx.serialization.Transient import rx.subjects.Subject @@ -26,8 +28,14 @@ open class Page( } @Transient - @Volatile - var progress: Int = 0 + private val _progressFlow = MutableStateFlow(0) + @Transient + val progressFlow = _progressFlow.asStateFlow() + var progress: Int + get() = _progressFlow.value + set(value) { + _progressFlow.value = value + } @Transient var statusSubject: Subject? = null