From 5a449ec80ae789cf4384ec56e991aa884ac33788 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 (cherry picked from commit 593172f891667c1510d1812917cad93bfa362363) # Conflicts: # app/src/main/java/eu/kanade/tachiyomi/ui/reader/viewer/pager/PagerPageHolder.kt --- .../reader/viewer/ReaderProgressIndicator.kt | 5 +- .../ui/reader/viewer/pager/PagerPageHolder.kt | 75 ++++++++----------- .../viewer/webtoon/WebtoonPageHolder.kt | 41 +++++----- .../eu/kanade/tachiyomi/source/model/Page.kt | 12 ++- 4 files changed, 64 insertions(+), 69 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 e565ee878..75f710686 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 8e66c1497..75cad483d 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 @@ -17,7 +17,11 @@ import eu.kanade.tachiyomi.util.lang.launchUI import eu.kanade.tachiyomi.util.system.ImageUtil import eu.kanade.tachiyomi.util.system.logcat import eu.kanade.tachiyomi.widget.ViewPagerAdapter +import kotlinx.coroutines.CoroutineScope +import kotlinx.coroutines.Dispatchers +import kotlinx.coroutines.Job import kotlinx.coroutines.delay +import kotlinx.coroutines.flow.collectLatest import logcat.LogPriority import rx.Observable import rx.Subscription @@ -27,7 +31,6 @@ import tachiyomi.decoder.ImageDecoder import java.io.BufferedInputStream import java.io.ByteArrayInputStream import java.io.InputStream -import java.util.concurrent.TimeUnit import kotlin.math.max import kotlin.math.roundToInt @@ -62,15 +65,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 for status changes of the page. @@ -80,7 +85,7 @@ class PagerPageHolder( /** * Subscription for progress changes of the page. */ - private var extraProgressSubscription: Subscription? = null + private var extraProgressJob: Job? = null /** * Subscription used to read the header of the image. This is needed in order to instantiate @@ -91,8 +96,6 @@ class PagerPageHolder( // SY --> var status: Page.State = Page.State.QUEUE var extraStatus: Page.State = Page.State.QUEUE - var progress: Int = 0 - var extraProgress: Int = 0 // SY <-- init { @@ -106,8 +109,8 @@ class PagerPageHolder( @SuppressLint("ClickableViewAccessibility") override fun onDetachedFromWindow() { super.onDetachedFromWindow() - unsubscribeProgress(1) - unsubscribeProgress(2) + cancelProgressJob(1) + cancelProgressJob(2) unsubscribeStatus(1) unsubscribeStatus(2) unsubscribeReadImageHeader() @@ -139,32 +142,19 @@ class PagerPageHolder( } } - /** - * 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) } + } } - private fun observeProgress2() { - extraProgressSubscription?.unsubscribe() + private fun launchProgressJob2() { + extraProgressJob?.cancel() val extraPage = extraPage ?: return - extraProgressSubscription = Observable.interval(100, TimeUnit.MILLISECONDS) - .map { extraPage.progress } - .distinctUntilChanged() - .onBackpressureLatest() - .observeOn(AndroidSchedulers.mainThread()) - .subscribe { value -> - extraProgress = value - progressIndicator.setProgress(((progress + extraProgress) / 2 * 0.95f).roundToInt()) - } + extraProgressJob = scope.launchUI { + extraPage.progressFlow.collectLatest { value -> progressIndicator.setProgress(((page.progressFlow.value + value) / 2 * 0.95f).roundToInt()) } + } } /** @@ -177,18 +167,18 @@ class PagerPageHolder( Page.State.QUEUE -> setQueued() Page.State.LOAD_PAGE -> setLoading() Page.State.DOWNLOAD_IMAGE -> { - observeProgress() + launchProgressJob() setDownloading() } Page.State.READY -> { if (extraStatus == Page.State.READY || extraPage == null) { setImage() } - unsubscribeProgress(1) + cancelProgressJob(1) } Page.State.ERROR -> { setError() - unsubscribeProgress(1) + cancelProgressJob(1) } } } @@ -203,18 +193,18 @@ class PagerPageHolder( Page.State.QUEUE -> setQueued() Page.State.LOAD_PAGE -> setLoading() Page.State.DOWNLOAD_IMAGE -> { - observeProgress2() + launchProgressJob2() setDownloading() } Page.State.READY -> { if (this.status == Page.State.READY) { setImage() } - unsubscribeProgress(2) + cancelProgressJob(2) } Page.State.ERROR -> { setError() - unsubscribeProgress(2) + cancelProgressJob(2) } } } @@ -228,13 +218,10 @@ class PagerPageHolder( if (page == 1) statusSubscription = null else extraStatusSubscription = null } - /** - * Unsubscribes from the progress subscription. - */ - private fun unsubscribeProgress(page: Int) { - val subscription = if (page == 1) progressSubscription else extraProgressSubscription - subscription?.unsubscribe() - if (page == 1) progressSubscription = null else extraProgressSubscription = null + private fun cancelProgressJob(page: Int) { + val job = if (page == 1) progressJob else extraProgressJob + job?.cancel() + if (page == 1) progressJob = null else extraProgressJob = 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 5755cad21..552de8123 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 9ec0f3882..b5e941813 100755 --- 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 @@ -27,8 +29,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