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 9444a5b5ea..ba38a5b760 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 @@ -1,5 +1,7 @@ package com.airbnb.lottie.compose +import com.airbnb.lottie.LottieComposition + /** * Specification for a [com.airbnb.lottie.LottieComposition]. Each subclass represents a different source. * A [com.airbnb.lottie.LottieComposition] is the stateless parsed version of a Lottie json file and is @@ -41,4 +43,9 @@ sealed interface LottieCompositionSpec { * Load an animation from its json string. */ inline class JsonString(val jsonString: String) : LottieCompositionSpec + + /** + * Load an animation from a custom factory. This will be called on an IO thread pool. + */ + inline class Custom(val factory: () -> LottieComposition) : LottieCompositionSpec } 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 42d3e3c198..f4f4187eff 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 @@ -13,18 +13,15 @@ import androidx.compose.ui.platform.LocalContext import com.airbnb.lottie.LottieComposition import com.airbnb.lottie.LottieCompositionFactory import com.airbnb.lottie.LottieImageAsset -import com.airbnb.lottie.LottieTask +import com.airbnb.lottie.LottieResult import com.airbnb.lottie.model.Font import com.airbnb.lottie.utils.Logger import com.airbnb.lottie.utils.Utils import kotlinx.coroutines.Dispatchers -import kotlinx.coroutines.suspendCancellableCoroutine import kotlinx.coroutines.withContext import java.io.FileInputStream import java.io.IOException import java.util.zip.ZipInputStream -import kotlin.coroutines.resume -import kotlin.coroutines.resumeWithException /** * Use this with [rememberLottieComposition#cacheKey]'s cacheKey parameter to generate a default @@ -81,6 +78,7 @@ fun rememberLottieComposition( ): LottieCompositionResult { val context = LocalContext.current val result by remember(spec) { mutableStateOf(LottieCompositionResultImpl()) } + LaunchedEffect(spec) { var exception: Throwable? = null var failedCount = 0 @@ -115,58 +113,63 @@ private suspend fun lottieComposition( fontFileExtension: String, cacheKey: String?, ): LottieComposition { - val task = when (spec) { + val result = parseCompositionSync(context, spec, cacheKey) + result.exception?.let { throw it } + + val composition = result.value!! + loadImagesFromAssets(context, composition, imageAssetsFolder) + loadFontsFromAssets(context, composition, fontAssetsFolder, fontFileExtension) + return composition +} + +private fun parseCompositionSync( + context: Context, + spec: LottieCompositionSpec, + cacheKey: String?, +): LottieResult { + return when (spec) { is LottieCompositionSpec.RawRes -> { if (cacheKey == DefaultCacheKey) { - LottieCompositionFactory.fromRawRes(context, spec.resId) + LottieCompositionFactory.fromRawResSync(context, spec.resId) } else { - LottieCompositionFactory.fromRawRes(context, spec.resId, cacheKey) + LottieCompositionFactory.fromRawResSync(context, spec.resId, cacheKey) } } is LottieCompositionSpec.Url -> { if (cacheKey == DefaultCacheKey) { - LottieCompositionFactory.fromUrl(context, spec.url) + LottieCompositionFactory.fromUrlSync(context, spec.url) } else { - LottieCompositionFactory.fromUrl(context, spec.url, cacheKey) + LottieCompositionFactory.fromUrlSync(context, spec.url, cacheKey) } } is LottieCompositionSpec.File -> { - val fis = withContext(Dispatchers.IO) { - @Suppress("BlockingMethodInNonBlockingContext") - FileInputStream(spec.fileName) - } + val fis = FileInputStream(spec.fileName) when { - spec.fileName.endsWith("zip") -> LottieCompositionFactory.fromZipStream( + spec.fileName.endsWith("zip") -> LottieCompositionFactory.fromZipStreamSync( ZipInputStream(fis), spec.fileName.takeIf { cacheKey != null }, ) - else -> LottieCompositionFactory.fromJsonInputStream(fis, spec.fileName.takeIf { cacheKey != null }) + else -> LottieCompositionFactory.fromJsonInputStreamSync(fis, spec.fileName.takeIf { cacheKey != null }) } } is LottieCompositionSpec.Asset -> { if (cacheKey == DefaultCacheKey) { - LottieCompositionFactory.fromAsset(context, spec.assetName) + LottieCompositionFactory.fromAssetSync(context, spec.assetName) } else { - LottieCompositionFactory.fromAsset(context, spec.assetName, null) + LottieCompositionFactory.fromAssetSync(context, spec.assetName, null) } } is LottieCompositionSpec.JsonString -> { val jsonStringCacheKey = if (cacheKey == DefaultCacheKey) spec.jsonString.hashCode().toString() else cacheKey - LottieCompositionFactory.fromJsonString(spec.jsonString, jsonStringCacheKey) + LottieCompositionFactory.fromJsonStringSync(spec.jsonString, jsonStringCacheKey) + } + is LottieCompositionSpec.Custom -> { + try { + LottieResult(spec.factory()) + } catch (e: Throwable) { + LottieResult(e) + } } - } - - val composition = task.await() - loadImagesFromAssets(context, composition, imageAssetsFolder) - loadFontsFromAssets(context, composition, fontAssetsFolder, fontFileExtension) - return composition -} - -private suspend fun LottieTask.await(): T = suspendCancellableCoroutine { cont -> - addListener { c -> - if (!cont.isCompleted) cont.resume(c) - }.addFailureListener { e -> - if (!cont.isCompleted) cont.resumeWithException(e) } }