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
/**
* 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,

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.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
}
/**

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.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
}
/**

View file

@ -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<State, State>? = null