Преглед на файлове

ReaderProgressIndicator: Convert to Compose (#9574)

Ivan Iskandar преди 1 година
родител
ревизия
39e4568460

+ 1 - 10
app/src/main/java/eu/kanade/tachiyomi/ui/reader/ReaderActivity.kt

@@ -15,7 +15,6 @@ import android.net.Uri
 import android.os.Build
 import android.os.Bundle
 import android.text.TextUtils
-import android.view.Gravity
 import android.view.KeyEvent
 import android.view.Menu
 import android.view.MenuItem
@@ -24,7 +23,6 @@ import android.view.View.LAYER_TYPE_HARDWARE
 import android.view.WindowManager
 import android.view.animation.Animation
 import android.view.animation.AnimationUtils
-import android.widget.FrameLayout
 import android.widget.Toast
 import androidx.activity.viewModels
 import androidx.compose.runtime.collectAsState
@@ -36,7 +34,6 @@ import androidx.core.view.WindowCompat
 import androidx.core.view.WindowInsetsCompat
 import androidx.core.view.WindowInsetsControllerCompat
 import androidx.core.view.isVisible
-import androidx.core.view.updateLayoutParams
 import androidx.lifecycle.lifecycleScope
 import com.davemorrissey.labs.subscaleview.SubsamplingScaleImageView
 import com.google.android.material.internal.ToolbarUtils
@@ -69,7 +66,6 @@ import eu.kanade.tachiyomi.ui.reader.viewer.pager.R2LPagerViewer
 import eu.kanade.tachiyomi.ui.webview.WebViewActivity
 import eu.kanade.tachiyomi.util.preference.toggle
 import eu.kanade.tachiyomi.util.system.applySystemAnimatorScale
-import eu.kanade.tachiyomi.util.system.createReaderThemeContext
 import eu.kanade.tachiyomi.util.system.hasDisplayCutout
 import eu.kanade.tachiyomi.util.system.isNightMode
 import eu.kanade.tachiyomi.util.system.toShareIntent
@@ -660,12 +656,7 @@ class ReaderActivity : BaseActivity() {
 
         supportActionBar?.title = manga.title
 
-        val loadingIndicatorContext = createReaderThemeContext()
-        loadingIndicator = ReaderProgressIndicator(loadingIndicatorContext).apply {
-            updateLayoutParams<FrameLayout.LayoutParams> {
-                gravity = Gravity.CENTER
-            }
-        }
+        loadingIndicator = ReaderProgressIndicator(this)
         binding.readerContainer.addView(loadingIndicator)
 
         startPostponedEnterTransition()

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

@@ -2,13 +2,21 @@ package eu.kanade.tachiyomi.ui.reader.viewer
 
 import android.content.Context
 import android.util.AttributeSet
+import android.view.Gravity
 import android.view.ViewGroup.LayoutParams.WRAP_CONTENT
-import android.view.animation.Animation
-import android.view.animation.LinearInterpolator
-import android.view.animation.RotateAnimation
 import android.widget.FrameLayout
 import androidx.annotation.IntRange
+import androidx.compose.material3.CircularProgressIndicator
+import androidx.compose.runtime.Composable
+import androidx.compose.runtime.getValue
+import androidx.compose.runtime.mutableFloatStateOf
+import androidx.compose.runtime.setValue
+import androidx.compose.ui.platform.AbstractComposeView
+import androidx.compose.ui.platform.ViewCompositionStrategy
+import androidx.core.view.isVisible
 import com.google.android.material.progressindicator.CircularProgressIndicator
+import eu.kanade.presentation.theme.TachiyomiTheme
+import tachiyomi.presentation.core.components.CombinedCircularProgressIndicator
 
 /**
  * A wrapper for [CircularProgressIndicator] that always rotates.
@@ -19,76 +27,31 @@ class ReaderProgressIndicator @JvmOverloads constructor(
     context: Context,
     attrs: AttributeSet? = null,
     defStyleAttr: Int = 0,
-) : FrameLayout(context, attrs, defStyleAttr) {
-
-    private val indicator: CircularProgressIndicator
-
-    private val rotateAnimation by lazy {
-        RotateAnimation(
-            0F,
-            360F,
-            Animation.RELATIVE_TO_SELF,
-            0.5F,
-            Animation.RELATIVE_TO_SELF,
-            0.5F,
-        ).apply {
-            interpolator = LinearInterpolator()
-            repeatCount = Animation.INFINITE
-            duration = 4000
-        }
-    }
+) : AbstractComposeView(context, attrs, defStyleAttr) {
 
     init {
-        layoutParams = LayoutParams(WRAP_CONTENT, WRAP_CONTENT)
-        indicator = CircularProgressIndicator(context)
-        indicator.max = 100
-        indicator.isIndeterminate = true
-        addView(indicator)
+        layoutParams = FrameLayout.LayoutParams(WRAP_CONTENT, WRAP_CONTENT, Gravity.CENTER)
+        setViewCompositionStrategy(ViewCompositionStrategy.DisposeOnDetachedFromWindowOrReleasedFromPool)
     }
 
-    override fun onAttachedToWindow() {
-        super.onAttachedToWindow()
-        updateRotateAnimation()
-    }
+    private var progress by mutableFloatStateOf(0f)
 
-    override fun onDetachedFromWindow() {
-        super.onDetachedFromWindow()
-        updateRotateAnimation()
+    @Composable
+    override fun Content() {
+        TachiyomiTheme {
+            CombinedCircularProgressIndicator(progress = progress)
+        }
     }
 
     fun show() {
-        indicator.show()
-        updateRotateAnimation()
+        isVisible = true
     }
 
     fun hide() {
-        indicator.hide()
-        updateRotateAnimation()
+        isVisible = false
     }
 
-    /**
-     * Sets the current indicator progress to the specified value.
-     *
-     * @param progress Indicator will be set indeterminate if this value is 0
-     */
-    fun setProgress(@IntRange(from = 0, to = 100) progress: Int, animated: Boolean = true) {
-        if (progress > 0) {
-            indicator.setProgressCompat(progress, animated)
-        } else if (!indicator.isIndeterminate) {
-            indicator.hide()
-            indicator.isIndeterminate = true
-            indicator.show()
-        }
-        updateRotateAnimation()
-    }
-
-    private fun updateRotateAnimation() {
-        if (isAttachedToWindow && indicator.isShown && !indicator.isIndeterminate) {
-            if (animation == null) {
-                startAnimation(rotateAnimation)
-            }
-        } else {
-            clearAnimation()
-        }
+    fun setProgress(@IntRange(from = 0, to = 100) progress: Int) {
+        this.progress = progress / 100f
     }
 }

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

@@ -2,10 +2,8 @@ package eu.kanade.tachiyomi.ui.reader.viewer.pager
 
 import android.annotation.SuppressLint
 import android.content.Context
-import android.view.Gravity
 import android.view.LayoutInflater
 import androidx.core.view.isVisible
-import androidx.core.view.updateLayoutParams
 import eu.kanade.tachiyomi.databinding.ReaderErrorBinding
 import eu.kanade.tachiyomi.source.model.Page
 import eu.kanade.tachiyomi.ui.reader.model.InsertPage
@@ -46,11 +44,7 @@ class PagerPageHolder(
     /**
      * Loading progress bar to indicate the current progress.
      */
-    private val progressIndicator: ReaderProgressIndicator = ReaderProgressIndicator(readerThemedContext).apply {
-        updateLayoutParams<LayoutParams> {
-            gravity = Gravity.CENTER
-        }
-    }
+    private val progressIndicator: ReaderProgressIndicator = ReaderProgressIndicator(readerThemedContext)
 
     /**
      * Error layout to show when the image fails to load.

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

@@ -1,7 +1,6 @@
 package eu.kanade.tachiyomi.ui.reader.viewer.webtoon
 
 import android.content.res.Resources
-import android.view.Gravity
 import android.view.LayoutInflater
 import android.view.ViewGroup
 import android.view.ViewGroup.LayoutParams.MATCH_PARENT
@@ -119,7 +118,7 @@ class WebtoonPageHolder(
 
         removeErrorLayout()
         frame.recycle()
-        progressIndicator.setProgress(0, animated = false)
+        progressIndicator.setProgress(0)
     }
 
     /**
@@ -288,7 +287,6 @@ class WebtoonPageHolder(
 
         val progress = ReaderProgressIndicator(context).apply {
             updateLayoutParams<FrameLayout.LayoutParams> {
-                gravity = Gravity.CENTER_HORIZONTAL
                 updateMargins(top = parentHeight / 4)
             }
         }

+ 110 - 0
presentation-core/src/main/java/tachiyomi/presentation/core/components/CircularProgressIndicator.kt

@@ -0,0 +1,110 @@
+package tachiyomi.presentation.core.components
+
+import androidx.compose.animation.AnimatedContent
+import androidx.compose.animation.core.LinearEasing
+import androidx.compose.animation.core.RepeatMode
+import androidx.compose.animation.core.animateFloat
+import androidx.compose.animation.core.animateFloatAsState
+import androidx.compose.animation.core.infiniteRepeatable
+import androidx.compose.animation.core.rememberInfiniteTransition
+import androidx.compose.animation.core.tween
+import androidx.compose.animation.fadeIn
+import androidx.compose.animation.fadeOut
+import androidx.compose.animation.togetherWith
+import androidx.compose.foundation.layout.Box
+import androidx.compose.foundation.layout.fillMaxSize
+import androidx.compose.foundation.layout.fillMaxWidth
+import androidx.compose.foundation.layout.padding
+import androidx.compose.material3.Button
+import androidx.compose.material3.CircularProgressIndicator
+import androidx.compose.material3.MaterialTheme
+import androidx.compose.material3.ProgressIndicatorDefaults
+import androidx.compose.material3.Scaffold
+import androidx.compose.material3.Text
+import androidx.compose.runtime.Composable
+import androidx.compose.runtime.getValue
+import androidx.compose.runtime.mutableFloatStateOf
+import androidx.compose.runtime.remember
+import androidx.compose.runtime.setValue
+import androidx.compose.ui.Alignment
+import androidx.compose.ui.Modifier
+import androidx.compose.ui.draw.rotate
+import androidx.compose.ui.tooling.preview.Preview
+
+/**
+ * A combined [CircularProgressIndicator] that always rotates.
+ *
+ * By always rotating we give the feedback to the user that the application isn't 'stuck'.
+ */
+@Composable
+fun CombinedCircularProgressIndicator(
+    progress: Float,
+) {
+    val animatedProgress by animateFloatAsState(
+        targetValue = progress,
+        animationSpec = ProgressIndicatorDefaults.ProgressAnimationSpec,
+        label = "progress",
+    )
+    AnimatedContent(
+        targetState = progress == 0f,
+        transitionSpec = { fadeIn() togetherWith fadeOut() },
+        label = "progressState",
+    ) { indeterminate ->
+        if (indeterminate) {
+            // Indeterminate
+            CircularProgressIndicator()
+        } else {
+            // Determinate
+            val infiniteTransition = rememberInfiniteTransition(label = "infiniteRotation")
+            val rotation by infiniteTransition.animateFloat(
+                initialValue = 0f,
+                targetValue = 360f,
+                animationSpec = infiniteRepeatable(
+                    animation = tween(2000, easing = LinearEasing),
+                    repeatMode = RepeatMode.Restart,
+                ),
+                label = "rotation",
+            )
+            CircularProgressIndicator(
+                progress = animatedProgress,
+                modifier = Modifier.rotate(rotation),
+            )
+        }
+    }
+}
+
+@Preview
+@Composable
+private fun CombinedCircularProgressIndicatorPreview() {
+    var progress by remember { mutableFloatStateOf(0f) }
+    MaterialTheme {
+        Scaffold(
+            bottomBar = {
+                Button(
+                    modifier = Modifier.fillMaxWidth(),
+                    onClick = {
+                        progress = when (progress) {
+                            0f -> 0.15f
+                            0.15f -> 0.25f
+                            0.25f -> 0.5f
+                            0.5f -> 0.75f
+                            0.75f -> 0.95f
+                            else -> 0f
+                        }
+                    },
+                ) {
+                    Text("change")
+                }
+            },
+        ) {
+            Box(
+                contentAlignment = Alignment.Center,
+                modifier = Modifier
+                    .fillMaxSize()
+                    .padding(it),
+            ) {
+                CombinedCircularProgressIndicator(progress = progress)
+            }
+        }
+    }
+}