Skip to content

Commit 37720c9

Browse files
committed
feat: add rive view parameters and initialization by rive file
1 parent c9cf2f4 commit 37720c9

File tree

21 files changed

+838
-163
lines changed

21 files changed

+838
-163
lines changed
Lines changed: 24 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,24 @@
1+
package com.margelo.nitro.rive
2+
3+
import androidx.annotation.Keep
4+
import app.rive.runtime.kotlin.core.File
5+
import com.facebook.proguard.annotations.DoNotStrip
6+
7+
@Keep
8+
@DoNotStrip
9+
class HybridRiveFile : HybridRiveFileSpec() {
10+
var riveFile: File? = null
11+
12+
override val name: String
13+
get() = ""
14+
15+
override fun release() {
16+
riveFile?.release()
17+
riveFile = null
18+
}
19+
20+
// Not sure how well this works, or if it's guaranteed to be called! But adding it.
21+
protected fun finalize() {
22+
release()
23+
}
24+
}
Lines changed: 77 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,77 @@
1+
package com.margelo.nitro.rive
2+
3+
import android.annotation.SuppressLint
4+
import androidx.annotation.Keep
5+
import app.rive.runtime.kotlin.core.File
6+
import com.facebook.proguard.annotations.DoNotStrip
7+
import com.margelo.nitro.core.ArrayBuffer
8+
import com.margelo.nitro.core.Promise
9+
import com.margelo.nitro.NitroModules
10+
import kotlinx.coroutines.Dispatchers
11+
import kotlinx.coroutines.withContext
12+
import java.net.URL
13+
14+
@Keep
15+
@DoNotStrip
16+
class HybridRiveFileFactory : HybridRiveFileFactorySpec() {
17+
override fun fromURL(url: String, loadCdn: Boolean): Promise<HybridRiveFileSpec> {
18+
return Promise.async {
19+
try {
20+
val riveFile = withContext(Dispatchers.IO) {
21+
val urlObj = URL(url)
22+
val riveData = urlObj.readBytes()
23+
// TODO: The File object in Android does not have the concept of loading CDN assets
24+
File(riveData)
25+
}
26+
27+
val hybridRiveFile = HybridRiveFile()
28+
hybridRiveFile.riveFile = riveFile
29+
hybridRiveFile
30+
} catch (e: Exception) {
31+
throw RuntimeException("Failed to download Rive file: ${e.message}")
32+
}
33+
}
34+
}
35+
36+
@SuppressLint("DiscouragedApi")
37+
override fun fromResource(resource: String, loadCdn: Boolean): Promise<HybridRiveFileSpec> {
38+
return Promise.async {
39+
try {
40+
val context = NitroModules.applicationContext
41+
?: throw Error("Could not load Rive file ($resource) from resource. No application context.")
42+
val riveFile = withContext(Dispatchers.IO) {
43+
val resourceId = context.resources.getIdentifier(resource, "raw", context.packageName)
44+
if (resourceId == 0) {
45+
throw Error("Could not find Rive file: $resource.riv")
46+
}
47+
val inputStream = context.resources.openRawResource(resourceId)
48+
val riveData = inputStream.readBytes()
49+
File(riveData)
50+
}
51+
52+
val hybridRiveFile = HybridRiveFile()
53+
hybridRiveFile.riveFile = riveFile
54+
hybridRiveFile
55+
} catch (e: Exception) {
56+
throw Error("Failed to load Rive file: ${e.message}")
57+
}
58+
}
59+
}
60+
61+
override fun fromBytes(bytes: ArrayBuffer, loadCdn: Boolean): Promise<HybridRiveFileSpec> {
62+
val buffer = bytes.getBuffer(false) // Use false to avoid creating a read-only buffer
63+
return Promise.async {
64+
try {
65+
val byteArray = ByteArray(buffer.remaining())
66+
buffer.get(byteArray)
67+
val riveFile = File(byteArray)
68+
val hybridRiveFile = HybridRiveFile()
69+
hybridRiveFile.riveFile = riveFile
70+
hybridRiveFile
71+
} catch (e: Exception) {
72+
throw RuntimeException("Failed to load Rive file from bytes: ${e.message}")
73+
}
74+
}
75+
}
76+
}
77+
Lines changed: 65 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -1,43 +1,94 @@
11
package com.margelo.nitro.rive
22

33
import android.util.Log
4+
import androidx.annotation.Keep
5+
import com.facebook.proguard.annotations.DoNotStrip
46
import com.facebook.react.uimanager.ThemedReactContext
57
import com.rive.RiveReactNativeView
8+
import com.rive.ViewConfiguration
9+
import app.rive.runtime.kotlin.core.Fit as RiveFit
610

11+
object DefaultConfiguration {
12+
const val AUTOBIND = false
13+
const val AUTOPLAY = true
14+
val fit = RiveFit.CONTAIN
15+
}
16+
17+
@Keep
18+
@DoNotStrip
719
class HybridRiveView(val context: ThemedReactContext) : HybridRiveViewSpec() {
820
override val view: RiveReactNativeView = RiveReactNativeView(context)
921

1022
//region View Props
11-
override var autoPlay: Boolean = false
12-
override var autoBind: Boolean = false
23+
private fun <T> changed(current: T, new: T, setter: (T) -> Unit) {
24+
if (current != new) {
25+
setter(new)
26+
needsReload = true
27+
}
28+
}
29+
30+
override var artboardName: String? = null
31+
set(value) { changed(field, value) { field = it } }
32+
override var stateMachineName: String? = null
33+
set(value) { changed(field, value) { field = it } }
34+
override var autoPlay: Boolean? = null
35+
set(value) { changed(field, value) { field = it } }
36+
override var autoBind: Boolean? = null
37+
set(value) { changed(field, value) { field = it } }
38+
override var file: HybridRiveFileSpec = HybridRiveFile()
39+
set(value) { changed(field, value) { field = it } }
40+
override var fit: Fit? = null
1341
//endregion
1442

1543
//region View Methods
1644
override fun play() = executeOnUiThread { view.play() }
1745
override fun pause() = executeOnUiThread { view.pause() }
1846
//endregion
1947

20-
//region Internal
21-
override fun beforeUpdate() {
22-
super.beforeUpdate()
23-
Log.d("rive", "Before Update")
24-
}
25-
48+
//region Update
2649
override fun afterUpdate() {
2750
super.afterUpdate()
28-
Log.d("rive", "After Update")
51+
val riveFile = (file as? HybridRiveFile)?.riveFile ?: return
52+
53+
val config = ViewConfiguration(
54+
artboardName = artboardName,
55+
stateMachineName = stateMachineName,
56+
autoPlay = autoPlay ?: DefaultConfiguration.AUTOPLAY,
57+
autoBind = autoBind ?: DefaultConfiguration.AUTOBIND,
58+
fit = convertFit(fit) ?: DefaultConfiguration.fit,
59+
riveFile = riveFile,
60+
)
61+
view.configure(config, needsReload)
62+
needsReload = false
2963
}
64+
//endregion
65+
66+
//region Internal State
67+
private var needsReload = false
68+
//endregion
3069

70+
//region Helpers
3171
private fun executeOnUiThread(action: () -> Unit) {
3272
try {
33-
context.runOnUiQueueThread {
34-
Log.d("rive", "Running on thread: ${Thread.currentThread().name}")
35-
action()
36-
}
73+
context.runOnUiQueueThread { action() }
3774
} catch (e: Exception) {
38-
Log.d("rive", e.message.toString())
3975
throw Error(e.message) // TODO: Correctly handling errors (https://nitro.margelo.com/docs/errors)
4076
}
4177
}
78+
79+
private fun convertFit(fit: Fit?): RiveFit? {
80+
if (fit == null) return null
81+
82+
return when (fit) {
83+
Fit.FILL -> RiveFit.FILL
84+
Fit.CONTAIN -> RiveFit.CONTAIN
85+
Fit.COVER -> RiveFit.COVER
86+
Fit.FITWIDTH -> RiveFit.FIT_WIDTH
87+
Fit.FITHEIGHT -> RiveFit.FIT_HEIGHT
88+
Fit.NONE -> RiveFit.NONE
89+
Fit.SCALEDOWN -> RiveFit.SCALE_DOWN
90+
Fit.LAYOUT -> RiveFit.LAYOUT
91+
}
92+
}
4293
//endregion
4394
}

android/src/main/java/com/margelo/nitro/rive/Rive.kt

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,9 @@
11
package com.margelo.nitro.rive
2-
2+
3+
import androidx.annotation.Keep
34
import com.facebook.proguard.annotations.DoNotStrip
45

6+
@Keep
57
@DoNotStrip
68
class Rive : HybridRiveSpec() {
79
override fun multiply(a: Double, b: Double): Double {
Lines changed: 25 additions & 30 deletions
Original file line numberDiff line numberDiff line change
@@ -1,17 +1,20 @@
11
package com.rive
22

33
import android.annotation.SuppressLint
4-
import android.util.Log
54
import android.widget.FrameLayout
65
import com.facebook.react.uimanager.ThemedReactContext
76
import app.rive.runtime.kotlin.RiveAnimationView
87
import app.rive.runtime.kotlin.core.File
98
import app.rive.runtime.kotlin.core.Fit
10-
import kotlinx.coroutines.CoroutineScope
11-
import kotlinx.coroutines.Dispatchers
12-
import kotlinx.coroutines.launch
13-
import kotlinx.coroutines.withContext
14-
import java.net.URL
9+
10+
data class ViewConfiguration(
11+
val artboardName: String?,
12+
val stateMachineName: String?,
13+
val autoBind: Boolean,
14+
val autoPlay: Boolean,
15+
val fit: Fit,
16+
val riveFile: File
17+
)
1518

1619
@SuppressLint("ViewConstructor")
1720
class RiveReactNativeView(private val context: ThemedReactContext) : FrameLayout(context) {
@@ -20,8 +23,6 @@ class RiveReactNativeView(private val context: ThemedReactContext) : FrameLayout
2023
init {
2124
riveAnimationView = RiveAnimationView(context)
2225
addView(riveAnimationView)
23-
24-
demoUrlResource()
2526
}
2627

2728
//region Public Methods (API)
@@ -30,33 +31,27 @@ class RiveReactNativeView(private val context: ThemedReactContext) : FrameLayout
3031
}
3132

3233
fun pause() {
33-
Log.d("findme", "yay calling pause")
3434
riveAnimationView?.pause();
3535
}
36-
//endregion
3736

38-
//region Internal
39-
private fun demoUrlResource() {
40-
val riveUrl = "https://cdn.rive.app/animations/vehicles.riv"
41-
CoroutineScope(Dispatchers.Main).launch {
42-
val riveBytes = withContext(Dispatchers.IO) {
43-
downloadRiveFile(riveUrl)
44-
}
45-
riveBytes?.let {
46-
val riveFile = File(it)
47-
riveAnimationView?.setRiveFile(riveFile, fit = Fit.CONTAIN, autoplay = true)
48-
riveFile.release()
49-
}
37+
fun configure(config: ViewConfiguration, reload: Boolean = false) {
38+
if (reload) {
39+
riveAnimationView?.setRiveFile(
40+
config.riveFile,
41+
artboardName = config.artboardName,
42+
stateMachineName = config.stateMachineName,
43+
autoplay = config.autoPlay,
44+
autoBind = config.autoBind,
45+
fit = config.fit
46+
)
47+
} else {
48+
riveAnimationView?.fit = config.fit
5049
}
51-
}
5250

53-
private fun downloadRiveFile(url: String): ByteArray? {
54-
return try {
55-
URL(url).openStream().use { it.readBytes() }
56-
} catch (e: Exception) {
57-
e.printStackTrace()
58-
null
59-
}
6051
}
6152
//endregion
53+
54+
//region Internal
55+
56+
//endregion
6257
}
212 KB
Binary file not shown.

example/ios/Podfile.lock

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -2008,7 +2008,7 @@ SPEC CHECKSUMS:
20082008
ReactAppDependencyProvider: d5dcc564f129632276bd3184e60f053fcd574d6b
20092009
ReactCodegen: fda99a79c866370190e162083a35602fdc314e5d
20102010
ReactCommon: 4d0da92a5eb8da86c08e3ec34bd23ab439fb2461
2011-
Rive: d78367a433fc043b9b60f5fc657b884ebf0b02a5
2011+
Rive: 7b49f5fe78bd9bd9dc5b8cf786841a0db1f4d55d
20122012
RiveRuntime: 1e72d73bea430242387177b9d0aa95126406b462
20132013
SocketRocket: d4aabe649be1e368d1318fdf28a022d714d65748
20142014
Yoga: c758bfb934100bb4bf9cbaccb52557cee35e8bdf

example/ios/RiveExample.xcodeproj/project.pbxproj

Lines changed: 12 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -3,7 +3,7 @@
33
archiveVersion = 1;
44
classes = {
55
};
6-
objectVersion = 54;
6+
objectVersion = 70;
77
objects = {
88

99
/* Begin PBXBuildFile section */
@@ -12,6 +12,7 @@
1212
35B325D75078933C519F2ACC /* PrivacyInfo.xcprivacy in Resources */ = {isa = PBXBuildFile; fileRef = 13B07FB81A68108700A75B9A /* PrivacyInfo.xcprivacy */; };
1313
761780ED2CA45674006654EE /* AppDelegate.swift in Sources */ = {isa = PBXBuildFile; fileRef = 761780EC2CA45674006654EE /* AppDelegate.swift */; };
1414
81AB9BB82411601600AC10FF /* LaunchScreen.storyboard in Resources */ = {isa = PBXBuildFile; fileRef = 81AB9BB72411601600AC10FF /* LaunchScreen.storyboard */; };
15+
F8EBA76E2DD7CE540010BBD0 /* rewards.riv in Resources */ = {isa = PBXBuildFile; fileRef = F8EBA76D2DD7CE540010BBD0 /* rewards.riv */; };
1516
/* End PBXBuildFile section */
1617

1718
/* Begin PBXFileReference section */
@@ -25,8 +26,13 @@
2526
761780EC2CA45674006654EE /* AppDelegate.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; name = AppDelegate.swift; path = RiveExample/AppDelegate.swift; sourceTree = "<group>"; };
2627
81AB9BB72411601600AC10FF /* LaunchScreen.storyboard */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = file.storyboard; name = LaunchScreen.storyboard; path = RiveExample/LaunchScreen.storyboard; sourceTree = "<group>"; };
2728
ED297162215061F000B7C4FE /* JavaScriptCore.framework */ = {isa = PBXFileReference; lastKnownFileType = wrapper.framework; name = JavaScriptCore.framework; path = System/Library/Frameworks/JavaScriptCore.framework; sourceTree = SDKROOT; };
29+
F8EBA76D2DD7CE540010BBD0 /* rewards.riv */ = {isa = PBXFileReference; lastKnownFileType = file; path = rewards.riv; sourceTree = "<group>"; };
2830
/* End PBXFileReference section */
2931

32+
/* Begin PBXFileSystemSynchronizedRootGroup section */
33+
F8EBA76C2DD7CE4A0010BBD0 /* Assets */ = {isa = PBXFileSystemSynchronizedRootGroup; explicitFileTypes = {}; explicitFolders = (); path = Assets; sourceTree = "<group>"; };
34+
/* End PBXFileSystemSynchronizedRootGroup section */
35+
3036
/* Begin PBXFrameworksBuildPhase section */
3137
13B07F8C1A680F5B00A75B9A /* Frameworks */ = {
3238
isa = PBXFrameworksBuildPhase;
@@ -47,6 +53,7 @@
4753
13B07FB61A68108700A75B9A /* Info.plist */,
4854
81AB9BB72411601600AC10FF /* LaunchScreen.storyboard */,
4955
13B07FB81A68108700A75B9A /* PrivacyInfo.xcprivacy */,
56+
F8EBA76D2DD7CE540010BBD0 /* rewards.riv */,
5057
);
5158
name = RiveExample;
5259
sourceTree = "<group>";
@@ -70,6 +77,7 @@
7077
83CBB9F61A601CBA00E9B192 = {
7178
isa = PBXGroup;
7279
children = (
80+
F8EBA76C2DD7CE4A0010BBD0 /* Assets */,
7381
13B07FAE1A68108700A75B9A /* RiveExample */,
7482
832341AE1AAA6A7D00B99B32 /* Libraries */,
7583
83CBBA001A601CBA00E9B192 /* Products */,
@@ -160,6 +168,7 @@
160168
files = (
161169
81AB9BB82411601600AC10FF /* LaunchScreen.storyboard in Resources */,
162170
13B07FBF1A68108700A75B9A /* Images.xcassets in Resources */,
171+
F8EBA76E2DD7CE540010BBD0 /* rewards.riv in Resources */,
163172
35B325D75078933C519F2ACC /* PrivacyInfo.xcprivacy in Resources */,
164173
);
165174
runOnlyForDeploymentPostprocessing = 0;
@@ -376,10 +385,7 @@
376385
"-DFOLLY_CFG_NO_COROUTINES=1",
377386
"-DFOLLY_HAVE_CLOCK_GETTIME=1",
378387
);
379-
OTHER_LDFLAGS = (
380-
"$(inherited)",
381-
" ",
382-
);
388+
OTHER_LDFLAGS = "$(inherited) ";
383389
REACT_NATIVE_PATH = "${PODS_ROOT}/../../node_modules/react-native";
384390
SDKROOT = iphoneos;
385391
SWIFT_ACTIVE_COMPILATION_CONDITIONS = "$(inherited) DEBUG";
@@ -448,10 +454,7 @@
448454
"-DFOLLY_CFG_NO_COROUTINES=1",
449455
"-DFOLLY_HAVE_CLOCK_GETTIME=1",
450456
);
451-
OTHER_LDFLAGS = (
452-
"$(inherited)",
453-
" ",
454-
);
457+
OTHER_LDFLAGS = "$(inherited) ";
455458
REACT_NATIVE_PATH = "${PODS_ROOT}/../../node_modules/react-native";
456459
SDKROOT = iphoneos;
457460
USE_HERMES = true;

example/ios/rewards.riv

212 KB
Binary file not shown.

0 commit comments

Comments
 (0)