ReaderProgressIndicator: Convert to Compose (#9574)

This commit is contained in:
Ivan Iskandar 2023-06-04 00:11:41 +07:00 committed by GitHub
parent 0d96791a84
commit 39e4568460
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
5 changed files with 137 additions and 81 deletions

View File

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

View File

@ -2,13 +2,21 @@ package eu.kanade.tachiyomi.ui.reader.viewer
import android.content.Context import android.content.Context
import android.util.AttributeSet import android.util.AttributeSet
import android.view.Gravity
import android.view.ViewGroup.LayoutParams.WRAP_CONTENT 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 android.widget.FrameLayout
import androidx.annotation.IntRange 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 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. * A wrapper for [CircularProgressIndicator] that always rotates.
@ -19,76 +27,31 @@ class ReaderProgressIndicator @JvmOverloads constructor(
context: Context, context: Context,
attrs: AttributeSet? = null, attrs: AttributeSet? = null,
defStyleAttr: Int = 0, defStyleAttr: Int = 0,
) : FrameLayout(context, attrs, defStyleAttr) { ) : AbstractComposeView(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
}
}
init { init {
layoutParams = LayoutParams(WRAP_CONTENT, WRAP_CONTENT) layoutParams = FrameLayout.LayoutParams(WRAP_CONTENT, WRAP_CONTENT, Gravity.CENTER)
indicator = CircularProgressIndicator(context) setViewCompositionStrategy(ViewCompositionStrategy.DisposeOnDetachedFromWindowOrReleasedFromPool)
indicator.max = 100
indicator.isIndeterminate = true
addView(indicator)
} }
override fun onAttachedToWindow() { private var progress by mutableFloatStateOf(0f)
super.onAttachedToWindow()
updateRotateAnimation()
}
override fun onDetachedFromWindow() { @Composable
super.onDetachedFromWindow() override fun Content() {
updateRotateAnimation() TachiyomiTheme {
CombinedCircularProgressIndicator(progress = progress)
}
} }
fun show() { fun show() {
indicator.show() isVisible = true
updateRotateAnimation()
} }
fun hide() { fun hide() {
indicator.hide() isVisible = false
updateRotateAnimation()
} }
/** fun setProgress(@IntRange(from = 0, to = 100) progress: Int) {
* Sets the current indicator progress to the specified value. this.progress = progress / 100f
*
* @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()
}
} }
} }

View File

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

View File

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

View File

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