Browse Source

Track Page progress with StateFlow (#8749)

* Update ReaderProgressIndicator documentation

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

* Track Page progress with StateFlow
Two-Ai 2 years ago
parent
commit
593172f891

+ 2 - 3
app/src/main/java/eu/kanade/tachiyomi/ui/reader/viewer/ReaderProgressIndicator.kt

@@ -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,

+ 21 - 25
app/src/main/java/eu/kanade/tachiyomi/ui/reader/viewer/pager/PagerPageHolder.kt

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

+ 21 - 20
app/src/main/java/eu/kanade/tachiyomi/ui/reader/viewer/webtoon/WebtoonPageHolder.kt

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

+ 10 - 2
source-api/src/main/java/eu/kanade/tachiyomi/source/model/Page.kt

@@ -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