From ab592a1cb95aeba32f3c1eec83e2056f5ce4d88d Mon Sep 17 00:00:00 2001 From: DaveyEke Date: Thu, 16 Apr 2026 18:40:16 +0100 Subject: [PATCH] feat: add onLoad prop --- .../project.pbxproj | 22 ++-- example/ios/Podfile.lock | 122 +++++++++--------- example/src/NitroImageTab.tsx | 1 + .../margelo/nitro/image/HybridImageView.kt | 2 + .../ios/HybridImageView.swift | 2 + .../generated/android/NitroImageOnLoad.cpp | 2 + .../generated/android/c++/JFunc_void.hpp | 75 +++++++++++ .../android/c++/JHybridNitroImageViewSpec.cpp | 22 +++- .../android/c++/JHybridNitroImageViewSpec.hpp | 2 + .../JHybridNitroImageViewStateUpdater.cpp | 4 + .../com/margelo/nitro/image/Func_void.kt | 80 ++++++++++++ .../nitro/image/HybridNitroImageViewSpec.kt | 14 ++ .../ios/NitroImage-Swift-Cxx-Bridge.hpp | 15 +++ .../ios/NitroImage-Swift-Cxx-Umbrella.hpp | 1 + .../ios/c++/HybridNitroImageViewSpecSwift.hpp | 10 +- .../views/HybridNitroImageViewComponent.mm | 5 + .../ios/swift/HybridNitroImageViewSpec.swift | 1 + .../swift/HybridNitroImageViewSpec_cxx.swift | 32 +++++ .../shared/c++/HybridNitroImageViewSpec.cpp | 2 + .../shared/c++/HybridNitroImageViewSpec.hpp | 5 +- .../views/HybridNitroImageViewComponent.cpp | 11 ++ .../views/HybridNitroImageViewComponent.hpp | 5 +- .../shared/json/NitroImageViewConfig.json | 1 + .../src/NitroImage.tsx | 8 +- .../src/specs/ImageView.nitro.ts | 4 + .../nitro/web/image/HybridWebImageLoader.kt | 2 + .../ios/HybridWebImageLoader.swift | 9 +- 27 files changed, 380 insertions(+), 79 deletions(-) create mode 100644 packages/react-native-nitro-image/nitrogen/generated/android/c++/JFunc_void.hpp create mode 100644 packages/react-native-nitro-image/nitrogen/generated/android/kotlin/com/margelo/nitro/image/Func_void.kt diff --git a/example/ios/NitroImageExample.xcodeproj/project.pbxproj b/example/ios/NitroImageExample.xcodeproj/project.pbxproj index 85fc3f65..37070e8b 100644 --- a/example/ios/NitroImageExample.xcodeproj/project.pbxproj +++ b/example/ios/NitroImageExample.xcodeproj/project.pbxproj @@ -191,10 +191,14 @@ inputFileListPaths = ( "${PODS_ROOT}/Target Support Files/Pods-NitroImageExample/Pods-NitroImageExample-frameworks-${CONFIGURATION}-input-files.xcfilelist", ); + inputPaths = ( + ); name = "[CP] Embed Pods Frameworks"; outputFileListPaths = ( "${PODS_ROOT}/Target Support Files/Pods-NitroImageExample/Pods-NitroImageExample-frameworks-${CONFIGURATION}-output-files.xcfilelist", ); + outputPaths = ( + ); runOnlyForDeploymentPostprocessing = 0; shellPath = /bin/sh; shellScript = "\"${PODS_ROOT}/Target Support Files/Pods-NitroImageExample/Pods-NitroImageExample-frameworks.sh\"\n"; @@ -230,10 +234,14 @@ inputFileListPaths = ( "${PODS_ROOT}/Target Support Files/Pods-NitroImageExample/Pods-NitroImageExample-resources-${CONFIGURATION}-input-files.xcfilelist", ); + inputPaths = ( + ); name = "[CP] Copy Pods Resources"; outputFileListPaths = ( "${PODS_ROOT}/Target Support Files/Pods-NitroImageExample/Pods-NitroImageExample-resources-${CONFIGURATION}-output-files.xcfilelist", ); + outputPaths = ( + ); runOnlyForDeploymentPostprocessing = 0; shellPath = /bin/sh; shellScript = "\"${PODS_ROOT}/Target Support Files/Pods-NitroImageExample/Pods-NitroImageExample-resources.sh\"\n"; @@ -260,7 +268,7 @@ ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon; CLANG_ENABLE_MODULES = YES; CURRENT_PROJECT_VERSION = 1; - DEVELOPMENT_TEAM = CJW62Q77E7; + DEVELOPMENT_TEAM = B3P8HDC2S6; ENABLE_BITCODE = NO; INFOPLIST_FILE = NitroImageExample/Info.plist; IPHONEOS_DEPLOYMENT_TARGET = 15.1; @@ -289,7 +297,7 @@ ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon; CLANG_ENABLE_MODULES = YES; CURRENT_PROJECT_VERSION = 1; - DEVELOPMENT_TEAM = CJW62Q77E7; + DEVELOPMENT_TEAM = B3P8HDC2S6; INFOPLIST_FILE = NitroImageExample/Info.plist; IPHONEOS_DEPLOYMENT_TARGET = 15.1; LD_RUNPATH_SEARCH_PATHS = ( @@ -378,10 +386,7 @@ "-DFOLLY_CFG_NO_COROUTINES=1", "-DFOLLY_HAVE_CLOCK_GETTIME=1", ); - OTHER_LDFLAGS = ( - "$(inherited)", - " ", - ); + OTHER_LDFLAGS = "$(inherited) "; REACT_NATIVE_PATH = "${PODS_ROOT}/../../../node_modules/react-native"; SDKROOT = iphoneos; SWIFT_ACTIVE_COMPILATION_CONDITIONS = "$(inherited) DEBUG"; @@ -450,10 +455,7 @@ "-DFOLLY_CFG_NO_COROUTINES=1", "-DFOLLY_HAVE_CLOCK_GETTIME=1", ); - OTHER_LDFLAGS = ( - "$(inherited)", - " ", - ); + OTHER_LDFLAGS = "$(inherited) "; REACT_NATIVE_PATH = "${PODS_ROOT}/../../../node_modules/react-native"; SDKROOT = iphoneos; USE_HERMES = true; diff --git a/example/ios/Podfile.lock b/example/ios/Podfile.lock index 8daa84c8..753a295a 100644 --- a/example/ios/Podfile.lock +++ b/example/ios/Podfile.lock @@ -2740,80 +2740,80 @@ SPEC CHECKSUMS: glog: 5683914934d5b6e4240e497e0f4a3b42d1854183 hermes-engine: e7491a2038f2618c8cd444ed411a6deb350a3742 libwebp: 02b23773aedb6ff1fd38cec7a77b81414c6842a8 - NitroImage: 66fb94f9002324f2183e921cc01ca5e606ad0036 - NitroModules: 0ffbade1b2ebcfa12f3821c970b17fa369320810 - NitroWebImage: 8a7989cd5e255e34f4610795885f2f5fef843686 - RCT-Folly: 59ec0ac1f2f39672a0c6e6cecdd39383b764646f + NitroImage: 447896fa03fe0de5213c2dde23752b87d61beced + NitroModules: 1dc73273d6e3aee937eec41ef3ed9470804b9884 + NitroWebImage: 02e8aa5d5555b0d6791d13fc670f91bae325cc73 + RCT-Folly: 846fda9475e61ec7bcbf8a3fe81edfcaeb090669 RCTDeprecation: 0735ab4f6b3ec93a7f98187b5da74d7916e2cf4c RCTRequired: 8fcc7801bfc433072287b0f24a662e2816e89d0c RCTTypeSafety: 2b2be515d6b968bcba7a68c4179d8199bd8c9b58 React: 1000c0e96d8fb9fbdaf13f7d31d0b09db3cbb4ac React-callinvoker: 7e52661bfaf5d8881a9cee049792627a00001fbe - React-Core: a9128dd77ec52432727bfbec8c55d17189f6c039 - React-CoreModules: 4597116bd78ae2b183547e3700be0dc9537918e9 - React-cxxreact: e3a02f535cc1f1b547ac1baafe6ac25552352362 + React-Core: 949b436ddfe76cf47ac96375152de2f3506a8421 + React-CoreModules: 0f27580d0d82d430fa4f2cf4d970b6ad1120d63a + React-cxxreact: 48754f11f47a29ea4800cbdd694c10f874a26b9b React-debug: 7a23d96f709f437c5e08973d6e06d0a54dd180a1 - React-defaultsnativemodule: f01b6e58a23efe4fc8d74db7dadeea112908f5d5 - React-domnativemodule: 2d9796d40ab675e0f91ae8aae26c796b6e9a7499 - React-Fabric: f4344b3a882292783de9a5404852023b6c4fdd2d - React-FabricComponents: 7c51eb1619473ae3ed92d8bbf5d5dd3be0c5ef9d - React-FabricImage: 9e743575e67a9c14242bec3ae0e26663eed641bb - React-featureflags: 5188951cc2fc81f4d249dc37e8f96dca7ef50e96 - React-featureflagsnativemodule: 0fa7473065377ca4e5651c75614796326ef57aa8 - React-graphics: f65ecd0a8c70f9c7dcdae322851c19b21c83ec27 - React-hermes: 8418dae38a0513aa66aaa0a1b0904e55c4448644 - React-idlecallbacksnativemodule: 540d6f743fcb595b26da8b182b28c878a1176a96 - React-ImageManager: 5f9f1e33611a852d21a63e1de76d211fb04ac935 - React-jserrorhandler: 9c0a7d69cd07c9ae08fab3a61150d526c0174c83 - React-jsi: b711b7a11d77357beb95fa2eabd30c1ae34dcf40 - React-jsiexecutor: 0d1c78e666c5be71ff7c0ff5ea7fb043e5b1f14c - React-jsinspector: 5fabd9f0be9390d5b5eb5fc88a8965d97e0c14ac - React-jsinspectorcdp: e78c65e25253999c0efd5e23c99e649e02fd0244 - React-jsinspectornetwork: b02c6f7fe00e12b575a7faea0ed9ec9ddbc1c20f - React-jsinspectortracing: c6d8da3c8bcd939b8dcfd5113e247d56af932e1b - React-jsitooling: 4ca9b158d65909590daf6bf30a345b663eb71964 - React-jsitracing: d9e9378d5a3e05febea2164a5d0c5fab06492872 - React-logger: 839abfd18a3fbdf88132824de584b226d0c5cbce - React-Mapbuffer: bd5b1120c9bbaac6203eb288735e239f04e03009 - React-microtasksnativemodule: 10892b00e612d79436022a11e5bc8bdf468a284f - react-native-safe-area-context: 54d812805f3c4e08a4580ad086cbde1d8780c2e4 - React-NativeModulesApple: 3f9e97a4a90eeec1ceade511f973b277632650bb + React-defaultsnativemodule: 569d9222a701ed3dc60a60b2ce066b5bd88da059 + React-domnativemodule: 34474bda3973bfd0ca2ea9f1b3db20db5d504cc7 + React-Fabric: 45c3e9b112075451e592f0e008cabd4b82575355 + React-FabricComponents: a428f23938c27a073baacc069d484b3478df85f3 + React-FabricImage: 4375129ba8a26e8a7074af1c2468870fb8aab723 + React-featureflags: ed973a134993f3be204d0b2d385d386603c9a0af + React-featureflagsnativemodule: aa3e1dc86bc185344d4875e7cb40cce0bd28de76 + React-graphics: b5b8709a8216075bb6a5f9e7bb68881212d924ee + React-hermes: c543ffa2866304c582bdcb135c184e0f776f0d0b + React-idlecallbacksnativemodule: f19c4060b12fffc3ad33ce5de190338751b462ef + React-ImageManager: ecaf317aa5dff5eebba178b0813ef998c62547ea + React-jserrorhandler: 92eea1ee4f8c56b466b34e0065def59805e5d3a9 + React-jsi: 7336786a4a14c473d104e6b37df935620d218fcd + React-jsiexecutor: 7c750f5b63fbc071d0f0e56e86f1a1589914f7b1 + React-jsinspector: da5f336c1aa174a05885d061559a92e1d07b8a80 + React-jsinspectorcdp: 0e807e4c2dc8ae8a07f0a6bfe50377f442079ba3 + React-jsinspectornetwork: 3399384f2b6b70b287d8b9675452af4cec21dc65 + React-jsinspectortracing: 030af0e9dca9a4eaa1d0ba258c7bd859fb90f61d + React-jsitooling: f8ed67814b17ebb124c48fccdf587ee1e02f16f4 + React-jsitracing: 5cf6b84d46a4653895e30956a0ce3a315244c10a + React-logger: 04ce9229cb57db2c2a8164eaec1105f89da7fb22 + React-Mapbuffer: e402e7a0535b2213c50727553621480fe8cd8ade + React-microtasksnativemodule: a63ce5595016996a9bac1f10c70a7a7fe6506649 + react-native-safe-area-context: c00143b4823773bba23f2f19f85663ae89ceb460 + React-NativeModulesApple: b3766e1f87b08064ebc459b9e1538da2447ca874 React-oscompat: 34f3d3c06cadcbc470bc4509c717fb9b919eaa8b - React-perflogger: 95dff8cc9901777360716cbdcb2998849f133a4f - React-performancetimeline: 2937a27399b52ca8baf46f22c39087f617e626b5 + React-perflogger: a1edb025fd5d44f61bf09307e248f7608d7b2dcf + React-performancetimeline: 1f86dc9782e3fe78727c5fbb3e2178b9fd1aa6fd React-RCTActionSheet: 550c9c6c2e7dcd85a51954dc08e2f3837a148e7c - React-RCTAnimation: 0008bfe273566acd3128da13598073383325ac7a - React-RCTAppDelegate: 8b9452baef5548856a22f4710d4135cf68746cf5 - React-RCTBlob: 60006ab743e5fd807aaf536092f5ce86e87df526 - React-RCTFabric: 8d5d1006b3812c35fd0f37c117ff7bcf6449e20d - React-RCTFBReactNativeSpec: 3cb4265fa9a4e4f8250ae89feb345edc542731da - React-RCTImage: f40a2ee0f79c1666e8b81da4ea2d9d1182c94962 - React-RCTLinking: cfe6995bdd8d08d0bb0df12771f4d28fd5fd54ff - React-RCTNetwork: 565c0cd46313f2cad0e4db70a44958b2842c372b - React-RCTRuntime: 971a71a42d8979475a380e5179083302e5506cdd - React-RCTSettings: afcec6060d916e9c0410004ad8419d45f9dbcd36 - React-RCTText: 952f2a1b618d3f3872e7e5a82aefc5e5082c59aa - React-RCTVibration: 2a7e7497ffefa135c7f0fee8ee10e3505ab5cc61 + React-RCTAnimation: 19d4bb6d2190983d1354b096b7b65dbd591924da + React-RCTAppDelegate: 6c71d16eef920831a312ff363355fc3b99c02a98 + React-RCTBlob: b81a0cffe1a083bcf9d8aa9f27f4d37864579e90 + React-RCTFabric: 01005d2fa799bba6e21aae18820498f56fe0be5f + React-RCTFBReactNativeSpec: 5adb84a81c4ed7a1f2661835d166e4b2c4320cd4 + React-RCTImage: 607e5e373fb56d72417464bd82e8046af81ab502 + React-RCTLinking: 301434c7bf1100458be5a3866326ba33491e3687 + React-RCTNetwork: a118a47bd123ac96c9877e04f5731a1d6545aba5 + React-RCTRuntime: 85fdbf469fe8a12c4db6c836731b190efc33d11d + React-RCTSettings: 5a5aa2cf9ac40f7a8897cc0f9d945ac803886604 + React-RCTText: e6e00bee9847a8af1218079b73c8bfed16c75b8d + React-RCTVibration: 5a05fa0ef05ee73d074a3314e57586afc969f1ba React-rendererconsistency: c2cb23365f4a7b511893748fe8cad1830bbae637 - React-renderercss: 621b2b85af14694e93c2bcd63986fb57bcceab2e - React-rendererdebug: 4ba0769131e20347b900757fcac3c7919b27080c - React-RuntimeApple: c1a211351c14d35805d45a94094cfb3e5649552c - React-RuntimeCore: b7c7d8dffa3728a9e9616e0e8b5b6b41037ebcca - React-runtimeexecutor: e931e48afc888fe459f6ffb481971e23bb34f7ee - React-RuntimeHermes: 5763230801ee57d9f414818f48e44b874f3ce1be - React-runtimescheduler: b2e99f9702705fc8c11cf3c51f9911f478ee2210 + React-renderercss: 0c1472d6572c05e493aee476598c3ed6234b6c33 + React-rendererdebug: d6335da9730fa5a151537aa976a16d48de6135e2 + React-RuntimeApple: 5684c2a5d8768e5728a5817c21e5dba798d54c58 + React-RuntimeCore: 52428a1b48fb3c50ddf4dd5eee494486e4ecffc6 + React-runtimeexecutor: 1b4e99e5c27d2cb8bdeca9773ff5f1a8eac7709c + React-RuntimeHermes: a688639233a3ea44b4f8e4d448f51943d7e00815 + React-runtimescheduler: b833f0fc8c788329a497e93f55ce30508f56307a React-timing: 25e8229ad1cf6874e9f0711515213cb2bc322215 - React-utils: 7ea6e4d300c43a763e4e08091413aec962588f93 - ReactAppDependencyProvider: 562d731311d0524a577cf8a01faa97874bacbdfe - ReactCodegen: 0fc801cfa34581b2acfb9568ef6180042043826a - ReactCommon: c235ebd26d63fde9a2dfa72cee9f8294b910fee1 - RNFastImage: 5c9c9fed9c076e521b3f509fe79e790418a544e8 - RNScreens: ec8bdc9f024d5828e5adf4f5e8870d5260cff616 + React-utils: 068cec677032ba78ca0700f2dcbe6d08a0939647 + ReactAppDependencyProvider: c91900fa724baee992f01c05eeb4c9e01a807f78 + ReactCodegen: c3a2e945d68bcf8839624acaf1b276acbb41e9ba + ReactCommon: 116d6ee71679243698620d8cd9a9042541e44aa6 + RNFastImage: 462a183c4b0b6b26fdfd639e1ed6ba37536c3b87 + RNScreens: afaf526a9c804c3b4503f950cf3e67ed81e29ada SDWebImage: a7f831e1a65eb5e285e3fb046a23fcfbf08e696d SDWebImageWebPCoder: 908b83b6adda48effe7667cd2b7f78c897e5111d SocketRocket: d4aabe649be1e368d1318fdf28a022d714d65748 - Yoga: b01392348aeea02064c21a2762a42893d82b60a7 + Yoga: 00013dd9cde63a2d98e8002fcc4f5ddb66c10782 PODFILE CHECKSUM: 8c90c25c7a6bc16ec7b3ed7968df16467ab0fc35 -COCOAPODS: 1.15.2 +COCOAPODS: 1.16.2 diff --git a/example/src/NitroImageTab.tsx b/example/src/NitroImageTab.tsx index 850af923..4a12e8c9 100644 --- a/example/src/NitroImageTab.tsx +++ b/example/src/NitroImageTab.tsx @@ -18,6 +18,7 @@ export function NitroImageTab() { image={{ url: url }} style={styles.image} resizeMode="cover" + onLoad={() => console.log(`[NitroImage] onLoad: ${url}`)} /> )} /> diff --git a/packages/react-native-nitro-image/android/src/main/java/com/margelo/nitro/image/HybridImageView.kt b/packages/react-native-nitro-image/android/src/main/java/com/margelo/nitro/image/HybridImageView.kt index 4e12b133..72c2a53c 100644 --- a/packages/react-native-nitro-image/android/src/main/java/com/margelo/nitro/image/HybridImageView.kt +++ b/packages/react-native-nitro-image/android/src/main/java/com/margelo/nitro/image/HybridImageView.kt @@ -48,6 +48,7 @@ class HybridImageView(context: Context): HybridNitroImageViewSpec(), RecyclableV resetImageBeforeLoad = field != value field = value } + override var onLoad: (() -> Unit)? = null override fun prepareForRecycle() { onDisappear() @@ -70,6 +71,7 @@ class HybridImageView(context: Context): HybridNitroImageViewSpec(), RecyclableV // Image if (actualImage is HybridImage) { imageView.setImageBitmap(actualImage.bitmap) + onLoad?.invoke() } else { throw Error("Image is a different type than HybridImage!") } diff --git a/packages/react-native-nitro-image/ios/HybridImageView.swift b/packages/react-native-nitro-image/ios/HybridImageView.swift index 96f47848..d47e0ca1 100644 --- a/packages/react-native-nitro-image/ios/HybridImageView.swift +++ b/packages/react-native-nitro-image/ios/HybridImageView.swift @@ -37,6 +37,7 @@ class HybridImageView: HybridNitroImageViewSpec { resetImageBeforeLoad = recyclingKey != oldValue } } + var onLoad: (() -> Void)? = nil private func updateResizeMode() { let mode = resizeMode ?? .cover @@ -60,6 +61,7 @@ class HybridImageView: HybridNitroImageViewSpec { fatalError("Can't set `image` to a type that doesn't conform to `NativeImage`!") } view.image = image.uiImage + onLoad?() case .second: // Image Loader - trigger a load or drop didSetImageLoader() diff --git a/packages/react-native-nitro-image/nitrogen/generated/android/NitroImageOnLoad.cpp b/packages/react-native-nitro-image/nitrogen/generated/android/NitroImageOnLoad.cpp index e3134c60..df603950 100644 --- a/packages/react-native-nitro-image/nitrogen/generated/android/NitroImageOnLoad.cpp +++ b/packages/react-native-nitro-image/nitrogen/generated/android/NitroImageOnLoad.cpp @@ -21,6 +21,7 @@ #include "JHybridImageLoaderFactorySpec.hpp" #include "JHybridImageUtilsSpec.hpp" #include "JHybridNitroImageViewSpec.hpp" +#include "JFunc_void.hpp" #include "views/JHybridNitroImageViewStateUpdater.hpp" #include @@ -76,6 +77,7 @@ void registerAllNatives() { margelo::nitro::image::JHybridImageLoaderFactorySpec::CxxPart::registerNatives(); margelo::nitro::image::JHybridImageUtilsSpec::CxxPart::registerNatives(); margelo::nitro::image::JHybridNitroImageViewSpec::CxxPart::registerNatives(); + margelo::nitro::image::JFunc_void_cxx::registerNatives(); margelo::nitro::image::views::JHybridNitroImageViewStateUpdater::registerNatives(); // Register Nitro Hybrid Objects diff --git a/packages/react-native-nitro-image/nitrogen/generated/android/c++/JFunc_void.hpp b/packages/react-native-nitro-image/nitrogen/generated/android/c++/JFunc_void.hpp new file mode 100644 index 00000000..285f886c --- /dev/null +++ b/packages/react-native-nitro-image/nitrogen/generated/android/c++/JFunc_void.hpp @@ -0,0 +1,75 @@ +/// +/// JFunc_void.hpp +/// This file was generated by nitrogen. DO NOT MODIFY THIS FILE. +/// https://github.com/mrousavy/nitro +/// Copyright © Marc Rousavy @ Margelo +/// + +#pragma once + +#include +#include + +#include +#include + +namespace margelo::nitro::image { + + using namespace facebook; + + /** + * Represents the Java/Kotlin callback `() -> Unit`. + * This can be passed around between C++ and Java/Kotlin. + */ + struct JFunc_void: public jni::JavaClass { + public: + static auto constexpr kJavaDescriptor = "Lcom/margelo/nitro/image/Func_void;"; + + public: + /** + * Invokes the function this `JFunc_void` instance holds through JNI. + */ + void invoke() const { + static const auto method = javaClassStatic()->getMethod("invoke"); + method(self()); + } + }; + + /** + * An implementation of Func_void that is backed by a C++ implementation (using `std::function<...>`) + */ + class JFunc_void_cxx final: public jni::HybridClass { + public: + static jni::local_ref fromCpp(const std::function& func) { + return JFunc_void_cxx::newObjectCxxArgs(func); + } + + public: + /** + * Invokes the C++ `std::function<...>` this `JFunc_void_cxx` instance holds. + */ + void invoke_cxx() { + _func(); + } + + public: + [[nodiscard]] + inline const std::function& getFunction() const { + return _func; + } + + public: + static auto constexpr kJavaDescriptor = "Lcom/margelo/nitro/image/Func_void_cxx;"; + static void registerNatives() { + registerHybrid({makeNativeMethod("invoke_cxx", JFunc_void_cxx::invoke_cxx)}); + } + + private: + explicit JFunc_void_cxx(const std::function& func): _func(func) { } + + private: + friend HybridBase; + std::function _func; + }; + +} // namespace margelo::nitro::image diff --git a/packages/react-native-nitro-image/nitrogen/generated/android/c++/JHybridNitroImageViewSpec.cpp b/packages/react-native-nitro-image/nitrogen/generated/android/c++/JHybridNitroImageViewSpec.cpp index cdb27588..d98b0e3c 100644 --- a/packages/react-native-nitro-image/nitrogen/generated/android/c++/JHybridNitroImageViewSpec.cpp +++ b/packages/react-native-nitro-image/nitrogen/generated/android/c++/JHybridNitroImageViewSpec.cpp @@ -14,11 +14,14 @@ namespace margelo::nitro::image { class HybridImageLoaderSpec; } // Forward declaration of `ResizeMode` to properly resolve imports. namespace margelo::nitro::image { enum class ResizeMode; } +#include +#include +#include "JFunc_void.hpp" +#include #include #include "HybridImageSpec.hpp" #include "HybridImageLoaderSpec.hpp" #include -#include #include "JVariant_HybridImageSpec_HybridImageLoaderSpec.hpp" #include "JHybridImageSpec.hpp" #include "JHybridImageLoaderSpec.hpp" @@ -56,6 +59,23 @@ namespace margelo::nitro::image { } // Properties + std::optional> JHybridNitroImageViewSpec::getOnLoad() { + static const auto method = _javaPart->javaClassStatic()->getMethod()>("getOnLoad_cxx"); + auto __result = method(_javaPart); + return __result != nullptr ? std::make_optional([&]() -> std::function { + if (__result->isInstanceOf(JFunc_void_cxx::javaClassStatic())) [[likely]] { + auto downcast = jni::static_ref_cast(__result); + return downcast->cthis()->getFunction(); + } else { + auto __resultRef = jni::make_global(__result); + return JNICallable(std::move(__resultRef)); + } + }()) : std::nullopt; + } + void JHybridNitroImageViewSpec::setOnLoad(const std::optional>& onLoad) { + static const auto method = _javaPart->javaClassStatic()->getMethod /* onLoad */)>("setOnLoad_cxx"); + method(_javaPart, onLoad.has_value() ? JFunc_void_cxx::fromCpp(onLoad.value()) : nullptr); + } std::optional, std::shared_ptr>> JHybridNitroImageViewSpec::getImage() { static const auto method = _javaPart->javaClassStatic()->getMethod()>("getImage"); auto __result = method(_javaPart); diff --git a/packages/react-native-nitro-image/nitrogen/generated/android/c++/JHybridNitroImageViewSpec.hpp b/packages/react-native-nitro-image/nitrogen/generated/android/c++/JHybridNitroImageViewSpec.hpp index d5c51091..4c7ac355 100644 --- a/packages/react-native-nitro-image/nitrogen/generated/android/c++/JHybridNitroImageViewSpec.hpp +++ b/packages/react-native-nitro-image/nitrogen/generated/android/c++/JHybridNitroImageViewSpec.hpp @@ -50,6 +50,8 @@ namespace margelo::nitro::image { public: // Properties + std::optional> getOnLoad() override; + void setOnLoad(const std::optional>& onLoad) override; std::optional, std::shared_ptr>> getImage() override; void setImage(const std::optional, std::shared_ptr>>& image) override; std::optional getResizeMode() override; diff --git a/packages/react-native-nitro-image/nitrogen/generated/android/c++/views/JHybridNitroImageViewStateUpdater.cpp b/packages/react-native-nitro-image/nitrogen/generated/android/c++/views/JHybridNitroImageViewStateUpdater.cpp index 81eb6e4c..a01be797 100644 --- a/packages/react-native-nitro-image/nitrogen/generated/android/c++/views/JHybridNitroImageViewStateUpdater.cpp +++ b/packages/react-native-nitro-image/nitrogen/generated/android/c++/views/JHybridNitroImageViewStateUpdater.cpp @@ -37,6 +37,10 @@ void JHybridNitroImageViewStateUpdater::updateViewProps(jni::alias_refonLoad.isDirty) { + hybridView->setOnLoad(props->onLoad.value); + props->onLoad.isDirty = false; + } if (props->image.isDirty) { hybridView->setImage(props->image.value); props->image.isDirty = false; diff --git a/packages/react-native-nitro-image/nitrogen/generated/android/kotlin/com/margelo/nitro/image/Func_void.kt b/packages/react-native-nitro-image/nitrogen/generated/android/kotlin/com/margelo/nitro/image/Func_void.kt new file mode 100644 index 00000000..fdbd6124 --- /dev/null +++ b/packages/react-native-nitro-image/nitrogen/generated/android/kotlin/com/margelo/nitro/image/Func_void.kt @@ -0,0 +1,80 @@ +/// +/// Func_void.kt +/// This file was generated by nitrogen. DO NOT MODIFY THIS FILE. +/// https://github.com/mrousavy/nitro +/// Copyright © Marc Rousavy @ Margelo +/// + +package com.margelo.nitro.image + +import androidx.annotation.Keep +import com.facebook.jni.HybridData +import com.facebook.proguard.annotations.DoNotStrip +import dalvik.annotation.optimization.FastNative + + +/** + * Represents the JavaScript callback `() => 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: () -> 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(): Unit +} + +/** + * Represents the JavaScript callback `() => 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_cxx: Func_void { + @DoNotStrip + @Keep + private val mHybridData: HybridData + + @DoNotStrip + @Keep + private constructor(hybridData: HybridData) { + mHybridData = hybridData + } + + @DoNotStrip + @Keep + override fun invoke(): Unit + = invoke_cxx() + + @FastNative + private external fun invoke_cxx(): Unit +} + +/** + * Represents the JavaScript callback `() => void`. + * This is implemented in Java/Kotlin, via a `() -> Unit`. + * The callback is always coming from native. + */ +@DoNotStrip +@Keep +@Suppress("ClassName", "RedundantUnitReturnType", "unused") +class Func_void_java(private val function: () -> Unit): Func_void { + @DoNotStrip + @Keep + override fun invoke(): Unit { + return this.function() + } +} diff --git a/packages/react-native-nitro-image/nitrogen/generated/android/kotlin/com/margelo/nitro/image/HybridNitroImageViewSpec.kt b/packages/react-native-nitro-image/nitrogen/generated/android/kotlin/com/margelo/nitro/image/HybridNitroImageViewSpec.kt index 2781d821..6c9b2299 100644 --- a/packages/react-native-nitro-image/nitrogen/generated/android/kotlin/com/margelo/nitro/image/HybridNitroImageViewSpec.kt +++ b/packages/react-native-nitro-image/nitrogen/generated/android/kotlin/com/margelo/nitro/image/HybridNitroImageViewSpec.kt @@ -26,6 +26,20 @@ import com.margelo.nitro.views.HybridView ) abstract class HybridNitroImageViewSpec: HybridView() { // Properties + abstract var onLoad: (() -> Unit)? + + private var onLoad_cxx: Func_void? + @Keep + @DoNotStrip + get() { + return onLoad?.let { Func_void_java(it) } + } + @Keep + @DoNotStrip + set(value) { + onLoad = value?.let { it } + } + @get:DoNotStrip @get:Keep @set:DoNotStrip diff --git a/packages/react-native-nitro-image/nitrogen/generated/ios/NitroImage-Swift-Cxx-Bridge.hpp b/packages/react-native-nitro-image/nitrogen/generated/ios/NitroImage-Swift-Cxx-Bridge.hpp index 9b216f61..e10fb972 100644 --- a/packages/react-native-nitro-image/nitrogen/generated/ios/NitroImage-Swift-Cxx-Bridge.hpp +++ b/packages/react-native-nitro-image/nitrogen/generated/ios/NitroImage-Swift-Cxx-Bridge.hpp @@ -540,6 +540,21 @@ namespace margelo::nitro::image::bridge::swift { return Result::withError(error); } + // pragma MARK: std::optional> + /** + * Specialized version of `std::optional>`. + */ + using std__optional_std__function_void____ = std::optional>; + inline std::optional> create_std__optional_std__function_void____(const std::function& value) noexcept { + return std::optional>(value); + } + inline bool has_value_std__optional_std__function_void____(const std::optional>& optional) noexcept { + return optional.has_value(); + } + inline std::function get_std__optional_std__function_void____(const std::optional>& optional) noexcept { + return optional.value(); + } + // pragma MARK: std::variant, std::shared_ptr> /** * Wrapper struct for `std::variant, std::shared_ptr>`. diff --git a/packages/react-native-nitro-image/nitrogen/generated/ios/NitroImage-Swift-Cxx-Umbrella.hpp b/packages/react-native-nitro-image/nitrogen/generated/ios/NitroImage-Swift-Cxx-Umbrella.hpp index 50d09b09..6ae2f865 100644 --- a/packages/react-native-nitro-image/nitrogen/generated/ios/NitroImage-Swift-Cxx-Umbrella.hpp +++ b/packages/react-native-nitro-image/nitrogen/generated/ios/NitroImage-Swift-Cxx-Umbrella.hpp @@ -50,6 +50,7 @@ namespace margelo::nitro::image { enum class ResizeMode; } #include #include #include +#include #include #include #include diff --git a/packages/react-native-nitro-image/nitrogen/generated/ios/c++/HybridNitroImageViewSpecSwift.hpp b/packages/react-native-nitro-image/nitrogen/generated/ios/c++/HybridNitroImageViewSpecSwift.hpp index b794f603..5e8c8ed6 100644 --- a/packages/react-native-nitro-image/nitrogen/generated/ios/c++/HybridNitroImageViewSpecSwift.hpp +++ b/packages/react-native-nitro-image/nitrogen/generated/ios/c++/HybridNitroImageViewSpecSwift.hpp @@ -19,11 +19,12 @@ namespace margelo::nitro::image { class HybridImageLoaderSpec; } // Forward declaration of `ResizeMode` to properly resolve imports. namespace margelo::nitro::image { enum class ResizeMode; } +#include +#include #include #include "HybridImageSpec.hpp" #include "HybridImageLoaderSpec.hpp" #include -#include #include "ResizeMode.hpp" #include @@ -73,6 +74,13 @@ namespace margelo::nitro::image { public: // Properties + inline std::optional> getOnLoad() noexcept override { + auto __result = _swiftPart.getOnLoad(); + return __result; + } + inline void setOnLoad(const std::optional>& onLoad) noexcept override { + _swiftPart.setOnLoad(onLoad); + } inline std::optional, std::shared_ptr>> getImage() noexcept override { auto __result = _swiftPart.getImage(); return __result; diff --git a/packages/react-native-nitro-image/nitrogen/generated/ios/c++/views/HybridNitroImageViewComponent.mm b/packages/react-native-nitro-image/nitrogen/generated/ios/c++/views/HybridNitroImageViewComponent.mm index e3ed3b73..2f1b9348 100644 --- a/packages/react-native-nitro-image/nitrogen/generated/ios/c++/views/HybridNitroImageViewComponent.mm +++ b/packages/react-native-nitro-image/nitrogen/generated/ios/c++/views/HybridNitroImageViewComponent.mm @@ -72,6 +72,11 @@ - (void) updateProps:(const std::shared_ptr&)props // 2. Update each prop individually swiftPart.beforeUpdate(); + // onLoad: optional + if (newViewProps.onLoad.isDirty) { + swiftPart.setOnLoad(newViewProps.onLoad.value); + newViewProps.onLoad.isDirty = false; + } // image: optional if (newViewProps.image.isDirty) { swiftPart.setImage(newViewProps.image.value); diff --git a/packages/react-native-nitro-image/nitrogen/generated/ios/swift/HybridNitroImageViewSpec.swift b/packages/react-native-nitro-image/nitrogen/generated/ios/swift/HybridNitroImageViewSpec.swift index 07709254..33fb0bc5 100644 --- a/packages/react-native-nitro-image/nitrogen/generated/ios/swift/HybridNitroImageViewSpec.swift +++ b/packages/react-native-nitro-image/nitrogen/generated/ios/swift/HybridNitroImageViewSpec.swift @@ -10,6 +10,7 @@ import NitroModules /// See ``HybridNitroImageViewSpec`` public protocol HybridNitroImageViewSpec_protocol: HybridObject, HybridView { // Properties + var onLoad: (() -> Void)? { get set } var image: Variant__any_HybridImageSpec___any_HybridImageLoaderSpec_? { get set } var resizeMode: ResizeMode? { get set } var recyclingKey: String? { get set } diff --git a/packages/react-native-nitro-image/nitrogen/generated/ios/swift/HybridNitroImageViewSpec_cxx.swift b/packages/react-native-nitro-image/nitrogen/generated/ios/swift/HybridNitroImageViewSpec_cxx.swift index 3e948702..60f0b9d6 100644 --- a/packages/react-native-nitro-image/nitrogen/generated/ios/swift/HybridNitroImageViewSpec_cxx.swift +++ b/packages/react-native-nitro-image/nitrogen/generated/ios/swift/HybridNitroImageViewSpec_cxx.swift @@ -121,6 +121,38 @@ open class HybridNitroImageViewSpec_cxx { } // Properties + public final var onLoad: bridge.std__optional_std__function_void____ { + @inline(__always) + get { + return { () -> bridge.std__optional_std__function_void____ in + if let __unwrappedValue = self.__implementation.onLoad { + return bridge.create_std__optional_std__function_void____({ () -> bridge.Func_void in + let __closureWrapper = Func_void(__unwrappedValue) + return bridge.create_Func_void(__closureWrapper.toUnsafe()) + }()) + } else { + return .init() + } + }() + } + @inline(__always) + set { + self.__implementation.onLoad = { () -> (() -> Void)? in + if bridge.has_value_std__optional_std__function_void____(newValue) { + let __unwrapped = bridge.get_std__optional_std__function_void____(newValue) + return { () -> () -> Void in + let __wrappedFunction = bridge.wrap_Func_void(__unwrapped) + return { () -> Void in + __wrappedFunction.call() + } + }() + } else { + return nil + } + }() + } + } + public final var image: bridge.std__optional_std__variant_std__shared_ptr_HybridImageSpec___std__shared_ptr_HybridImageLoaderSpec___ { @inline(__always) get { diff --git a/packages/react-native-nitro-image/nitrogen/generated/shared/c++/HybridNitroImageViewSpec.cpp b/packages/react-native-nitro-image/nitrogen/generated/shared/c++/HybridNitroImageViewSpec.cpp index 04c55de6..85312637 100644 --- a/packages/react-native-nitro-image/nitrogen/generated/shared/c++/HybridNitroImageViewSpec.cpp +++ b/packages/react-native-nitro-image/nitrogen/generated/shared/c++/HybridNitroImageViewSpec.cpp @@ -14,6 +14,8 @@ namespace margelo::nitro::image { HybridObject::loadHybridMethods(); // load custom methods/properties registerHybrids(this, [](Prototype& prototype) { + prototype.registerHybridGetter("onLoad", &HybridNitroImageViewSpec::getOnLoad); + prototype.registerHybridSetter("onLoad", &HybridNitroImageViewSpec::setOnLoad); prototype.registerHybridGetter("image", &HybridNitroImageViewSpec::getImage); prototype.registerHybridSetter("image", &HybridNitroImageViewSpec::setImage); prototype.registerHybridGetter("resizeMode", &HybridNitroImageViewSpec::getResizeMode); diff --git a/packages/react-native-nitro-image/nitrogen/generated/shared/c++/HybridNitroImageViewSpec.hpp b/packages/react-native-nitro-image/nitrogen/generated/shared/c++/HybridNitroImageViewSpec.hpp index 61e4e744..8a26dc9c 100644 --- a/packages/react-native-nitro-image/nitrogen/generated/shared/c++/HybridNitroImageViewSpec.hpp +++ b/packages/react-native-nitro-image/nitrogen/generated/shared/c++/HybridNitroImageViewSpec.hpp @@ -20,11 +20,12 @@ namespace margelo::nitro::image { class HybridImageLoaderSpec; } // Forward declaration of `ResizeMode` to properly resolve imports. namespace margelo::nitro::image { enum class ResizeMode; } +#include +#include #include #include "HybridImageSpec.hpp" #include "HybridImageLoaderSpec.hpp" #include -#include #include "ResizeMode.hpp" #include @@ -55,6 +56,8 @@ namespace margelo::nitro::image { public: // Properties + virtual std::optional> getOnLoad() = 0; + virtual void setOnLoad(const std::optional>& onLoad) = 0; virtual std::optional, std::shared_ptr>> getImage() = 0; virtual void setImage(const std::optional, std::shared_ptr>>& image) = 0; virtual std::optional getResizeMode() = 0; diff --git a/packages/react-native-nitro-image/nitrogen/generated/shared/c++/views/HybridNitroImageViewComponent.cpp b/packages/react-native-nitro-image/nitrogen/generated/shared/c++/views/HybridNitroImageViewComponent.cpp index cdae2e81..b5f595e9 100644 --- a/packages/react-native-nitro-image/nitrogen/generated/shared/c++/views/HybridNitroImageViewComponent.cpp +++ b/packages/react-native-nitro-image/nitrogen/generated/shared/c++/views/HybridNitroImageViewComponent.cpp @@ -26,6 +26,16 @@ namespace margelo::nitro::image::views { const HybridNitroImageViewProps& sourceProps, const react::RawProps& rawProps): react::ViewProps(context, sourceProps, rawProps, filterObjectKeys), + onLoad([&]() -> CachedProp>> { + try { + const react::RawValue* rawValue = rawProps.at("onLoad", nullptr, nullptr); + if (rawValue == nullptr) return sourceProps.onLoad; + const auto& [runtime, value] = (std::pair)*rawValue; + return CachedProp>>::fromRawValue(*runtime, value.asObject(*runtime).getProperty(*runtime, PropNameIDCache::get(*runtime, "f")), sourceProps.onLoad); + } catch (const std::exception& exc) { + throw std::runtime_error(std::string("NitroImageView.onLoad: ") + exc.what()); + } + }()), image([&]() -> CachedProp, std::shared_ptr>>> { try { const react::RawValue* rawValue = rawProps.at("image", nullptr, nullptr); @@ -69,6 +79,7 @@ namespace margelo::nitro::image::views { bool HybridNitroImageViewProps::filterObjectKeys(const std::string& propName) { switch (hashString(propName)) { + case hashString("onLoad"): return true; case hashString("image"): return true; case hashString("resizeMode"): return true; case hashString("recyclingKey"): return true; diff --git a/packages/react-native-nitro-image/nitrogen/generated/shared/c++/views/HybridNitroImageViewComponent.hpp b/packages/react-native-nitro-image/nitrogen/generated/shared/c++/views/HybridNitroImageViewComponent.hpp index 91248b9e..5bce9dd2 100644 --- a/packages/react-native-nitro-image/nitrogen/generated/shared/c++/views/HybridNitroImageViewComponent.hpp +++ b/packages/react-native-nitro-image/nitrogen/generated/shared/c++/views/HybridNitroImageViewComponent.hpp @@ -16,15 +16,15 @@ #include #include +#include +#include #include #include "HybridImageSpec.hpp" #include "HybridImageLoaderSpec.hpp" #include -#include #include "ResizeMode.hpp" #include #include "HybridNitroImageViewSpec.hpp" -#include namespace margelo::nitro::image::views { @@ -46,6 +46,7 @@ namespace margelo::nitro::image::views { const react::RawProps& rawProps); public: + CachedProp>> onLoad; CachedProp, std::shared_ptr>>> image; CachedProp> resizeMode; CachedProp> recyclingKey; diff --git a/packages/react-native-nitro-image/nitrogen/generated/shared/json/NitroImageViewConfig.json b/packages/react-native-nitro-image/nitrogen/generated/shared/json/NitroImageViewConfig.json index e376831b..6766af87 100644 --- a/packages/react-native-nitro-image/nitrogen/generated/shared/json/NitroImageViewConfig.json +++ b/packages/react-native-nitro-image/nitrogen/generated/shared/json/NitroImageViewConfig.json @@ -4,6 +4,7 @@ "bubblingEventTypes": {}, "directEventTypes": {}, "validAttributes": { + "onLoad": true, "image": true, "resizeMode": true, "recyclingKey": true, diff --git a/packages/react-native-nitro-image/src/NitroImage.tsx b/packages/react-native-nitro-image/src/NitroImage.tsx index 004ad210..fc86db52 100644 --- a/packages/react-native-nitro-image/src/NitroImage.tsx +++ b/packages/react-native-nitro-image/src/NitroImage.tsx @@ -1,6 +1,7 @@ // biome-ignore lint/correctness/noUnusedImports: Needed for JSX runtime import React from 'react' import type { HostComponent } from 'react-native' +import { callback } from 'react-native-nitro-modules' import type { AsyncImageSource } from './AsyncImageSource' import { NativeNitroImage } from './NativeNitroImage' import { useImageLoader } from './useImageLoader' @@ -30,7 +31,10 @@ export interface NitroImageProps extends Omit { * } * ``` */ -export function NitroImage({ image, ...props }: NitroImageProps) { +export function NitroImage({ image, onLoad, ...props }: NitroImageProps) { const actualImage = useImageLoader(image) - return + const wrappedOnLoad = onLoad ? callback(onLoad) : undefined + return ( + + ) } diff --git a/packages/react-native-nitro-image/src/specs/ImageView.nitro.ts b/packages/react-native-nitro-image/src/specs/ImageView.nitro.ts index 572a3a69..a9658f0f 100644 --- a/packages/react-native-nitro-image/src/specs/ImageView.nitro.ts +++ b/packages/react-native-nitro-image/src/specs/ImageView.nitro.ts @@ -16,6 +16,10 @@ import type { ImageLoader } from './ImageLoader.nitro' export type ResizeMode = 'cover' | 'contain' | 'center' | 'stretch' export interface NativeNitroImageViewProps extends HybridViewProps { + /** + * Called when the image has been loaded and is displayed in the view. + */ + onLoad?: () => void /** * Represents the image actually shown in this Image View. * - {@linkcode Image}: Shows a specific in-memory {@linkcode Image} diff --git a/packages/react-native-nitro-web-image/android/src/main/java/com/margelo/nitro/web/image/HybridWebImageLoader.kt b/packages/react-native-nitro-web-image/android/src/main/java/com/margelo/nitro/web/image/HybridWebImageLoader.kt index efa44355..047aead7 100644 --- a/packages/react-native-nitro-web-image/android/src/main/java/com/margelo/nitro/web/image/HybridWebImageLoader.kt +++ b/packages/react-native-nitro-web-image/android/src/main/java/com/margelo/nitro/web/image/HybridWebImageLoader.kt @@ -18,10 +18,12 @@ class HybridWebImageLoader(private val imageLoader: ImageLoader, } override fun requestImage(forView: HybridNitroImageViewSpec) { + val onLoad = forView.onLoad val imageView = forView.view as? ImageView ?: return imageView.load(url, imageLoader) { this.applyOptions(options) + listener(onSuccess = { _, _ -> onLoad?.invoke() }) } } diff --git a/packages/react-native-nitro-web-image/ios/HybridWebImageLoader.swift b/packages/react-native-nitro-web-image/ios/HybridWebImageLoader.swift index e95e8986..44dbce8f 100644 --- a/packages/react-native-nitro-web-image/ios/HybridWebImageLoader.swift +++ b/packages/react-native-nitro-web-image/ios/HybridWebImageLoader.swift @@ -32,6 +32,7 @@ class HybridWebImageLoader: HybridImageLoaderSpec { } func requestImage(forView view: (any HybridNitroImageViewSpec)) throws { + let onLoad = view.onLoad guard let view = view as? NativeImageView else { throw RuntimeError.error(withMessage: "Invalid view type!") } let webImageOptions = options?.toSDWebImageOptions() ?? [] @@ -39,7 +40,13 @@ class HybridWebImageLoader: HybridImageLoaderSpec { view.imageView.sd_setImage(with: url, placeholderImage: view.imageView.image, options: webImageOptions, - context: webImageContext) + context: webImageContext, + progress: nil, + completed: { [onLoad] image, error, cacheType, url in + if image != nil && error == nil { + onLoad?() + } + }) } func dropImage(forView view: (any HybridNitroImageViewSpec)) throws {