From a73df3de1009d1f988d7a07cca19ffc7dfad2bd5 Mon Sep 17 00:00:00 2001 From: Dave Mkpa-Eke Date: Tue, 26 May 2026 15:28:03 +0100 Subject: [PATCH 1/2] feat: cancel image requests when isActive prop is false --- example/ios/Podfile.lock | 182 ++++++++++-------- .../margelo/nitro/image/HybridImageView.kt | 22 ++- .../ios/HybridImageView.swift | 18 +- .../android/c++/JHybridNitroImageViewSpec.cpp | 5 +- .../android/c++/JHybridNitroImageViewSpec.hpp | 2 +- .../kotlin/com/margelo/nitro/image/Color.kt | 2 +- .../margelo/nitro/image/EncodedImageData.kt | 2 +- .../nitro/image/HybridNitroImageViewSpec.kt | 4 +- .../com/margelo/nitro/image/RawPixelData.kt | 2 +- .../ios/c++/HybridNitroImageViewSpecSwift.hpp | 7 +- .../ios/swift/HybridNitroImageViewSpec.swift | 2 +- .../swift/HybridNitroImageViewSpec_cxx.swift | 11 ++ .../shared/c++/HybridNitroImageViewSpec.cpp | 1 + .../shared/c++/HybridNitroImageViewSpec.hpp | 2 +- .../src/NitroImage.tsx | 26 ++- .../src/specs/ImageView.nitro.ts | 6 +- .../nitro/web/image/HybridWebImageLoader.kt | 4 +- .../ios/HybridWebImageLoader.swift | 3 +- .../nitro/web/image/AsyncImageLoadOptions.kt | 2 +- 19 files changed, 205 insertions(+), 98 deletions(-) diff --git a/example/ios/Podfile.lock b/example/ios/Podfile.lock index b2b3a496..28adf96c 100644 --- a/example/ios/Podfile.lock +++ b/example/ios/Podfile.lock @@ -3,8 +3,36 @@ PODS: - DoubleConversion (1.1.6) - fast_float (8.0.0) - FBLazyVector (0.81.0) - - fmt (11.0.2) + - fmt (12.1.0) - glog (0.3.5) + - HarnessUI (1.1.0): + - boost + - DoubleConversion + - fast_float + - fmt + - glog + - hermes-engine + - RCT-Folly + - RCT-Folly/Fabric + - RCTRequired + - RCTTypeSafety + - React-Core + - React-debug + - React-Fabric + - React-featureflags + - React-graphics + - React-ImageManager + - React-jsi + - React-NativeModulesApple + - React-RCTFabric + - React-renderercss + - React-rendererdebug + - React-utils + - ReactCodegen + - ReactCommon/turbomodule/bridging + - ReactCommon/turbomodule/core + - SocketRocket + - Yoga - hermes-engine (0.81.0): - hermes-engine/Pre-built (= 0.81.0) - hermes-engine/Pre-built (0.81.0) @@ -50,7 +78,7 @@ PODS: - ReactCommon/turbomodule/core - SocketRocket - Yoga - - NitroModules (0.35.5): + - NitroModules (0.35.7): - boost - DoubleConversion - fast_float @@ -115,20 +143,20 @@ PODS: - boost - DoubleConversion - fast_float (= 8.0.0) - - fmt (= 11.0.2) + - fmt (= 12.1.0) - glog - RCT-Folly/Default (= 2024.11.18.00) - RCT-Folly/Default (2024.11.18.00): - boost - DoubleConversion - fast_float (= 8.0.0) - - fmt (= 11.0.2) + - fmt (= 12.1.0) - glog - RCT-Folly/Fabric (2024.11.18.00): - boost - DoubleConversion - fast_float (= 8.0.0) - - fmt (= 11.0.2) + - fmt (= 12.1.0) - glog - RCTDeprecation (0.81.0) - RCTRequired (0.81.0) @@ -1822,7 +1850,7 @@ PODS: - React-RCTFBReactNativeSpec - ReactCommon/turbomodule/core - SocketRocket - - react-native-safe-area-context (5.6.2): + - react-native-safe-area-context (5.7.0): - boost - DoubleConversion - fast_float @@ -1840,8 +1868,8 @@ PODS: - React-graphics - React-ImageManager - React-jsi - - react-native-safe-area-context/common (= 5.6.2) - - react-native-safe-area-context/fabric (= 5.6.2) + - react-native-safe-area-context/common (= 5.7.0) + - react-native-safe-area-context/fabric (= 5.7.0) - React-NativeModulesApple - React-RCTFabric - React-renderercss @@ -1852,7 +1880,7 @@ PODS: - ReactCommon/turbomodule/core - SocketRocket - Yoga - - react-native-safe-area-context/common (5.6.2): + - react-native-safe-area-context/common (5.7.0): - boost - DoubleConversion - fast_float @@ -1880,7 +1908,7 @@ PODS: - ReactCommon/turbomodule/core - SocketRocket - Yoga - - react-native-safe-area-context/fabric (5.6.2): + - react-native-safe-area-context/fabric (5.7.0): - boost - DoubleConversion - fast_float @@ -2417,7 +2445,7 @@ PODS: - React-Core - SDWebImage (~> 5.11.1) - SDWebImageWebPCoder (~> 0.8.4) - - RNScreens (4.23.0): + - RNScreens (4.24.0): - boost - DoubleConversion - fast_float @@ -2444,10 +2472,10 @@ PODS: - ReactCodegen - ReactCommon/turbomodule/bridging - ReactCommon/turbomodule/core - - RNScreens/common (= 4.23.0) + - RNScreens/common (= 4.24.0) - SocketRocket - Yoga - - RNScreens/common (4.23.0): + - RNScreens/common (4.24.0): - boost - DoubleConversion - fast_float @@ -2492,6 +2520,7 @@ DEPENDENCIES: - FBLazyVector (from `../../node_modules/react-native/Libraries/FBLazyVector`) - fmt (from `../../node_modules/react-native/third-party-podspecs/fmt.podspec`) - glog (from `../../node_modules/react-native/third-party-podspecs/glog.podspec`) + - "HarnessUI (from `../../node_modules/@react-native-harness/ui`)" - hermes-engine (from `../../node_modules/react-native/sdks/hermes-engine/hermes-engine.podspec`) - NitroImage (from `../../node_modules/react-native-nitro-image`) - NitroModules (from `../../node_modules/react-native-nitro-modules`) @@ -2587,6 +2616,8 @@ EXTERNAL SOURCES: :podspec: "../../node_modules/react-native/third-party-podspecs/fmt.podspec" glog: :podspec: "../../node_modules/react-native/third-party-podspecs/glog.podspec" + HarnessUI: + :path: "../../node_modules/@react-native-harness/ui" hermes-engine: :podspec: "../../node_modules/react-native/sdks/hermes-engine/hermes-engine.podspec" :tag: hermes-2025-07-07-RNv0.81.0-e0fc67142ec0763c6b6153ca2bf96df815539782 @@ -2736,84 +2767,85 @@ SPEC CHECKSUMS: DoubleConversion: cb417026b2400c8f53ae97020b2be961b59470cb fast_float: b32c788ed9c6a8c584d114d0047beda9664e7cc6 FBLazyVector: a867936a67af0d09c37935a1b900a1a3c795b6d1 - fmt: a40bb5bd0294ea969aaaba240a927bd33d878cdd + fmt: 530618a01105dae0fa3a2f27c81ae11fa8f67eac glog: 5683914934d5b6e4240e497e0f4a3b42d1854183 + HarnessUI: 01740b858c62c55d42995d4ca459ead036b96c9a hermes-engine: e7491a2038f2618c8cd444ed411a6deb350a3742 libwebp: 02b23773aedb6ff1fd38cec7a77b81414c6842a8 - NitroImage: 75df80efc3bebd26e5fbe66a546ca83122773919 - NitroModules: 6e73058747e2da022cb4fca5654f39eaf61e3c4f - NitroWebImage: eb1228b7e7dff379f73865465f08b14cad0b7afa - RCT-Folly: 59ec0ac1f2f39672a0c6e6cecdd39383b764646f + NitroImage: 4ffcf183d975de179ae1662b7c3b4b3b37747c7e + NitroModules: 9ec4a2e0b9af22ba1f1f550e1dd9be94143afd18 + NitroWebImage: 688e0b088f33ab0bae13304526857370110c7757 + RCT-Folly: b29feb752b08042c62badaef7d453f3bb5e6ae23 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: befb5404eb8a16fdc07fa2bebab3568ecabcbb8a + 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: 7f643ee0fd1407dc5085c7795460bd93da113b8f SDWebImage: a7f831e1a65eb5e285e3fb046a23fcfbf08e696d SDWebImageWebPCoder: 908b83b6adda48effe7667cd2b7f78c897e5111d SocketRocket: d4aabe649be1e368d1318fdf28a022d714d65748 - Yoga: b01392348aeea02064c21a2762a42893d82b60a7 + Yoga: 00013dd9cde63a2d98e8002fcc4f5ddb66c10782 PODFILE CHECKSUM: 8c90c25c7a6bc16ec7b3ed7968df16467ab0fc35 -COCOAPODS: 1.15.2 +COCOAPODS: 1.16.2 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 672c3e2c..62dd57f5 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 @@ -49,6 +49,15 @@ class HybridImageView(context: Context): HybridNitroImageViewSpec(), RecyclableV field = value } + private var isActive: Boolean = true + + override fun setIsActive(isActive: Boolean) { + uiScope.launch { + this@HybridImageView.isActive = isActive + refreshLoadingState() + } + } + override fun prepareForRecycle() { onDisappear() imageView.setImageBitmap(null) @@ -75,13 +84,22 @@ class HybridImageView(context: Context): HybridNitroImageViewSpec(), RecyclableV } }, { _: HybridImageLoaderSpec -> - // ImageLoader - onAppear() + // ImageLoader - trigger a load or drop based on current visibility + isActive + refreshLoadingState() } ) } + private fun refreshLoadingState() { + if (imageView.isAttachedToWindow && isActive) { + onAppear() + } else { + onDisappear() + } + } + private fun onAppear() { + if (!isActive) return val imageLoader = image?.asSecondOrNull() ?: return try { if (resetImageBeforeLoad) { diff --git a/packages/react-native-nitro-image/ios/HybridImageView.swift b/packages/react-native-nitro-image/ios/HybridImageView.swift index 96f47848..21d32019 100644 --- a/packages/react-native-nitro-image/ios/HybridImageView.swift +++ b/packages/react-native-nitro-image/ios/HybridImageView.swift @@ -37,6 +37,14 @@ class HybridImageView: HybridNitroImageViewSpec { resetImageBeforeLoad = recyclingKey != oldValue } } + private var isActive: Bool = true + + func setIsActive(isActive: Bool) throws { + DispatchQueue.runOnMain { + self.isActive = isActive + self.refreshLoadingState() + } + } private func updateResizeMode() { let mode = resizeMode ?? .cover @@ -61,17 +69,16 @@ class HybridImageView: HybridNitroImageViewSpec { } view.image = image.uiImage case .second: - // Image Loader - trigger a load or drop - didSetImageLoader() + // Image Loader - trigger a load or drop based on current visibility + isActive + refreshLoadingState() case nil: // No Image view.image = nil } } - private func didSetImageLoader() { - // An ImageLoader was set - trigger an update (load or drop) - if view.isVisible { + private func refreshLoadingState() { + if view.isVisible && isActive { willShow() } else { willHide() @@ -92,6 +99,7 @@ extension HybridImageView: ViewLifecycleDelegate { } func willShow() { + guard isActive else { return } guard let imageLoader else { return } if resetImageBeforeLoad { view.image = nil 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..91c55b45 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 @@ -85,6 +85,9 @@ namespace margelo::nitro::image { } // Methods - + void JHybridNitroImageViewSpec::setIsActive(bool isActive) { + static const auto method = _javaPart->javaClassStatic()->getMethod("setIsActive"); + method(_javaPart, isActive); + } } // namespace margelo::nitro::image 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 42681e55..c05721f1 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 @@ -59,7 +59,7 @@ namespace margelo::nitro::image { public: // Methods - + void setIsActive(bool isActive) override; private: jni::global_ref _javaPart; diff --git a/packages/react-native-nitro-image/nitrogen/generated/android/kotlin/com/margelo/nitro/image/Color.kt b/packages/react-native-nitro-image/nitrogen/generated/android/kotlin/com/margelo/nitro/image/Color.kt index 8bd3a854..0b9a685c 100644 --- a/packages/react-native-nitro-image/nitrogen/generated/android/kotlin/com/margelo/nitro/image/Color.kt +++ b/packages/react-native-nitro-image/nitrogen/generated/android/kotlin/com/margelo/nitro/image/Color.kt @@ -43,7 +43,7 @@ data class Color( } override fun hashCode(): Int { - return arrayOf( + return arrayOf( r, g, b, diff --git a/packages/react-native-nitro-image/nitrogen/generated/android/kotlin/com/margelo/nitro/image/EncodedImageData.kt b/packages/react-native-nitro-image/nitrogen/generated/android/kotlin/com/margelo/nitro/image/EncodedImageData.kt index b07a2cfa..240af55c 100644 --- a/packages/react-native-nitro-image/nitrogen/generated/android/kotlin/com/margelo/nitro/image/EncodedImageData.kt +++ b/packages/react-native-nitro-image/nitrogen/generated/android/kotlin/com/margelo/nitro/image/EncodedImageData.kt @@ -43,7 +43,7 @@ data class EncodedImageData( } override fun hashCode(): Int { - return arrayOf( + return arrayOf( buffer, width, height, 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..fe5be03b 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 @@ -45,7 +45,9 @@ abstract class HybridNitroImageViewSpec: HybridView() { abstract var recyclingKey: String? // Methods - + @DoNotStrip + @Keep + abstract fun setIsActive(isActive: Boolean): Unit // Default implementation of `HybridObject.toString()` override fun toString(): String { diff --git a/packages/react-native-nitro-image/nitrogen/generated/android/kotlin/com/margelo/nitro/image/RawPixelData.kt b/packages/react-native-nitro-image/nitrogen/generated/android/kotlin/com/margelo/nitro/image/RawPixelData.kt index 1284ef23..0142bc18 100644 --- a/packages/react-native-nitro-image/nitrogen/generated/android/kotlin/com/margelo/nitro/image/RawPixelData.kt +++ b/packages/react-native-nitro-image/nitrogen/generated/android/kotlin/com/margelo/nitro/image/RawPixelData.kt @@ -43,7 +43,7 @@ data class RawPixelData( } override fun hashCode(): Int { - return arrayOf( + return arrayOf( buffer, width, height, 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..e240a8b1 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 @@ -97,7 +97,12 @@ namespace margelo::nitro::image { public: // Methods - + inline void setIsActive(bool isActive) override { + auto __result = _swiftPart.setIsActive(std::forward(isActive)); + if (__result.hasError()) [[unlikely]] { + std::rethrow_exception(__result.error()); + } + } private: NitroImage::HybridNitroImageViewSpec_cxx _swiftPart; 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..3a687471 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 @@ -15,7 +15,7 @@ public protocol HybridNitroImageViewSpec_protocol: HybridObject, HybridView { var recyclingKey: String? { get set } // Methods - + func setIsActive(isActive: Bool) throws -> Void } public extension HybridNitroImageViewSpec_protocol { 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 7f878b7c..fa5c5f7e 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 @@ -220,6 +220,17 @@ open class HybridNitroImageViewSpec_cxx { } // Methods + @inline(__always) + public final func setIsActive(isActive: Bool) -> bridge.Result_void_ { + do { + try self.__implementation.setIsActive(isActive: isActive) + return bridge.create_Result_void_() + } catch (let __error) { + let __exceptionPtr = __error.toCpp() + return bridge.create_Result_void_(__exceptionPtr) + } + } + public final func getView() -> UnsafeMutableRawPointer { return Unmanaged.passRetained(__implementation.view).toOpaque() } 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..34eaa3f8 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 @@ -20,6 +20,7 @@ namespace margelo::nitro::image { prototype.registerHybridSetter("resizeMode", &HybridNitroImageViewSpec::setResizeMode); prototype.registerHybridGetter("recyclingKey", &HybridNitroImageViewSpec::getRecyclingKey); prototype.registerHybridSetter("recyclingKey", &HybridNitroImageViewSpec::setRecyclingKey); + prototype.registerHybridMethod("setIsActive", &HybridNitroImageViewSpec::setIsActive); }); } 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..7c8146f2 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 @@ -64,7 +64,7 @@ namespace margelo::nitro::image { public: // Methods - + virtual void setIsActive(bool isActive) = 0; protected: // Hybrid Setup diff --git a/packages/react-native-nitro-image/src/NitroImage.tsx b/packages/react-native-nitro-image/src/NitroImage.tsx index 004ad210..ec6b2af3 100644 --- a/packages/react-native-nitro-image/src/NitroImage.tsx +++ b/packages/react-native-nitro-image/src/NitroImage.tsx @@ -1,8 +1,8 @@ -// biome-ignore lint/correctness/noUnusedImports: Needed for JSX runtime import React from 'react' import type { HostComponent } from 'react-native' import type { AsyncImageSource } from './AsyncImageSource' import { NativeNitroImage } from './NativeNitroImage' +import type { NitroImageView } from './specs/ImageView.nitro' import { useImageLoader } from './useImageLoader' type ReactProps = T extends HostComponent ? P : never @@ -10,6 +10,12 @@ type NativeImageProps = ReactProps export interface NitroImageProps extends Omit { image: AsyncImageSource + /** + * When `false`, cancels the in-flight {@linkcode ImageLoader} request. + * When `true`, resumes it. Typically driven by `useIsFocused()`. + * @default true + */ + isActive?: boolean } /** @@ -30,7 +36,21 @@ export interface NitroImageProps extends Omit { * } * ``` */ -export function NitroImage({ image, ...props }: NitroImageProps) { +export function NitroImage({ + image, + isActive = true, + ...props +}: NitroImageProps) { const actualImage = useImageLoader(image) - return + const viewRef = React.useRef(null) + React.useEffect(() => { + viewRef.current?.setIsActive(isActive) + }, [isActive]) + return ( + (viewRef.current = r) }} + {...props} + /> + ) } 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..52698b2a 100644 --- a/packages/react-native-nitro-image/src/specs/ImageView.nitro.ts +++ b/packages/react-native-nitro-image/src/specs/ImageView.nitro.ts @@ -58,7 +58,11 @@ export interface NativeNitroImageViewProps extends HybridViewProps { } export interface NativeNitroImageViewMethods extends HybridViewMethods { - // no methods + /** + * When `false`, cancels the in-flight {@linkcode ImageLoader} request. + * When `true`, resumes it. + */ + setIsActive(isActive: boolean): void } export type NitroImageView = HybridView< 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 d4fd6b40..bbad915d 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 @@ -3,6 +3,7 @@ package com.margelo.nitro.web.image import android.content.Context import android.widget.ImageView import coil3.ImageLoader +import coil3.dispose import coil3.load import com.margelo.nitro.core.Promise import com.margelo.nitro.image.HybridImageSpec @@ -28,6 +29,7 @@ class HybridWebImageLoader(private val imageLoader: ImageLoader, } override fun dropImage(forView: HybridNitroImageViewSpec) { - // Coil automatically handles recycling here - I _think_. + val imageView = forView.view as? ImageView ?: return + imageView.dispose() } } diff --git a/packages/react-native-nitro-web-image/ios/HybridWebImageLoader.swift b/packages/react-native-nitro-web-image/ios/HybridWebImageLoader.swift index e95e8986..e95b609b 100644 --- a/packages/react-native-nitro-web-image/ios/HybridWebImageLoader.swift +++ b/packages/react-native-nitro-web-image/ios/HybridWebImageLoader.swift @@ -43,7 +43,8 @@ class HybridWebImageLoader: HybridImageLoaderSpec { } func dropImage(forView view: (any HybridNitroImageViewSpec)) throws { - // TODO: Do we need to reset the image here or not? + guard let view = view as? NativeImageView else { return } + view.imageView.sd_cancelCurrentImageLoad() } public static func loadImage(url: URL, options: AsyncImageLoadOptions?) -> Promise { diff --git a/packages/react-native-nitro-web-image/nitrogen/generated/android/kotlin/com/margelo/nitro/web/image/AsyncImageLoadOptions.kt b/packages/react-native-nitro-web-image/nitrogen/generated/android/kotlin/com/margelo/nitro/web/image/AsyncImageLoadOptions.kt index 737c13c5..00c765f9 100644 --- a/packages/react-native-nitro-web-image/nitrogen/generated/android/kotlin/com/margelo/nitro/web/image/AsyncImageLoadOptions.kt +++ b/packages/react-native-nitro-web-image/nitrogen/generated/android/kotlin/com/margelo/nitro/web/image/AsyncImageLoadOptions.kt @@ -71,7 +71,7 @@ data class AsyncImageLoadOptions( } override fun hashCode(): Int { - return arrayOf( + return arrayOf( priority, forceRefresh, cacheKey, From e13e99da35524f59a0a2880398c327f83d34ff0c Mon Sep 17 00:00:00 2001 From: Dave Mkpa-Eke Date: Tue, 26 May 2026 16:55:52 +0100 Subject: [PATCH 2/2] remove isAttachedToWindow check --- .../main/java/com/margelo/nitro/image/HybridImageView.kt | 6 +----- 1 file changed, 1 insertion(+), 5 deletions(-) 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 62dd57f5..c3f70795 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 @@ -91,11 +91,7 @@ class HybridImageView(context: Context): HybridNitroImageViewSpec(), RecyclableV } private fun refreshLoadingState() { - if (imageView.isAttachedToWindow && isActive) { - onAppear() - } else { - onDisappear() - } + if (isActive) onAppear() else onDisappear() } private fun onAppear() {