Added the ability to set dynamic properties
diff --git a/CHANGELOG_COMPOSE.md b/CHANGELOG_COMPOSE.md
index 2e6fe0d..0cbd03d 100644
--- a/CHANGELOG_COMPOSE.md
+++ b/CHANGELOG_COMPOSE.md
@@ -16,6 +16,8 @@
 There are overloaded version of `LottieAnimation` that merge the properties for convenience. Please
 refer to the docs for `LottieAnimation`, `LottieAnimatable`, `animateLottieCompositionAsState`
 and `rememberLottieComposition` for more information.
+* Added the ability to clip the progress bounds of an animation.
+* Added the ability to set and control dynamic properties.
 
 # 1.0.0-beta07-1
 * Compatible with Jetpack Compose Beta 07
diff --git a/lottie-compose/src/main/java/com/airbnb/lottie/compose/LottieAnimation.kt b/lottie-compose/src/main/java/com/airbnb/lottie/compose/LottieAnimation.kt
index 4a8a6c9..5ea2c46 100644
--- a/lottie-compose/src/main/java/com/airbnb/lottie/compose/LottieAnimation.kt
+++ b/lottie-compose/src/main/java/com/airbnb/lottie/compose/LottieAnimation.kt
@@ -70,6 +70,8 @@
  *                         features so it defaults to off. The only way to know if your animation will work
  *                         well with merge paths or not is to try it. If your animation has merge paths and
  *                         doesn't render correctly, please file an issue.
+ * @param dynamicProperties Allows you to change the properties of an animation dynamically. To use them, use
+ *                          [rememberLottieDynamicProperties]. Refer to its docs for more info.
  */
 @Composable
 fun LottieAnimation(
@@ -81,9 +83,11 @@
     outlineMasksAndMattes: Boolean = false,
     applyOpacityToLayers: Boolean = false,
     enableMergePaths: Boolean = false,
+    dynamicProperties: LottieDynamicProperties? = null,
 ) {
     val drawable = remember { LottieDrawable() }
     var imageAssetManager by remember { mutableStateOf<ImageAssetManager?>(null) }
+    var setDynamicProperties: LottieDynamicProperties? by remember { mutableStateOf(null) }
 
     if (composition == null || composition.duration == 0f) return Box(modifier)
 
@@ -105,6 +109,11 @@
                 scale(size.width / composition.bounds.width().toFloat(), size.height / composition.bounds.height().toFloat(), Offset.Zero)
             }) {
                 drawable.composition = composition
+                if (dynamicProperties !== setDynamicProperties) {
+                    setDynamicProperties?.removeFrom(drawable)
+                    dynamicProperties?.addTo(drawable)
+                    setDynamicProperties = dynamicProperties
+                }
                 drawable.setOutlineMasksAndMattes(outlineMasksAndMattes)
                 drawable.isApplyingOpacityToLayersEnabled = applyOpacityToLayers
                 drawable.enableMergePathsForKitKatAndAbove(enableMergePaths)
@@ -137,6 +146,7 @@
     outlineMasksAndMattes: Boolean = false,
     applyOpacityToLayers: Boolean = false,
     enableMergePaths: Boolean = false,
+    dynamicProperties: LottieDynamicProperties? = null,
 ) {
     val progress by animateLottieCompositionAsState(
         composition,
@@ -155,6 +165,7 @@
         outlineMasksAndMattes,
         applyOpacityToLayers,
         enableMergePaths,
+        dynamicProperties,
     )
 }
 
diff --git a/lottie-compose/src/main/java/com/airbnb/lottie/compose/LottieDynamicProperties.kt b/lottie-compose/src/main/java/com/airbnb/lottie/compose/LottieDynamicProperties.kt
new file mode 100644
index 0000000..188e375
--- /dev/null
+++ b/lottie-compose/src/main/java/com/airbnb/lottie/compose/LottieDynamicProperties.kt
@@ -0,0 +1,167 @@
+package com.airbnb.lottie.compose
+
+import android.graphics.ColorFilter
+import android.graphics.PointF
+import androidx.compose.runtime.Composable
+import androidx.compose.runtime.getValue
+import androidx.compose.runtime.remember
+import androidx.compose.runtime.rememberUpdatedState
+import com.airbnb.lottie.LottieDrawable
+import com.airbnb.lottie.model.KeyPath
+import com.airbnb.lottie.value.LottieFrameInfo
+import com.airbnb.lottie.value.LottieValueCallback
+import com.airbnb.lottie.value.ScaleXY
+
+/**
+ * Use this function when you want to apply one or more dynamic properties to an animation.
+ * This takes a vararg of individual dynamic properties which should be created with [rememberLottieDynamicProperty].
+ *
+ * @see rememberLottieDynamicProperty
+ */
+@Composable
+fun rememberLottieDynamicProperties(
+    vararg properties: LottieDynamicProperty<*>,
+): LottieDynamicProperties {
+    @Suppress("UNCHECKED_CAST")
+    return remember(properties) {
+        val intProperties = properties.filter { it.property is Int } as List<LottieDynamicProperty<Int>>
+        val pointFProperties = properties.filter { it.property is PointF } as List<LottieDynamicProperty<PointF>>
+        val floatProperties = properties.filter { it.property is Float } as List<LottieDynamicProperty<Float>>
+        val scaleProperties = properties.filter { it.property is ScaleXY } as List<LottieDynamicProperty<ScaleXY>>
+        val colorFilterProperties = properties.filter { it.property is ColorFilter } as List<LottieDynamicProperty<ColorFilter>>
+        val intArrayProperties = properties.filter { it.property is IntArray } as List<LottieDynamicProperty<IntArray>>
+        LottieDynamicProperties(
+            intProperties,
+            pointFProperties,
+            floatProperties,
+            scaleProperties,
+            colorFilterProperties,
+            intArrayProperties,
+        )
+    }
+}
+
+/**
+ * Use this to create a single dynamic property for an animation.
+ *
+ * @param property should be one of [com.airbnb.lottie.LottieProperty].
+ * @param value the desired value to use as this property's value.
+ * @param keyPath the string parts of a [com.airbnb.lottie.model.KeyPath] that specify which animation element
+ *                the property resides on.
+ */
+@Composable
+fun <T> rememberLottieDynamicProperty(
+    property: T,
+    value: T,
+    vararg keyPath: String,
+): LottieDynamicProperty<T> {
+    val keyPathObj = remember(keyPath) { KeyPath(*keyPath) }
+    val callback: (LottieFrameInfo<T>) -> T = remember(value) { { value } }
+    val currentCallback by rememberUpdatedState(callback)
+    return remember(keyPathObj, property) {
+        LottieDynamicProperty(
+            keyPathObj,
+            property,
+            object : LottieValueCallback<T>() {
+                override fun getValue(frameInfo: LottieFrameInfo<T>): T {
+                    return currentCallback(frameInfo)
+                }
+            },
+        )
+    }
+}
+
+/**
+ * Use this to create a single dynamic property for an animation.
+ *
+ * @param property Should be one of [com.airbnb.lottie.LottieProperty].
+ * @param keyPath The string parts of a [com.airbnb.lottie.model.KeyPath] that specify which animation element
+ *                the property resides on.
+ * @param callback A callback that will be invoked during the drawing pass with current frame info. The frame
+ *                 info can be used to alter the property's value based on the original animation data or it
+ *                 can be completely ignored and an arbitrary value can be returned. In this case, you may want
+ *                 the overloaded version of this function that takes a static value instead of a callback.
+ */
+@Composable
+fun <T> rememberLottieDynamicProperty(
+    property: T,
+    vararg keyPath: String,
+    callback: (frameInfo: LottieFrameInfo<T>) -> T
+): LottieDynamicProperty<T> {
+    val keyPathObj = remember(keyPath) { KeyPath(*keyPath) }
+    val currentCallback by rememberUpdatedState(callback)
+    return remember(keyPathObj, property) {
+        LottieDynamicProperty(
+            keyPathObj,
+            property,
+            object : LottieValueCallback<T>() {
+                override fun getValue(frameInfo: LottieFrameInfo<T>): T {
+                    return currentCallback(frameInfo)
+                }
+            },
+        )
+    }
+}
+
+/**
+ * @see rememberLottieDynamicProperty
+ */
+class LottieDynamicProperty<T> internal constructor(
+    internal val keyPath: KeyPath,
+    internal val property: T,
+    internal val valueCallback: LottieValueCallback<T>,
+)
+
+/**
+ * @see rememberLottieDynamicProperties
+ */
+class LottieDynamicProperties internal constructor(
+    private val intProperties: List<LottieDynamicProperty<Int>>,
+    private val pointFProperties: List<LottieDynamicProperty<PointF>>,
+    private val floatProperties: List<LottieDynamicProperty<Float>>,
+    private val scaleProperties: List<LottieDynamicProperty<ScaleXY>>,
+    private val colorFilterProperties: List<LottieDynamicProperty<ColorFilter>>,
+    private val intArrayProperties: List<LottieDynamicProperty<IntArray>>,
+) {
+    internal fun addTo(drawable: LottieDrawable) {
+        intProperties.forEach { p ->
+            drawable.addValueCallback(p.keyPath, p.property, p.valueCallback)
+        }
+        pointFProperties.forEach { p ->
+            drawable.addValueCallback(p.keyPath, p.property, p.valueCallback)
+        }
+        floatProperties.forEach { p ->
+            drawable.addValueCallback(p.keyPath, p.property, p.valueCallback)
+        }
+        scaleProperties.forEach { p ->
+            drawable.addValueCallback(p.keyPath, p.property, p.valueCallback)
+        }
+        colorFilterProperties.forEach { p ->
+            drawable.addValueCallback(p.keyPath, p.property, p.valueCallback)
+        }
+        intArrayProperties.forEach { p ->
+            drawable.addValueCallback(p.keyPath, p.property, p.valueCallback)
+        }
+    }
+
+    internal fun removeFrom(drawable: LottieDrawable) {
+        intProperties.forEach { p ->
+            drawable.addValueCallback(p.keyPath, p.property, null as LottieValueCallback<Int>?)
+        }
+        pointFProperties.forEach { p ->
+            drawable.addValueCallback(p.keyPath, p.property, null as LottieValueCallback<PointF>?)
+        }
+        floatProperties.forEach { p ->
+            drawable.addValueCallback(p.keyPath, p.property, null as LottieValueCallback<Float>?)
+        }
+        scaleProperties.forEach { p ->
+            drawable.addValueCallback(p.keyPath, p.property, null as LottieValueCallback<ScaleXY>?)
+        }
+        colorFilterProperties.forEach { p ->
+            drawable.addValueCallback(p.keyPath, p.property, null as LottieValueCallback<ColorFilter>?)
+        }
+        intArrayProperties.forEach { p ->
+            drawable.addValueCallback(p.keyPath, p.property, null as LottieValueCallback<IntArray>?)
+        }
+    }
+}
\ No newline at end of file
diff --git a/lottie/src/main/java/com/airbnb/lottie/LottieDrawable.java b/lottie/src/main/java/com/airbnb/lottie/LottieDrawable.java
index 4ef2f0f..a0e26e1 100644
--- a/lottie/src/main/java/com/airbnb/lottie/LottieDrawable.java
+++ b/lottie/src/main/java/com/airbnb/lottie/LottieDrawable.java
@@ -1026,7 +1026,7 @@
    * {@link #resolveKeyPath(KeyPath)} and will resolve it if it hasn't.
    */
   public <T> void addValueCallback(
-      final KeyPath keyPath, final T property, final LottieValueCallback<T> callback) {
+      final KeyPath keyPath, final T property, @Nullable final LottieValueCallback<T> callback) {
     if (compositionLayer == null) {
       lazyCompositionTasks.add(new LazyCompositionTask() {
         @Override
diff --git a/sample-compose/src/main/java/com/airbnb/lottie/sample/compose/ComposeActivity.kt b/sample-compose/src/main/java/com/airbnb/lottie/sample/compose/ComposeActivity.kt
index a810e90..3bd72ec 100644
--- a/sample-compose/src/main/java/com/airbnb/lottie/sample/compose/ComposeActivity.kt
+++ b/sample-compose/src/main/java/com/airbnb/lottie/sample/compose/ComposeActivity.kt
@@ -26,6 +26,7 @@
 import com.airbnb.lottie.compose.LottieCompositionSpec
 import com.airbnb.lottie.sample.compose.examples.AnimatableExamplesPage
 import com.airbnb.lottie.sample.compose.examples.BasicUsageExamplesPage
+import com.airbnb.lottie.sample.compose.examples.DynamicPropertiesExamplesPage
 import com.airbnb.lottie.sample.compose.examples.ExamplesPage
 import com.airbnb.lottie.sample.compose.examples.NetworkExamplesPage
 import com.airbnb.lottie.sample.compose.examples.TransitionsExamplesPage
@@ -96,6 +97,7 @@
                         composable(Route.TransitionsExamples.route) { TransitionsExamplesPage() }
                         composable(Route.ViewPagerExample.route) { ViewPagerExamplePage() }
                         composable(Route.NetworkExamples.route) { NetworkExamplesPage() }
+                        composable(Route.DynamicProperties.route) { DynamicPropertiesExamplesPage() }
                         composable(
                             Route.Player.fullRoute,
                             arguments = Route.Player.args
diff --git a/sample-compose/src/main/java/com/airbnb/lottie/sample/compose/Route.kt b/sample-compose/src/main/java/com/airbnb/lottie/sample/compose/Route.kt
index daa5f19..2dd9b5d 100644
--- a/sample-compose/src/main/java/com/airbnb/lottie/sample/compose/Route.kt
+++ b/sample-compose/src/main/java/com/airbnb/lottie/sample/compose/Route.kt
@@ -26,6 +26,8 @@
 
     object NetworkExamples : Route("network examples")
 
+    object DynamicProperties : Route("dynamic properties examples")
+
     object Player : Route(
         "player",
         listOf(
diff --git a/sample-compose/src/main/java/com/airbnb/lottie/sample/compose/examples/DynamicPropertiesExamplesPage.kt b/sample-compose/src/main/java/com/airbnb/lottie/sample/compose/examples/DynamicPropertiesExamplesPage.kt
new file mode 100644
index 0000000..fe33fc4
--- /dev/null
+++ b/sample-compose/src/main/java/com/airbnb/lottie/sample/compose/examples/DynamicPropertiesExamplesPage.kt
@@ -0,0 +1,155 @@
+package com.airbnb.lottie.sample.compose.examples
+
+import android.graphics.PointF
+import androidx.compose.foundation.clickable
+import androidx.compose.foundation.interaction.MutableInteractionSource
+import androidx.compose.foundation.layout.Column
+import androidx.compose.foundation.layout.fillMaxWidth
+import androidx.compose.foundation.rememberScrollState
+import androidx.compose.foundation.verticalScroll
+import androidx.compose.runtime.Composable
+import androidx.compose.runtime.derivedStateOf
+import androidx.compose.runtime.getValue
+import androidx.compose.runtime.mutableStateOf
+import androidx.compose.runtime.remember
+import androidx.compose.runtime.setValue
+import androidx.compose.ui.Modifier
+import androidx.compose.ui.graphics.Color
+import androidx.compose.ui.graphics.toArgb
+import androidx.compose.ui.platform.LocalDensity
+import androidx.compose.ui.unit.dp
+import com.airbnb.lottie.LottieProperty
+import com.airbnb.lottie.compose.LottieAnimation
+import com.airbnb.lottie.compose.LottieCompositionSpec
+import com.airbnb.lottie.compose.LottieConstants
+import com.airbnb.lottie.compose.rememberLottieComposition
+import com.airbnb.lottie.compose.rememberLottieDynamicProperties
+import com.airbnb.lottie.compose.rememberLottieDynamicProperty
+import com.airbnb.lottie.sample.compose.R
+
+@Composable
+fun DynamicPropertiesExamplesPage() {
+    UsageExamplePageScaffold {
+        Column(
+            modifier = Modifier
+                .fillMaxWidth()
+                .verticalScroll(rememberScrollState())
+        ) {
+            ExampleCard("Heart Color", "Click to change color") {
+                HeartColor()
+            }
+            ExampleCard("Jump Height", "Click to jump heiht") {
+                JumpHeight()
+            }
+            ExampleCard("Change Properties", "Click to toggle whether the dynamic property is used") {
+                ToggleProperty()
+            }
+        }
+    }
+}
+
+@Composable
+private fun HeartColor() {
+    val composition by rememberLottieComposition(LottieCompositionSpec.RawRes(R.raw.heart))
+    val colors = remember {
+        listOf(
+            Color.Red,
+            Color.Green,
+            Color.Blue,
+            Color.Yellow,
+        )
+    }
+    var colorIndex by remember { mutableStateOf(0) }
+    val color by derivedStateOf { colors[colorIndex] }
+
+    val dynamicProperties = rememberLottieDynamicProperties(
+        rememberLottieDynamicProperty(
+            property = LottieProperty.COLOR,
+            value = color.toArgb(),
+            keyPath = arrayOf(
+                "H2",
+                "Shape 1",
+                "Fill 1",
+            )
+        ),
+    )
+    LottieAnimation(
+        composition,
+        iterations = LottieConstants.IterateForever,
+        dynamicProperties = dynamicProperties,
+        modifier = Modifier
+            .clickable(
+                interactionSource = remember { MutableInteractionSource() },
+                indication = null,
+                onClick = { colorIndex = (colorIndex + 1) % colors.size },
+            )
+    )
+}
+
+@Composable
+private fun JumpHeight() {
+    val composition by rememberLottieComposition(LottieCompositionSpec.Asset("AndroidWave.json"))
+    val extraJumpHeights = remember { listOf(0.dp, 24.dp, 48.dp, 128.dp) }
+    var extraJumpIndex by remember { mutableStateOf(0) }
+    val extraJumpHeight by derivedStateOf { extraJumpHeights[extraJumpIndex] }
+    val extraJumpHeightPx = with(LocalDensity.current) { extraJumpHeight.toPx() }
+
+    val point = remember { PointF() }
+    val dynamicProperties = rememberLottieDynamicProperties(
+        rememberLottieDynamicProperty(LottieProperty.TRANSFORM_POSITION, "Body") { frameInfo ->
+            var startY = frameInfo.startValue.y
+            var endY = frameInfo.endValue.y
+            when {
+                startY > endY -> startY += extraJumpHeightPx
+                else -> endY += extraJumpHeightPx
+            }
+            point.set(frameInfo.startValue.x, lerp(startY, endY, frameInfo.interpolatedKeyframeProgress))
+            point
+        }
+    )
+    LottieAnimation(
+        composition,
+        iterations = LottieConstants.IterateForever,
+        dynamicProperties = dynamicProperties,
+        modifier = Modifier
+            .clickable(
+                interactionSource = remember { MutableInteractionSource() },
+                indication = null,
+                onClick = { extraJumpIndex = (extraJumpIndex + 1) % extraJumpHeights.size },
+            )
+    )
+}
+
+@Composable
+private fun ToggleProperty() {
+    val composition by rememberLottieComposition(LottieCompositionSpec.RawRes(R.raw.heart))
+    var useDynamicProperty by remember { mutableStateOf(true) }
+    val dynamicProperties = rememberLottieDynamicProperties(
+        rememberLottieDynamicProperty(
+            property = LottieProperty.COLOR,
+            value = Color.Green.toArgb(),
+            keyPath = arrayOf(
+                "H2",
+                "Shape 1",
+                "Fill 1",
+            )
+        ),
+    )
+    LottieAnimation(
+        composition,
+        iterations = LottieConstants.IterateForever,
+        dynamicProperties = dynamicProperties.takeIf { useDynamicProperty },
+        modifier = Modifier
+            .clickable(
+                interactionSource = remember { MutableInteractionSource() },
+                indication = null,
+                onClick = { useDynamicProperty = !useDynamicProperty },
+            )
+    )
+}
+
+private fun lerp(valueA: Float, valueB: Float, progress: Float): Float {
+    val smallerY = minOf(valueA, valueB)
+    val largerY = maxOf(valueA, valueB)
+    return smallerY + progress * (largerY - smallerY)
+}
\ No newline at end of file
diff --git a/sample-compose/src/main/java/com/airbnb/lottie/sample/compose/examples/ExamplesPage.kt b/sample-compose/src/main/java/com/airbnb/lottie/sample/compose/examples/ExamplesPage.kt
index c8eaaa0..e2097ec 100644
--- a/sample-compose/src/main/java/com/airbnb/lottie/sample/compose/examples/ExamplesPage.kt
+++ b/sample-compose/src/main/java/com/airbnb/lottie/sample/compose/examples/ExamplesPage.kt
@@ -52,5 +52,11 @@
             modifier = Modifier
                 .clickable { navController.navigate(Route.NetworkExamples) }
         )
+        ListItem(
+            text = { Text("Dynamic Properties") },
+            secondaryText = { Text("Setting dynamic properties") },
+            modifier = Modifier
+                .clickable { navController.navigate(Route.DynamicProperties) }
+        )
     }
 }
\ No newline at end of file