diff --git a/android/src/main/java/com/margelo/nitro/rive/HybridRiveView.kt b/android/src/main/java/com/margelo/nitro/rive/HybridRiveView.kt index 5bca170b..7678469e 100644 --- a/android/src/main/java/com/margelo/nitro/rive/HybridRiveView.kt +++ b/android/src/main/java/com/margelo/nitro/rive/HybridRiveView.kt @@ -10,6 +10,7 @@ import com.rive.RiveReactNativeView import com.rive.ViewConfiguration import app.rive.runtime.kotlin.core.Fit as RiveFit import app.rive.runtime.kotlin.core.Alignment as RiveAlignment +import app.rive.runtime.kotlin.core.errors.* import kotlinx.coroutines.Dispatchers import kotlinx.coroutines.withContext @@ -89,6 +90,7 @@ class HybridRiveView(val context: ThemedReactContext) : HybridRiveViewSpec() { dataBindingChanged = true } } + override var onError: (error: RiveError) -> Unit = {} //endregion //region View Methods @@ -227,12 +229,33 @@ class HybridRiveView(val context: ThemedReactContext) : HybridRiveViewSpec() { } } + private fun detectErrorType(exception: Exception): Pair { + val message = exception.message ?: exception.toString() + val type = when (exception) { + is ArtboardException -> RiveErrorType.INCORRECTARTBOARDNAME + is StateMachineException -> RiveErrorType.INCORRECTSTATEMACHINENAME + is AnimationException -> RiveErrorType.UNKNOWN + is MalformedFileException -> RiveErrorType.MALFORMEDFILE + is StateMachineInputException -> RiveErrorType.INCORRECTSTATEMACHINEINPUTNAME + is TextValueRunException -> RiveErrorType.UNKNOWN + is ViewModelException -> RiveErrorType.VIEWMODELINSTANCENOTFOUND + else -> RiveErrorType.UNKNOWN + } + return Pair(type, message) + } + fun logged(tag: String, note: String? = null, fn: () -> Unit) { try { fn() } catch (e: Exception) { - // TODO add onError callback - Log.e("[RIVE]", "$tag ${note ?: ""} $e") + val (errorType, errorDescription) = detectErrorType(e) + val noteString = note?.let { " $it" } ?: "" + val errorMessage = "[RIVE] $tag$noteString $errorDescription" + val riveError = RiveError( + type = errorType, + message = errorMessage + ) + onError(riveError) } } //endregion diff --git a/ios/HybridRiveView.swift b/ios/HybridRiveView.swift index 3cd95eee..55d1ece3 100644 --- a/ios/HybridRiveView.swift +++ b/ios/HybridRiveView.swift @@ -56,6 +56,7 @@ class HybridRiveView: HybridRiveViewSpec { var alignment: Alignment? var fit: Fit? var layoutScaleFactor: Double? + var onError: (RiveError) -> Void = { _ in } func awaitViewReady() throws -> Promise { return Promise.async { [self] in @@ -141,7 +142,7 @@ class HybridRiveView: HybridRiveViewSpec { ) let riveView = try getRiveView() - riveView.configure( + try riveView.configure( config, dataBindingChanged: dataBindingChanged, reload: needsReload, initialUpdate: initialUpdate) needsReload = false @@ -187,3 +188,51 @@ class HybridRiveView: HybridRiveViewSpec { } } } + +extension HybridRiveView { + func logged(tag: String, note: String? = nil, _ fn: () throws -> Void) { + do { + return try fn() + } catch (let e) { + let (errorType, errorDescription) = detectErrorType(e) + let noteString = note.map { " \($0)" } ?? "" + let errorMessage = "[RIVE] \(tag)\(noteString) \(errorDescription)" + + let riveError = RiveError( + message: errorMessage, + type: errorType + ) + onError(riveError) + } + } + + private func detectErrorType(_ error: Error) -> (RiveErrorType, String) { + switch error { + case NitroRiveError.instanceNotFound(let message): + return (.viewmodelinstancenotfound, message) + case NitroRiveError.fileNotFound(let message): + return (.filenotfound, message) + default: + break + } + + let nsError = error as NSError + let message = nsError.localizedDescription + + // RiveErrorCode from RiveRuntime + switch nsError.code { + case RiveErrorCode.noArtboardFound.rawValue: + return (.incorrectartboardname, message) + case RiveErrorCode.noStateMachineFound.rawValue: + return (.incorrectstatemachinename, message) + case RiveErrorCode.noAnimationFound.rawValue: + return (.unknown, message) + case RiveErrorCode.malformedFile.rawValue: + return (.malformedfile, message) + case RiveErrorCode.noStateMachineInputFound.rawValue: + return (.incorrectstatemachineinputname, message) + default: + return (.unknown, message) + } + } +} diff --git a/ios/ReferencedAssetLoader.swift b/ios/ReferencedAssetLoader.swift index 0939a524..53828562 100644 --- a/ios/ReferencedAssetLoader.swift +++ b/ios/ReferencedAssetLoader.swift @@ -23,18 +23,14 @@ func createIncorrectRiveURL(_ url: String) -> NSError { ]) } -func createAssetFileError(_ assetName: String) -> NSError { - return NSError( - domain: RiveErrorDomain, code: 801, - userInfo: [ - NSLocalizedDescriptionKey: "Could not load Rive asset: \(assetName)", "name": "FileNotFound", - ]) +func createAssetFileError(_ assetName: String) -> NitroRiveError { + return NitroRiveError.fileNotFound(message: "Could not load Rive asset: \(assetName)") } final class ReferencedAssetLoader { - private func handleRiveError(error: NSError) { + private func handleRiveError(error: Error) { // TODO allow user to specify onError callback - RCTLogError(error.localizedDescription) + RCTLogError("\(error)") } private func handleInvalidUrlError(url: String) { diff --git a/ios/RiveReactNativeView.swift b/ios/RiveReactNativeView.swift index 3c99e8e2..45d49842 100644 --- a/ios/RiveReactNativeView.swift +++ b/ios/RiveReactNativeView.swift @@ -26,6 +26,11 @@ struct ViewConfiguration { let bindData: BindData } +enum NitroRiveError: Error { + case instanceNotFound(message: String) + case fileNotFound(message: String) +} + class RiveReactNativeView: UIView, RiveStateMachineDelegate { // MARK: Internal Properties private var riveView: RiveView? @@ -50,7 +55,7 @@ class RiveReactNativeView: UIView, RiveStateMachineDelegate { return true } - func configure(_ config: ViewConfiguration, dataBindingChanged: Bool = false, reload: Bool = false, initialUpdate: Bool = false) { + func configure(_ config: ViewConfiguration, dataBindingChanged: Bool = false, reload: Bool = false, initialUpdate: Bool = false) throws { if reload { cleanup() let model = RiveModel(riveFile: config.riveFile) @@ -75,7 +80,7 @@ class RiveReactNativeView: UIView, RiveStateMachineDelegate { } if dataBindingChanged || initialUpdate { - applyDataBinding(config.bindData) + try applyDataBinding(config.bindData) } } @@ -87,7 +92,7 @@ class RiveReactNativeView: UIView, RiveStateMachineDelegate { return baseViewModel?.riveModel?.stateMachine?.viewModelInstance } - func applyDataBinding(_ bindData: BindData) { + func applyDataBinding(_ bindData: BindData) throws { let stateMachine = baseViewModel?.riveModel?.stateMachine let artboard = baseViewModel?.riveModel?.artboard @@ -106,7 +111,7 @@ class RiveReactNativeView: UIView, RiveStateMachineDelegate { let viewModel = riveFile.defaultViewModel(for: artboard), let instance = viewModel.createInstance(fromName: name) else { - return + throw NitroRiveError.instanceNotFound(message: "\(name) instance not found") } stateMachine?.bind(viewModelInstance: instance) // this should be added if we support only playing artboards on their own - https://github.com/rive-app/rive-nitro-react-native/pull/23#discussion_r2534698281 diff --git a/ios/Utils.swift b/ios/Utils.swift index f5ef2bbc..ad8ed6a8 100644 --- a/ios/Utils.swift +++ b/ios/Utils.swift @@ -13,12 +13,3 @@ final class SendableRef: @unchecked Sendable { self.value = value } } - -/// Executes a closure, logging any thrown error with an optional note and tag using RCTLogError. -func logged(tag: String, note: String? = nil, _ fn: () throws -> Void) { - do { - return try fn() - } catch (let e) { - RCTLogError("[RIVE] \(tag) \(note ?? "") \(e)") - } -} diff --git a/nitrogen/generated/android/c++/JFunc_void_RiveError.hpp b/nitrogen/generated/android/c++/JFunc_void_RiveError.hpp new file mode 100644 index 00000000..12d35760 --- /dev/null +++ b/nitrogen/generated/android/c++/JFunc_void_RiveError.hpp @@ -0,0 +1,79 @@ +/// +/// JFunc_void_RiveError.hpp +/// This file was generated by nitrogen. DO NOT MODIFY THIS FILE. +/// https://github.com/mrousavy/nitro +/// Copyright © 2025 Marc Rousavy @ Margelo +/// + +#pragma once + +#include +#include + +#include "RiveError.hpp" +#include +#include "JRiveError.hpp" +#include +#include "RiveErrorType.hpp" +#include "JRiveErrorType.hpp" + +namespace margelo::nitro::rive { + + using namespace facebook; + + /** + * Represents the Java/Kotlin callback `(error: RiveError) -> Unit`. + * This can be passed around between C++ and Java/Kotlin. + */ + struct JFunc_void_RiveError: public jni::JavaClass { + public: + static auto constexpr kJavaDescriptor = "Lcom/margelo/nitro/rive/Func_void_RiveError;"; + + public: + /** + * Invokes the function this `JFunc_void_RiveError` instance holds through JNI. + */ + void invoke(const RiveError& error) const { + static const auto method = javaClassStatic()->getMethod /* error */)>("invoke"); + method(self(), JRiveError::fromCpp(error)); + } + }; + + /** + * An implementation of Func_void_RiveError that is backed by a C++ implementation (using `std::function<...>`) + */ + struct JFunc_void_RiveError_cxx final: public jni::HybridClass { + public: + static jni::local_ref fromCpp(const std::function& func) { + return JFunc_void_RiveError_cxx::newObjectCxxArgs(func); + } + + public: + /** + * Invokes the C++ `std::function<...>` this `JFunc_void_RiveError_cxx` instance holds. + */ + void invoke_cxx(jni::alias_ref error) { + _func(error->toCpp()); + } + + public: + [[nodiscard]] + inline const std::function& getFunction() const { + return _func; + } + + public: + static auto constexpr kJavaDescriptor = "Lcom/margelo/nitro/rive/Func_void_RiveError_cxx;"; + static void registerNatives() { + registerHybrid({makeNativeMethod("invoke_cxx", JFunc_void_RiveError_cxx::invoke_cxx)}); + } + + private: + explicit JFunc_void_RiveError_cxx(const std::function& func): _func(func) { } + + private: + friend HybridBase; + std::function _func; + }; + +} // namespace margelo::nitro::rive diff --git a/nitrogen/generated/android/c++/JHybridRiveViewSpec.cpp b/nitrogen/generated/android/c++/JHybridRiveViewSpec.cpp index 6ac225d0..586a02cb 100644 --- a/nitrogen/generated/android/c++/JHybridRiveViewSpec.cpp +++ b/nitrogen/generated/android/c++/JHybridRiveViewSpec.cpp @@ -19,6 +19,10 @@ namespace margelo::nitro::rive { class HybridViewModelInstanceSpec; } namespace margelo::nitro::rive { enum class DataBindMode; } // Forward declaration of `DataBindByName` to properly resolve imports. namespace margelo::nitro::rive { struct DataBindByName; } +// Forward declaration of `RiveError` to properly resolve imports. +namespace margelo::nitro::rive { struct RiveError; } +// Forward declaration of `RiveErrorType` to properly resolve imports. +namespace margelo::nitro::rive { enum class RiveErrorType; } // Forward declaration of `UnifiedRiveEvent` to properly resolve imports. namespace margelo::nitro::rive { struct UnifiedRiveEvent; } // Forward declaration of `RiveEventType` to properly resolve imports. @@ -41,10 +45,15 @@ namespace margelo::nitro::rive { enum class RiveEventType; } #include "JHybridViewModelInstanceSpec.hpp" #include "JDataBindMode.hpp" #include "JDataBindByName.hpp" +#include "RiveError.hpp" +#include +#include "JFunc_void_RiveError.hpp" +#include "JRiveError.hpp" +#include "RiveErrorType.hpp" +#include "JRiveErrorType.hpp" #include #include #include "UnifiedRiveEvent.hpp" -#include #include "JFunc_void_UnifiedRiveEvent.hpp" #include "JUnifiedRiveEvent.hpp" #include "RiveEventType.hpp" @@ -153,6 +162,25 @@ namespace margelo::nitro::rive { static const auto method = javaClassStatic()->getMethod /* dataBind */)>("setDataBind"); method(_javaPart, dataBind.has_value() ? JVariant_HybridViewModelInstanceSpec_DataBindMode_DataBindByName::fromCpp(dataBind.value()) : nullptr); } + std::function JHybridRiveViewSpec::getOnError() { + static const auto method = javaClassStatic()->getMethod()>("getOnError_cxx"); + auto __result = method(_javaPart); + return [&]() -> std::function { + if (__result->isInstanceOf(JFunc_void_RiveError_cxx::javaClassStatic())) [[likely]] { + auto downcast = jni::static_ref_cast(__result); + return downcast->cthis()->getFunction(); + } else { + auto __resultRef = jni::make_global(__result); + return [__resultRef](RiveError error) -> void { + return __resultRef->invoke(error); + }; + } + }(); + } + void JHybridRiveViewSpec::setOnError(const std::function& onError) { + static const auto method = javaClassStatic()->getMethod /* onError */)>("setOnError_cxx"); + method(_javaPart, JFunc_void_RiveError_cxx::fromCpp(onError)); + } // Methods std::shared_ptr> JHybridRiveViewSpec::awaitViewReady() { diff --git a/nitrogen/generated/android/c++/JHybridRiveViewSpec.hpp b/nitrogen/generated/android/c++/JHybridRiveViewSpec.hpp index 7ffd4db1..9ba7c91b 100644 --- a/nitrogen/generated/android/c++/JHybridRiveViewSpec.hpp +++ b/nitrogen/generated/android/c++/JHybridRiveViewSpec.hpp @@ -66,6 +66,8 @@ namespace margelo::nitro::rive { void setLayoutScaleFactor(std::optional layoutScaleFactor) override; std::optional, DataBindMode, DataBindByName>> getDataBind() override; void setDataBind(const std::optional, DataBindMode, DataBindByName>>& dataBind) override; + std::function getOnError() override; + void setOnError(const std::function& onError) override; public: // Methods diff --git a/nitrogen/generated/android/c++/JRiveError.hpp b/nitrogen/generated/android/c++/JRiveError.hpp new file mode 100644 index 00000000..5980d308 --- /dev/null +++ b/nitrogen/generated/android/c++/JRiveError.hpp @@ -0,0 +1,63 @@ +/// +/// JRiveError.hpp +/// This file was generated by nitrogen. DO NOT MODIFY THIS FILE. +/// https://github.com/mrousavy/nitro +/// Copyright © 2025 Marc Rousavy @ Margelo +/// + +#pragma once + +#include +#include "RiveError.hpp" + +#include "JRiveErrorType.hpp" +#include "RiveErrorType.hpp" +#include + +namespace margelo::nitro::rive { + + using namespace facebook; + + /** + * The C++ JNI bridge between the C++ struct "RiveError" and the the Kotlin data class "RiveError". + */ + struct JRiveError final: public jni::JavaClass { + public: + static auto constexpr kJavaDescriptor = "Lcom/margelo/nitro/rive/RiveError;"; + + public: + /** + * Convert this Java/Kotlin-based struct to the C++ struct RiveError by copying all values to C++. + */ + [[maybe_unused]] + [[nodiscard]] + RiveError toCpp() const { + static const auto clazz = javaClassStatic(); + static const auto fieldMessage = clazz->getField("message"); + jni::local_ref message = this->getFieldValue(fieldMessage); + static const auto fieldType = clazz->getField("type"); + jni::local_ref type = this->getFieldValue(fieldType); + return RiveError( + message->toStdString(), + type->toCpp() + ); + } + + public: + /** + * Create a Java/Kotlin-based struct by copying all values from the given C++ struct to Java. + */ + [[maybe_unused]] + static jni::local_ref fromCpp(const RiveError& value) { + using JSignature = JRiveError(jni::alias_ref, jni::alias_ref); + static const auto clazz = javaClassStatic(); + static const auto create = clazz->getStaticMethod("fromCpp"); + return create( + clazz, + jni::make_jstring(value.message), + JRiveErrorType::fromCpp(value.type) + ); + } + }; + +} // namespace margelo::nitro::rive diff --git a/nitrogen/generated/android/c++/JRiveErrorType.hpp b/nitrogen/generated/android/c++/JRiveErrorType.hpp new file mode 100644 index 00000000..0d601834 --- /dev/null +++ b/nitrogen/generated/android/c++/JRiveErrorType.hpp @@ -0,0 +1,74 @@ +/// +/// JRiveErrorType.hpp +/// This file was generated by nitrogen. DO NOT MODIFY THIS FILE. +/// https://github.com/mrousavy/nitro +/// Copyright © 2025 Marc Rousavy @ Margelo +/// + +#pragma once + +#include +#include "RiveErrorType.hpp" + +namespace margelo::nitro::rive { + + using namespace facebook; + + /** + * The C++ JNI bridge between the C++ enum "RiveErrorType" and the the Kotlin enum "RiveErrorType". + */ + struct JRiveErrorType final: public jni::JavaClass { + public: + static auto constexpr kJavaDescriptor = "Lcom/margelo/nitro/rive/RiveErrorType;"; + + public: + /** + * Convert this Java/Kotlin-based enum to the C++ enum RiveErrorType. + */ + [[maybe_unused]] + [[nodiscard]] + RiveErrorType toCpp() const { + static const auto clazz = javaClassStatic(); + static const auto fieldOrdinal = clazz->getField("value"); + int ordinal = this->getFieldValue(fieldOrdinal); + return static_cast(ordinal); + } + + public: + /** + * Create a Java/Kotlin-based enum with the given C++ enum's value. + */ + [[maybe_unused]] + static jni::alias_ref fromCpp(RiveErrorType value) { + static const auto clazz = javaClassStatic(); + static const auto fieldUNKNOWN = clazz->getStaticField("UNKNOWN"); + static const auto fieldFILENOTFOUND = clazz->getStaticField("FILENOTFOUND"); + static const auto fieldMALFORMEDFILE = clazz->getStaticField("MALFORMEDFILE"); + static const auto fieldINCORRECTARTBOARDNAME = clazz->getStaticField("INCORRECTARTBOARDNAME"); + static const auto fieldINCORRECTSTATEMACHINENAME = clazz->getStaticField("INCORRECTSTATEMACHINENAME"); + static const auto fieldVIEWMODELINSTANCENOTFOUND = clazz->getStaticField("VIEWMODELINSTANCENOTFOUND"); + static const auto fieldINCORRECTSTATEMACHINEINPUTNAME = clazz->getStaticField("INCORRECTSTATEMACHINEINPUTNAME"); + + switch (value) { + case RiveErrorType::UNKNOWN: + return clazz->getStaticFieldValue(fieldUNKNOWN); + case RiveErrorType::FILENOTFOUND: + return clazz->getStaticFieldValue(fieldFILENOTFOUND); + case RiveErrorType::MALFORMEDFILE: + return clazz->getStaticFieldValue(fieldMALFORMEDFILE); + case RiveErrorType::INCORRECTARTBOARDNAME: + return clazz->getStaticFieldValue(fieldINCORRECTARTBOARDNAME); + case RiveErrorType::INCORRECTSTATEMACHINENAME: + return clazz->getStaticFieldValue(fieldINCORRECTSTATEMACHINENAME); + case RiveErrorType::VIEWMODELINSTANCENOTFOUND: + return clazz->getStaticFieldValue(fieldVIEWMODELINSTANCENOTFOUND); + case RiveErrorType::INCORRECTSTATEMACHINEINPUTNAME: + return clazz->getStaticFieldValue(fieldINCORRECTSTATEMACHINEINPUTNAME); + default: + std::string stringValue = std::to_string(static_cast(value)); + throw std::invalid_argument("Invalid enum value (" + stringValue + "!"); + } + } + }; + +} // namespace margelo::nitro::rive diff --git a/nitrogen/generated/android/c++/views/JHybridRiveViewStateUpdater.cpp b/nitrogen/generated/android/c++/views/JHybridRiveViewStateUpdater.cpp index a54a6113..590d2b83 100644 --- a/nitrogen/generated/android/c++/views/JHybridRiveViewStateUpdater.cpp +++ b/nitrogen/generated/android/c++/views/JHybridRiveViewStateUpdater.cpp @@ -68,6 +68,10 @@ void JHybridRiveViewStateUpdater::updateViewProps(jni::alias_ref /* view->setDataBind(props.dataBind.value); // TODO: Set isDirty = false } + if (props.onError.isDirty) { + view->setOnError(props.onError.value); + // TODO: Set isDirty = false + } // Update hybridRef if it changed if (props.hybridRef.isDirty) { diff --git a/nitrogen/generated/android/kotlin/com/margelo/nitro/rive/Func_void_RiveError.kt b/nitrogen/generated/android/kotlin/com/margelo/nitro/rive/Func_void_RiveError.kt new file mode 100644 index 00000000..2f706a95 --- /dev/null +++ b/nitrogen/generated/android/kotlin/com/margelo/nitro/rive/Func_void_RiveError.kt @@ -0,0 +1,80 @@ +/// +/// Func_void_RiveError.kt +/// This file was generated by nitrogen. DO NOT MODIFY THIS FILE. +/// https://github.com/mrousavy/nitro +/// Copyright © 2025 Marc Rousavy @ Margelo +/// + +package com.margelo.nitro.rive + +import androidx.annotation.Keep +import com.facebook.jni.HybridData +import com.facebook.proguard.annotations.DoNotStrip +import dalvik.annotation.optimization.FastNative + + +/** + * Represents the JavaScript callback `(error: struct) => void`. + * This can be either implemented in C++ (in which case it might be a callback coming from JS), + * or in Kotlin/Java (in which case it is a native callback). + */ +@DoNotStrip +@Keep +@Suppress("ClassName", "RedundantUnitReturnType") +fun interface Func_void_RiveError: (RiveError) -> Unit { + /** + * Call the given JS callback. + * @throws Throwable if the JS function itself throws an error, or if the JS function/runtime has already been deleted. + */ + @DoNotStrip + @Keep + override fun invoke(error: RiveError): Unit +} + +/** + * Represents the JavaScript callback `(error: struct) => void`. + * This is implemented in C++, via a `std::function<...>`. + * The callback might be coming from JS. + */ +@DoNotStrip +@Keep +@Suppress( + "KotlinJniMissingFunction", "unused", + "RedundantSuppression", "RedundantUnitReturnType", "FunctionName", + "ConvertSecondaryConstructorToPrimary", "ClassName", "LocalVariableName", +) +class Func_void_RiveError_cxx: Func_void_RiveError { + @DoNotStrip + @Keep + private val mHybridData: HybridData + + @DoNotStrip + @Keep + private constructor(hybridData: HybridData) { + mHybridData = hybridData + } + + @DoNotStrip + @Keep + override fun invoke(error: RiveError): Unit + = invoke_cxx(error) + + @FastNative + private external fun invoke_cxx(error: RiveError): Unit +} + +/** + * Represents the JavaScript callback `(error: struct) => void`. + * This is implemented in Java/Kotlin, via a `(RiveError) -> Unit`. + * The callback is always coming from native. + */ +@DoNotStrip +@Keep +@Suppress("ClassName", "RedundantUnitReturnType", "unused") +class Func_void_RiveError_java(private val function: (RiveError) -> Unit): Func_void_RiveError { + @DoNotStrip + @Keep + override fun invoke(error: RiveError): Unit { + return this.function(error) + } +} diff --git a/nitrogen/generated/android/kotlin/com/margelo/nitro/rive/HybridRiveViewSpec.kt b/nitrogen/generated/android/kotlin/com/margelo/nitro/rive/HybridRiveViewSpec.kt index 94744b36..4fb35639 100644 --- a/nitrogen/generated/android/kotlin/com/margelo/nitro/rive/HybridRiveViewSpec.kt +++ b/nitrogen/generated/android/kotlin/com/margelo/nitro/rive/HybridRiveViewSpec.kt @@ -90,6 +90,20 @@ abstract class HybridRiveViewSpec: HybridView() { @set:DoNotStrip @set:Keep abstract var dataBind: Variant_HybridViewModelInstanceSpec_DataBindMode_DataBindByName? + + abstract var onError: (error: RiveError) -> Unit + + private var onError_cxx: Func_void_RiveError + @Keep + @DoNotStrip + get() { + return Func_void_RiveError_java(onError) + } + @Keep + @DoNotStrip + set(value) { + onError = value + } // Methods @DoNotStrip diff --git a/nitrogen/generated/android/kotlin/com/margelo/nitro/rive/RiveError.kt b/nitrogen/generated/android/kotlin/com/margelo/nitro/rive/RiveError.kt new file mode 100644 index 00000000..4124419f --- /dev/null +++ b/nitrogen/generated/android/kotlin/com/margelo/nitro/rive/RiveError.kt @@ -0,0 +1,41 @@ +/// +/// RiveError.kt +/// This file was generated by nitrogen. DO NOT MODIFY THIS FILE. +/// https://github.com/mrousavy/nitro +/// Copyright © 2025 Marc Rousavy @ Margelo +/// + +package com.margelo.nitro.rive + +import androidx.annotation.Keep +import com.facebook.proguard.annotations.DoNotStrip + + +/** + * Represents the JavaScript object/struct "RiveError". + */ +@DoNotStrip +@Keep +data class RiveError( + @DoNotStrip + @Keep + val message: String, + @DoNotStrip + @Keep + val type: RiveErrorType +) { + /* primary constructor */ + + private companion object { + /** + * Constructor called from C++ + */ + @DoNotStrip + @Keep + @Suppress("unused") + @JvmStatic + private fun fromCpp(message: String, type: RiveErrorType): RiveError { + return RiveError(message, type) + } + } +} diff --git a/nitrogen/generated/android/kotlin/com/margelo/nitro/rive/RiveErrorType.kt b/nitrogen/generated/android/kotlin/com/margelo/nitro/rive/RiveErrorType.kt new file mode 100644 index 00000000..b2aac955 --- /dev/null +++ b/nitrogen/generated/android/kotlin/com/margelo/nitro/rive/RiveErrorType.kt @@ -0,0 +1,26 @@ +/// +/// RiveErrorType.kt +/// This file was generated by nitrogen. DO NOT MODIFY THIS FILE. +/// https://github.com/mrousavy/nitro +/// Copyright © 2025 Marc Rousavy @ Margelo +/// + +package com.margelo.nitro.rive + +import androidx.annotation.Keep +import com.facebook.proguard.annotations.DoNotStrip + +/** + * Represents the JavaScript enum/union "RiveErrorType". + */ +@DoNotStrip +@Keep +enum class RiveErrorType(@DoNotStrip @Keep val value: Int) { + UNKNOWN(0), + FILENOTFOUND(1), + MALFORMEDFILE(2), + INCORRECTARTBOARDNAME(3), + INCORRECTSTATEMACHINENAME(4), + VIEWMODELINSTANCENOTFOUND(6), + INCORRECTSTATEMACHINEINPUTNAME(8); +} diff --git a/nitrogen/generated/android/riveOnLoad.cpp b/nitrogen/generated/android/riveOnLoad.cpp index 9b0561a5..26442587 100644 --- a/nitrogen/generated/android/riveOnLoad.cpp +++ b/nitrogen/generated/android/riveOnLoad.cpp @@ -19,6 +19,7 @@ #include "JHybridRiveFileSpec.hpp" #include "JHybridRiveFileFactorySpec.hpp" #include "JHybridRiveViewSpec.hpp" +#include "JFunc_void_RiveError.hpp" #include "JFunc_void_UnifiedRiveEvent.hpp" #include "views/JHybridRiveViewStateUpdater.hpp" #include "JHybridViewModelSpec.hpp" @@ -49,6 +50,7 @@ int initialize(JavaVM* vm) { margelo::nitro::rive::JHybridRiveFileSpec::registerNatives(); margelo::nitro::rive::JHybridRiveFileFactorySpec::registerNatives(); margelo::nitro::rive::JHybridRiveViewSpec::registerNatives(); + margelo::nitro::rive::JFunc_void_RiveError_cxx::registerNatives(); margelo::nitro::rive::JFunc_void_UnifiedRiveEvent_cxx::registerNatives(); margelo::nitro::rive::views::JHybridRiveViewStateUpdater::registerNatives(); margelo::nitro::rive::JHybridViewModelSpec::registerNatives(); diff --git a/nitrogen/generated/ios/c++/HybridRiveViewSpecSwift.hpp b/nitrogen/generated/ios/c++/HybridRiveViewSpecSwift.hpp index d13488e9..3b856170 100644 --- a/nitrogen/generated/ios/c++/HybridRiveViewSpecSwift.hpp +++ b/nitrogen/generated/ios/c++/HybridRiveViewSpecSwift.hpp @@ -24,6 +24,10 @@ namespace margelo::nitro::rive { class HybridViewModelInstanceSpec; } namespace margelo::nitro::rive { enum class DataBindMode; } // Forward declaration of `DataBindByName` to properly resolve imports. namespace margelo::nitro::rive { struct DataBindByName; } +// Forward declaration of `RiveError` to properly resolve imports. +namespace margelo::nitro::rive { struct RiveError; } +// Forward declaration of `RiveErrorType` to properly resolve imports. +namespace margelo::nitro::rive { enum class RiveErrorType; } // Forward declaration of `UnifiedRiveEvent` to properly resolve imports. namespace margelo::nitro::rive { struct UnifiedRiveEvent; } // Forward declaration of `RiveEventType` to properly resolve imports. @@ -39,9 +43,11 @@ namespace margelo::nitro::rive { enum class RiveEventType; } #include "DataBindMode.hpp" #include "DataBindByName.hpp" #include +#include "RiveError.hpp" +#include +#include "RiveErrorType.hpp" #include #include "UnifiedRiveEvent.hpp" -#include #include "RiveEventType.hpp" #include @@ -141,6 +147,13 @@ namespace margelo::nitro::rive { inline void setDataBind(const std::optional, DataBindMode, DataBindByName>>& dataBind) noexcept override { _swiftPart.setDataBind(dataBind); } + inline std::function getOnError() noexcept override { + auto __result = _swiftPart.getOnError(); + return __result; + } + inline void setOnError(const std::function& onError) noexcept override { + _swiftPart.setOnError(onError); + } public: // Methods diff --git a/nitrogen/generated/ios/c++/views/HybridRiveViewComponent.mm b/nitrogen/generated/ios/c++/views/HybridRiveViewComponent.mm index 01607d5b..d951553c 100644 --- a/nitrogen/generated/ios/c++/views/HybridRiveViewComponent.mm +++ b/nitrogen/generated/ios/c++/views/HybridRiveViewComponent.mm @@ -111,6 +111,11 @@ - (void) updateProps:(const std::shared_ptr&)props swiftPart.setDataBind(newViewProps.dataBind.value); newViewProps.dataBind.isDirty = false; } + // onError: function + if (newViewProps.onError.isDirty) { + swiftPart.setOnError(newViewProps.onError.value); + newViewProps.onError.isDirty = false; + } swiftPart.afterUpdate(); diff --git a/nitrogen/generated/ios/react_native_rive-Swift-Cxx-Bridge.cpp b/nitrogen/generated/ios/react_native_rive-Swift-Cxx-Bridge.cpp index 4ce34ca9..53079583 100644 --- a/nitrogen/generated/ios/react_native_rive-Swift-Cxx-Bridge.cpp +++ b/nitrogen/generated/ios/react_native_rive-Swift-Cxx-Bridge.cpp @@ -121,6 +121,14 @@ namespace margelo::nitro::rive::bridge::swift { return swiftPart.toUnsafe(); } + // pragma MARK: std::function + Func_void_RiveError create_Func_void_RiveError(void* NON_NULL swiftClosureWrapper) noexcept { + auto swiftClosure = react_native_rive::Func_void_RiveError::fromUnsafe(swiftClosureWrapper); + return [swiftClosure = std::move(swiftClosure)](const RiveError& error) mutable -> void { + swiftClosure.call(error); + }; + } + // pragma MARK: std::function Func_void_bool create_Func_void_bool(void* NON_NULL swiftClosureWrapper) noexcept { auto swiftClosure = react_native_rive::Func_void_bool::fromUnsafe(swiftClosureWrapper); diff --git a/nitrogen/generated/ios/react_native_rive-Swift-Cxx-Bridge.hpp b/nitrogen/generated/ios/react_native_rive-Swift-Cxx-Bridge.hpp index cd00009e..36218aaf 100644 --- a/nitrogen/generated/ios/react_native_rive-Swift-Cxx-Bridge.hpp +++ b/nitrogen/generated/ios/react_native_rive-Swift-Cxx-Bridge.hpp @@ -50,6 +50,10 @@ namespace margelo::nitro::rive { class HybridViewModelTriggerPropertySpec; } namespace margelo::nitro::rive { struct ReferencedAssetsType; } // Forward declaration of `ResolvedReferencedAsset` to properly resolve imports. namespace margelo::nitro::rive { struct ResolvedReferencedAsset; } +// Forward declaration of `RiveErrorType` to properly resolve imports. +namespace margelo::nitro::rive { enum class RiveErrorType; } +// Forward declaration of `RiveError` to properly resolve imports. +namespace margelo::nitro::rive { struct RiveError; } // Forward declaration of `RiveEventType` to properly resolve imports. namespace margelo::nitro::rive { enum class RiveEventType; } // Forward declaration of `UnifiedRiveEvent` to properly resolve imports. @@ -105,6 +109,8 @@ namespace react_native_rive { class HybridViewModelTriggerPropertySpec_cxx; } #include "HybridViewModelTriggerPropertySpec.hpp" #include "ReferencedAssetsType.hpp" #include "ResolvedReferencedAsset.hpp" +#include "RiveError.hpp" +#include "RiveErrorType.hpp" #include "RiveEventType.hpp" #include "UnifiedRiveEvent.hpp" #include @@ -486,6 +492,28 @@ namespace margelo::nitro::rive::bridge::swift { return *optional; } + // pragma MARK: std::function + /** + * Specialized version of `std::function`. + */ + using Func_void_RiveError = std::function; + /** + * Wrapper class for a `std::function`, this can be used from Swift. + */ + class Func_void_RiveError_Wrapper final { + public: + explicit Func_void_RiveError_Wrapper(std::function&& func): _function(std::make_unique>(std::move(func))) {} + inline void call(RiveError error) const noexcept { + _function->operator()(error); + } + private: + std::unique_ptr> _function; + } SWIFT_NONCOPYABLE; + Func_void_RiveError create_Func_void_RiveError(void* NON_NULL swiftClosureWrapper) noexcept; + inline Func_void_RiveError_Wrapper wrap_Func_void_RiveError(Func_void_RiveError value) noexcept { + return Func_void_RiveError_Wrapper(std::move(value)); + } + // pragma MARK: std::shared_ptr> /** * Specialized version of `std::shared_ptr>`. diff --git a/nitrogen/generated/ios/react_native_rive-Swift-Cxx-Umbrella.hpp b/nitrogen/generated/ios/react_native_rive-Swift-Cxx-Umbrella.hpp index 407dffac..a6540a2d 100644 --- a/nitrogen/generated/ios/react_native_rive-Swift-Cxx-Umbrella.hpp +++ b/nitrogen/generated/ios/react_native_rive-Swift-Cxx-Umbrella.hpp @@ -50,6 +50,10 @@ namespace margelo::nitro::rive { class HybridViewModelTriggerPropertySpec; } namespace margelo::nitro::rive { struct ReferencedAssetsType; } // Forward declaration of `ResolvedReferencedAsset` to properly resolve imports. namespace margelo::nitro::rive { struct ResolvedReferencedAsset; } +// Forward declaration of `RiveErrorType` to properly resolve imports. +namespace margelo::nitro::rive { enum class RiveErrorType; } +// Forward declaration of `RiveError` to properly resolve imports. +namespace margelo::nitro::rive { struct RiveError; } // Forward declaration of `RiveEventType` to properly resolve imports. namespace margelo::nitro::rive { enum class RiveEventType; } // Forward declaration of `UnifiedRiveEvent` to properly resolve imports. @@ -77,6 +81,8 @@ namespace margelo::nitro::rive { struct UnifiedRiveEvent; } #include "HybridViewModelTriggerPropertySpec.hpp" #include "ReferencedAssetsType.hpp" #include "ResolvedReferencedAsset.hpp" +#include "RiveError.hpp" +#include "RiveErrorType.hpp" #include "RiveEventType.hpp" #include "UnifiedRiveEvent.hpp" #include diff --git a/nitrogen/generated/ios/swift/Func_void_RiveError.swift b/nitrogen/generated/ios/swift/Func_void_RiveError.swift new file mode 100644 index 00000000..3d6af09a --- /dev/null +++ b/nitrogen/generated/ios/swift/Func_void_RiveError.swift @@ -0,0 +1,47 @@ +/// +/// Func_void_RiveError.swift +/// This file was generated by nitrogen. DO NOT MODIFY THIS FILE. +/// https://github.com/mrousavy/nitro +/// Copyright © 2025 Marc Rousavy @ Margelo +/// + +import NitroModules + + +/** + * Wraps a Swift `(_ error: RiveError) -> Void` as a class. + * This class can be used from C++, e.g. to wrap the Swift closure as a `std::function`. + */ +public final class Func_void_RiveError { + public typealias bridge = margelo.nitro.rive.bridge.swift + + private let closure: (_ error: RiveError) -> Void + + public init(_ closure: @escaping (_ error: RiveError) -> Void) { + self.closure = closure + } + + @inline(__always) + public func call(error: RiveError) -> Void { + self.closure(error) + } + + /** + * Casts this instance to a retained unsafe raw pointer. + * This acquires one additional strong reference on the object! + */ + @inline(__always) + public func toUnsafe() -> UnsafeMutableRawPointer { + return Unmanaged.passRetained(self).toOpaque() + } + + /** + * Casts an unsafe pointer to a `Func_void_RiveError`. + * The pointer has to be a retained opaque `Unmanaged`. + * This removes one strong reference from the object! + */ + @inline(__always) + public static func fromUnsafe(_ pointer: UnsafeMutableRawPointer) -> Func_void_RiveError { + return Unmanaged.fromOpaque(pointer).takeRetainedValue() + } +} diff --git a/nitrogen/generated/ios/swift/HybridRiveViewSpec.swift b/nitrogen/generated/ios/swift/HybridRiveViewSpec.swift index 8e4520d2..a4ba5ba6 100644 --- a/nitrogen/generated/ios/swift/HybridRiveViewSpec.swift +++ b/nitrogen/generated/ios/swift/HybridRiveViewSpec.swift @@ -20,6 +20,7 @@ public protocol HybridRiveViewSpec_protocol: HybridObject, HybridView { var fit: Fit? { get set } var layoutScaleFactor: Double? { get set } var dataBind: Variant__any_HybridViewModelInstanceSpec__DataBindMode_DataBindByName? { get set } + var onError: (_ error: RiveError) -> Void { get set } // Methods func awaitViewReady() throws -> Promise diff --git a/nitrogen/generated/ios/swift/HybridRiveViewSpec_cxx.swift b/nitrogen/generated/ios/swift/HybridRiveViewSpec_cxx.swift index 486f0b04..087d5111 100644 --- a/nitrogen/generated/ios/swift/HybridRiveViewSpec_cxx.swift +++ b/nitrogen/generated/ios/swift/HybridRiveViewSpec_cxx.swift @@ -310,6 +310,25 @@ open class HybridRiveViewSpec_cxx { }() } } + + public final var onError: bridge.Func_void_RiveError { + @inline(__always) + get { + return { () -> bridge.Func_void_RiveError in + let __closureWrapper = Func_void_RiveError(self.__implementation.onError) + return bridge.create_Func_void_RiveError(__closureWrapper.toUnsafe()) + }() + } + @inline(__always) + set { + self.__implementation.onError = { () -> (RiveError) -> Void in + let __wrappedFunction = bridge.wrap_Func_void_RiveError(newValue) + return { (__error: RiveError) -> Void in + __wrappedFunction.call(__error) + } + }() + } + } // Methods @inline(__always) diff --git a/nitrogen/generated/ios/swift/RiveError.swift b/nitrogen/generated/ios/swift/RiveError.swift new file mode 100644 index 00000000..36b146ba --- /dev/null +++ b/nitrogen/generated/ios/swift/RiveError.swift @@ -0,0 +1,46 @@ +/// +/// RiveError.swift +/// This file was generated by nitrogen. DO NOT MODIFY THIS FILE. +/// https://github.com/mrousavy/nitro +/// Copyright © 2025 Marc Rousavy @ Margelo +/// + +import NitroModules + +/** + * Represents an instance of `RiveError`, backed by a C++ struct. + */ +public typealias RiveError = margelo.nitro.rive.RiveError + +public extension RiveError { + private typealias bridge = margelo.nitro.rive.bridge.swift + + /** + * Create a new instance of `RiveError`. + */ + init(message: String, type: RiveErrorType) { + self.init(std.string(message), type) + } + + var message: String { + @inline(__always) + get { + return String(self.__message) + } + @inline(__always) + set { + self.__message = std.string(newValue) + } + } + + var type: RiveErrorType { + @inline(__always) + get { + return self.__type + } + @inline(__always) + set { + self.__type = newValue + } + } +} diff --git a/nitrogen/generated/ios/swift/RiveErrorType.swift b/nitrogen/generated/ios/swift/RiveErrorType.swift new file mode 100644 index 00000000..b5ef2e41 --- /dev/null +++ b/nitrogen/generated/ios/swift/RiveErrorType.swift @@ -0,0 +1,60 @@ +/// +/// RiveErrorType.swift +/// This file was generated by nitrogen. DO NOT MODIFY THIS FILE. +/// https://github.com/mrousavy/nitro +/// Copyright © 2025 Marc Rousavy @ Margelo +/// + +/** + * Represents the JS enum `RiveErrorType`, backed by a C++ enum. + */ +public typealias RiveErrorType = margelo.nitro.rive.RiveErrorType + +public extension RiveErrorType { + /** + * Get a RiveErrorType for the given String value, or + * return `nil` if the given value was invalid/unknown. + */ + init?(fromString string: String) { + switch string { + case "Unknown": + self = .unknown + case "FileNotFound": + self = .filenotfound + case "MalformedFile": + self = .malformedfile + case "IncorrectArtboardName": + self = .incorrectartboardname + case "IncorrectStateMachineName": + self = .incorrectstatemachinename + case "ViewModelInstanceNotFound": + self = .viewmodelinstancenotfound + case "IncorrectStateMachineInputName": + self = .incorrectstatemachineinputname + default: + return nil + } + } + + /** + * Get the String value this RiveErrorType represents. + */ + var stringValue: String { + switch self { + case .unknown: + return "Unknown" + case .filenotfound: + return "FileNotFound" + case .malformedfile: + return "MalformedFile" + case .incorrectartboardname: + return "IncorrectArtboardName" + case .incorrectstatemachinename: + return "IncorrectStateMachineName" + case .viewmodelinstancenotfound: + return "ViewModelInstanceNotFound" + case .incorrectstatemachineinputname: + return "IncorrectStateMachineInputName" + } + } +} diff --git a/nitrogen/generated/shared/c++/HybridRiveViewSpec.cpp b/nitrogen/generated/shared/c++/HybridRiveViewSpec.cpp index 5f347257..05e959f2 100644 --- a/nitrogen/generated/shared/c++/HybridRiveViewSpec.cpp +++ b/nitrogen/generated/shared/c++/HybridRiveViewSpec.cpp @@ -30,6 +30,8 @@ namespace margelo::nitro::rive { prototype.registerHybridSetter("layoutScaleFactor", &HybridRiveViewSpec::setLayoutScaleFactor); prototype.registerHybridGetter("dataBind", &HybridRiveViewSpec::getDataBind); prototype.registerHybridSetter("dataBind", &HybridRiveViewSpec::setDataBind); + prototype.registerHybridGetter("onError", &HybridRiveViewSpec::getOnError); + prototype.registerHybridSetter("onError", &HybridRiveViewSpec::setOnError); prototype.registerHybridMethod("awaitViewReady", &HybridRiveViewSpec::awaitViewReady); prototype.registerHybridMethod("bindViewModelInstance", &HybridRiveViewSpec::bindViewModelInstance); prototype.registerHybridMethod("getViewModelInstance", &HybridRiveViewSpec::getViewModelInstance); diff --git a/nitrogen/generated/shared/c++/HybridRiveViewSpec.hpp b/nitrogen/generated/shared/c++/HybridRiveViewSpec.hpp index c4d4b0be..0bf16814 100644 --- a/nitrogen/generated/shared/c++/HybridRiveViewSpec.hpp +++ b/nitrogen/generated/shared/c++/HybridRiveViewSpec.hpp @@ -25,6 +25,8 @@ namespace margelo::nitro::rive { class HybridViewModelInstanceSpec; } namespace margelo::nitro::rive { enum class DataBindMode; } // Forward declaration of `DataBindByName` to properly resolve imports. namespace margelo::nitro::rive { struct DataBindByName; } +// Forward declaration of `RiveError` to properly resolve imports. +namespace margelo::nitro::rive { struct RiveError; } // Forward declaration of `UnifiedRiveEvent` to properly resolve imports. namespace margelo::nitro::rive { struct UnifiedRiveEvent; } @@ -38,9 +40,10 @@ namespace margelo::nitro::rive { struct UnifiedRiveEvent; } #include "DataBindMode.hpp" #include "DataBindByName.hpp" #include +#include "RiveError.hpp" +#include #include #include "UnifiedRiveEvent.hpp" -#include namespace margelo::nitro::rive { @@ -85,6 +88,8 @@ namespace margelo::nitro::rive { virtual void setLayoutScaleFactor(std::optional layoutScaleFactor) = 0; virtual std::optional, DataBindMode, DataBindByName>> getDataBind() = 0; virtual void setDataBind(const std::optional, DataBindMode, DataBindByName>>& dataBind) = 0; + virtual std::function getOnError() = 0; + virtual void setOnError(const std::function& onError) = 0; public: // Methods diff --git a/nitrogen/generated/shared/c++/RiveError.hpp b/nitrogen/generated/shared/c++/RiveError.hpp new file mode 100644 index 00000000..f798ac64 --- /dev/null +++ b/nitrogen/generated/shared/c++/RiveError.hpp @@ -0,0 +1,81 @@ +/// +/// RiveError.hpp +/// This file was generated by nitrogen. DO NOT MODIFY THIS FILE. +/// https://github.com/mrousavy/nitro +/// Copyright © 2025 Marc Rousavy @ Margelo +/// + +#pragma once + +#if __has_include() +#include +#else +#error NitroModules cannot be found! Are you sure you installed NitroModules properly? +#endif +#if __has_include() +#include +#else +#error NitroModules cannot be found! Are you sure you installed NitroModules properly? +#endif +#if __has_include() +#include +#else +#error NitroModules cannot be found! Are you sure you installed NitroModules properly? +#endif + +// Forward declaration of `RiveErrorType` to properly resolve imports. +namespace margelo::nitro::rive { enum class RiveErrorType; } + +#include +#include "RiveErrorType.hpp" + +namespace margelo::nitro::rive { + + /** + * A struct which can be represented as a JavaScript object (RiveError). + */ + struct RiveError { + public: + std::string message SWIFT_PRIVATE; + RiveErrorType type SWIFT_PRIVATE; + + public: + RiveError() = default; + explicit RiveError(std::string message, RiveErrorType type): message(message), type(type) {} + }; + +} // namespace margelo::nitro::rive + +namespace margelo::nitro { + + // C++ RiveError <> JS RiveError (object) + template <> + struct JSIConverter final { + static inline margelo::nitro::rive::RiveError fromJSI(jsi::Runtime& runtime, const jsi::Value& arg) { + jsi::Object obj = arg.asObject(runtime); + return margelo::nitro::rive::RiveError( + JSIConverter::fromJSI(runtime, obj.getProperty(runtime, "message")), + JSIConverter::fromJSI(runtime, obj.getProperty(runtime, "type")) + ); + } + static inline jsi::Value toJSI(jsi::Runtime& runtime, const margelo::nitro::rive::RiveError& arg) { + jsi::Object obj(runtime); + obj.setProperty(runtime, "message", JSIConverter::toJSI(runtime, arg.message)); + obj.setProperty(runtime, "type", JSIConverter::toJSI(runtime, arg.type)); + return obj; + } + static inline bool canConvert(jsi::Runtime& runtime, const jsi::Value& value) { + if (!value.isObject()) { + return false; + } + jsi::Object obj = value.getObject(runtime); + if (!nitro::isPlainObject(runtime, obj)) { + return false; + } + if (!JSIConverter::canConvert(runtime, obj.getProperty(runtime, "message"))) return false; + if (!JSIConverter::canConvert(runtime, obj.getProperty(runtime, "type"))) return false; + return true; + } + }; + +} // namespace margelo::nitro diff --git a/nitrogen/generated/shared/c++/RiveErrorType.hpp b/nitrogen/generated/shared/c++/RiveErrorType.hpp new file mode 100644 index 00000000..28ee3c9b --- /dev/null +++ b/nitrogen/generated/shared/c++/RiveErrorType.hpp @@ -0,0 +1,75 @@ +/// +/// RiveErrorType.hpp +/// This file was generated by nitrogen. DO NOT MODIFY THIS FILE. +/// https://github.com/mrousavy/nitro +/// Copyright © 2025 Marc Rousavy @ Margelo +/// + +#pragma once + +#if __has_include() +#include +#else +#error NitroModules cannot be found! Are you sure you installed NitroModules properly? +#endif +#if __has_include() +#include +#else +#error NitroModules cannot be found! Are you sure you installed NitroModules properly? +#endif + +namespace margelo::nitro::rive { + + /** + * An enum which can be represented as a JavaScript enum (RiveErrorType). + */ + enum class RiveErrorType { + UNKNOWN SWIFT_NAME(unknown) = 0, + FILENOTFOUND SWIFT_NAME(filenotfound) = 1, + MALFORMEDFILE SWIFT_NAME(malformedfile) = 2, + INCORRECTARTBOARDNAME SWIFT_NAME(incorrectartboardname) = 3, + INCORRECTSTATEMACHINENAME SWIFT_NAME(incorrectstatemachinename) = 4, + VIEWMODELINSTANCENOTFOUND SWIFT_NAME(viewmodelinstancenotfound) = 6, + INCORRECTSTATEMACHINEINPUTNAME SWIFT_NAME(incorrectstatemachineinputname) = 8, + } CLOSED_ENUM; + +} // namespace margelo::nitro::rive + +namespace margelo::nitro { + + // C++ RiveErrorType <> JS RiveErrorType (enum) + template <> + struct JSIConverter final { + static inline margelo::nitro::rive::RiveErrorType fromJSI(jsi::Runtime& runtime, const jsi::Value& arg) { + int enumValue = JSIConverter::fromJSI(runtime, arg); + return static_cast(enumValue); + } + static inline jsi::Value toJSI(jsi::Runtime& runtime, margelo::nitro::rive::RiveErrorType arg) { + int enumValue = static_cast(arg); + return JSIConverter::toJSI(runtime, enumValue); + } + static inline bool canConvert(jsi::Runtime&, const jsi::Value& value) { + if (!value.isNumber()) { + return false; + } + double number = value.getNumber(); + int integer = static_cast(number); + if (number != integer) { + // The integer is not the same value as the double - we truncated floating points. + // Enums are all integers, so the input floating point number is obviously invalid. + return false; + } + switch (integer) { + case 0 /* UNKNOWN */: return true; + case 1 /* FILENOTFOUND */: return true; + case 2 /* MALFORMEDFILE */: return true; + case 3 /* INCORRECTARTBOARDNAME */: return true; + case 4 /* INCORRECTSTATEMACHINENAME */: return true; + case 6 /* VIEWMODELINSTANCENOTFOUND */: return true; + case 8 /* INCORRECTSTATEMACHINEINPUTNAME */: return true; + default: return false; + } + } + }; + +} // namespace margelo::nitro diff --git a/nitrogen/generated/shared/c++/views/HybridRiveViewComponent.cpp b/nitrogen/generated/shared/c++/views/HybridRiveViewComponent.cpp index 0d9069d6..c1edd20a 100644 --- a/nitrogen/generated/shared/c++/views/HybridRiveViewComponent.cpp +++ b/nitrogen/generated/shared/c++/views/HybridRiveViewComponent.cpp @@ -105,6 +105,16 @@ namespace margelo::nitro::rive::views { throw std::runtime_error(std::string("RiveView.dataBind: ") + exc.what()); } }()), + onError([&]() -> CachedProp> { + try { + const react::RawValue* rawValue = rawProps.at("onError", nullptr, nullptr); + if (rawValue == nullptr) return sourceProps.onError; + const auto& [runtime, value] = (std::pair)*rawValue; + return CachedProp>::fromRawValue(*runtime, value.asObject(*runtime).getProperty(*runtime, "f"), sourceProps.onError); + } catch (const std::exception& exc) { + throw std::runtime_error(std::string("RiveView.onError: ") + exc.what()); + } + }()), hybridRef([&]() -> CachedProp& /* ref */)>>> { try { const react::RawValue* rawValue = rawProps.at("hybridRef", nullptr, nullptr); @@ -126,6 +136,7 @@ namespace margelo::nitro::rive::views { fit(other.fit), layoutScaleFactor(other.layoutScaleFactor), dataBind(other.dataBind), + onError(other.onError), hybridRef(other.hybridRef) { } bool HybridRiveViewProps::filterObjectKeys(const std::string& propName) { @@ -138,6 +149,7 @@ namespace margelo::nitro::rive::views { case hashString("fit"): return true; case hashString("layoutScaleFactor"): return true; case hashString("dataBind"): return true; + case hashString("onError"): return true; case hashString("hybridRef"): return true; default: return false; } diff --git a/nitrogen/generated/shared/c++/views/HybridRiveViewComponent.hpp b/nitrogen/generated/shared/c++/views/HybridRiveViewComponent.hpp index 664315c3..1ea801c1 100644 --- a/nitrogen/generated/shared/c++/views/HybridRiveViewComponent.hpp +++ b/nitrogen/generated/shared/c++/views/HybridRiveViewComponent.hpp @@ -26,8 +26,9 @@ #include "DataBindMode.hpp" #include "DataBindByName.hpp" #include -#include "HybridRiveViewSpec.hpp" +#include "RiveError.hpp" #include +#include "HybridRiveViewSpec.hpp" namespace margelo::nitro::rive::views { @@ -58,6 +59,7 @@ namespace margelo::nitro::rive::views { CachedProp> fit; CachedProp> layoutScaleFactor; CachedProp, DataBindMode, DataBindByName>>> dataBind; + CachedProp> onError; CachedProp& /* ref */)>>> hybridRef; private: diff --git a/nitrogen/generated/shared/json/RiveViewConfig.json b/nitrogen/generated/shared/json/RiveViewConfig.json index 19eab869..8eab6292 100644 --- a/nitrogen/generated/shared/json/RiveViewConfig.json +++ b/nitrogen/generated/shared/json/RiveViewConfig.json @@ -12,6 +12,7 @@ "fit": true, "layoutScaleFactor": true, "dataBind": true, + "onError": true, "hybridRef": true } } diff --git a/src/core/Errors.ts b/src/core/Errors.ts new file mode 100644 index 00000000..757fc409 --- /dev/null +++ b/src/core/Errors.ts @@ -0,0 +1,14 @@ +export enum RiveErrorType { + Unknown = 0, + FileNotFound = 1, + MalformedFile = 2, + IncorrectArtboardName = 3, + IncorrectStateMachineName = 4, + ViewModelInstanceNotFound = 6, + IncorrectStateMachineInputName = 8, +} + +export interface RiveError { + message: string; + type: RiveErrorType; +} diff --git a/src/core/NitroRiveViewComponent.ts b/src/core/NitroRiveViewComponent.ts new file mode 100644 index 00000000..be594bed --- /dev/null +++ b/src/core/NitroRiveViewComponent.ts @@ -0,0 +1,18 @@ +import { + getHostComponent, + type ReactNativeView, +} from 'react-native-nitro-modules'; +import type { + RiveViewMethods, + RiveViewTSMethods, + RiveViewProps as NativeRiveViewProps, +} from '../specs/RiveView.nitro'; +import RiveViewConfig from '../../nitrogen/generated/shared/json/RiveViewConfig.json'; + +export const NitroRiveView = getHostComponent< + NativeRiveViewProps, + RiveViewMethods +>('RiveView', () => RiveViewConfig) as ReactNativeView< + NativeRiveViewProps, + RiveViewTSMethods +>; diff --git a/src/core/RiveFile.ts b/src/core/RiveFile.ts index 364e901f..c6626667 100644 --- a/src/core/RiveFile.ts +++ b/src/core/RiveFile.ts @@ -4,9 +4,7 @@ import type { RiveFileFactory as RiveFileFactoryInternal, } from '../specs/RiveFile.nitro'; -// This import path isn't handled by @types/react-native -// @ts-ignore -import resolveAssetSource from 'react-native/Libraries/Image/resolveAssetSource'; +import { Image } from 'react-native'; import type { ResolvedReferencedAssets } from './ReferencedAssets'; const RiveFileInternal = @@ -119,7 +117,9 @@ export namespace RiveFileFactory { const assetID = typeof source === 'number' ? source : null; const sourceURI = typeof source === 'object' ? source.uri : null; - const assetURI = assetID ? resolveAssetSource(assetID)?.uri : sourceURI; + const assetURI = assetID + ? Image.resolveAssetSource(assetID)?.uri + : sourceURI; if (!assetURI) { throw new Error( diff --git a/src/core/RiveView.tsx b/src/core/RiveView.tsx new file mode 100644 index 00000000..ab09ab89 --- /dev/null +++ b/src/core/RiveView.tsx @@ -0,0 +1,48 @@ +import type { ComponentProps } from 'react'; +import { NitroRiveView } from './NitroRiveViewComponent'; +import { RiveErrorType, type RiveError } from './Errors'; + +export interface RiveViewProps + extends Omit, 'onError'> { + onError?: (error: RiveError) => void; +} + +const defaultOnError = (error: RiveError) => + console.error(`[${RiveErrorType[error.type]}] ${error.message}`); + +/** + * RiveView is a React Native component that renders Rive graphics. + * It provides a seamless way to display and control Rive graphics in your app. + * + * @example + * ```tsx + * + * ``` + * + * @property {RiveFile} file - The Rive file to be displayed + * @property {string} [artboardName] - Name of the artboard to display from the Rive file + * @property {string} [stateMachineName] - Name of the state machine to play + * @property {ViewModelInstance | DataBindMode | DataBindByName} [dataBind] - Data binding configuration for the state machine, defaults to DataBindMode.Auto + * @property {boolean} [autoPlay=true] - Whether to automatically start playing the state machine + * @property {Alignment} [alignment] - How the Rive graphic should be aligned within its container + * @property {Fit} [fit] - How the Rive graphic should fit within its container + * @property {Object} [style] - React Native style object for container customization + * @property {(error: RiveError) => void} [onError] - Callback function that is called when an error occurs + * + * The component also exposes methods for controlling playback: + * - play(): Starts playing the Rive graphic + * - pause(): Pauses the Rive graphic + */ +export function RiveView(props: RiveViewProps) { + const { onError, ...rest } = props; + const wrappedOnError = onError ?? defaultOnError; + + return ; +} diff --git a/src/hooks/useRive.ts b/src/hooks/useRive.ts index a634381b..26c3af6a 100644 --- a/src/hooks/useRive.ts +++ b/src/hooks/useRive.ts @@ -1,57 +1,50 @@ import { useRef, useCallback, useState } from 'react'; -import type { HybridView } from 'react-native-nitro-modules'; -import type { RiveViewProps, RiveViewMethods } from 'react-native-rive'; +import type { RiveViewRef } from 'react-native-rive'; export function useRive() { - const riveRef = useRef>(null); - const [riveViewRef, setRiveViewRef] = useState | null>(null); + const riveRef = useRef(null); + const [riveViewRef, setRiveViewRef] = useState(null); const timeoutRef = useRef | null>(null); - const setRef = useCallback( - (node: HybridView | null) => { - if (riveRef.current !== node) { - riveRef.current = node; + const setRef = useCallback((node: RiveViewRef | null) => { + if (riveRef.current !== node) { + riveRef.current = node; - // Clear any existing timeout - if (timeoutRef.current) { - clearTimeout(timeoutRef.current); - } + // Clear any existing timeout + if (timeoutRef.current) { + clearTimeout(timeoutRef.current); + } - const timeout = new Promise((_, reject) => { - timeoutRef.current = setTimeout(() => { - reject(new Error('Rive view ready timeout')); - }, 5000); - }); + const timeout = new Promise((_, reject) => { + timeoutRef.current = setTimeout(() => { + reject(new Error('Rive view ready timeout')); + }, 5000); + }); - // TODO: Need to clear out the awaitViewReady promise if it times out - // or add this timeout natively and return false - Promise.race([node?.awaitViewReady(), timeout]) - .then((result) => { - if (result === true) { - setRiveViewRef(node); - } else { - console.warn('Rive view ready check returned false'); - setRiveViewRef(null); - } - }) - .catch((error) => { - console.warn('Failed to initialize Rive view:', error); + // TODO: Need to clear out the awaitViewReady promise if it times out + // or add this timeout natively and return false + Promise.race([node?.awaitViewReady(), timeout]) + .then((result) => { + if (result === true) { + setRiveViewRef(node); + } else { + console.warn('Rive view ready check returned false'); setRiveViewRef(null); - }) - .finally(() => { - // Clear the timeout in both success and error cases - if (timeoutRef.current) { - clearTimeout(timeoutRef.current); - timeoutRef.current = null; - } - }); - } - }, - [] - ); + } + }) + .catch((error) => { + console.warn('Failed to initialize Rive view:', error); + setRiveViewRef(null); + }) + .finally(() => { + // Clear the timeout in both success and error cases + if (timeoutRef.current) { + clearTimeout(timeoutRef.current); + timeoutRef.current = null; + } + }); + } + }, []); return { riveRef, diff --git a/src/index.tsx b/src/index.tsx index 7b8bdf1a..5388a8a0 100644 --- a/src/index.tsx +++ b/src/index.tsx @@ -1,13 +1,8 @@ -import { - getHostComponent, - NitroModules, - type ReactNativeView, -} from 'react-native-nitro-modules'; -import type { Rive } from './specs/Rive.nitro'; +import { type HybridView } from 'react-native-nitro-modules'; import { type RiveViewMethods, type RiveViewTSMethods, - type RiveViewProps, + type RiveViewProps as NativeRiveViewProps, DataBindMode, type DataBindByName as DataBindByNameInterface, } from './specs/RiveView.nitro'; @@ -19,49 +14,11 @@ export class DataBindByName implements DataBindByNameInterface { } } -import RiveViewConfig from '../nitrogen/generated/shared/json/RiveViewConfig.json'; - -const RiveHybridObject = NitroModules.createHybridObject('Rive'); - -export function multiply(a: number, b: number): number { - return RiveHybridObject.multiply(a, b); -} - -/** - * RiveView is a React Native component that renders Rive graphics. - * It provides a seamless way to display and control Rive graphics in your app. - * - * @example - * ```tsx - * - * ``` - * - * @property {RiveFile} file - The Rive file to be displayed - * @property {string} [artboardName] - Name of the artboard to display from the Rive file - * @property {string} [stateMachineName] - Name of the state machine to play - * @property {boolean} [autoBind=true] - Whether to automatically bind the state machine and artboard - * @property {boolean} [autoPlay=true] - Whether to automatically start playing the state machine - * @property {Alignment} [alignment] - How the Rive graphic should be aligned within its container - * @property {Fit} [fit] - How the Rive graphic should fit within its container - * @property {Object} [style] - React Native style object for container customization - * - * The component also exposes methods for controlling playback: - * - play(): Starts playing the Rive graphic - * - pause(): Pauses the Rive graphic - */ -export const RiveView = getHostComponent( - 'RiveView', - () => RiveViewConfig -) as ReactNativeView; +export { NitroRiveView } from './core/NitroRiveViewComponent'; -export type { RiveViewProps, RiveViewMethods }; +export { RiveView, type RiveViewProps } from './core/RiveView'; +export type { RiveViewMethods }; +export type RiveViewRef = HybridView; export type { RiveFile } from './specs/RiveFile.nitro'; export type { ViewModel, @@ -78,6 +35,7 @@ export { Alignment } from './core/Alignment'; export { RiveFileFactory } from './core/RiveFile'; export { RiveColor } from './core/RiveColor'; export { type RiveEvent, RiveEventType } from './core/Events'; +export { type RiveError, RiveErrorType } from './core/Errors'; export { ArtboardByIndex, ArtboardByName } from './specs/ArtboardBy'; export { useRive } from './hooks/useRive'; export { useRiveNumber } from './hooks/useRiveNumber'; diff --git a/src/specs/RiveView.nitro.ts b/src/specs/RiveView.nitro.ts index 9f0a1baf..6625f6d9 100644 --- a/src/specs/RiveView.nitro.ts +++ b/src/specs/RiveView.nitro.ts @@ -8,6 +8,7 @@ import { Fit } from '../core/Fit'; import type { ViewModelInstance } from './ViewModel.nitro'; import type { Alignment } from '../core/Alignment'; import type { UnifiedRiveEvent, RiveEvent } from '../core/Events'; +import type { RiveError } from '../core/Errors'; export enum DataBindMode { Auto, @@ -38,6 +39,8 @@ export interface RiveViewProps extends HybridViewProps { layoutScaleFactor?: number; /** The view model instance to bind, to the state machine. Defaults to DataBindMode.Auto */ dataBind?: ViewModelInstance | DataBindMode | DataBindByName; + /** Callback function that is called when an error occurs */ + onError: (error: RiveError) => void; } /**