From ba93060e591fccf3c85995b50f496bc937ae8ae4 Mon Sep 17 00:00:00 2001 From: Ivan Iskandar <12537387+ivaniskandar@users.noreply.github.com> Date: Tue, 12 Jul 2022 09:21:00 +0700 Subject: [PATCH] Fix scroll animation when system animation is disabled (#7509) --- .../browse/ExtensionLangFilterScreen.kt | 2 +- .../category/components/CategoryContent.kt | 2 +- .../presentation/components/LazyList.kt | 38 ++++++++++-- .../kanade/presentation/manga/MangaScreen.kt | 2 +- .../eu/kanade/presentation/util/Scrollable.kt | 60 +++++++++++++++++++ 5 files changed, 97 insertions(+), 7 deletions(-) create mode 100644 app/src/main/java/eu/kanade/presentation/util/Scrollable.kt diff --git a/app/src/main/java/eu/kanade/presentation/browse/ExtensionLangFilterScreen.kt b/app/src/main/java/eu/kanade/presentation/browse/ExtensionLangFilterScreen.kt index c24484b675..8532e3ba3e 100644 --- a/app/src/main/java/eu/kanade/presentation/browse/ExtensionLangFilterScreen.kt +++ b/app/src/main/java/eu/kanade/presentation/browse/ExtensionLangFilterScreen.kt @@ -3,7 +3,6 @@ package eu.kanade.presentation.browse import androidx.compose.foundation.layout.WindowInsets import androidx.compose.foundation.layout.asPaddingValues import androidx.compose.foundation.layout.navigationBars -import androidx.compose.foundation.lazy.LazyColumn import androidx.compose.foundation.lazy.items import androidx.compose.material3.Switch import androidx.compose.material3.Text @@ -15,6 +14,7 @@ import androidx.compose.ui.input.nestedscroll.NestedScrollConnection import androidx.compose.ui.input.nestedscroll.nestedScroll import androidx.compose.ui.platform.LocalContext import eu.kanade.presentation.components.EmptyScreen +import eu.kanade.presentation.components.LazyColumn import eu.kanade.presentation.components.LoadingScreen import eu.kanade.presentation.components.PreferenceRow import eu.kanade.tachiyomi.R diff --git a/app/src/main/java/eu/kanade/presentation/category/components/CategoryContent.kt b/app/src/main/java/eu/kanade/presentation/category/components/CategoryContent.kt index 3b61957b8a..191609e421 100644 --- a/app/src/main/java/eu/kanade/presentation/category/components/CategoryContent.kt +++ b/app/src/main/java/eu/kanade/presentation/category/components/CategoryContent.kt @@ -2,12 +2,12 @@ package eu.kanade.presentation.category.components import androidx.compose.foundation.layout.Arrangement import androidx.compose.foundation.layout.PaddingValues -import androidx.compose.foundation.lazy.LazyColumn import androidx.compose.foundation.lazy.LazyListState import androidx.compose.foundation.lazy.itemsIndexed import androidx.compose.runtime.Composable import androidx.compose.ui.unit.dp import eu.kanade.domain.category.model.Category +import eu.kanade.presentation.components.LazyColumn @Composable fun CategoryContent( diff --git a/app/src/main/java/eu/kanade/presentation/components/LazyList.kt b/app/src/main/java/eu/kanade/presentation/components/LazyList.kt index f525593f6f..6698444e6d 100644 --- a/app/src/main/java/eu/kanade/presentation/components/LazyList.kt +++ b/app/src/main/java/eu/kanade/presentation/components/LazyList.kt @@ -1,11 +1,9 @@ package eu.kanade.presentation.components import androidx.compose.foundation.gestures.FlingBehavior -import androidx.compose.foundation.gestures.ScrollableDefaults import androidx.compose.foundation.layout.Arrangement import androidx.compose.foundation.layout.PaddingValues import androidx.compose.foundation.layout.calculateEndPadding -import androidx.compose.foundation.lazy.LazyColumn import androidx.compose.foundation.lazy.LazyListScope import androidx.compose.foundation.lazy.LazyListState import androidx.compose.foundation.lazy.rememberLazyListState @@ -17,6 +15,38 @@ import androidx.compose.ui.platform.LocalDensity import androidx.compose.ui.platform.LocalLayoutDirection import androidx.compose.ui.unit.dp import eu.kanade.presentation.util.drawVerticalScrollbar +import eu.kanade.presentation.util.flingBehaviorIgnoringMotionScale + +/** + * LazyColumn with fling animation fix + * + * @see flingBehaviorIgnoringMotionScale + */ +@Composable +fun LazyColumn( + modifier: Modifier = Modifier, + state: LazyListState = rememberLazyListState(), + contentPadding: PaddingValues = PaddingValues(0.dp), + reverseLayout: Boolean = false, + verticalArrangement: Arrangement.Vertical = + if (!reverseLayout) Arrangement.Top else Arrangement.Bottom, + horizontalAlignment: Alignment.Horizontal = Alignment.Start, + flingBehavior: FlingBehavior = flingBehaviorIgnoringMotionScale(), + userScrollEnabled: Boolean = true, + content: LazyListScope.() -> Unit, +) { + androidx.compose.foundation.lazy.LazyColumn( + modifier = modifier, + state = state, + contentPadding = contentPadding, + reverseLayout = reverseLayout, + verticalArrangement = verticalArrangement, + horizontalAlignment = horizontalAlignment, + flingBehavior = flingBehavior, + userScrollEnabled = userScrollEnabled, + content = content, + ) +} /** * LazyColumn with scrollbar. @@ -30,7 +60,7 @@ fun ScrollbarLazyColumn( verticalArrangement: Arrangement.Vertical = if (!reverseLayout) Arrangement.Top else Arrangement.Bottom, horizontalAlignment: Alignment.Horizontal = Alignment.Start, - flingBehavior: FlingBehavior = ScrollableDefaults.flingBehavior(), + flingBehavior: FlingBehavior = flingBehaviorIgnoringMotionScale(), userScrollEnabled: Boolean = true, content: LazyListScope.() -> Unit, ) { @@ -69,7 +99,7 @@ fun FastScrollLazyColumn( verticalArrangement: Arrangement.Vertical = if (!reverseLayout) Arrangement.Top else Arrangement.Bottom, horizontalAlignment: Alignment.Horizontal = Alignment.Start, - flingBehavior: FlingBehavior = ScrollableDefaults.flingBehavior(), + flingBehavior: FlingBehavior = flingBehaviorIgnoringMotionScale(), userScrollEnabled: Boolean = true, content: LazyListScope.() -> Unit, ) { diff --git a/app/src/main/java/eu/kanade/presentation/manga/MangaScreen.kt b/app/src/main/java/eu/kanade/presentation/manga/MangaScreen.kt index 718a540adf..fa5a2df88a 100644 --- a/app/src/main/java/eu/kanade/presentation/manga/MangaScreen.kt +++ b/app/src/main/java/eu/kanade/presentation/manga/MangaScreen.kt @@ -20,7 +20,6 @@ import androidx.compose.foundation.layout.navigationBars import androidx.compose.foundation.layout.only import androidx.compose.foundation.layout.padding import androidx.compose.foundation.layout.systemBars -import androidx.compose.foundation.lazy.LazyColumn import androidx.compose.foundation.lazy.LazyListScope import androidx.compose.foundation.lazy.items import androidx.compose.foundation.lazy.rememberLazyListState @@ -52,6 +51,7 @@ import com.google.accompanist.swiperefresh.SwipeRefresh import com.google.accompanist.swiperefresh.rememberSwipeRefreshState import eu.kanade.domain.chapter.model.Chapter import eu.kanade.presentation.components.ExtendedFloatingActionButton +import eu.kanade.presentation.components.LazyColumn import eu.kanade.presentation.components.Scaffold import eu.kanade.presentation.components.SwipeRefreshIndicator import eu.kanade.presentation.components.VerticalFastScroller diff --git a/app/src/main/java/eu/kanade/presentation/util/Scrollable.kt b/app/src/main/java/eu/kanade/presentation/util/Scrollable.kt new file mode 100644 index 0000000000..6b7316a70b --- /dev/null +++ b/app/src/main/java/eu/kanade/presentation/util/Scrollable.kt @@ -0,0 +1,60 @@ +package eu.kanade.presentation.util + +import androidx.compose.animation.core.AnimationState +import androidx.compose.animation.core.DecayAnimationSpec +import androidx.compose.animation.core.animateDecay +import androidx.compose.animation.rememberSplineBasedDecay +import androidx.compose.foundation.gestures.FlingBehavior +import androidx.compose.foundation.gestures.ScrollScope +import androidx.compose.runtime.Composable +import androidx.compose.runtime.remember +import androidx.compose.ui.MotionDurationScale +import kotlinx.coroutines.withContext +import kotlin.math.abs + +/** + * FlingBehavior that always uses the default motion scale. + * + * This makes the scrolling animation works like View's lists + * when "Remove animation" settings is on. + */ +@Composable +fun flingBehaviorIgnoringMotionScale(): FlingBehavior { + val flingSpec = rememberSplineBasedDecay() + return remember(flingSpec) { + DefaultFlingBehavior(flingSpec) + } +} + +private val DefaultMotionDurationScale = object : MotionDurationScale { + // Use default motion scale factor + override val scaleFactor: Float = 1f +} + +private class DefaultFlingBehavior( + private val flingDecay: DecayAnimationSpec, +) : FlingBehavior { + override suspend fun ScrollScope.performFling(initialVelocity: Float): Float { + // come up with the better threshold, but we need it since spline curve gives us NaNs + return if (abs(initialVelocity) > 1f) { + var velocityLeft = initialVelocity + var lastValue = 0f + withContext(DefaultMotionDurationScale) { + AnimationState( + initialValue = 0f, + initialVelocity = initialVelocity, + ).animateDecay(flingDecay) { + val delta = value - lastValue + val consumed = scrollBy(delta) + lastValue = value + velocityLeft = this.velocity + // avoid rounding errors and stop if anything is unconsumed + if (abs(delta - consumed) > 0.5f) this.cancelAnimation() + } + } + velocityLeft + } else { + initialVelocity + } + } +}