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:
parent
4243d389f8
commit
5a449ec80a
@ -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,
|
||||
|
@ -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
|
||||
}
|
||||
|
||||
/**
|
||||
|
@ -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
|
||||
}
|
||||
|
||||
/**
|
||||
|
@ -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
|
||||
|
Loading…
x
Reference in New Issue
Block a user