blob: 090b4ee00339852cc809e39ac9471637b041d25f [file] [log] [blame]
package com.airbnb.lottie.compose
import android.content.Context
import androidx.compose.runtime.*
import androidx.compose.ui.platform.LocalContext
import com.airbnb.lottie.LottieComposition
import com.airbnb.lottie.LottieCompositionFactory
import kotlinx.coroutines.channels.BufferOverflow
import kotlinx.coroutines.channels.Channel
import kotlinx.coroutines.channels.sendBlocking
import kotlinx.coroutines.suspendCancellableCoroutine
import java.io.FileInputStream
import java.util.zip.ZipInputStream
import kotlin.coroutines.resume
import kotlin.coroutines.resumeWithException
/**
* Takes a [LottieCompositionSpec], attempts to load and parse the animation, and returns a [LottieCompositionResult].
*
* [LottieCompositionResult] allows you to explicitly check for loading, failures, call
* [LottieCompositionResult.await], or invoke it like a function to get the nullable composition.
*
* [LottieCompositionResult] implements State<LottieComposition?> so if you don't need the full result class,
* you can use this function like:
* ```
* val compositionResult: LottieCompositionResult = lottieComposition(spec)
* // or...
* val composition: State<LottieComposition?> by lottieComposition(spec)
* ```
*
* @param spec The [LottieCompositionSpec] that defines which LottieComposition should be loaded.
* @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
* so you can do things like add a backoff delay or await an internet connection before
* retrying again. [rememberLottieRetrySignal] can be used to handle explicit retires.
*/
@Composable
fun lottieComposition(
spec: LottieCompositionSpec,
onRetry: suspend (failedCount: Int, previousException: Throwable) -> Boolean = { _, _ -> false },
): LottieCompositionResult {
val context = LocalContext.current
val result: LottieCompositionResult by remember(spec) { mutableStateOf(LottieCompositionResult()) }
LaunchedEffect(spec) {
var exception: Throwable? = null
var failedCount = 0
while (!result.isSuccess && (failedCount == 0 || onRetry(failedCount, exception!!))) {
try {
result.complete(lottieComposition(context, spec))
} catch (e: Throwable) {
exception = e
failedCount++
}
}
if (!result.isComplete && exception != null) {
result.completeExceptionally(exception)
}
}
return result
}
private suspend fun lottieComposition(
context: Context,
spec: LottieCompositionSpec
): LottieComposition = suspendCancellableCoroutine { cont ->
val task = when (spec) {
is LottieCompositionSpec.RawRes -> LottieCompositionFactory.fromRawRes(context, spec.resId)
is LottieCompositionSpec.Url -> LottieCompositionFactory.fromUrl(context, spec.url, null)
is LottieCompositionSpec.File -> {
val fis = FileInputStream(spec.fileName)
when {
spec.fileName.endsWith("zip") -> LottieCompositionFactory.fromZipStream(ZipInputStream(fis), spec.fileName)
else -> LottieCompositionFactory.fromJsonInputStream(fis, spec.fileName)
}
}
is LottieCompositionSpec.Asset -> LottieCompositionFactory.fromAsset(context, spec.assetName)
is LottieCompositionSpec.JsonString -> LottieCompositionFactory.fromJsonString(spec.jsonString, spec.cacheKey)
}
task.addListener { c ->
if (!cont.isCompleted) cont.resume(c)
}.addFailureListener { e ->
if (!cont.isCompleted) cont.resumeWithException(e)
}
}