All Downloads are FREE. Search and download functionalities are using the official Maven repository.

net.peanuuutz.fork.ui.animation.AnimateAsState.kt Maven / Gradle / Ivy

The newest version!
/*
 * Copyright 2020 The Android Open Source Project
 * Modifications Copyright 2022 Peanuuutz
 *
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *
 *      http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */

package net.peanuuutz.fork.ui.animation

import androidx.compose.runtime.Composable
import androidx.compose.runtime.LaunchedEffect
import androidx.compose.runtime.SideEffect
import androidx.compose.runtime.State
import androidx.compose.runtime.getValue
import androidx.compose.runtime.remember
import androidx.compose.runtime.rememberUpdatedState
import kotlinx.coroutines.channels.Channel
import kotlinx.coroutines.launch
import net.peanuuutz.fork.ui.animation.spec.target.DefaultColorAnimationSpec
import net.peanuuutz.fork.ui.animation.spec.target.DefaultFloatAnimationSpec
import net.peanuuutz.fork.ui.animation.spec.target.DefaultFloatOffsetAnimationSpec
import net.peanuuutz.fork.ui.animation.spec.target.DefaultFloatSizeAnimationSpec
import net.peanuuutz.fork.ui.animation.spec.target.DefaultIntAnimationSpec
import net.peanuuutz.fork.ui.animation.spec.target.DefaultIntOffsetAnimationSpec
import net.peanuuutz.fork.ui.animation.spec.target.DefaultIntSizeAnimationSpec
import net.peanuuutz.fork.ui.animation.spec.target.DefaultRectAnimationSpec
import net.peanuuutz.fork.ui.animation.spec.target.composite.AnimationSpec
import net.peanuuutz.fork.ui.animation.spec.target.composite.SpringSpec
import net.peanuuutz.fork.ui.animation.vector.AnimationVector
import net.peanuuutz.fork.ui.animation.vector.VectorConvertor
import net.peanuuutz.fork.ui.ui.draw.shape.Rect
import net.peanuuutz.fork.ui.ui.unit.FloatOffset
import net.peanuuutz.fork.ui.ui.unit.FloatSize
import net.peanuuutz.fork.ui.ui.unit.IntOffset
import net.peanuuutz.fork.ui.ui.unit.IntSize
import net.peanuuutz.fork.util.common.Color

@Composable
fun animateFloatAsState(
    targetValue: Float,
    animationSpec: AnimationSpec = DefaultFloatAnimationSpec,
    onFinish: ((Float) -> Unit)? = null
): State {
    return animateValueAsState(
        convertor = Float.VectorConvertor,
        targetValue = targetValue,
        animationSpec = animationSpec,
        onFinish = onFinish
    )
}

@Composable
fun animateIntAsState(
    targetValue: Int,
    animationSpec: AnimationSpec = DefaultIntAnimationSpec,
    onFinish: ((Int) -> Unit)? = null
): State {
    return animateValueAsState(
        convertor = Int.VectorConvertor,
        targetValue = targetValue,
        animationSpec = animationSpec,
        onFinish = onFinish
    )
}

@Composable
fun animateFloatOffsetAsState(
    targetValue: FloatOffset,
    animationSpec: AnimationSpec = DefaultFloatOffsetAnimationSpec,
    onFinish: ((FloatOffset) -> Unit)? = null
): State {
    return animateValueAsState(
        convertor = FloatOffset.VectorConvertor,
        targetValue = targetValue,
        animationSpec = animationSpec,
        onFinish = onFinish
    )
}

@Composable
fun animateIntOffsetAsState(
    targetValue: IntOffset,
    animationSpec: AnimationSpec = DefaultIntOffsetAnimationSpec,
    onFinish: ((IntOffset) -> Unit)? = null
): State {
    return animateValueAsState(
        convertor = IntOffset.VectorConvertor,
        targetValue = targetValue,
        animationSpec = animationSpec,
        onFinish = onFinish
    )
}

@Composable
fun animateFloatSizeAsState(
    targetValue: FloatSize,
    animationSpec: AnimationSpec = DefaultFloatSizeAnimationSpec,
    onFinish: ((FloatSize) -> Unit)? = null
): State {
    return animateValueAsState(
        convertor = FloatSize.VectorConvertor,
        targetValue = targetValue,
        animationSpec = animationSpec,
        onFinish = onFinish
    )
}

@Composable
fun animateIntSizeAsState(
    targetValue: IntSize,
    animationSpec: AnimationSpec = DefaultIntSizeAnimationSpec,
    onFinish: ((IntSize) -> Unit)? = null
): State {
    return animateValueAsState(
        convertor = IntSize.VectorConvertor,
        targetValue = targetValue,
        animationSpec = animationSpec,
        onFinish = onFinish
    )
}

@Composable
fun animateColorAsState(
    targetValue: Color,
    animationSpec: AnimationSpec = DefaultColorAnimationSpec,
    onFinish: ((Color) -> Unit)? = null
): State {
    return animateValueAsState(
        convertor = Color.VectorConvertor,
        targetValue = targetValue,
        animationSpec = animationSpec,
        onFinish = onFinish
    )
}

@Composable
fun animateRectAsState(
    targetValue: Rect,
    animationSpec: AnimationSpec = DefaultRectAnimationSpec,
    onFinish: ((Rect) -> Unit)? = null
): State {
    return animateValueAsState(
        convertor = Rect.VectorConvertor,
        targetValue = targetValue,
        animationSpec = animationSpec,
        onFinish = onFinish
    )
}

@Composable
fun > animateValueAsState(
    convertor: VectorConvertor,
    targetValue: S,
    animationSpec: AnimationSpec = SpringSpec(),
    onFinish: ((S) -> Unit)? = null
): State {
    val animatable = remember { Animatable(targetValue, convertor) }
    val onFinishRemembered by rememberUpdatedState(onFinish)
    val animationSpecRemembered by rememberUpdatedState(animationSpec)
    val channel = remember { Channel(capacity = Channel.CONFLATED) }

    LaunchedEffect(Unit) {
        for (attemptTargetValue in channel) {
            val candidateTargetValue = channel.tryReceive().getOrNull() ?: attemptTargetValue
            if (candidateTargetValue != animatable.targetValue) {
                launch {
                    animatable.animateTo(candidateTargetValue, animationSpecRemembered)
                    onFinishRemembered?.invoke(animatable.value)
                }
            }
        }
    }

    SideEffect {
        channel.trySend(targetValue)
    }

    return animatable.animationState
}