mirror of
https://github.com/tachiyomiorg/tachiyomi.git
synced 2024-11-10 06:17:48 +01:00
ReaderProgressIndicator: Convert to Compose (#9574)
This commit is contained in:
parent
0d96791a84
commit
39e4568460
5 changed files with 137 additions and 81 deletions
|
@ -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()
|
||||
|
|
|
@ -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
|
||||
}
|
||||
}
|
||||
|
|
|
@ -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,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)
|
||||
}
|
||||
}
|
||||
|
|
|
@ -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)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
Loading…
Reference in a new issue