Skip to content
Merged
Show file tree
Hide file tree
Changes from 23 commits
Commits
Show all changes
24 commits
Select commit Hold shift + click to select a range
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions Rive.podspec
Original file line number Diff line number Diff line change
Expand Up @@ -43,6 +43,7 @@ Pod::Spec.new do |s|

s.source_files = "ios/**/*.{h,m,mm,swift}"

s.public_header_files = ['ios/RCTSwiftLog.h']
load 'nitrogen/generated/ios/Rive+autolinking.rb'
add_nitrogen_files(s)

Expand Down
54 changes: 54 additions & 0 deletions android/src/main/java/com/margelo/nitro/rive/HybridRiveFile.kt
Original file line number Diff line number Diff line change
Expand Up @@ -3,11 +3,23 @@ package com.margelo.nitro.rive
import androidx.annotation.Keep
import app.rive.runtime.kotlin.core.File
import com.facebook.proguard.annotations.DoNotStrip
import com.margelo.nitro.NitroModules
import java.lang.ref.WeakReference
import kotlinx.coroutines.CoroutineScope
import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.SupervisorJob
import kotlinx.coroutines.awaitAll
import kotlinx.coroutines.cancel
import kotlinx.coroutines.launch

@Keep
@DoNotStrip
class HybridRiveFile : HybridRiveFileSpec() {
var riveFile: File? = null
var referencedAssetCache: ReferencedAssetCache? = null
var assetLoader: ReferencedAssetLoader? = null
private val weakViews = mutableListOf<WeakReference<HybridRiveView>>()
private val scope = CoroutineScope(Dispatchers.Main + SupervisorJob())

override val viewModelCount: Double?
get() = riveFile?.viewModelCount?.toDouble()
Expand Down Expand Up @@ -37,8 +49,50 @@ class HybridRiveFile : HybridRiveFileSpec() {
}
}

fun registerView(view: HybridRiveView) {
weakViews.add(WeakReference(view))
}

fun unregisterView(view: HybridRiveView) {
weakViews.removeAll { it.get() == view }
}

private fun refreshAfterAssetChange() {
weakViews.removeAll { it.get() == null }

for (weakView in weakViews) {
weakView.get()?.refreshAfterAssetChange()
}
}

override fun updateReferencedAssets(referencedAssets: ReferencedAssetsType) {
val assetsData = referencedAssets.data ?: return
val cache = referencedAssetCache ?: return
val loader = assetLoader ?: return
val context = NitroModules.applicationContext ?: return

val loadJobs = mutableListOf<kotlinx.coroutines.Deferred<Unit>>()

for ((key, assetData) in assetsData) {
val asset = cache[key] ?: continue
loadJobs.add(loader.updateAsset(assetData, asset, context))
}

if (loadJobs.isNotEmpty()) {
scope.launch {
loadJobs.awaitAll()
refreshAfterAssetChange()
}
}
}

override fun release() {
scope.cancel()
assetLoader?.dispose()
assetLoader = null
riveFile?.release()
riveFile = null
referencedAssetCache?.clear()
referencedAssetCache = null
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@ package com.margelo.nitro.rive
import android.annotation.SuppressLint
import androidx.annotation.Keep
import app.rive.runtime.kotlin.core.File
import app.rive.runtime.kotlin.core.Rive
import com.facebook.proguard.annotations.DoNotStrip
import com.margelo.nitro.core.ArrayBuffer
import com.margelo.nitro.core.Promise
Expand All @@ -13,29 +14,54 @@ import java.io.File as JavaFile
import java.net.URI
import java.net.URL

data class FileAndCache(
val file: File,
val cache: ReferencedAssetCache,
val loader: ReferencedAssetLoader?
)

@Keep
@DoNotStrip
class HybridRiveFileFactory : HybridRiveFileFactorySpec() {
override fun fromURL(url: String, loadCdn: Boolean): Promise<HybridRiveFileSpec> {
private fun buildRiveFile(
data: ByteArray,
referencedAssets: ReferencedAssetsType?
): FileAndCache {
val cache = mutableMapOf<String, app.rive.runtime.kotlin.core.FileAsset>()
val loader = ReferencedAssetLoader()
val customLoader = loader.createCustomLoader(referencedAssets, cache)

// TODO: The File object in Android does not have the concept of loading CDN assets
val riveFile = if (customLoader != null) {
File(data, Rive.defaultRendererType, customLoader)
} else {
File(data)
}

return FileAndCache(riveFile, cache, if (customLoader != null) loader else null)
}

override fun fromURL(url: String, loadCdn: Boolean, referencedAssets: ReferencedAssetsType?): Promise<HybridRiveFileSpec> {
return Promise.async {
try {
val riveFile = withContext(Dispatchers.IO) {
val fileAndCache = withContext(Dispatchers.IO) {
val urlObj = URL(url)
val riveData = urlObj.readBytes()
// TODO: The File object in Android does not have the concept of loading CDN assets
File(riveData)
buildRiveFile(riveData, referencedAssets)
}

val hybridRiveFile = HybridRiveFile()
hybridRiveFile.riveFile = riveFile
hybridRiveFile.riveFile = fileAndCache.file
hybridRiveFile.referencedAssetCache = fileAndCache.cache
hybridRiveFile.assetLoader = fileAndCache.loader
hybridRiveFile
} catch (e: Exception) {
throw Error("Failed to download Rive file: ${e.message}")
}
}
}

override fun fromFileURL(fileURL: String, loadCdn: Boolean): Promise<HybridRiveFileSpec> {
override fun fromFileURL(fileURL: String, loadCdn: Boolean, referencedAssets: ReferencedAssetsType?): Promise<HybridRiveFileSpec> {
if (!fileURL.startsWith("file://")) {
throw Error("fromFileURL: URL must be a file URL: $fileURL")
}
Expand All @@ -45,14 +71,16 @@ class HybridRiveFileFactory : HybridRiveFileFactorySpec() {
val uri = URI(fileURL)
val path = uri.path ?: throw Error("fromFileURL: Invalid URL: $fileURL")

val riveFile = withContext(Dispatchers.IO) {
val fileAndCache = withContext(Dispatchers.IO) {
val file = JavaFile(path)
val riveData = file.readBytes()
File(riveData)
buildRiveFile(riveData, referencedAssets)
}

val hybridRiveFile = HybridRiveFile()
hybridRiveFile.riveFile = riveFile
hybridRiveFile.riveFile = fileAndCache.file
hybridRiveFile.referencedAssetCache = fileAndCache.cache
hybridRiveFile.assetLoader = fileAndCache.loader
hybridRiveFile
} catch (e: Exception) {
throw Error("Failed to load Rive file: ${e.message}")
Expand All @@ -61,39 +89,43 @@ class HybridRiveFileFactory : HybridRiveFileFactorySpec() {
}

@SuppressLint("DiscouragedApi")
override fun fromResource(resource: String, loadCdn: Boolean): Promise<HybridRiveFileSpec> {
override fun fromResource(resource: String, loadCdn: Boolean, referencedAssets: ReferencedAssetsType?): Promise<HybridRiveFileSpec> {
return Promise.async {
try {
val context = NitroModules.applicationContext
?: throw Error("Could not load Rive file ($resource) from resource. No application context.")
val riveFile = withContext(Dispatchers.IO) {
val fileAndCache = withContext(Dispatchers.IO) {
val resourceId = context.resources.getIdentifier(resource, "raw", context.packageName)
if (resourceId == 0) {
throw Error("Could not find Rive file: $resource.riv")
}
val inputStream = context.resources.openRawResource(resourceId)
val riveData = inputStream.readBytes()
File(riveData)
buildRiveFile(riveData, referencedAssets)
}

val hybridRiveFile = HybridRiveFile()
hybridRiveFile.riveFile = riveFile
hybridRiveFile.riveFile = fileAndCache.file
hybridRiveFile.referencedAssetCache = fileAndCache.cache
hybridRiveFile.assetLoader = fileAndCache.loader
hybridRiveFile
} catch (e: Exception) {
throw Error("Failed to load Rive file: ${e.message}")
}
}
}

override fun fromBytes(bytes: ArrayBuffer, loadCdn: Boolean): Promise<HybridRiveFileSpec> {
val buffer = bytes.getBuffer(false) // Use false to avoid creating a read-only buffer
override fun fromBytes(bytes: ArrayBuffer, loadCdn: Boolean, referencedAssets: ReferencedAssetsType?): Promise<HybridRiveFileSpec> {
val buffer = bytes.getBuffer(false)
return Promise.async {
try {
val byteArray = ByteArray(buffer.remaining())
buffer.get(byteArray)
val riveFile = File(byteArray)
val fileAndCache = buildRiveFile(byteArray, referencedAssets)
val hybridRiveFile = HybridRiveFile()
hybridRiveFile.riveFile = riveFile
hybridRiveFile.riveFile = fileAndCache.file
hybridRiveFile.referencedAssetCache = fileAndCache.cache
hybridRiveFile.assetLoader = fileAndCache.loader
hybridRiveFile
} catch (e: Exception) {
throw Error("Failed to load Rive file from bytes: ${e.message}")
Expand Down
18 changes: 17 additions & 1 deletion android/src/main/java/com/margelo/nitro/rive/HybridRiveView.kt
Original file line number Diff line number Diff line change
Expand Up @@ -25,6 +25,7 @@ class HybridRiveView(val context: ThemedReactContext) : HybridRiveViewSpec() {
//region State
override val view: RiveReactNativeView = RiveReactNativeView(context)
private var needsReload = false
private var registeredFile: HybridRiveFile? = null
//endregion

//region View Props
Expand All @@ -46,6 +47,10 @@ class HybridRiveView(val context: ThemedReactContext) : HybridRiveViewSpec() {
}
override var file: HybridRiveFileSpec = HybridRiveFile()
set(value) {
if (field != value) {
registeredFile?.unregisterView(this)
registeredFile = null
}
changed(field, value) { field = it }
}
override var alignment: Alignment? = null
Expand Down Expand Up @@ -99,8 +104,13 @@ class HybridRiveView(val context: ThemedReactContext) : HybridRiveViewSpec() {
//endregion

//region Update
fun refreshAfterAssetChange() {
afterUpdate()
}

override fun afterUpdate() {
val riveFile = (file as? HybridRiveFile)?.riveFile ?: return
val hybridFile = file as? HybridRiveFile
val riveFile = hybridFile?.riveFile ?: return

val config = ViewConfiguration(
artboardName = artboardName,
Expand All @@ -113,6 +123,12 @@ class HybridRiveView(val context: ThemedReactContext) : HybridRiveViewSpec() {
layoutScaleFactor = layoutScaleFactor?.toFloat() ?: DefaultConfiguration.LAYOUTSCALEFACTOR,
)
view.configure(config, needsReload)

if (needsReload && hybridFile != null) {
hybridFile.registerView(this)
registeredFile = hybridFile
}

needsReload = false
super.afterUpdate()
}
Expand Down
Loading
Loading