Skip to content

Commit

Permalink
Core network module (#22)
Browse files Browse the repository at this point in the history
* wip

* wip

* add url param name

* refactor

* fix duplicate jvm class name, hide io dispatcher

* cleanup

* cleanup

* ktor3 by default

---------

Co-authored-by: Zhirkevich Alexander Y <[email protected]>
  • Loading branch information
alexzhirkevich and Zhirkevich Alexander Y authored Sep 17, 2024
1 parent d239c08 commit 68e357e
Show file tree
Hide file tree
Showing 38 changed files with 516 additions and 715 deletions.
34 changes: 8 additions & 26 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -18,12 +18,13 @@ Compose Multiplatform Adobe After Effects Bodymovin (Lottie) animations renderin
> <br>Please [report](https://github.com/alexzhirkevich/compottie/issues) if you find any, preferably with a reproducible animation.
> <br>List of supported AE Lottie features can be found [here](/supported_features.md)
| Module | Description |
| :----: | ------------- |
| `compottie` | Main module with rendering engine and `JsonString` animation spec. Currently has two branches - 1.x (with platform renderers - Skottie and lottie-android) and 2.x (with own renderer). 1.x is maintained until the new renderer becomes stable |
| `compottie⁠-⁠dot` | Contains [dotLottie](https://dotlottie.io/) and ZIP animation spec. For Compottie 2.x only |
| `compottie⁠-⁠network` | Contains `Url` animation spec and asset/font managers (with [Ktor](https://ktor.io/) and local cache with [Okio](https://square.github.io/okio/)). Allows loading animations and assets from web. For Compottie 2.x only |
| `compottie⁠-⁠resources` | Contains asset and font managers powered by official Compose resources. For Compottie 2.x only |
| Module | Description |
|:--------------------------:|-------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------|
| `compottie` | Main module with rendering engine and `JsonString` animation spec. Currently has two branches - 1.x (with platform renderers - Skottie and lottie-android) and 2.x (with own renderer). 1.x is maintained until the new renderer becomes stable |
| `compottie⁠-⁠dot` | Contains [dotLottie](https://dotlottie.io/) and ZIP animation spec. For Compottie 2.x only |
| `compottie⁠-⁠network` | Contains `Url` animation spec and asset/font managers (with [Ktor3](https://ktor.io/) and local cache with [Okio](https://square.github.io/okio/)). Allows loading animations and assets from web. For Compottie 2.x only |
| `compottie⁠-⁠network-core` | Contains base HttpClient-free implementations for `network` module. Allows to specify custom HTTP client (Ktor3 or any other). |
| `compottie⁠-⁠resources` | Contains asset and font managers powered by official Compose resources. For Compottie 2.x only |

[![Maven Central](https://maven-badges.herokuapp.com/maven-central/io.github.alexzhirkevich/compottie/badge.svg)](https://maven-badges.herokuapp.com/maven-central/io.github.alexzhirkevich/compottie)

Expand Down Expand Up @@ -234,7 +235,7 @@ Images should be avoided whenever possible. They are much larger, less performan
val painter = rememberLottiePainter(
composition = composition,
assetsManager = rememberResourcesAssetsManager(
directory = "files" // by default,
directory = "files", // by default
readBytes = Res::readBytes
)
)
Expand Down Expand Up @@ -285,22 +286,6 @@ The network module also brings the `NetworkAssetsManager` that have similar para
If you are using Url composition spec then specifying `NetworkAssetsManager` is redundant.
Url composition spec automatically prepares url assets

There is no stable Ktor client for wasm so to use network module on this target you need to add
the following to the bottom of your build script:

```kotlin
configurations
.filter { it.name.contains("wasmJs") }
.onEach {
it.resolutionStrategy.eachDependency {
if (requested.group.startsWith("io.ktor") &&
requested.name.startsWith("ktor-client-")
) {
useVersion("3.0.0-wasm2")
}
}
}
```
## Dynamic Properties

Lottie allows you to update animation properties at runtime. Some reasons you may want to do this are:
Expand Down Expand Up @@ -346,9 +331,6 @@ val painter = rememberLottiePainter(

// for each layer named 'Shape Layer 4' on any level deep
shapeLayer("**", "Shape Layer 4") {
transform {
rotation { current -> current * progress }
}
// for each fill named 'Fill 4' on the 2nd level deep
fill("*", "Fill 4") {
color { Color.Red }
Expand Down
1 change: 0 additions & 1 deletion build.gradle.kts
Original file line number Diff line number Diff line change
Expand Up @@ -10,7 +10,6 @@ plugins {
alias(libs.plugins.android.library) apply false
alias(libs.plugins.compose).apply(false)
alias(libs.plugins.composeCompiler).apply(false)
alias(libs.plugins.dokka).apply(false)
alias(libs.plugins.serialization).apply(false)
}

Expand Down
2 changes: 1 addition & 1 deletion compottie-dot/build.gradle.kts
Original file line number Diff line number Diff line change
Expand Up @@ -8,12 +8,12 @@ kotlin {

sourceSets {
commonMain.dependencies {
api(project(":compottie"))
implementation(compose.ui)
implementation(libs.serialization)
implementation(libs.okio)
implementation(libs.okio.fakefilesystem)
implementation(libs.coroutines.core)
api(project(":compottie"))
}
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -50,7 +50,7 @@ private class DotLottieCompositionSpec(

@OptIn(InternalCompottieApi::class)
override suspend fun load(): LottieComposition {
return withContext(ioDispatcher()) {
return withContext(Compottie.ioDispatcher()) {
val fileSystem = FakeFileSystem()
val path = "lottie".toPath()

Expand Down
16 changes: 16 additions & 0 deletions compottie-network-core/build.gradle.kts
Original file line number Diff line number Diff line change
@@ -0,0 +1,16 @@
plugins {
id("kotlinx-atomicfu")
}

kotlin {
sourceSets {
commonMain.dependencies {
api(project(":compottie"))
implementation(project(":compottie-dot"))
implementation(compose.ui)
implementation(libs.serialization)
api(libs.okio)
implementation(libs.coroutines.core)
}
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -3,8 +3,6 @@ package io.github.alexzhirkevich.compottie
import androidx.compose.ui.text.font.Font
import io.github.alexzhirkevich.compottie.assets.LottieFontSpec
import okio.Path
import java.io.FileOutputStream
import java.io.FilterOutputStream

private const val CACHE_FONT_DIR = "compottie_font_cache"

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -119,7 +119,7 @@ public fun DiskCache(
directory: Path = FileSystem.SYSTEM_TEMPORARY_DIRECTORY.resolve("compottie_disc_cache".toPath()),
fileSystem : FileSystem = defaultFileSystem(),
maxSizeBytes : Long = MB_250,
cleanupDispatcher : CoroutineDispatcher = ioDispatcher()
cleanupDispatcher : CoroutineDispatcher = Compottie.ioDispatcher()
) : DiskCache = RealDiskCache(
maxSize = maxSizeBytes,
directory = directory,
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -60,8 +60,10 @@ public class DiskCacheStrategy(

private fun key(url: String) = url.encodeUtf8().sha256().hex()

internal companion object {
val Instance by lazy {
public companion object {

@InternalCompottieApi
public val Instance: DiskCacheStrategy by lazy {
DiskCacheStrategy()
}
}
Expand Down
Original file line number Diff line number Diff line change
@@ -1,10 +1,5 @@
package io.github.alexzhirkevich.compottie

import io.ktor.client.HttpClient
import io.ktor.client.statement.bodyAsChannel
import io.ktor.http.URLParserException
import io.ktor.http.Url
import io.ktor.util.toByteArray
import kotlinx.coroutines.withContext
import okio.Path

Expand All @@ -13,12 +8,11 @@ private val NetworkLock = MapMutex()

@OptIn(InternalCompottieApi::class)
internal suspend fun networkLoad(
client: HttpClient,
request : suspend (url: String) -> ByteArray,
cacheStrategy: LottieCacheStrategy,
request : NetworkRequest,
url: String
): Pair<Path?, ByteArray?> {
return withContext(ioDispatcher()) {
return withContext(Compottie.ioDispatcher()) {
NetworkLock.withLock(url) {
try {
try {
Expand All @@ -28,13 +22,7 @@ internal suspend fun networkLoad(
} catch (_: Throwable) {
}

val ktorUrl = try {
Url(url)
} catch (t: URLParserException) {
return@withLock null to null
}

val bytes = request(client, ktorUrl).execute().bodyAsChannel().toByteArray()
val bytes = request(url)

try {
cacheStrategy.save(url, bytes)?.let {
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,60 @@
@file:JvmName("CoreNetworkAssetsManager")


package io.github.alexzhirkevich.compottie

import androidx.compose.runtime.Stable
import io.github.alexzhirkevich.compottie.assets.ImageRepresentable
import io.github.alexzhirkevich.compottie.assets.LottieAssetsManager
import io.github.alexzhirkevich.compottie.assets.LottieImageSpec
import kotlin.jvm.JvmName

/**
* Asset manager that load images from web using [request] .
*
* @param request network request used for loading assets
* @param cacheStrategy caching strategy. Caching to system temp dir by default
* */
@OptIn(InternalCompottieApi::class)
@Stable
public fun NetworkAssetsManager(
request : suspend (url: String) -> ByteArray,
cacheStrategy: LottieCacheStrategy = DiskCacheStrategy.Instance,
) : LottieAssetsManager = NetworkAssetsManagerImpl(
request = request,
cacheStrategy = cacheStrategy,
)


@Stable
private class NetworkAssetsManagerImpl(
private val request : suspend (url: String) -> ByteArray,
private val cacheStrategy: LottieCacheStrategy,
) : LottieAssetsManager {

override suspend fun image(image: LottieImageSpec): ImageRepresentable? {
return networkLoad(
request = request,
cacheStrategy = cacheStrategy,
url = image.path + image.name
).second?.let(ImageRepresentable::Bytes)
}

override fun equals(other: Any?): Boolean {
if (this === other) return true
if (other == null || this::class != other::class) return false

other as NetworkAssetsManagerImpl

if (request != other.request) return false
if (cacheStrategy != other.cacheStrategy) return false

return true
}

override fun hashCode(): Int {
var result = request.hashCode()
result = 31 * result + cacheStrategy.hashCode()
return result
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,79 @@
@file:JvmName("CoreNetworkFontManager")


package io.github.alexzhirkevich.compottie

import androidx.compose.runtime.Stable
import androidx.compose.ui.text.font.Font
import io.github.alexzhirkevich.compottie.assets.LottieFontManager
import io.github.alexzhirkevich.compottie.assets.LottieFontSpec
import okio.Path
import kotlin.jvm.JvmName

/**
* Font manager that loads fonts from the web using [request].
*
* Guaranteed to work only with [LottieFontSpec.FontOrigin.FontUrl] .ttf fonts
* (support may be higher on non-Android platforms).
*
* Note: [LottieCacheStrategy.path] should return valid file system paths to make [NetworkFontManager] work.
* Default [DiskCacheStrategy] supports it.
*
* @param request network request used for loading fonts
* @param cacheStrategy caching strategy. Caching to system temp dir by default
* */
@OptIn(InternalCompottieApi::class)
@Stable
public fun NetworkFontManager(
request : suspend (url: String) -> ByteArray,
cacheStrategy: LottieCacheStrategy = DiskCacheStrategy.Instance,
) : LottieFontManager = NetworkFontManagerImpl(
request = request,
cacheStrategy = cacheStrategy,
)

@Stable
private class NetworkFontManagerImpl(
private val request : suspend (url: String) -> ByteArray,
private val cacheStrategy: LottieCacheStrategy,
) : LottieFontManager {

override suspend fun font(font: LottieFontSpec): Font? {

if (font.origin != LottieFontSpec.FontOrigin.FontUrl){
return null
}

val (path, bytes) = networkLoad(
request = request,
cacheStrategy = cacheStrategy,
url = font.path ?: return null
)

if (path == null || bytes == null){
return null
}

return makeFont(font, path, bytes)
}

override fun equals(other: Any?): Boolean {
if (this === other) return true
if (other == null || this::class != other::class) return false

other as NetworkFontManagerImpl

if (request != other.request) return false
if (cacheStrategy != other.cacheStrategy) return false

return true
}

override fun hashCode(): Int {
var result = request.hashCode()
result = 31 * result + cacheStrategy.hashCode()
return result
}
}

internal expect suspend fun makeFont(spec: LottieFontSpec, path: Path, bytes: ByteArray) : Font
Loading

0 comments on commit 68e357e

Please sign in to comment.