diff --git a/app/src/main/java/eu/kanade/tachiyomi/ui/reader/ReaderActivity.kt b/app/src/main/java/eu/kanade/tachiyomi/ui/reader/ReaderActivity.kt index 965dbd41c5..d6e127adbb 100644 --- a/app/src/main/java/eu/kanade/tachiyomi/ui/reader/ReaderActivity.kt +++ b/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 { - gravity = Gravity.CENTER - } - } + loadingIndicator = ReaderProgressIndicator(this) binding.readerContainer.addView(loadingIndicator) startPostponedEnterTransition() diff --git a/app/src/main/java/eu/kanade/tachiyomi/ui/reader/viewer/ReaderProgressIndicator.kt b/app/src/main/java/eu/kanade/tachiyomi/ui/reader/viewer/ReaderProgressIndicator.kt index 75f710686a..2bf9cf95fa 100644 --- a/app/src/main/java/eu/kanade/tachiyomi/ui/reader/viewer/ReaderProgressIndicator.kt +++ b/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 } } diff --git a/app/src/main/java/eu/kanade/tachiyomi/ui/reader/viewer/pager/PagerPageHolder.kt b/app/src/main/java/eu/kanade/tachiyomi/ui/reader/viewer/pager/PagerPageHolder.kt index 76a920c22e..e1fd80ff5a 100644 --- a/app/src/main/java/eu/kanade/tachiyomi/ui/reader/viewer/pager/PagerPageHolder.kt +++ b/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 { - gravity = Gravity.CENTER - } - } + private val progressIndicator: ReaderProgressIndicator = ReaderProgressIndicator(readerThemedContext) /** * Error layout to show when the image fails to load. diff --git a/app/src/main/java/eu/kanade/tachiyomi/ui/reader/viewer/webtoon/WebtoonPageHolder.kt b/app/src/main/java/eu/kanade/tachiyomi/ui/reader/viewer/webtoon/WebtoonPageHolder.kt index d29c2497ba..749a48e334 100644 --- a/app/src/main/java/eu/kanade/tachiyomi/ui/reader/viewer/webtoon/WebtoonPageHolder.kt +++ b/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 { - gravity = Gravity.CENTER_HORIZONTAL updateMargins(top = parentHeight / 4) } } diff --git a/presentation-core/src/main/java/tachiyomi/presentation/core/components/CircularProgressIndicator.kt b/presentation-core/src/main/java/tachiyomi/presentation/core/components/CircularProgressIndicator.kt new file mode 100644 index 0000000000..dafe3237d2 --- /dev/null +++ b/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) + } + } + } +}