diff --git a/navi-uitron/src/main/java/com/navi/uitron/helpers/animationHelper/AnimationSpecHelper.kt b/navi-uitron/src/main/java/com/navi/uitron/helpers/animationHelper/AnimationSpecHelper.kt index 28528de..3932fbc 100644 --- a/navi-uitron/src/main/java/com/navi/uitron/helpers/animationHelper/AnimationSpecHelper.kt +++ b/navi-uitron/src/main/java/com/navi/uitron/helpers/animationHelper/AnimationSpecHelper.kt @@ -87,12 +87,13 @@ fun animatedSpec(animationSpec: AnimationSpec? = null): FiniteAnimationSpec< * * @return Easing - The easing */ -fun EasingType.toEasing(): Easing = +fun EasingType?.toEasing(): Easing = when (this) { EasingType.FastOutSlowInEasing -> FastOutSlowInEasing EasingType.LinearOutSlowInEasing -> LinearOutSlowInEasing EasingType.FastOutLinearInEasing -> FastOutLinearInEasing EasingType.LinearEasing -> LinearEasing + else -> LinearEasing } /** diff --git a/navi-uitron/src/main/java/com/navi/uitron/helpers/animationHelper/CreateAnimationProperty.kt b/navi-uitron/src/main/java/com/navi/uitron/helpers/animationHelper/CreateAnimationProperty.kt index 520521c..79cf866 100644 --- a/navi-uitron/src/main/java/com/navi/uitron/helpers/animationHelper/CreateAnimationProperty.kt +++ b/navi-uitron/src/main/java/com/navi/uitron/helpers/animationHelper/CreateAnimationProperty.kt @@ -24,6 +24,7 @@ import com.navi.uitron.model.animations.animateColorSet import com.navi.uitron.model.animations.animateFloatSet import com.navi.uitron.model.animations.animateIntSet import com.navi.uitron.model.animations.animateOffsetSet +import com.navi.uitron.model.animations.getKeyFrames import com.navi.uitron.model.ui.AnimationState import com.navi.uitron.model.ui.BaseProperty import com.navi.uitron.model.ui.TextProperty @@ -57,8 +58,8 @@ fun createAnimationProperty( } } - animatePropertiesList?.let { - it.forEach { propertyAnimator -> + animatePropertiesList?.let { list -> + list.forEach { propertyAnimator -> when (val propertyName = propertyAnimator?.propertyName) { in animateFloatSet -> { val floatData = propertyAnimator as? FloatInterpolator @@ -69,7 +70,8 @@ fun createAnimationProperty( targetToInitAnimSpec = propertyAnimator.targetToInitAnimSpec, label = propertyName?.name, targetValue = floatData.targetFloatValue ?: 0f, - defaultValue = floatData.initialFloatValue ?: 0f + defaultValue = floatData.initialFloatValue ?: 0f, + keyFrames = getKeyFrames(propertyAnimator.floatKeyFrames) { it } ) SetFloatProperty( propertyName = propertyName, @@ -95,7 +97,11 @@ fun createAnimationProperty( Offset( offsetData.initialOffset?.x ?: 0f, offsetData.initialOffset?.y ?: 0f - ) + ), + keyFrames = + getKeyFrames(propertyAnimator.offsetKeyFrames) { + Offset(it.x ?: 0f, it.y ?: 0f) + } ) SetOffsetProperty( propertyName = propertyName, @@ -124,7 +130,11 @@ fun createAnimationProperty( ?.textColor ?.hexToComposeColor ?: Color.Black else -> Color.Transparent - } + }, + keyFrames = + getKeyFrames(propertyAnimator.colorKeyFrames) { + it.hexToComposeColor + } ) SetColorProperty( propertyName = propertyName, @@ -145,7 +155,8 @@ fun createAnimationProperty( defaultValue = intData.initialIntValue ?: (baseProperty as? TextProperty)?.fontSize - ?: 0 + ?: 0, + keyFrames = getKeyFrames(propertyAnimator.intKeyFrames) { it } ) SetIntProperty( propertyName = propertyName, @@ -163,7 +174,7 @@ fun createAnimationProperty( } @Composable -fun SetFloatProperty( +private fun SetFloatProperty( propertyName: AnimateProperty?, animatedProperties: AnimatedProperties, value: State @@ -185,7 +196,7 @@ fun SetFloatProperty( } @Composable -fun SetIntProperty( +private fun SetIntProperty( propertyName: AnimateProperty?, animatedProperties: AnimatedProperties, value: State @@ -197,7 +208,7 @@ fun SetIntProperty( } @Composable -fun SetColorProperty( +private fun SetColorProperty( propertyName: AnimateProperty?, animatedProperties: AnimatedProperties, value: State @@ -210,7 +221,7 @@ fun SetColorProperty( } @Composable -fun SetOffsetProperty( +private fun SetOffsetProperty( propertyName: AnimateProperty?, animatedProperties: AnimatedProperties, value: State diff --git a/navi-uitron/src/main/java/com/navi/uitron/helpers/animationHelper/TransitionHelper.kt b/navi-uitron/src/main/java/com/navi/uitron/helpers/animationHelper/TransitionHelper.kt index 8cd890a..f85875b 100644 --- a/navi-uitron/src/main/java/com/navi/uitron/helpers/animationHelper/TransitionHelper.kt +++ b/navi-uitron/src/main/java/com/navi/uitron/helpers/animationHelper/TransitionHelper.kt @@ -9,6 +9,7 @@ package com.navi.uitron.helpers.animationHelper import android.os.Parcelable import androidx.compose.animation.animateColor +import androidx.compose.animation.core.KeyframesSpec import androidx.compose.animation.core.Transition import androidx.compose.animation.core.animateFloat import androidx.compose.animation.core.animateInt @@ -30,14 +31,16 @@ fun Transition.anFloat( targetToInitAnimSpec: AnimationSpec?, label: String?, targetValue: Float?, - defaultValue: Float + defaultValue: Float, + keyFrames: KeyframesSpec? ): State { return animateFloat( transitionSpec = { - when { - false isTransitioningTo true -> animatedSpec(initToTargetAnimSpec) - else -> animatedSpec(targetToInitAnimSpec) - } + keyFrames + ?: when { + false isTransitioningTo true -> animatedSpec(initToTargetAnimSpec) + else -> animatedSpec(targetToInitAnimSpec) + } }, label = label ?: FLOAT_ANIMATION ) { @@ -55,14 +58,16 @@ fun Transition.anColor( targetToInitAnimSpec: AnimationSpec?, label: String?, targetColor: Color?, - initialColor: Color + initialColor: Color, + keyFrames: KeyframesSpec? ): State { return animateColor( transitionSpec = { - when { - false isTransitioningTo true -> animatedSpec(initToTargetAnimSpec) - else -> animatedSpec(targetToInitAnimSpec) - } + keyFrames + ?: when { + false isTransitioningTo true -> animatedSpec(initToTargetAnimSpec) + else -> animatedSpec(targetToInitAnimSpec) + } }, label = label ?: COLOR_ANIMATION ) { @@ -80,14 +85,16 @@ fun Transition.anInt( targetToInitAnimSpec: AnimationSpec?, label: String?, targetValue: Int?, - defaultValue: Int + defaultValue: Int, + keyFrames: KeyframesSpec? ): State { return animateInt( transitionSpec = { - when { - false isTransitioningTo true -> animatedSpec(initToTargetAnimSpec) - else -> animatedSpec(targetToInitAnimSpec) - } + keyFrames + ?: when { + false isTransitioningTo true -> animatedSpec(initToTargetAnimSpec) + else -> animatedSpec(targetToInitAnimSpec) + } }, label = label ?: INT_ANIMATION ) { @@ -105,14 +112,16 @@ fun Transition.anOffset( targetToInitAnimSpec: AnimationSpec?, label: String?, targetOffset: Offset?, - defaultOffset: Offset + defaultOffset: Offset, + keyFrames: KeyframesSpec? ): State { return animateOffset( transitionSpec = { - when { - false isTransitioningTo true -> animatedSpec(initToTargetAnimSpec) - else -> animatedSpec(targetToInitAnimSpec) - } + keyFrames + ?: when { + false isTransitioningTo true -> animatedSpec(initToTargetAnimSpec) + else -> animatedSpec(targetToInitAnimSpec) + } }, label = label ?: OFFSET_ANIMATION ) { diff --git a/navi-uitron/src/main/java/com/navi/uitron/model/animations/AnimateProperty.kt b/navi-uitron/src/main/java/com/navi/uitron/model/animations/AnimateProperty.kt index 3ce108f..7f6277f 100644 --- a/navi-uitron/src/main/java/com/navi/uitron/model/animations/AnimateProperty.kt +++ b/navi-uitron/src/main/java/com/navi/uitron/model/animations/AnimateProperty.kt @@ -24,21 +24,29 @@ open class PropertyAnimator( @Parcelize data class FloatInterpolator( var initialFloatValue: Float? = null, - var targetFloatValue: Float? = null + var targetFloatValue: Float? = null, + var floatKeyFrames: AnimationKeyFrames? = null ) : PropertyAnimator() @Parcelize -data class IntInterpolator(var initialIntValue: Int? = null, var targetIntValue: Int? = null) : - PropertyAnimator() +data class IntInterpolator( + var initialIntValue: Int? = null, + var targetIntValue: Int? = null, + var intKeyFrames: AnimationKeyFrames? = null +) : PropertyAnimator() @Parcelize -data class ColorInterpolator(var initialColor: String? = null, var targetColor: String? = null) : - PropertyAnimator() +data class ColorInterpolator( + var initialColor: String? = null, + var targetColor: String? = null, + var colorKeyFrames: AnimationKeyFrames? = null +) : PropertyAnimator() @Parcelize data class OffsetInterpolator( var initialOffset: OffSetData? = null, - var targetOffset: OffSetData? = null + var targetOffset: OffSetData? = null, + var offsetKeyFrames: AnimationKeyFrames? = null ) : PropertyAnimator() data class AnimatedProperties( diff --git a/navi-uitron/src/main/java/com/navi/uitron/model/animations/AnimationKeyFrames.kt b/navi-uitron/src/main/java/com/navi/uitron/model/animations/AnimationKeyFrames.kt new file mode 100644 index 0000000..229b2e1 --- /dev/null +++ b/navi-uitron/src/main/java/com/navi/uitron/model/animations/AnimationKeyFrames.kt @@ -0,0 +1,78 @@ +/* + * + * * Copyright © 2024 by Navi Technologies Limited + * * All rights reserved. Strictly confidential + * + */ + +package com.navi.uitron.model.animations + +import android.os.Parcelable +import androidx.compose.animation.core.KeyframesSpec +import androidx.compose.animation.core.keyframes +import com.navi.uitron.helpers.animationHelper.toEasing +import com.navi.uitron.utils.DEFAULT_DELAY_MILLIS +import com.navi.uitron.utils.DEFAULT_DURATION_MILLIS +import kotlinx.parcelize.Parcelize +import kotlinx.parcelize.RawValue + +/** + * Holds a bunch of keyframes for an animation. + * + * @param T The type of value that's changing over time. + * @property durationMillis How long the animation runs in milliseconds. + * @property delayMillis How long to wait before starting the animation, in milliseconds. + * @property keyFrames List of keyframes with values and their timings. + */ +@Parcelize +data class AnimationKeyFrames( + var durationMillis: Int = DEFAULT_DURATION_MILLIS, + var delayMillis: Int = DEFAULT_DELAY_MILLIS, + val keyFrames: List> +) : Parcelable + +/** + * Represents a single keyframe in an animation. + * + * @param T The type of value this keyframe holds. + * @property timeMarker When this keyframe happens, in milliseconds. + * @property timeFraction The fraction of the animation's total duration at which this keyframe + * occurs. Should be between 0 and 1. + * @property frameValue The value at this time. + * @property easingType How the value should ease in or out (e.g., fast, slow). Default is linear. + */ +@Parcelize +data class Frame( + val timeMarker: Int? = null, + val timeFraction: Float? = null, + val frameValue: @RawValue T, + val easingType: EasingType? = null +) : Parcelable + +/** + * Converts a set of keyframes into a `KeyframesSpec` for animations. + * + * @param T Original value type in the keyframes. + * @param R New value type after conversion. + * @param keyFrames Keyframes to convert, or `null` if there aren’t any. + * @param convert Function to change a value from type `T` to type `R`. + * @return A `KeyframesSpec` for `R`, or `null` if there are no keyframes. + */ +fun getKeyFrames(keyFrames: AnimationKeyFrames?, convert: (T) -> R): KeyframesSpec? { + return keyFrames?.let { + keyframes { + durationMillis = it.durationMillis + delayMillis = it.delayMillis + it.keyFrames.forEach { frame -> + frame.timeFraction?.let { fraction -> + convert(frame.frameValue) + .atFraction(fraction) + .using(frame.easingType.toEasing()) + } + ?: frame.timeMarker?.let { marker -> + convert(frame.frameValue).at(marker).using(frame.easingType.toEasing()) + } + } + } + } +}