[Compose] Allow custom cache keys, dynamic properties for images, and remove font remapping (#1847)

This set of changes is all around composition caching:

1) Increase the caching flexibility by allowing arbitrary cache keys.
2) Remove the cacheKey parameter from LottieCompositionSpec.JsonString because it was ambiguous with the new cacheKey parameter.
3) Add dynamic properties for bitmaps. This is helpful because it allows you to set a bitmap on a single LottieAnimation without overwriting the bitmap for the cacheable LottieComposition.
4) Removed fontRemapping for rememberLottieComposition because there was no way to know how to handle caching. Instead, dynamic properties can be used.
diff --git a/.idea/inspectionProfiles/Project_Default.xml b/.idea/inspectionProfiles/Project_Default.xml
index ac4ba28..2269c5a 100644
--- a/.idea/inspectionProfiles/Project_Default.xml
+++ b/.idea/inspectionProfiles/Project_Default.xml
@@ -20,5 +20,6 @@
     <inspection_tool class="PreviewNeedsComposableAnnotation" enabled="true" level="ERROR" enabled_by_default="true">
       <option name="previewFile" value="true" />
     </inspection_tool>
+    <inspection_tool class="SameParameterValue" enabled="false" level="WARNING" enabled_by_default="false" />
   </profile>
 </component>
\ No newline at end of file
diff --git a/lottie-compose/src/main/java/com/airbnb/lottie/compose/LottieCompositionSpec.kt b/lottie-compose/src/main/java/com/airbnb/lottie/compose/LottieCompositionSpec.kt
index 69877fb..e12f808 100644
--- a/lottie-compose/src/main/java/com/airbnb/lottie/compose/LottieCompositionSpec.kt
+++ b/lottie-compose/src/main/java/com/airbnb/lottie/compose/LottieCompositionSpec.kt
@@ -6,6 +6,7 @@
  * passed into [rememberLottieComposition] or [LottieAnimation].
  */
 sealed class LottieCompositionSpec {
+
     /**
      * Load an animation from res/raw.
      */
@@ -39,5 +40,5 @@
     /**
      * Load an animation from its json string.
      */
-    data class JsonString(val jsonString: String, val cacheKey: String? = null) : LottieCompositionSpec()
+    data class JsonString(val jsonString: String) : LottieCompositionSpec()
 }
\ No newline at end of file
diff --git a/lottie-compose/src/main/java/com/airbnb/lottie/compose/LottieConstants.kt b/lottie-compose/src/main/java/com/airbnb/lottie/compose/LottieConstants.kt
index 9ca32d9..882fda0 100644
--- a/lottie-compose/src/main/java/com/airbnb/lottie/compose/LottieConstants.kt
+++ b/lottie-compose/src/main/java/com/airbnb/lottie/compose/LottieConstants.kt
@@ -2,7 +2,7 @@
 
 object LottieConstants {
     /**
-     * Use this with [animateLottieComposition#iterations] to repeat forever.
+     * Use this with [animateLottieCompositionAsState]'s iterations parameter to repeat forever.
      */
     const val IterateForever = Integer.MAX_VALUE
 }
\ No newline at end of file
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
index ef791a9..c6333a1 100644
--- a/lottie-compose/src/main/java/com/airbnb/lottie/compose/LottieDynamicProperties.kt
+++ b/lottie-compose/src/main/java/com/airbnb/lottie/compose/LottieDynamicProperties.kt
@@ -1,5 +1,6 @@
 package com.airbnb.lottie.compose
 
+import android.graphics.Bitmap
 import android.graphics.ColorFilter
 import android.graphics.PointF
 import android.graphics.Typeface
@@ -97,6 +98,7 @@
     private val colorFilterProperties: List<LottieDynamicProperty<ColorFilter>>,
     private val intArrayProperties: List<LottieDynamicProperty<IntArray>>,
     private val typefaceProperties: List<LottieDynamicProperty<Typeface>>,
+    private val bitmapProperties: List<LottieDynamicProperty<Bitmap>>,
 ) {
     @Suppress("UNCHECKED_CAST")
     constructor(properties: List<LottieDynamicProperty<*>>) : this(
@@ -107,6 +109,7 @@
         properties.filter { it.property is ColorFilter } as List<LottieDynamicProperty<ColorFilter>>,
         properties.filter { it.property is IntArray } as List<LottieDynamicProperty<IntArray>>,
         properties.filter { it.property is Typeface } as List<LottieDynamicProperty<Typeface>>,
+        properties.filter { it.property is Bitmap } as List<LottieDynamicProperty<Bitmap>>,
     )
 
     internal fun addTo(drawable: LottieDrawable) {
@@ -131,6 +134,10 @@
         typefaceProperties.forEach { p ->
             drawable.addValueCallback(p.keyPath, p.property, p.callback.toValueCallback())
         }
+        bitmapProperties.forEach { p ->
+            drawable.addValueCallback(p.keyPath, p.property, p.callback.toValueCallback())
+        }
+
     }
 
     internal fun removeFrom(drawable: LottieDrawable) {
@@ -155,6 +162,9 @@
         typefaceProperties.forEach { p ->
             drawable.addValueCallback(p.keyPath, p.property, null as LottieValueCallback<Typeface>?)
         }
+        bitmapProperties.forEach { p ->
+            drawable.addValueCallback(p.keyPath, p.property, null as LottieValueCallback<Bitmap>?)
+        }
     }
 }
 
diff --git a/lottie-compose/src/main/java/com/airbnb/lottie/compose/rememberLottieComposition.kt b/lottie-compose/src/main/java/com/airbnb/lottie/compose/rememberLottieComposition.kt
index e3ae50b..42d3e3c 100644
--- a/lottie-compose/src/main/java/com/airbnb/lottie/compose/rememberLottieComposition.kt
+++ b/lottie-compose/src/main/java/com/airbnb/lottie/compose/rememberLottieComposition.kt
@@ -27,6 +27,12 @@
 import kotlin.coroutines.resumeWithException
 
 /**
+ * Use this with [rememberLottieComposition#cacheKey]'s cacheKey parameter to generate a default
+ * cache key for the composition.
+ */
+private const val DefaultCacheKey = "__LottieInternalDefaultCacheKey__"
+
+/**
  * Takes a [LottieCompositionSpec], attempts to load and parse the animation, and returns a [LottieCompositionResult].
  *
  * [LottieCompositionResult] allows you to explicitly check for loading, failures, call
@@ -54,11 +60,10 @@
  *                         and should be set via fontRemapping or via dynamic properties.
  * @param fontFileExtension The default file extension for font files specified in the fontAssetsFolder or fontRemapping.
  *                          Defaults to ttf.
- * @param fontRemapping Remaps family names as specified in the Lottie json file to font files stored in the fontAssetsFolder.
- *                      This will automatically add the fontFileExtension so you should not include the font file extension
- *                      in your remapping.
- * @param cacheComposition Whether or not to cache the composition. If set to true, the next time an composition with this
- *                         spec is fetched, it will return the existing one instead of parsing it again.
+ * @param cacheKey Set a cache key for this composition. When set, subsequent calls to fetch this composition will
+ *                 return directly from the cache instead of having to reload and parse the animation. Set this to
+ *                 null to skip the cache. By default, this will automatically generate a cache key derived
+ *                 from your [LottieCompositionSpec].
  * @param onRetry An optional callback that will be called if loading the animation fails.
  *                It is passed the failed count (the number of times it has failed) and the exception
  *                from the previous attempt to load the composition. [onRetry] is a suspending function
@@ -71,8 +76,7 @@
     imageAssetsFolder: String? = null,
     fontAssetsFolder: String = "fonts/",
     fontFileExtension: String = ".ttf",
-    fontRemapping: Map<String, String> = emptyMap(),
-    cacheComposition: Boolean = true,
+    cacheKey: String? = DefaultCacheKey,
     onRetry: suspend (failCount: Int, previousException: Throwable) -> Boolean = { _, _ -> false },
 ): LottieCompositionResult {
     val context = LocalContext.current
@@ -88,8 +92,7 @@
                     imageAssetsFolder.ensureTrailingSlash(),
                     fontAssetsFolder.ensureTrailingSlash(),
                     fontFileExtension.ensureLeadingPeriod(),
-                    fontRemapping,
-                    cacheComposition,
+                    cacheKey,
                 )
                 result.complete(composition)
             } catch (e: Throwable) {
@@ -110,22 +113,21 @@
     imageAssetsFolder: String?,
     fontAssetsFolder: String?,
     fontFileExtension: String,
-    fontRemapping: Map<String, String>,
-    cacheComposition: Boolean,
+    cacheKey: String?,
 ): LottieComposition {
     val task = when (spec) {
         is LottieCompositionSpec.RawRes -> {
-            if (cacheComposition) {
+            if (cacheKey == DefaultCacheKey) {
                 LottieCompositionFactory.fromRawRes(context, spec.resId)
             } else {
-                LottieCompositionFactory.fromRawRes(context, spec.resId, null)
+                LottieCompositionFactory.fromRawRes(context, spec.resId, cacheKey)
             }
         }
         is LottieCompositionSpec.Url -> {
-            if (cacheComposition) {
+            if (cacheKey == DefaultCacheKey) {
                 LottieCompositionFactory.fromUrl(context, spec.url)
             } else {
-                LottieCompositionFactory.fromUrl(context, spec.url, null)
+                LottieCompositionFactory.fromUrl(context, spec.url, cacheKey)
             }
         }
         is LottieCompositionSpec.File -> {
@@ -136,26 +138,27 @@
             when {
                 spec.fileName.endsWith("zip") -> LottieCompositionFactory.fromZipStream(
                     ZipInputStream(fis),
-                    spec.fileName.takeIf { cacheComposition },
+                    spec.fileName.takeIf { cacheKey != null },
                 )
-                else -> LottieCompositionFactory.fromJsonInputStream(fis, spec.fileName.takeIf { cacheComposition })
+                else -> LottieCompositionFactory.fromJsonInputStream(fis, spec.fileName.takeIf { cacheKey != null })
             }
         }
         is LottieCompositionSpec.Asset -> {
-            if (cacheComposition) {
+            if (cacheKey == DefaultCacheKey) {
                 LottieCompositionFactory.fromAsset(context, spec.assetName)
             } else {
                 LottieCompositionFactory.fromAsset(context, spec.assetName, null)
             }
         }
         is LottieCompositionSpec.JsonString -> {
-            LottieCompositionFactory.fromJsonString(spec.jsonString, spec.cacheKey.takeIf { cacheComposition })
+            val jsonStringCacheKey = if (cacheKey == DefaultCacheKey) spec.jsonString.hashCode().toString() else cacheKey
+            LottieCompositionFactory.fromJsonString(spec.jsonString, jsonStringCacheKey)
         }
     }
 
     val composition = task.await()
     loadImagesFromAssets(context, composition, imageAssetsFolder)
-    loadFontsFromAssets(context, composition, fontAssetsFolder, fontFileExtension, fontRemapping)
+    loadFontsFromAssets(context, composition, fontAssetsFolder, fontFileExtension)
     return composition
 }
 
@@ -230,12 +233,11 @@
     composition: LottieComposition,
     fontAssetsFolder: String?,
     fontFileExtension: String,
-    fontRemapping: Map<String, String>,
 ) {
     if (composition.fonts.isEmpty()) return
     withContext(Dispatchers.IO) {
         for (font in composition.fonts.values) {
-            maybeLoadTypefaceFromAssets(context, font, fontAssetsFolder, fontFileExtension, fontRemapping[font.family])
+            maybeLoadTypefaceFromAssets(context, font, fontAssetsFolder, fontFileExtension)
         }
     }
 }
@@ -245,9 +247,8 @@
     font: Font,
     fontAssetsFolder: String?,
     fontFileExtension: String,
-    remappedFontPath: String?,
 ) {
-    val path = remappedFontPath ?: "$fontAssetsFolder${font.family}${fontFileExtension}"
+    val path = "$fontAssetsFolder${font.family}${fontFileExtension}"
     val typefaceWithDefaultStyle = try {
         Typeface.createFromAsset(context.assets, path)
     } catch (e: Exception) {
diff --git a/lottie/src/main/java/com/airbnb/lottie/LottieComposition.java b/lottie/src/main/java/com/airbnb/lottie/LottieComposition.java
index 8a34ed9..cc78abf 100644
--- a/lottie/src/main/java/com/airbnb/lottie/LottieComposition.java
+++ b/lottie/src/main/java/com/airbnb/lottie/LottieComposition.java
@@ -30,7 +30,7 @@
 
 /**
  * After Effects/Bodymovin composition model. This is the serialized model from which the
- * animation will be created.
+ * animation will be created. It is designed to be stateless, cacheable, and shareable.
  * <p>
  * To create one, use {@link LottieCompositionFactory}.
  * <p>
diff --git a/lottie/src/main/java/com/airbnb/lottie/LottieImageAsset.java b/lottie/src/main/java/com/airbnb/lottie/LottieImageAsset.java
index faa7e9e..f1acbe6 100644
--- a/lottie/src/main/java/com/airbnb/lottie/LottieImageAsset.java
+++ b/lottie/src/main/java/com/airbnb/lottie/LottieImageAsset.java
@@ -56,7 +56,12 @@
   }
 
   /**
-   * TODO
+   * Permanently sets the bitmap on this LottieImageAsset. This will:
+   * 1) Overwrite any existing Bitmaps.
+   * 2) Apply to *all* animations that use this LottieComposition.
+   *
+   * If you only want to replace the bitmap for this animation, use dynamic properties
+   * with {@link LottieProperty#IMAGE}.
    */
   public void setBitmap(@Nullable Bitmap bitmap) {
     this.bitmap = bitmap;
diff --git a/lottie/src/main/java/com/airbnb/lottie/LottieProperty.java b/lottie/src/main/java/com/airbnb/lottie/LottieProperty.java
index 0686156..e28128c 100644
--- a/lottie/src/main/java/com/airbnb/lottie/LottieProperty.java
+++ b/lottie/src/main/java/com/airbnb/lottie/LottieProperty.java
@@ -1,5 +1,7 @@
 package com.airbnb.lottie;
 
+import android.graphics.Bitmap;
+import android.graphics.BitmapFactory;
 import android.graphics.ColorFilter;
 import android.graphics.PointF;
 import android.graphics.Typeface;
@@ -163,8 +165,17 @@
   Float TEXT_SIZE = 14f;
 
   ColorFilter COLOR_FILTER = new ColorFilter();
-
+  /**
+   * Array of ARGB colors that map to position stops in the original gradient.
+   * For example, a gradient from red to blue could be remapped with [0xFF00FF00, 0xFFFF00FF] (green to purple).
+   */
   Integer[] GRADIENT_COLOR = new Integer[0];
-
+  /**
+   * Set on text layers.
+   */
   Typeface TYPEFACE = Typeface.DEFAULT;
+  /**
+   * Set on image layers.
+   */
+  Bitmap IMAGE = Bitmap.createBitmap(1, 1, Bitmap.Config.ALPHA_8);
 }
diff --git a/lottie/src/main/java/com/airbnb/lottie/model/layer/ImageLayer.java b/lottie/src/main/java/com/airbnb/lottie/model/layer/ImageLayer.java
index fa855ae..1653449 100644
--- a/lottie/src/main/java/com/airbnb/lottie/model/layer/ImageLayer.java
+++ b/lottie/src/main/java/com/airbnb/lottie/model/layer/ImageLayer.java
@@ -25,6 +25,7 @@
   private final Rect src = new Rect();
   private final Rect dst = new Rect();
   @Nullable private BaseKeyframeAnimation<ColorFilter, ColorFilter> colorFilterAnimation;
+  @Nullable private BaseKeyframeAnimation<Bitmap, Bitmap> imageAnimation;
 
   ImageLayer(LottieDrawable lottieDrawable, Layer layerModel) {
     super(lottieDrawable, layerModel);
@@ -60,6 +61,11 @@
 
   @Nullable
   private Bitmap getBitmap() {
+    if (imageAnimation != null) {
+      Bitmap callbackBitmap = imageAnimation.getValue();
+      if (callbackBitmap != null)
+        return callbackBitmap;
+    }
     String refId = layerModel.getRefId();
     return lottieDrawable.getImageAsset(refId);
   }
@@ -76,6 +82,14 @@
         colorFilterAnimation =
             new ValueCallbackKeyframeAnimation<>((LottieValueCallback<ColorFilter>) callback);
       }
+    } else if (property == LottieProperty.IMAGE) {
+      if (callback == null) {
+        imageAnimation = null;
+      } else {
+        //noinspection unchecked
+        imageAnimation =
+            new ValueCallbackKeyframeAnimation<>((LottieValueCallback<Bitmap>) callback);
+      }
     }
   }
 }
diff --git a/sample-compose/src/main/assets/Images/android.png b/sample-compose/src/main/assets/Images/android.png
new file mode 100644
index 0000000..4bb66b0
--- /dev/null
+++ b/sample-compose/src/main/assets/Images/android.png
Binary files differ
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 1fa3253..44c4030 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.CachingExamplesPage
 import com.airbnb.lottie.sample.compose.examples.ContentScaleExamplesPage
 import com.airbnb.lottie.sample.compose.examples.DynamicPropertiesExamplesPage
 import com.airbnb.lottie.sample.compose.examples.ExamplesPage
@@ -104,6 +105,7 @@
                         composable(Route.ImagesExamples.route) { ImagesExamplesPage() }
                         composable(Route.TextExamples.route) { TextExamplesPage() }
                         composable(Route.ContentScaleExamples.route) { ContentScaleExamplesPage() }
+                        composable(Route.CachingExamples.route) { CachingExamplesPage() }
                         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 1be37a2..3879298 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
@@ -34,6 +34,8 @@
 
     object ContentScaleExamples : Route("ContentScale examples")
 
+    object CachingExamples : Route("Caching examples")
+
     object Player : Route(
         "player",
         listOf(
diff --git a/sample-compose/src/main/java/com/airbnb/lottie/sample/compose/examples/CachingExamplesPage.kt b/sample-compose/src/main/java/com/airbnb/lottie/sample/compose/examples/CachingExamplesPage.kt
new file mode 100644
index 0000000..832fada
--- /dev/null
+++ b/sample-compose/src/main/java/com/airbnb/lottie/sample/compose/examples/CachingExamplesPage.kt
@@ -0,0 +1,61 @@
+package com.airbnb.lottie.sample.compose.examples
+
+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.getValue
+import androidx.compose.ui.Modifier
+import com.airbnb.lottie.compose.LottieAnimation
+import com.airbnb.lottie.compose.LottieCompositionSpec
+import com.airbnb.lottie.compose.rememberLottieComposition
+import com.airbnb.lottie.sample.compose.R
+
+@Composable
+fun CachingExamplesPage() {
+    UsageExamplePageScaffold {
+        Column(
+            modifier = Modifier
+                .fillMaxWidth()
+                .verticalScroll(rememberScrollState())
+        ) {
+            ExampleCard("Default Caching", "Lottie caches compositions by default") {
+                Example1()
+            }
+            ExampleCard("Day/Night", "Animations in raw/res will automatically respect day and night mode") {
+                Example2()
+            }
+            ExampleCard("Skip Cache", "Skip the cache") {
+                Example3()
+            }
+        }
+    }
+}
+
+@Composable
+private fun Example1() {
+    // By default, Lottie will cache compositions with a key derived from your LottieCompositionSpec.
+    // If you request the composition multiple times or request it again at some point later, it
+    // will return the previous composition. LottieComposition itself it stateless. All stateful
+    // actions should happen within LottieAnimation.
+    val composition by rememberLottieComposition(LottieCompositionSpec.RawRes(R.raw.heart))
+    LottieAnimation(composition)
+}
+
+@Composable
+private fun Example2() {
+    val composition by rememberLottieComposition(LottieCompositionSpec.RawRes(R.raw.sun_moon))
+    LottieAnimation(composition)
+}
+
+@Composable
+private fun Example3() {
+    val composition by rememberLottieComposition(
+        LottieCompositionSpec.RawRes(R.raw.we_accept_inline_image),
+        // Don't cache this composition. You may want to do this for animations that have images
+        // because the bitmaps are much larger to store than the rest of the animation.
+        cacheKey = null,
+    )
+    LottieAnimation(composition)
+}
\ 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 e2a80e8..80f6135 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
@@ -76,5 +76,11 @@
             modifier = Modifier
                 .clickable { navController.navigate(Route.ContentScaleExamples) }
         )
+        ListItem(
+            text = { Text("Caching") },
+            secondaryText = { Text("Interacting with Lottie's composition cache") },
+            modifier = Modifier
+                .clickable { navController.navigate(Route.CachingExamples) }
+        )
     }
 }
\ No newline at end of file
diff --git a/sample-compose/src/main/java/com/airbnb/lottie/sample/compose/examples/ImagesExamplesPage.kt b/sample-compose/src/main/java/com/airbnb/lottie/sample/compose/examples/ImagesExamplesPage.kt
index c9046dd..0bedae9 100644
--- a/sample-compose/src/main/java/com/airbnb/lottie/sample/compose/examples/ImagesExamplesPage.kt
+++ b/sample-compose/src/main/java/com/airbnb/lottie/sample/compose/examples/ImagesExamplesPage.kt
@@ -11,15 +11,19 @@
 import androidx.compose.runtime.LaunchedEffect
 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.platform.LocalContext
-import com.airbnb.lottie.LottieImageAsset
+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
-import com.airbnb.lottie.utils.Utils
 import kotlinx.coroutines.Dispatchers
 import kotlinx.coroutines.withContext
 
@@ -37,8 +41,11 @@
             ExampleCard("Assets Image", "Image stored in assets") {
                 ImageAssets()
             }
-            ExampleCard("Assets Image Callback", "Load an image manually") {
-                ImageAssetCallback()
+            ExampleCard("Dynamic Properties", "Replace an image with dynamic properties") {
+                DynamicProperties()
+            }
+            ExampleCard("Store on LottieImageAsset", "Store the bitmap within LottieImageAsset") {
+                StoredOnImageAsset()
             }
         }
     }
@@ -49,7 +56,7 @@
     // Don't cache the composition so the bitmaps can get released once the animation is no longer being used.
     val composition by rememberLottieComposition(
         LottieCompositionSpec.RawRes(R.raw.we_accept_inline_image),
-        cacheComposition = false,
+        cacheKey = null,
     )
     LottieAnimation(
         composition,
@@ -62,7 +69,7 @@
     // Don't cache the composition so the bitmaps can get released once the animation is no longer being used.
     val composition by rememberLottieComposition(
         LottieCompositionSpec.RawRes(R.raw.we_accept),
-        cacheComposition = false,
+        cacheKey = null,
         imageAssetsFolder = "Images/WeAccept",
     )
     LottieAnimation(
@@ -73,14 +80,40 @@
 }
 
 @Composable
-fun ImageAssetCallback() {
+fun DynamicProperties() {
     // Don't cache the composition so the bitmaps can get released once the animation is no longer being used.
-    val composition by rememberLottieComposition(LottieCompositionSpec.RawRes(R.raw.we_accept), cacheComposition = false)
+    val composition by rememberLottieComposition(
+        LottieCompositionSpec.RawRes(R.raw.we_accept),
+        cacheKey = null,
+    )
+    val bitmap = rememberBitmapFromAssets("Images/android.png")
+
+    val dynamicProperties = rememberLottieDynamicProperties(
+        rememberLottieDynamicProperty(LottieProperty.IMAGE, bitmap, "weaccept.jpg")
+    )
+
+    LottieAnimation(
+        composition,
+        iterations = LottieConstants.IterateForever,
+        dynamicProperties = dynamicProperties,
+    )
+}
+
+@Composable
+fun StoredOnImageAsset() {
+    // Don't cache the composition so the bitmaps can get released once the animation is no longer being used.
+    val composition by rememberLottieComposition(
+        LottieCompositionSpec.RawRes(R.raw.we_accept),
+        cacheKey = null,
+    )
     val imageAsset by derivedStateOf { composition?.images?.get("image_0") }
-    val context = LocalContext.current
-    LaunchedEffect(imageAsset) {
-        withContext(Dispatchers.IO) {
-            imageAsset?.bitmap = loadBitmapFromAssets(context, imageAsset)
+    val bitmap = rememberBitmapFromAssets("Images/android.png")
+    LaunchedEffect(imageAsset, bitmap) {
+        if (imageAsset != null && bitmap != null) {
+            // this stores the bitmap on the original composition's image asset which means that it
+            // will affect *all* LottieAnimation composables that are rendering this LottieComposition.
+            // Use with caution.
+            imageAsset?.bitmap = bitmap
         }
     }
     LottieAnimation(
@@ -89,25 +122,27 @@
     )
 }
 
-private fun loadBitmapFromAssets(context: Context, asset: LottieImageAsset?): Bitmap? {
+@Composable
+private fun rememberBitmapFromAssets(asset: String): Bitmap? {
+    var bitmap: Bitmap? by remember { mutableStateOf(null) }
+    val context = LocalContext.current
+    LaunchedEffect(asset) {
+        withContext(Dispatchers.IO) {
+            bitmap = loadBitmapFromAssets(context, asset)
+        }
+    }
+    return bitmap
+}
+
+private fun loadBitmapFromAssets(context: Context, asset: String?): Bitmap? {
     asset ?: return null
     return try {
-        val inputSteam = context.assets.open("Images/WeAccept/${asset.fileName}")
+        val inputSteam = context.assets.open(asset)
         val opts = BitmapFactory.Options()
         opts.inScaled = true
         opts.inDensity = 1606
-        val bitmap = BitmapFactory.decodeStream(inputSteam, null, opts)
-        bitmap?.resizeTo(asset.width, asset.height)
+        BitmapFactory.decodeStream(inputSteam, null, opts)
     } catch (e: Exception) {
         null
     }
-}
-
-private fun Bitmap.resizeTo(width: Int, height: Int): Bitmap? {
-    if (width == width && height == height) {
-        return this
-    }
-    val resizedBitmap = Bitmap.createScaledBitmap(this, width, height, true)
-    recycle()
-    return resizedBitmap
 }
\ No newline at end of file
diff --git a/sample-compose/src/main/java/com/airbnb/lottie/sample/compose/examples/TextExamplesPage.kt b/sample-compose/src/main/java/com/airbnb/lottie/sample/compose/examples/TextExamplesPage.kt
index 52a0140..6c17fb3 100644
--- a/sample-compose/src/main/java/com/airbnb/lottie/sample/compose/examples/TextExamplesPage.kt
+++ b/sample-compose/src/main/java/com/airbnb/lottie/sample/compose/examples/TextExamplesPage.kt
@@ -34,11 +34,8 @@
             ExampleCard("Default", "Loading fonts using default asset paths") {
                 Example1()
             }
-            ExampleCard("Font Remapping", "Replace fonts using font remapping") {
-                Example2()
-            }
             ExampleCard("Dynamic Properties", "Replace fonts with custom typefaces") {
-                Example3()
+                Example2()
             }
         }
     }
@@ -60,21 +57,6 @@
 
 @Composable
 private fun Example2() {
-    val composition by rememberLottieComposition(
-        LottieCompositionSpec.RawRes(R.raw.name),
-        // Comic Neue is the font family set in the Lottie json file.
-        fontRemapping = mapOf("Comic Neue" to "fonts/Roboto.ttf"),
-        // Don't cache the composition because it has a custom font remapping.
-        cacheComposition = false,
-    )
-    LottieAnimation(
-        composition,
-        progress = 0f,
-    )
-}
-
-@Composable
-private fun Example3() {
     val composition by rememberLottieComposition(LottieCompositionSpec.RawRes(R.raw.name))
     val typeface = rememberTypeface("fonts/Roboto.ttf")
     val dynamicProperties = rememberLottieDynamicProperties(
diff --git a/sample-compose/src/main/res/raw-night/sun_moon.json b/sample-compose/src/main/res/raw-night/sun_moon.json
new file mode 100644
index 0000000..0672295
--- /dev/null
+++ b/sample-compose/src/main/res/raw-night/sun_moon.json
@@ -0,0 +1 @@
+{"v":"5.1.1","fr":60,"ip":0,"op":180,"w":256,"h":256,"nm":"Moon","ddd":0,"assets":[],"layers":[{"ddd":0,"ind":1,"ty":4,"nm":"star","sr":1,"ks":{"o":{"a":1,"k":[{"i":{"x":[0.833],"y":[0.833]},"o":{"x":[0.167],"y":[0.167]},"n":["0p833_0p833_0p167_0p167"],"t":0,"s":[100],"e":[0]},{"i":{"x":[0.833],"y":[0.833]},"o":{"x":[0.167],"y":[0.167]},"n":["0p833_0p833_0p167_0p167"],"t":45,"s":[0],"e":[100]},{"i":{"x":[0.833],"y":[0.833]},"o":{"x":[0.167],"y":[0.167]},"n":["0p833_0p833_0p167_0p167"],"t":90,"s":[100],"e":[0]},{"i":{"x":[0.833],"y":[0.833]},"o":{"x":[0.167],"y":[0.167]},"n":["0p833_0p833_0p167_0p167"],"t":135,"s":[0],"e":[100]},{"t":179}],"ix":11},"r":{"a":0,"k":0,"ix":10},"p":{"a":0,"k":[202,219,0],"ix":2},"a":{"a":0,"k":[11,11,0],"ix":1},"s":{"a":0,"k":[100,100,100],"ix":6}},"ao":0,"shapes":[{"ty":"gr","it":[{"ind":0,"ty":"sh","ix":1,"ks":{"a":0,"k":{"i":[[5.27,-1.61],[-1.61,-5.27],[-5.26,1.61],[1.61,5.27]],"o":[[5.27,1.61],[1.61,-5.27],[-5.26,-1.61],[-1.61,5.27]],"v":[[0,11],[11,22],[22,11],[11,0]],"c":true},"ix":2},"nm":"Path 1","mn":"ADBE Vector Shape - Group","hd":false},{"ty":"fl","c":{"a":0,"k":[1,0.788235008717,0.188234999776,1],"ix":4},"o":{"a":0,"k":100,"ix":5},"r":1,"nm":"Fill 1","mn":"ADBE Vector Graphic - Fill","hd":false},{"ty":"tr","p":{"a":0,"k":[0,0],"ix":2},"a":{"a":0,"k":[0,0],"ix":1},"s":{"a":0,"k":[100,100],"ix":3},"r":{"a":0,"k":0,"ix":6},"o":{"a":0,"k":100,"ix":7},"sk":{"a":0,"k":0,"ix":4},"sa":{"a":0,"k":0,"ix":5},"nm":"Transform"}],"nm":"Group 1","np":2,"cix":2,"ix":1,"mn":"ADBE Vector Group","hd":false}],"ip":0,"op":180,"st":0,"bm":0},{"ddd":0,"ind":2,"ty":4,"nm":"star","sr":1,"ks":{"o":{"a":1,"k":[{"i":{"x":[0.833],"y":[0.833]},"o":{"x":[0.167],"y":[0.167]},"n":["0p833_0p833_0p167_0p167"],"t":10,"s":[0],"e":[100]},{"i":{"x":[0.833],"y":[0.833]},"o":{"x":[0.167],"y":[0.167]},"n":["0p833_0p833_0p167_0p167"],"t":50,"s":[100],"e":[0]},{"i":{"x":[0.833],"y":[0.833]},"o":{"x":[0.167],"y":[0.167]},"n":["0p833_0p833_0p167_0p167"],"t":100,"s":[0],"e":[100]},{"i":{"x":[0.833],"y":[0.833]},"o":{"x":[0.167],"y":[0.167]},"n":["0p833_0p833_0p167_0p167"],"t":140,"s":[100],"e":[0]},{"t":175}],"ix":11},"r":{"a":0,"k":0,"ix":10},"p":{"a":0,"k":[184.5,63.5,0],"ix":2},"a":{"a":0,"k":[6.5,6.5,0],"ix":1},"s":{"a":0,"k":[100,100,100],"ix":6}},"ao":0,"shapes":[{"ty":"gr","it":[{"ind":0,"ty":"sh","ix":1,"ks":{"a":0,"k":{"i":[[3.11,-0.95],[-0.95,-3.11],[-3.11,0.95],[0.95,3.11]],"o":[[3.11,0.95],[0.95,-3.11],[-3.11,-0.95],[-0.95,3.11]],"v":[[0,6.5],[6.5,13],[13,6.5],[6.5,0]],"c":true},"ix":2},"nm":"Path 1","mn":"ADBE Vector Shape - Group","hd":false},{"ty":"fl","c":{"a":0,"k":[1,0.788235008717,0.188234999776,1],"ix":4},"o":{"a":0,"k":100,"ix":5},"r":1,"nm":"Fill 1","mn":"ADBE Vector Graphic - Fill","hd":false},{"ty":"tr","p":{"a":0,"k":[0,0],"ix":2},"a":{"a":0,"k":[0,0],"ix":1},"s":{"a":0,"k":[100,100],"ix":3},"r":{"a":0,"k":0,"ix":6},"o":{"a":0,"k":100,"ix":7},"sk":{"a":0,"k":0,"ix":4},"sa":{"a":0,"k":0,"ix":5},"nm":"Transform"}],"nm":"Group 1","np":2,"cix":2,"ix":1,"mn":"ADBE Vector Group","hd":false}],"ip":0,"op":180,"st":0,"bm":0},{"ddd":0,"ind":3,"ty":4,"nm":"star","sr":1,"ks":{"o":{"a":1,"k":[{"i":{"x":[0.833],"y":[0.833]},"o":{"x":[0.167],"y":[0.167]},"n":["0p833_0p833_0p167_0p167"],"t":0,"s":[0],"e":[100]},{"i":{"x":[0.833],"y":[0.833]},"o":{"x":[0.167],"y":[0.167]},"n":["0p833_0p833_0p167_0p167"],"t":5,"s":[100],"e":[0]},{"i":{"x":[0.833],"y":[0.833]},"o":{"x":[0.167],"y":[0.167]},"n":["0p833_0p833_0p167_0p167"],"t":40,"s":[0],"e":[100]},{"i":{"x":[0.833],"y":[0.833]},"o":{"x":[0.167],"y":[0.167]},"n":["0p833_0p833_0p167_0p167"],"t":80,"s":[100],"e":[0]},{"i":{"x":[0.833],"y":[0.833]},"o":{"x":[0.167],"y":[0.167]},"n":["0p833_0p833_0p167_0p167"],"t":120,"s":[0],"e":[100]},{"i":{"x":[0.833],"y":[0.833]},"o":{"x":[0.167],"y":[0.167]},"n":["0p833_0p833_0p167_0p167"],"t":160,"s":[100],"e":[0]},{"t":179}],"ix":11},"r":{"a":0,"k":0,"ix":10},"p":{"a":0,"k":[23.5,170.5,0],"ix":2},"a":{"a":0,"k":[8.5,8.5,0],"ix":1},"s":{"a":0,"k":[100,100,100],"ix":6}},"ao":0,"shapes":[{"ty":"gr","it":[{"ind":0,"ty":"sh","ix":1,"ks":{"a":0,"k":{"i":[[4.07,-1.24],[-1.24,-4.07],[-4.07,1.24],[1.24,4.07]],"o":[[4.07,1.24],[1.24,-4.07],[-4.07,-1.24],[-1.24,4.07]],"v":[[0,8.5],[8.5,17],[17,8.5],[8.5,0]],"c":true},"ix":2},"nm":"Path 1","mn":"ADBE Vector Shape - Group","hd":false},{"ty":"fl","c":{"a":0,"k":[1,0.788235008717,0.188234999776,1],"ix":4},"o":{"a":0,"k":100,"ix":5},"r":1,"nm":"Fill 1","mn":"ADBE Vector Graphic - Fill","hd":false},{"ty":"tr","p":{"a":0,"k":[0,0],"ix":2},"a":{"a":0,"k":[0,0],"ix":1},"s":{"a":0,"k":[100,100],"ix":3},"r":{"a":0,"k":0,"ix":6},"o":{"a":0,"k":100,"ix":7},"sk":{"a":0,"k":0,"ix":4},"sa":{"a":0,"k":0,"ix":5},"nm":"Transform"}],"nm":"Group 1","np":2,"cix":2,"ix":1,"mn":"ADBE Vector Group","hd":false}],"ip":0,"op":180,"st":0,"bm":0},{"ddd":0,"ind":4,"ty":4,"nm":"star","sr":1,"ks":{"o":{"a":1,"k":[{"i":{"x":[0.833],"y":[0.833]},"o":{"x":[0.167],"y":[0.167]},"n":["0p833_0p833_0p167_0p167"],"t":0,"s":[0],"e":[100]},{"i":{"x":[0.833],"y":[0.833]},"o":{"x":[0.167],"y":[0.167]},"n":["0p833_0p833_0p167_0p167"],"t":15,"s":[100],"e":[0]},{"i":{"x":[0.833],"y":[0.833]},"o":{"x":[0.167],"y":[0.167]},"n":["0p833_0p833_0p167_0p167"],"t":55,"s":[0],"e":[100]},{"i":{"x":[0.833],"y":[0.833]},"o":{"x":[0.167],"y":[0.167]},"n":["0p833_0p833_0p167_0p167"],"t":95,"s":[100],"e":[0]},{"i":{"x":[0.833],"y":[0.833]},"o":{"x":[0.167],"y":[0.167]},"n":["0p833_0p833_0p167_0p167"],"t":135,"s":[0],"e":[100]},{"i":{"x":[0.833],"y":[0.833]},"o":{"x":[0.167],"y":[0.167]},"n":["0p833_0p833_0p167_0p167"],"t":169,"s":[100],"e":[0]},{"t":179}],"ix":11},"r":{"a":0,"k":0,"ix":10},"p":{"a":0,"k":[148,107,0],"ix":2},"a":{"a":0,"k":[9,9,0],"ix":1},"s":{"a":0,"k":[100,100,100],"ix":6}},"ao":0,"shapes":[{"ty":"gr","it":[{"ind":0,"ty":"sh","ix":1,"ks":{"a":0,"k":{"i":[[4.31,-1.32],[-1.31,-4.31],[-4.31,1.32],[1.32,4.31]],"o":[[4.31,1.31],[1.32,-4.31],[-4.31,-1.32],[-1.31,4.31]],"v":[[0,9],[9,18],[18,9],[9,0]],"c":true},"ix":2},"nm":"Path 1","mn":"ADBE Vector Shape - Group","hd":false},{"ty":"fl","c":{"a":0,"k":[1,0.788235008717,0.188234999776,1],"ix":4},"o":{"a":0,"k":100,"ix":5},"r":1,"nm":"Fill 1","mn":"ADBE Vector Graphic - Fill","hd":false},{"ty":"tr","p":{"a":0,"k":[0,0],"ix":2},"a":{"a":0,"k":[0,0],"ix":1},"s":{"a":0,"k":[100,100],"ix":3},"r":{"a":0,"k":0,"ix":6},"o":{"a":0,"k":100,"ix":7},"sk":{"a":0,"k":0,"ix":4},"sa":{"a":0,"k":0,"ix":5},"nm":"Transform"}],"nm":"Group 1","np":2,"cix":2,"ix":1,"mn":"ADBE Vector Group","hd":false}],"ip":0,"op":180,"st":0,"bm":0},{"ddd":0,"ind":5,"ty":4,"nm":"moon","sr":1,"ks":{"o":{"a":0,"k":100,"ix":11},"r":{"a":0,"k":0,"ix":10},"p":{"a":0,"k":[128.4,128.2,0],"ix":2},"a":{"a":0,"k":[90.4,89.2,0],"ix":1},"s":{"a":0,"k":[100,100,100],"ix":6}},"ao":0,"shapes":[{"ty":"gr","it":[{"ind":0,"ty":"sh","ix":1,"ks":{"a":0,"k":{"i":[[-40.4,9.1],[0,-11.8],[-42.4,0],[-11.9,7.5],[46.8,0],[0,49.9]],"o":[[-4.8,10],[0,42.3],[15.1,0],[-4.8,45.3],[-50,0],[0,-43]],"v":[[70.5,0],[62.9,33.1],[139.7,109.6],[180.8,97.7],[90.6,178.4],[0,88.1]],"c":true},"ix":2},"nm":"Path 1","mn":"ADBE Vector Shape - Group","hd":false},{"ty":"fl","c":{"a":0,"k":[1,0.788235008717,0.188234999776,1],"ix":4},"o":{"a":0,"k":100,"ix":5},"r":1,"nm":"Fill 1","mn":"ADBE Vector Graphic - Fill","hd":false},{"ty":"tr","p":{"a":0,"k":[0,0],"ix":2},"a":{"a":0,"k":[0,0],"ix":1},"s":{"a":0,"k":[100,100],"ix":3},"r":{"a":0,"k":0,"ix":6},"o":{"a":0,"k":100,"ix":7},"sk":{"a":0,"k":0,"ix":4},"sa":{"a":0,"k":0,"ix":5},"nm":"Transform"}],"nm":"Group 1","np":2,"cix":2,"ix":1,"mn":"ADBE Vector Group","hd":false}],"ip":0,"op":180,"st":0,"bm":0},{"ddd":0,"ind":6,"ty":4,"nm":"bond","sr":1,"ks":{"o":{"a":0,"k":100,"ix":11},"r":{"a":0,"k":0,"ix":10},"p":{"a":0,"k":[128,128,0],"ix":2},"a":{"a":0,"k":[0,0,0],"ix":1},"s":{"a":0,"k":[100,100,100],"ix":6}},"ao":0,"shapes":[{"ty":"gr","it":[{"ty":"rc","d":1,"s":{"a":0,"k":[256,256],"ix":2},"p":{"a":0,"k":[0,0],"ix":3},"r":{"a":0,"k":0,"ix":4},"nm":"Rectangle Path 1","mn":"ADBE Vector Shape - Rect","hd":false},{"ty":"tr","p":{"a":0,"k":[0,0],"ix":2},"a":{"a":0,"k":[0,0],"ix":1},"s":{"a":0,"k":[100,100],"ix":3},"r":{"a":0,"k":0,"ix":6},"o":{"a":0,"k":100,"ix":7},"sk":{"a":0,"k":0,"ix":4},"sa":{"a":0,"k":0,"ix":5},"nm":"Transform"}],"nm":"bond","np":1,"cix":2,"ix":1,"mn":"ADBE Vector Group","hd":false}],"ip":0,"op":180,"st":0,"bm":0}],"markers":[]}
\ No newline at end of file
diff --git a/sample-compose/src/main/res/raw/sun_moon.json b/sample-compose/src/main/res/raw/sun_moon.json
new file mode 100644
index 0000000..135330d
--- /dev/null
+++ b/sample-compose/src/main/res/raw/sun_moon.json
@@ -0,0 +1 @@
+{"v":"4.10.2","fr":25,"ip":0,"op":82,"w":528,"h":500,"nm":"Sun","ddd":0,"assets":[],"layers":[{"ddd":0,"ind":1,"ty":4,"nm":"Circle Inside","sr":1,"ks":{"o":{"a":0,"k":100,"ix":11},"r":{"a":0,"k":26.025,"ix":10},"p":{"a":0,"k":[251.392,207.776,0],"ix":2},"a":{"a":0,"k":[-12.608,-90.224,0],"ix":1},"s":{"a":1,"k":[{"i":{"x":[0.833,0.833,0.833],"y":[0.833,0.833,0.833]},"o":{"x":[0.167,0.167,0.167],"y":[0.167,0.167,0.167]},"n":["0p833_0p833_0p167_0p167","0p833_0p833_0p167_0p167","0p833_0p833_0p167_0p167"],"t":0,"s":[0,0,100],"e":[100,100,100]},{"i":{"x":[0.833,0.833,0.833],"y":[0.833,0.833,0.833]},"o":{"x":[0.167,0.167,0.167],"y":[0.167,0.167,0.167]},"n":["0p833_0p833_0p167_0p167","0p833_0p833_0p167_0p167","0p833_0p833_0p167_0p167"],"t":4,"s":[100,100,100],"e":[141,141,100]},{"i":{"x":[0.833,0.833,0.833],"y":[0.833,0.833,0.833]},"o":{"x":[0.167,0.167,0.167],"y":[0.167,0.167,0.167]},"n":["0p833_0p833_0p167_0p167","0p833_0p833_0p167_0p167","0p833_0p833_0p167_0p167"],"t":6.5,"s":[141,141,100],"e":[100,100,100]},{"t":12}],"ix":6}},"ao":0,"shapes":[{"ty":"gr","it":[{"d":1,"ty":"el","s":{"a":0,"k":[125.105,125.105],"ix":2},"p":{"a":0,"k":[0,0],"ix":3},"nm":"Ellipse Path 1","mn":"ADBE Vector Shape - Ellipse","hd":false},{"ty":"st","c":{"a":0,"k":[1,0.945098039216,0.513725490196,1],"ix":3},"o":{"a":1,"k":[{"i":{"x":[0.833],"y":[0.833]},"o":{"x":[0.167],"y":[0.167]},"n":["0p833_0p833_0p167_0p167"],"t":63,"s":[0],"e":[98]},{"i":{"x":[0.833],"y":[0.833]},"o":{"x":[0.167],"y":[0.167]},"n":["0p833_0p833_0p167_0p167"],"t":73,"s":[98],"e":[100]},{"t":82}],"ix":4},"w":{"a":0,"k":0,"ix":5},"lc":1,"lj":1,"ml":4,"nm":"Stroke 1","mn":"ADBE Vector Graphic - Stroke","hd":false},{"ty":"fl","c":{"a":0,"k":[1,0.947015321021,0.472457107843,1],"ix":4},"o":{"a":0,"k":100,"ix":5},"r":1,"nm":"Fill 1","mn":"ADBE Vector Graphic - Fill","hd":false},{"ty":"tr","p":{"a":0,"k":[-11.842,-90.135],"ix":2},"a":{"a":0,"k":[0,0],"ix":1},"s":{"a":0,"k":[100,100],"ix":3},"r":{"a":0,"k":0,"ix":6},"o":{"a":0,"k":100,"ix":7},"sk":{"a":0,"k":0,"ix":4},"sa":{"a":0,"k":0,"ix":5},"nm":"Transform"}],"nm":"Ellipse 1","np":3,"cix":2,"ix":1,"mn":"ADBE Vector Group","hd":false}],"ip":0,"op":4337,"st":0,"bm":0},{"ddd":0,"ind":2,"ty":4,"nm":"Sun Ring Orange","sr":1,"ks":{"o":{"a":0,"k":100,"ix":11},"r":{"a":1,"k":[{"i":{"x":[0.833],"y":[0.833]},"o":{"x":[0.167],"y":[0.167]},"n":["0p833_0p833_0p167_0p167"],"t":14,"s":[0],"e":[212]},{"t":101}],"ix":10},"p":{"a":0,"k":[251.392,207.776,0],"ix":2},"a":{"a":0,"k":[-64.608,-92.224,0],"ix":1},"s":{"a":1,"k":[{"i":{"x":[0.833,0.833,0.833],"y":[0.833,0.833,0.833]},"o":{"x":[0.167,0.167,0.167],"y":[0.167,0.167,0.167]},"n":["0p833_0p833_0p167_0p167","0p833_0p833_0p167_0p167","0p833_0p833_0p167_0p167"],"t":1,"s":[0,0,100],"e":[100,100,100]},{"i":{"x":[0.833,0.833,0.833],"y":[0.833,0.833,0.833]},"o":{"x":[0.167,0.167,0.167],"y":[0.167,0.167,0.167]},"n":["0p833_0p833_0p167_0p167","0p833_0p833_0p167_0p167","0p833_0p833_0p167_0p167"],"t":3,"s":[100,100,100],"e":[141,141,100]},{"i":{"x":[0.833,0.833,0.833],"y":[0.833,0.833,0.833]},"o":{"x":[0.167,0.167,0.167],"y":[0.167,0.167,0.167]},"n":["0p833_0p833_0p167_0p167","0p833_0p833_0p167_0p167","0p833_0p833_0p167_0p167"],"t":8.5,"s":[141,141,100],"e":[100,100,100]},{"i":{"x":[0.833,0.833,0.833],"y":[0.833,0.833,0.833]},"o":{"x":[0.167,0.167,0.167],"y":[0.167,0.167,0.167]},"n":["0p833_0p833_0p167_0p167","0p833_0p833_0p167_0p167","0p833_0p833_0p167_0p167"],"t":14,"s":[100,100,100],"e":[100,100,100]},{"i":{"x":[0.833,0.833,0.833],"y":[0.833,0.833,0.833]},"o":{"x":[0.167,0.167,0.167],"y":[0.167,0.167,0.167]},"n":["0p833_0p833_0p167_0p167","0p833_0p833_0p167_0p167","0p833_0p833_0p167_0p167"],"t":66,"s":[100,100,100],"e":[0,0,100]},{"t":80}],"ix":6}},"ao":0,"shapes":[{"ty":"gr","it":[{"ty":"sr","sy":1,"d":1,"pt":{"a":0,"k":10,"ix":3},"p":{"a":0,"k":[0,0],"ix":4},"r":{"a":0,"k":0,"ix":5},"ir":{"a":0,"k":90.71,"ix":6},"is":{"a":0,"k":-16,"ix":8},"or":{"a":0,"k":109.42,"ix":7},"os":{"a":0,"k":0,"ix":9},"ix":1,"nm":"Polystar Path 1","mn":"ADBE Vector Shape - Star","hd":false},{"ty":"fl","c":{"a":0,"k":[1,0.700795611213,0.081449149637,1],"ix":4},"o":{"a":0,"k":100,"ix":5},"r":1,"nm":"Fill 1","mn":"ADBE Vector Graphic - Fill","hd":false},{"ty":"tr","p":{"a":0,"k":[-65.312,-95.172],"ix":2},"a":{"a":0,"k":[-0.93,-3.891],"ix":1},"s":{"a":0,"k":[75.78,75.78],"ix":3},"r":{"a":0,"k":0,"ix":6},"o":{"a":0,"k":100,"ix":7},"sk":{"a":0,"k":0,"ix":4},"sa":{"a":0,"k":0,"ix":5},"nm":"Transform"}],"nm":"Polystar 1","np":3,"cix":2,"ix":1,"mn":"ADBE Vector Group","hd":false}],"ip":0,"op":4337,"st":0,"bm":0},{"ddd":0,"ind":3,"ty":4,"nm":"Sun Ring 2","sr":1,"ks":{"o":{"a":0,"k":100,"ix":11},"r":{"a":1,"k":[{"i":{"x":[0.833],"y":[0.833]},"o":{"x":[0.167],"y":[0.167]},"n":["0p833_0p833_0p167_0p167"],"t":16,"s":[17],"e":[260]},{"t":101}],"ix":10},"p":{"a":0,"k":[251.392,207.776,0],"ix":2},"a":{"a":0,"k":[-64.608,-92.224,0],"ix":1},"s":{"a":1,"k":[{"i":{"x":[0.833,0.833,0.833],"y":[0.833,0.833,0.833]},"o":{"x":[0.167,0.167,0.167],"y":[0.167,0.167,0.167]},"n":["0p833_0p833_0p167_0p167","0p833_0p833_0p167_0p167","0p833_0p833_0p167_0p167"],"t":0,"s":[0,0,100],"e":[100,100,100]},{"i":{"x":[0.833,0.833,0.833],"y":[0.833,0.833,0.833]},"o":{"x":[0.167,0.167,0.167],"y":[0.167,0.167,0.167]},"n":["0p833_0p833_0p167_0p167","0p833_0p833_0p167_0p167","0p833_0p833_0p167_0p167"],"t":4,"s":[100,100,100],"e":[141,141,100]},{"i":{"x":[0.833,0.833,0.833],"y":[0.833,0.833,0.833]},"o":{"x":[0.167,0.167,0.167],"y":[0.167,0.167,0.167]},"n":["0p833_0p833_0p167_0p167","0p833_0p833_0p167_0p167","0p833_0p833_0p167_0p167"],"t":6.5,"s":[141,141,100],"e":[100,100,100]},{"i":{"x":[0.833,0.833,0.833],"y":[0.833,0.833,0.833]},"o":{"x":[0.167,0.167,0.167],"y":[0.167,0.167,0.167]},"n":["0p833_0p833_0p167_0p167","0p833_0p833_0p167_0p167","0p833_0p833_0p167_0p167"],"t":12,"s":[100,100,100],"e":[100,100,100]},{"i":{"x":[0.833,0.833,0.833],"y":[0.833,0.833,0.833]},"o":{"x":[0.167,0.167,0.167],"y":[0.167,0.167,0.167]},"n":["0p833_0p833_0p167_0p167","0p833_0p833_0p167_0p167","0p833_0p833_0p167_0p167"],"t":66,"s":[100,100,100],"e":[0,0,100]},{"t":80}],"ix":6}},"ao":0,"shapes":[{"ty":"gr","it":[{"ty":"sr","sy":1,"d":1,"pt":{"a":0,"k":10,"ix":3},"p":{"a":0,"k":[0,0],"ix":4},"r":{"a":0,"k":0,"ix":5},"ir":{"a":0,"k":90.71,"ix":6},"is":{"a":0,"k":-16,"ix":8},"or":{"a":0,"k":109.42,"ix":7},"os":{"a":0,"k":0,"ix":9},"ix":1,"nm":"Polystar Path 1","mn":"ADBE Vector Shape - Star","hd":false},{"ty":"fl","c":{"a":0,"k":[1,0.907947076536,0.10337006812,1],"ix":4},"o":{"a":0,"k":100,"ix":5},"r":1,"nm":"Fill 1","mn":"ADBE Vector Graphic - Fill","hd":false},{"ty":"tr","p":{"a":0,"k":[-65.312,-95.172],"ix":2},"a":{"a":0,"k":[-0.93,-3.891],"ix":1},"s":{"a":0,"k":[75.78,75.78],"ix":3},"r":{"a":0,"k":0,"ix":6},"o":{"a":0,"k":100,"ix":7},"sk":{"a":0,"k":0,"ix":4},"sa":{"a":0,"k":0,"ix":5},"nm":"Transform"}],"nm":"Polystar 1","np":3,"cix":2,"ix":1,"mn":"ADBE Vector Group","hd":false}],"ip":0,"op":4337,"st":0,"bm":0}]}
\ No newline at end of file