Track Page progress with StateFlow (#8749)

* Update ReaderProgressIndicator documentation

ReaderProgressIndicator is not always determinate (cc554530, #5605).

* Track Page progress with StateFlow
This commit is contained in:
Two-Ai 2022-12-16 22:18:50 -05:00 committed by GitHub
parent e20c66b156
commit 593172f891
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
4 changed files with 54 additions and 50 deletions

View file

@ -11,10 +11,9 @@ import androidx.annotation.IntRange
import com.google.android.material.progressindicator.CircularProgressIndicator 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', * 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.
*/ */
class ReaderProgressIndicator @JvmOverloads constructor( class ReaderProgressIndicator @JvmOverloads constructor(
context: Context, context: Context,

View file

@ -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.ReaderPageImageView
import eu.kanade.tachiyomi.ui.reader.viewer.ReaderProgressIndicator import eu.kanade.tachiyomi.ui.reader.viewer.ReaderProgressIndicator
import eu.kanade.tachiyomi.ui.webview.WebViewActivity 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.ImageUtil
import eu.kanade.tachiyomi.widget.ViewPagerAdapter 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.Observable
import rx.Subscription import rx.Subscription
import rx.android.schedulers.AndroidSchedulers import rx.android.schedulers.AndroidSchedulers
@ -22,7 +27,6 @@ import rx.schedulers.Schedulers
import java.io.BufferedInputStream import java.io.BufferedInputStream
import java.io.ByteArrayInputStream import java.io.ByteArrayInputStream
import java.io.InputStream import java.io.InputStream
import java.util.concurrent.TimeUnit
/** /**
* View of the ViewPager that contains a page of a chapter. * View of the ViewPager that contains a page of a chapter.
@ -54,15 +58,17 @@ class PagerPageHolder(
*/ */
private var errorLayout: ReaderErrorBinding? = null private var errorLayout: ReaderErrorBinding? = null
private val scope = CoroutineScope(Dispatchers.IO)
/** /**
* Subscription for status changes of the page. * Subscription for status changes of the page.
*/ */
private var statusSubscription: Subscription? = null 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 * Subscription used to read the header of the image. This is needed in order to instantiate
@ -81,7 +87,7 @@ class PagerPageHolder(
@SuppressLint("ClickableViewAccessibility") @SuppressLint("ClickableViewAccessibility")
override fun onDetachedFromWindow() { override fun onDetachedFromWindow() {
super.onDetachedFromWindow() super.onDetachedFromWindow()
unsubscribeProgress() cancelProgressJob()
unsubscribeStatus() unsubscribeStatus()
unsubscribeReadImageHeader() unsubscribeReadImageHeader()
} }
@ -100,18 +106,11 @@ class PagerPageHolder(
.subscribe { processStatus(it) } .subscribe { processStatus(it) }
} }
/** private fun launchProgressJob() {
* Observes the progress of the page and updates view. progressJob?.cancel()
*/ progressJob = scope.launchUI {
private fun observeProgress() { page.progressFlow.collectLatest { value -> progressIndicator.setProgress(value) }
progressSubscription?.unsubscribe() }
progressSubscription = Observable.interval(100, TimeUnit.MILLISECONDS)
.map { page.progress }
.distinctUntilChanged()
.onBackpressureLatest()
.observeOn(AndroidSchedulers.mainThread())
.subscribe { value -> progressIndicator.setProgress(value) }
} }
/** /**
@ -124,16 +123,16 @@ class PagerPageHolder(
Page.State.QUEUE -> setQueued() Page.State.QUEUE -> setQueued()
Page.State.LOAD_PAGE -> setLoading() Page.State.LOAD_PAGE -> setLoading()
Page.State.DOWNLOAD_IMAGE -> { Page.State.DOWNLOAD_IMAGE -> {
observeProgress() launchProgressJob()
setDownloading() setDownloading()
} }
Page.State.READY -> { Page.State.READY -> {
setImage() setImage()
unsubscribeProgress() cancelProgressJob()
} }
Page.State.ERROR -> { Page.State.ERROR -> {
setError() setError()
unsubscribeProgress() cancelProgressJob()
} }
} }
} }
@ -146,12 +145,9 @@ class PagerPageHolder(
statusSubscription = null statusSubscription = null
} }
/** private fun cancelProgressJob() {
* Unsubscribes from the progress subscription. progressJob?.cancel()
*/ progressJob = null
private fun unsubscribeProgress() {
progressSubscription?.unsubscribe()
progressSubscription = null
} }
/** /**

View file

@ -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.ReaderPageImageView
import eu.kanade.tachiyomi.ui.reader.viewer.ReaderProgressIndicator import eu.kanade.tachiyomi.ui.reader.viewer.ReaderProgressIndicator
import eu.kanade.tachiyomi.ui.webview.WebViewActivity 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.ImageUtil
import eu.kanade.tachiyomi.util.system.dpToPx 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.Observable
import rx.Subscription import rx.Subscription
import rx.android.schedulers.AndroidSchedulers import rx.android.schedulers.AndroidSchedulers
import rx.schedulers.Schedulers import rx.schedulers.Schedulers
import java.io.BufferedInputStream import java.io.BufferedInputStream
import java.io.InputStream import java.io.InputStream
import java.util.concurrent.TimeUnit
/** /**
* Holder of the webtoon reader for a single page of a chapter. * Holder of the webtoon reader for a single page of a chapter.
@ -67,15 +71,17 @@ class WebtoonPageHolder(
*/ */
private var page: ReaderPage? = null private var page: ReaderPage? = null
private val scope = CoroutineScope(Dispatchers.IO)
/** /**
* Subscription for status changes of the page. * Subscription for status changes of the page.
*/ */
private var statusSubscription: Subscription? = null 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 * 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() { override fun recycle() {
unsubscribeStatus() unsubscribeStatus()
unsubscribeProgress() cancelProgressJob()
unsubscribeReadImageHeader() unsubscribeReadImageHeader()
removeErrorLayout() removeErrorLayout()
@ -145,19 +151,14 @@ class WebtoonPageHolder(
/** /**
* Observes the progress of the page and updates view. * Observes the progress of the page and updates view.
*/ */
private fun observeProgress() { private fun launchProgressJob() {
unsubscribeProgress() cancelProgressJob()
val page = page ?: return val page = page ?: return
progressSubscription = Observable.interval(100, TimeUnit.MILLISECONDS) progressJob = scope.launchUI {
.map { page.progress } page.progressFlow.collectLatest { value -> progressIndicator.setProgress(value) }
.distinctUntilChanged() }
.onBackpressureLatest()
.observeOn(AndroidSchedulers.mainThread())
.subscribe { value -> progressIndicator.setProgress(value) }
addSubscription(progressSubscription)
} }
/** /**
@ -170,16 +171,16 @@ class WebtoonPageHolder(
Page.State.QUEUE -> setQueued() Page.State.QUEUE -> setQueued()
Page.State.LOAD_PAGE -> setLoading() Page.State.LOAD_PAGE -> setLoading()
Page.State.DOWNLOAD_IMAGE -> { Page.State.DOWNLOAD_IMAGE -> {
observeProgress() launchProgressJob()
setDownloading() setDownloading()
} }
Page.State.READY -> { Page.State.READY -> {
setImage() setImage()
unsubscribeProgress() cancelProgressJob()
} }
Page.State.ERROR -> { Page.State.ERROR -> {
setError() setError()
unsubscribeProgress() cancelProgressJob()
} }
} }
} }
@ -195,9 +196,9 @@ class WebtoonPageHolder(
/** /**
* Unsubscribes from the progress subscription. * Unsubscribes from the progress subscription.
*/ */
private fun unsubscribeProgress() { private fun cancelProgressJob() {
removeSubscription(progressSubscription) progressJob?.cancel()
progressSubscription = null progressJob = null
} }
/** /**

View file

@ -2,6 +2,8 @@ package eu.kanade.tachiyomi.source.model
import android.net.Uri import android.net.Uri
import eu.kanade.tachiyomi.network.ProgressListener import eu.kanade.tachiyomi.network.ProgressListener
import kotlinx.coroutines.flow.MutableStateFlow
import kotlinx.coroutines.flow.asStateFlow
import kotlinx.serialization.Serializable import kotlinx.serialization.Serializable
import kotlinx.serialization.Transient import kotlinx.serialization.Transient
import rx.subjects.Subject import rx.subjects.Subject
@ -26,8 +28,14 @@ open class Page(
} }
@Transient @Transient
@Volatile private val _progressFlow = MutableStateFlow(0)
var progress: Int = 0 @Transient
val progressFlow = _progressFlow.asStateFlow()
var progress: Int
get() = _progressFlow.value
set(value) {
_progressFlow.value = value
}
@Transient @Transient
var statusSubject: Subject<State, State>? = null var statusSubject: Subject<State, State>? = null