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
This commit is contained in:
Two-Ai 2022-12-16 22:18:50 -05:00 committed by Jobobby04
parent 4243d389f8
commit 5a449ec80a
4 changed files with 64 additions and 69 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

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

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