diff --git a/android/src/main/java/com/margelo/nitro/rive/HybridRiveFileFactory.kt b/android/src/main/java/com/margelo/nitro/rive/HybridRiveFileFactory.kt index be235f9e..6e390af2 100644 --- a/android/src/main/java/com/margelo/nitro/rive/HybridRiveFileFactory.kt +++ b/android/src/main/java/com/margelo/nitro/rive/HybridRiveFileFactory.kt @@ -9,6 +9,8 @@ import com.margelo.nitro.core.Promise import com.margelo.nitro.NitroModules import kotlinx.coroutines.Dispatchers import kotlinx.coroutines.withContext +import java.io.File as JavaFile +import java.net.URI import java.net.URL @Keep @@ -33,6 +35,31 @@ class HybridRiveFileFactory : HybridRiveFileFactorySpec() { } } + override fun fromFileURL(fileURL: String, loadCdn: Boolean): Promise { + if (!fileURL.startsWith("file://")) { + throw Error("fromFileURL: URL must be a file URL: $fileURL") + } + + return Promise.async { + try { + val uri = URI(fileURL) + val path = uri.path ?: throw Error("fromFileURL: Invalid URL: $fileURL") + + val riveFile = withContext(Dispatchers.IO) { + val file = JavaFile(path) + val riveData = file.readBytes() + File(riveData) + } + + val hybridRiveFile = HybridRiveFile() + hybridRiveFile.riveFile = riveFile + hybridRiveFile + } catch (e: Exception) { + throw Error("Failed to load Rive file: ${e.message}") + } + } + } + @SuppressLint("DiscouragedApi") override fun fromResource(resource: String, loadCdn: Boolean): Promise { return Promise.async { diff --git a/ios/HybridRiveFileFactory.swift b/ios/HybridRiveFileFactory.swift index 49aaa12e..80e7d512 100644 --- a/ios/HybridRiveFileFactory.swift +++ b/ios/HybridRiveFileFactory.swift @@ -38,6 +38,45 @@ class HybridRiveFileFactory: HybridRiveFileFactorySpec { } } + func fromFileURL(fileURL: String, loadCdn: Bool) throws -> Promise<(any HybridRiveFileSpec)> { + guard let url = URL(string:fileURL) else { + throw RuntimeError.error(withMessage: "fromFileURL: Invalid URL: \(fileURL)") + } + + guard url.isFileURL else { + throw RuntimeError.error(withMessage: "fromFileURL: URL must be a file URL: \(fileURL)") + } + + return Promise.async { + do { + let riveFile = try await withCheckedThrowingContinuation { continuation in + DispatchQueue.global(qos: .userInitiated).async { + do { + let data = try Data(contentsOf: url) + + let riveFile = try RiveFile(data: data, loadCdn: loadCdn) + DispatchQueue.main.async { + continuation.resume(returning: riveFile) + } + } catch { + DispatchQueue.main.async { + continuation.resume(throwing: error) + } + } + } + } + + let hybridRiveFile = HybridRiveFile() + hybridRiveFile.riveFile = riveFile + return hybridRiveFile + } catch let error as NSError { + throw RuntimeError.error(withMessage: "Failed to load Rive file: \(error.localizedDescription)") + } catch { + throw RuntimeError.error(withMessage: "Unknown error occurred while loading Rive file") + } + } + } + func fromResource(resource: String, loadCdn: Bool) throws -> Promise<(any HybridRiveFileSpec)> { guard let _ = Bundle.main.path(forResource: resource, ofType: "riv") else { throw RuntimeError.error(withMessage: "Could not find Rive file: \(resource).riv") diff --git a/nitrogen/generated/android/c++/JHybridRiveFileFactorySpec.cpp b/nitrogen/generated/android/c++/JHybridRiveFileFactorySpec.cpp index 5c8af834..2e214051 100644 --- a/nitrogen/generated/android/c++/JHybridRiveFileFactorySpec.cpp +++ b/nitrogen/generated/android/c++/JHybridRiveFileFactorySpec.cpp @@ -68,6 +68,22 @@ namespace margelo::nitro::rive { return __promise; }(); } + std::shared_ptr>> JHybridRiveFileFactorySpec::fromFileURL(const std::string& fileURL, bool loadCdn) { + static const auto method = javaClassStatic()->getMethod(jni::alias_ref /* fileURL */, jboolean /* loadCdn */)>("fromFileURL"); + auto __result = method(_javaPart, jni::make_jstring(fileURL), loadCdn); + return [&]() { + auto __promise = Promise>::create(); + __result->cthis()->addOnResolvedListener([=](const jni::alias_ref& __boxedResult) { + auto __result = jni::static_ref_cast(__boxedResult); + __promise->resolve(__result->cthis()->shared_cast()); + }); + __result->cthis()->addOnRejectedListener([=](const jni::alias_ref& __throwable) { + jni::JniException __jniError(__throwable); + __promise->reject(std::make_exception_ptr(__jniError)); + }); + return __promise; + }(); + } std::shared_ptr>> JHybridRiveFileFactorySpec::fromResource(const std::string& resource, bool loadCdn) { static const auto method = javaClassStatic()->getMethod(jni::alias_ref /* resource */, jboolean /* loadCdn */)>("fromResource"); auto __result = method(_javaPart, jni::make_jstring(resource), loadCdn); diff --git a/nitrogen/generated/android/c++/JHybridRiveFileFactorySpec.hpp b/nitrogen/generated/android/c++/JHybridRiveFileFactorySpec.hpp index 3cc081f8..63f95a3e 100644 --- a/nitrogen/generated/android/c++/JHybridRiveFileFactorySpec.hpp +++ b/nitrogen/generated/android/c++/JHybridRiveFileFactorySpec.hpp @@ -55,6 +55,7 @@ namespace margelo::nitro::rive { public: // Methods std::shared_ptr>> fromURL(const std::string& url, bool loadCdn) override; + std::shared_ptr>> fromFileURL(const std::string& fileURL, bool loadCdn) override; std::shared_ptr>> fromResource(const std::string& resource, bool loadCdn) override; std::shared_ptr>> fromBytes(const std::shared_ptr& bytes, bool loadCdn) override; diff --git a/nitrogen/generated/android/kotlin/com/margelo/nitro/rive/HybridRiveFileFactorySpec.kt b/nitrogen/generated/android/kotlin/com/margelo/nitro/rive/HybridRiveFileFactorySpec.kt index 9d08b46a..a8c24d52 100644 --- a/nitrogen/generated/android/kotlin/com/margelo/nitro/rive/HybridRiveFileFactorySpec.kt +++ b/nitrogen/generated/android/kotlin/com/margelo/nitro/rive/HybridRiveFileFactorySpec.kt @@ -51,6 +51,10 @@ abstract class HybridRiveFileFactorySpec: HybridObject() { @Keep abstract fun fromURL(url: String, loadCdn: Boolean): Promise + @DoNotStrip + @Keep + abstract fun fromFileURL(fileURL: String, loadCdn: Boolean): Promise + @DoNotStrip @Keep abstract fun fromResource(resource: String, loadCdn: Boolean): Promise diff --git a/nitrogen/generated/ios/c++/HybridRiveFileFactorySpecSwift.hpp b/nitrogen/generated/ios/c++/HybridRiveFileFactorySpecSwift.hpp index f13df8a8..ea715f39 100644 --- a/nitrogen/generated/ios/c++/HybridRiveFileFactorySpecSwift.hpp +++ b/nitrogen/generated/ios/c++/HybridRiveFileFactorySpecSwift.hpp @@ -76,6 +76,14 @@ namespace margelo::nitro::rive { auto __value = std::move(__result.value()); return __value; } + inline std::shared_ptr>> fromFileURL(const std::string& fileURL, bool loadCdn) override { + auto __result = _swiftPart.fromFileURL(fileURL, std::forward(loadCdn)); + if (__result.hasError()) [[unlikely]] { + std::rethrow_exception(__result.error()); + } + auto __value = std::move(__result.value()); + return __value; + } inline std::shared_ptr>> fromResource(const std::string& resource, bool loadCdn) override { auto __result = _swiftPart.fromResource(resource, std::forward(loadCdn)); if (__result.hasError()) [[unlikely]] { diff --git a/nitrogen/generated/ios/swift/HybridRiveFileFactorySpec.swift b/nitrogen/generated/ios/swift/HybridRiveFileFactorySpec.swift index 932d5a4b..c944d7c8 100644 --- a/nitrogen/generated/ios/swift/HybridRiveFileFactorySpec.swift +++ b/nitrogen/generated/ios/swift/HybridRiveFileFactorySpec.swift @@ -16,6 +16,7 @@ public protocol HybridRiveFileFactorySpec_protocol: HybridObject { // Methods func fromURL(url: String, loadCdn: Bool) throws -> Promise<(any HybridRiveFileSpec)> + func fromFileURL(fileURL: String, loadCdn: Bool) throws -> Promise<(any HybridRiveFileSpec)> func fromResource(resource: String, loadCdn: Bool) throws -> Promise<(any HybridRiveFileSpec)> func fromBytes(bytes: ArrayBuffer, loadCdn: Bool) throws -> Promise<(any HybridRiveFileSpec)> } diff --git a/nitrogen/generated/ios/swift/HybridRiveFileFactorySpec_cxx.swift b/nitrogen/generated/ios/swift/HybridRiveFileFactorySpec_cxx.swift index c0a8ef4a..f08a5d25 100644 --- a/nitrogen/generated/ios/swift/HybridRiveFileFactorySpec_cxx.swift +++ b/nitrogen/generated/ios/swift/HybridRiveFileFactorySpec_cxx.swift @@ -140,6 +140,28 @@ open class HybridRiveFileFactorySpec_cxx { } } + @inline(__always) + public final func fromFileURL(fileURL: std.string, loadCdn: Bool) -> bridge.Result_std__shared_ptr_Promise_std__shared_ptr_HybridRiveFileSpec____ { + do { + let __result = try self.__implementation.fromFileURL(fileURL: String(fileURL), loadCdn: loadCdn) + let __resultCpp = { () -> bridge.std__shared_ptr_Promise_std__shared_ptr_HybridRiveFileSpec___ in + let __promise = bridge.create_std__shared_ptr_Promise_std__shared_ptr_HybridRiveFileSpec___() + let __promiseHolder = bridge.wrap_std__shared_ptr_Promise_std__shared_ptr_HybridRiveFileSpec___(__promise) + __result + .then({ __result in __promiseHolder.resolve({ () -> bridge.std__shared_ptr_HybridRiveFileSpec_ in + let __cxxWrapped = __result.getCxxWrapper() + return __cxxWrapped.getCxxPart() + }()) }) + .catch({ __error in __promiseHolder.reject(__error.toCpp()) }) + return __promise + }() + return bridge.create_Result_std__shared_ptr_Promise_std__shared_ptr_HybridRiveFileSpec____(__resultCpp) + } catch (let __error) { + let __exceptionPtr = __error.toCpp() + return bridge.create_Result_std__shared_ptr_Promise_std__shared_ptr_HybridRiveFileSpec____(__exceptionPtr) + } + } + @inline(__always) public final func fromResource(resource: std.string, loadCdn: Bool) -> bridge.Result_std__shared_ptr_Promise_std__shared_ptr_HybridRiveFileSpec____ { do { diff --git a/nitrogen/generated/shared/c++/HybridRiveFileFactorySpec.cpp b/nitrogen/generated/shared/c++/HybridRiveFileFactorySpec.cpp index c4013f28..34545b01 100644 --- a/nitrogen/generated/shared/c++/HybridRiveFileFactorySpec.cpp +++ b/nitrogen/generated/shared/c++/HybridRiveFileFactorySpec.cpp @@ -15,6 +15,7 @@ namespace margelo::nitro::rive { // load custom methods/properties registerHybrids(this, [](Prototype& prototype) { prototype.registerHybridMethod("fromURL", &HybridRiveFileFactorySpec::fromURL); + prototype.registerHybridMethod("fromFileURL", &HybridRiveFileFactorySpec::fromFileURL); prototype.registerHybridMethod("fromResource", &HybridRiveFileFactorySpec::fromResource); prototype.registerHybridMethod("fromBytes", &HybridRiveFileFactorySpec::fromBytes); }); diff --git a/nitrogen/generated/shared/c++/HybridRiveFileFactorySpec.hpp b/nitrogen/generated/shared/c++/HybridRiveFileFactorySpec.hpp index 8a3a2842..b84d85d7 100644 --- a/nitrogen/generated/shared/c++/HybridRiveFileFactorySpec.hpp +++ b/nitrogen/generated/shared/c++/HybridRiveFileFactorySpec.hpp @@ -54,6 +54,7 @@ namespace margelo::nitro::rive { public: // Methods virtual std::shared_ptr>> fromURL(const std::string& url, bool loadCdn) = 0; + virtual std::shared_ptr>> fromFileURL(const std::string& fileURL, bool loadCdn) = 0; virtual std::shared_ptr>> fromResource(const std::string& resource, bool loadCdn) = 0; virtual std::shared_ptr>> fromBytes(const std::shared_ptr& bytes, bool loadCdn) = 0; diff --git a/src/core/RiveFile.ts b/src/core/RiveFile.ts index d5be8abe..cdebe93a 100644 --- a/src/core/RiveFile.ts +++ b/src/core/RiveFile.ts @@ -29,6 +29,19 @@ export namespace RiveFileFactory { return RiveFileInternal.fromURL(url, loadCdn); } + /** + * Creates a RiveFile instance from a local file path URL. + * @param pathURL - The local file path of the Rive graphic file + * @param loadCdn - Whether to load from CDN (default: true) + * @returns Promise that resolves to a RiveFile instance + */ + export async function fromFileURL( + fileURL: string, + loadCdn: boolean = true + ): Promise { + return RiveFileInternal.fromFileURL(fileURL, loadCdn); + } + /** * Creates a RiveFile instance from a local resource. * @param resource - The name of the local resource @@ -100,11 +113,7 @@ export namespace RiveFileFactory { // handle iOS bundled asset if (assetURI.match(/file:\/\//)) { - const match = assetURI.match(/file:\/\/(.*\/)+(.*)\.riv/); - if (!match) { - throw new Error(`Invalid iOS asset path format: ${assetURI}`); - } - return RiveFileFactory.fromResource(match[2], loadCdn); + return RiveFileFactory.fromFileURL(assetURI, loadCdn); } // handle Android bundled asset or resource name uri diff --git a/src/specs/RiveFile.nitro.ts b/src/specs/RiveFile.nitro.ts index 0337a8c9..9db60746 100644 --- a/src/specs/RiveFile.nitro.ts +++ b/src/specs/RiveFile.nitro.ts @@ -22,6 +22,7 @@ export interface RiveFile export interface RiveFileFactory extends HybridObject<{ ios: 'swift'; android: 'kotlin' }> { fromURL(url: string, loadCdn: boolean): Promise; + fromFileURL(fileURL: string, loadCdn: boolean): Promise; fromResource(resource: string, loadCdn: boolean): Promise; fromBytes(bytes: ArrayBuffer, loadCdn: boolean): Promise; }