Simplify PageHolder load Job (#9086)
Inline statusJob into loadJob, using supervisorScope to load the page and track status changes in parallel. - supervisorScope does not complete until both the child loadPage coroutine and statusFlow.collectLatest have completed. - Cancelling supervisorScope cancels the child loadPage coroutine and statusFlow.collectLatest. - Use supervisorScope instead of coroutineScope to let status collection continue if loadPage fails. Inline progressJob into loadJob, using collectLatest's cancellation to avoid cancelling the progressFlow collection explicitly. - collectLatest cancels the previous action block when the flow emits a new value. This means the DOWNLOAD_IMAGE progressFlow.collectLatest gets automatically cancelled when statusFlow emits a new state. Convert launchLoadJob to suspend function, move job launch to caller, and rename as loadPageAndProcessStatus. (cherry picked from commit 4635e58405eedce0b49fc69f7ccb190a7c600da9) # Conflicts: # app/src/main/java/eu/kanade/tachiyomi/ui/reader/viewer/pager/PagerPageHolder.kt
This commit is contained in:
parent
57113014ec
commit
288fe0d888
@ -20,18 +20,19 @@ import kotlinx.coroutines.MainScope
|
|||||||
import kotlinx.coroutines.delay
|
import kotlinx.coroutines.delay
|
||||||
import kotlinx.coroutines.flow.collectLatest
|
import kotlinx.coroutines.flow.collectLatest
|
||||||
import kotlinx.coroutines.launch
|
import kotlinx.coroutines.launch
|
||||||
|
import kotlinx.coroutines.supervisorScope
|
||||||
import logcat.LogPriority
|
import logcat.LogPriority
|
||||||
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 tachiyomi.core.util.lang.launchIO
|
||||||
import tachiyomi.core.util.system.logcat
|
import tachiyomi.core.util.system.logcat
|
||||||
import tachiyomi.decoder.ImageDecoder
|
import tachiyomi.decoder.ImageDecoder
|
||||||
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 kotlin.math.max
|
import kotlin.math.max
|
||||||
import kotlin.math.roundToInt
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* View of the ViewPager that contains a page of a chapter.
|
* View of the ViewPager that contains a page of a chapter.
|
||||||
@ -67,35 +68,15 @@ class PagerPageHolder(
|
|||||||
private val scope = MainScope()
|
private val scope = MainScope()
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Job for loading the page.
|
* Job for loading the page and processing changes to the page's status.
|
||||||
*/
|
*/
|
||||||
private var loadJob: Job? = null
|
private var loadJob: Job? = null
|
||||||
|
|
||||||
/**
|
|
||||||
* Job for status changes of the page.
|
|
||||||
*/
|
|
||||||
private var statusJob: Job? = null
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Job for progress changes of the page.
|
|
||||||
*/
|
|
||||||
private var progressJob: Job? = null
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Job for loading the page.
|
* Job for loading the page.
|
||||||
*/
|
*/
|
||||||
private var extraLoadJob: Job? = null
|
private var extraLoadJob: Job? = null
|
||||||
|
|
||||||
/**
|
|
||||||
* Job for status changes of the page.
|
|
||||||
*/
|
|
||||||
private var extraStatusJob: Job? = null
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Job for progress changes of the page.
|
|
||||||
*/
|
|
||||||
private var extraProgressJob: 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
|
||||||
* the appropiate image view depending if the image is animated (GIF).
|
* the appropiate image view depending if the image is animated (GIF).
|
||||||
@ -104,7 +85,8 @@ class PagerPageHolder(
|
|||||||
|
|
||||||
init {
|
init {
|
||||||
addView(progressIndicator)
|
addView(progressIndicator)
|
||||||
launchLoadJob()
|
loadJob = scope.launch { loadPageAndProcessStatus(1) }
|
||||||
|
extraLoadJob = scope.launch { loadPageAndProcessStatus(2) }
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@ -113,132 +95,47 @@ class PagerPageHolder(
|
|||||||
@SuppressLint("ClickableViewAccessibility")
|
@SuppressLint("ClickableViewAccessibility")
|
||||||
override fun onDetachedFromWindow() {
|
override fun onDetachedFromWindow() {
|
||||||
super.onDetachedFromWindow()
|
super.onDetachedFromWindow()
|
||||||
cancelProgressJob(1)
|
loadJob?.cancel()
|
||||||
cancelProgressJob(2)
|
loadJob = null
|
||||||
cancelLoadJob(1)
|
extraLoadJob?.cancel()
|
||||||
cancelLoadJob(2)
|
extraLoadJob = null
|
||||||
unsubscribeReadImageHeader()
|
unsubscribeReadImageHeader()
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Starts loading the page and processing changes to the page's status.
|
* Loads the page and processes changes to the page's status.
|
||||||
*
|
*
|
||||||
* @see processStatus
|
* Returns immediately if the page has no PageLoader.
|
||||||
|
* Otherwise, this function does not return. It will continue to process status changes until
|
||||||
|
* the Job is cancelled.
|
||||||
*/
|
*/
|
||||||
private fun launchLoadJob() {
|
private suspend fun loadPageAndProcessStatus(pageIndex: Int) {
|
||||||
loadJob?.cancel()
|
|
||||||
statusJob?.cancel()
|
|
||||||
|
|
||||||
val loader = page.chapter.pageLoader ?: return
|
|
||||||
loadJob = scope.launch {
|
|
||||||
loader.loadPage(page)
|
|
||||||
}
|
|
||||||
statusJob = scope.launch {
|
|
||||||
page.statusFlow.collectLatest { processStatus(it) }
|
|
||||||
}
|
|
||||||
|
|
||||||
// SY -->
|
// SY -->
|
||||||
val extraPage = extraPage ?: return
|
val page = if (pageIndex == 1) page else extraPage
|
||||||
val loader2 = extraPage.chapter.pageLoader ?: return
|
page ?: return
|
||||||
extraLoadJob = scope.launch {
|
|
||||||
loader2.loadPage(extraPage)
|
|
||||||
}
|
|
||||||
extraStatusJob = scope.launch {
|
|
||||||
extraPage.statusFlow.collectLatest { processStatus2(it) }
|
|
||||||
}
|
|
||||||
// SY <--
|
// SY <--
|
||||||
}
|
val loader = page.chapter.pageLoader ?: return
|
||||||
|
supervisorScope {
|
||||||
private fun launchProgressJob() {
|
launchIO {
|
||||||
progressJob?.cancel()
|
loader.loadPage(page)
|
||||||
progressJob = scope.launch {
|
|
||||||
page.progressFlow.collectLatest { value -> progressIndicator.setProgress(value) }
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
private fun launchProgressJob2() {
|
|
||||||
extraProgressJob?.cancel()
|
|
||||||
val extraPage = extraPage ?: return
|
|
||||||
extraProgressJob = scope.launch {
|
|
||||||
extraPage.progressFlow.collectLatest { value -> progressIndicator.setProgress(((page.progressFlow.value + value) / 2 * 0.95f).roundToInt()) }
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Called when the status of the page changes.
|
|
||||||
*
|
|
||||||
* @param status the new status of the page.
|
|
||||||
*/
|
|
||||||
private fun processStatus(status: Page.State) {
|
|
||||||
when (status) {
|
|
||||||
Page.State.QUEUE -> setQueued()
|
|
||||||
Page.State.LOAD_PAGE -> setLoading()
|
|
||||||
Page.State.DOWNLOAD_IMAGE -> {
|
|
||||||
launchProgressJob()
|
|
||||||
setDownloading()
|
|
||||||
}
|
}
|
||||||
Page.State.READY -> {
|
page.statusFlow.collectLatest { state ->
|
||||||
if (extraPage?.status == Page.State.READY || extraPage == null) {
|
when (state) {
|
||||||
setImage()
|
Page.State.QUEUE -> setQueued()
|
||||||
|
Page.State.LOAD_PAGE -> setLoading()
|
||||||
|
Page.State.DOWNLOAD_IMAGE -> {
|
||||||
|
setDownloading()
|
||||||
|
page.progressFlow.collectLatest { value ->
|
||||||
|
progressIndicator.setProgress(value)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
Page.State.READY -> setImage()
|
||||||
|
Page.State.ERROR -> setError()
|
||||||
}
|
}
|
||||||
cancelProgressJob(1)
|
|
||||||
}
|
|
||||||
Page.State.ERROR -> {
|
|
||||||
setError()
|
|
||||||
cancelProgressJob(1)
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
|
||||||
* Called when the status of the page changes.
|
|
||||||
*
|
|
||||||
* @param status the new status of the page.
|
|
||||||
*/
|
|
||||||
private fun processStatus2(status: Page.State) {
|
|
||||||
when (status) {
|
|
||||||
Page.State.QUEUE -> setQueued()
|
|
||||||
Page.State.LOAD_PAGE -> setLoading()
|
|
||||||
Page.State.DOWNLOAD_IMAGE -> {
|
|
||||||
launchProgressJob2()
|
|
||||||
setDownloading()
|
|
||||||
}
|
|
||||||
Page.State.READY -> {
|
|
||||||
if (page.status == Page.State.READY) {
|
|
||||||
setImage()
|
|
||||||
}
|
|
||||||
cancelProgressJob(2)
|
|
||||||
}
|
|
||||||
Page.State.ERROR -> {
|
|
||||||
setError()
|
|
||||||
cancelProgressJob(2)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Cancels loading the page and processing changes to the page's status.
|
|
||||||
*/
|
|
||||||
private fun cancelLoadJob(page: Int) {
|
|
||||||
if (page == 1) {
|
|
||||||
loadJob?.cancel()
|
|
||||||
loadJob = null
|
|
||||||
statusJob?.cancel()
|
|
||||||
statusJob = null
|
|
||||||
} else if (page == 2) {
|
|
||||||
extraLoadJob?.cancel()
|
|
||||||
extraLoadJob = null
|
|
||||||
extraStatusJob?.cancel()
|
|
||||||
extraStatusJob = null
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
private fun cancelProgressJob(page: Int) {
|
|
||||||
val job = if (page == 1) progressJob else extraProgressJob
|
|
||||||
job?.cancel()
|
|
||||||
if (page == 1) progressJob = null else extraProgressJob = null
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Unsubscribes from the read image header subscription.
|
* Unsubscribes from the read image header subscription.
|
||||||
*/
|
*/
|
||||||
|
@ -24,10 +24,12 @@ import kotlinx.coroutines.Job
|
|||||||
import kotlinx.coroutines.MainScope
|
import kotlinx.coroutines.MainScope
|
||||||
import kotlinx.coroutines.flow.collectLatest
|
import kotlinx.coroutines.flow.collectLatest
|
||||||
import kotlinx.coroutines.launch
|
import kotlinx.coroutines.launch
|
||||||
|
import kotlinx.coroutines.supervisorScope
|
||||||
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 tachiyomi.core.util.lang.launchIO
|
||||||
import java.io.BufferedInputStream
|
import java.io.BufferedInputStream
|
||||||
import java.io.InputStream
|
import java.io.InputStream
|
||||||
|
|
||||||
@ -77,16 +79,6 @@ class WebtoonPageHolder(
|
|||||||
*/
|
*/
|
||||||
private var loadJob: Job? = null
|
private var loadJob: Job? = null
|
||||||
|
|
||||||
/**
|
|
||||||
* Job for status changes of the page.
|
|
||||||
*/
|
|
||||||
private var statusJob: Job? = null
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Job for progress changes of the page.
|
|
||||||
*/
|
|
||||||
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
|
||||||
* the appropriate image view depending if the image is animated (GIF).
|
* the appropriate image view depending if the image is animated (GIF).
|
||||||
@ -106,7 +98,8 @@ class WebtoonPageHolder(
|
|||||||
*/
|
*/
|
||||||
fun bind(page: ReaderPage) {
|
fun bind(page: ReaderPage) {
|
||||||
this.page = page
|
this.page = page
|
||||||
launchLoadJob()
|
loadJob?.cancel()
|
||||||
|
loadJob = scope.launch { loadPageAndProcessStatus() }
|
||||||
refreshLayoutParams()
|
refreshLayoutParams()
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -126,8 +119,8 @@ class WebtoonPageHolder(
|
|||||||
* Called when the view is recycled and added to the view pool.
|
* Called when the view is recycled and added to the view pool.
|
||||||
*/
|
*/
|
||||||
override fun recycle() {
|
override fun recycle() {
|
||||||
cancelLoadJob()
|
loadJob?.cancel()
|
||||||
cancelProgressJob()
|
loadJob = null
|
||||||
unsubscribeReadImageHeader()
|
unsubscribeReadImageHeader()
|
||||||
|
|
||||||
removeErrorLayout()
|
removeErrorLayout()
|
||||||
@ -136,78 +129,36 @@ class WebtoonPageHolder(
|
|||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Starts loading the page and processing changes to the page's status.
|
* Loads the page and processes changes to the page's status.
|
||||||
*
|
*
|
||||||
* @see processStatus
|
* Returns immediately if there is no page or the page has no PageLoader.
|
||||||
|
* Otherwise, this function does not return. It will continue to process status changes until
|
||||||
|
* the Job is cancelled.
|
||||||
*/
|
*/
|
||||||
private fun launchLoadJob() {
|
private suspend fun loadPageAndProcessStatus() {
|
||||||
cancelLoadJob()
|
|
||||||
|
|
||||||
val page = page ?: return
|
val page = page ?: return
|
||||||
val loader = page.chapter.pageLoader ?: return
|
val loader = page.chapter.pageLoader ?: return
|
||||||
loadJob = scope.launch {
|
supervisorScope {
|
||||||
loader.loadPage(page)
|
launchIO {
|
||||||
}
|
loader.loadPage(page)
|
||||||
statusJob = scope.launch {
|
|
||||||
page.statusFlow.collectLatest { processStatus(it) }
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Observes the progress of the page and updates view.
|
|
||||||
*/
|
|
||||||
private fun launchProgressJob() {
|
|
||||||
cancelProgressJob()
|
|
||||||
|
|
||||||
val page = page ?: return
|
|
||||||
|
|
||||||
progressJob = scope.launch {
|
|
||||||
page.progressFlow.collectLatest { value -> progressIndicator.setProgress(value) }
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Called when the status of the page changes.
|
|
||||||
*
|
|
||||||
* @param status the new status of the page.
|
|
||||||
*/
|
|
||||||
private fun processStatus(status: Page.State) {
|
|
||||||
when (status) {
|
|
||||||
Page.State.QUEUE -> setQueued()
|
|
||||||
Page.State.LOAD_PAGE -> setLoading()
|
|
||||||
Page.State.DOWNLOAD_IMAGE -> {
|
|
||||||
launchProgressJob()
|
|
||||||
setDownloading()
|
|
||||||
}
|
}
|
||||||
Page.State.READY -> {
|
page.statusFlow.collectLatest { state ->
|
||||||
setImage()
|
when (state) {
|
||||||
cancelProgressJob()
|
Page.State.QUEUE -> setQueued()
|
||||||
}
|
Page.State.LOAD_PAGE -> setLoading()
|
||||||
Page.State.ERROR -> {
|
Page.State.DOWNLOAD_IMAGE -> {
|
||||||
setError()
|
setDownloading()
|
||||||
cancelProgressJob()
|
page.progressFlow.collectLatest { value ->
|
||||||
|
progressIndicator.setProgress(value)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
Page.State.READY -> setImage()
|
||||||
|
Page.State.ERROR -> setError()
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
|
||||||
* Cancels loading the page and processing changes to the page's status.
|
|
||||||
*/
|
|
||||||
private fun cancelLoadJob() {
|
|
||||||
loadJob?.cancel()
|
|
||||||
loadJob = null
|
|
||||||
statusJob?.cancel()
|
|
||||||
statusJob = null
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Unsubscribes from the progress subscription.
|
|
||||||
*/
|
|
||||||
private fun cancelProgressJob() {
|
|
||||||
progressJob?.cancel()
|
|
||||||
progressJob = null
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Unsubscribes from the read image header subscription.
|
* Unsubscribes from the read image header subscription.
|
||||||
*/
|
*/
|
||||||
|
Loading…
x
Reference in New Issue
Block a user