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