diff --git a/bun.lock b/bun.lock
index ba9fded4..1ce4eb7e 100644
--- a/bun.lock
+++ b/bun.lock
@@ -1,5 +1,6 @@
{
"lockfileVersion": 1,
+ "configVersion": 0,
"workspaces": {
"": {
"name": "react-native-nitro-image-monorepo",
@@ -29,7 +30,7 @@
"react-native-fast-image": "^8.6.3",
"react-native-harness": "^1.0.0-alpha.25",
"react-native-nitro-image": "workspace:*",
- "react-native-nitro-modules": "0.35.5",
+ "react-native-nitro-modules": "0.35.7",
"react-native-nitro-web-image": "workspace:*",
"react-native-safe-area-context": "^5.6.0",
"react-native-screens": "^4.14.1",
@@ -56,10 +57,10 @@
"version": "0.14.0",
"devDependencies": {
"@types/react": "^19.0.6",
- "nitrogen": "0.35.5",
+ "nitrogen": "0.35.7",
"react": "19.1.0",
"react-native": "0.81.0",
- "react-native-nitro-modules": "0.35.5",
+ "react-native-nitro-modules": "0.35.7",
"typescript": "5.8.3",
},
"peerDependencies": {
@@ -73,10 +74,10 @@
"version": "0.14.0",
"devDependencies": {
"@types/react": "^19.0.6",
- "nitrogen": "0.35.5",
+ "nitrogen": "0.35.7",
"react": "19.1.0",
"react-native": "0.81.0",
- "react-native-nitro-modules": "0.35.5",
+ "react-native-nitro-modules": "0.35.7",
"typescript": "5.8.3",
},
"peerDependencies": {
@@ -620,7 +621,7 @@
"@tootallnate/quickjs-emscripten": ["@tootallnate/quickjs-emscripten@0.23.0", "", {}, "sha512-C5Mc6rdnsaJDjO3UpGW/CQTHtCKaYlScZTly4JIu97Jxo/odCiH0ITnDXSJPTOrEKk/ycSZ0AOgTmkDtkOsvIA=="],
- "@ts-morph/common": ["@ts-morph/common@0.28.1", "", { "dependencies": { "minimatch": "^10.0.1", "path-browserify": "^1.0.1", "tinyglobby": "^0.2.14" } }, "sha512-W74iWf7ILp1ZKNYXY5qbddNaml7e9Sedv5lvU1V8lftlitkc9Pq1A+jlH23ltDgWYeZFFEqGCD1Ies9hqu3O+g=="],
+ "@ts-morph/common": ["@ts-morph/common@0.29.0", "", { "dependencies": { "minimatch": "^10.0.1", "path-browserify": "^1.0.1", "tinyglobby": "^0.2.14" } }, "sha512-35oUmphHbJvQ/+UTwFNme/t2p3FoKiGJ5auTjjpNTop2dyREspirjMy82PLSC1pnDJ8ah1GU98hwpVt64YXQsg=="],
"@tsconfig/react-native": ["@tsconfig/react-native@2.0.3", "", {}, "sha512-jE58snEKBd9DXfyR4+ssZmYJ/W2mOSnNrvljR0aLyQJL9JKX6vlWELHkRjb3HBbcM9Uy0hZGijXbqEAjOERW2A=="],
@@ -1456,7 +1457,7 @@
"new-github-release-url": ["new-github-release-url@2.0.0", "", { "dependencies": { "type-fest": "^2.5.1" } }, "sha512-NHDDGYudnvRutt/VhKFlX26IotXe1w0cmkDm6JGquh5bz/bDTw0LufSmH/GxTjEdpHEO+bVKFTwdrcGa/9XlKQ=="],
- "nitrogen": ["nitrogen@0.35.5", "", { "dependencies": { "chalk": "^5.3.0", "react-native-nitro-modules": "^0.35.5", "ts-morph": "^27.0.0", "yargs": "^18.0.0", "zod": "^4.0.5" }, "bin": { "nitrogen": "lib/index.js" } }, "sha512-Ofl4aTW2rd44+hBcUwLXu01ViHikn2SDI7zOYc+6NcKSpnhoj42k1FwyvRj1QZPDMUT64Bwlb0DYw7Hrfd3BfQ=="],
+ "nitrogen": ["nitrogen@0.35.7", "", { "dependencies": { "chalk": "^5.3.0", "react-native-nitro-modules": "^0.35.7", "ts-morph": "^28.0.0", "yargs": "^18.0.0", "zod": "^4.0.5" }, "bin": { "nitrogen": "lib/index.js" } }, "sha512-+uXdeK1NhUfXW562qCAnN/mMzVuEUw1K58V1oelhBBQMBTZlTvfO4yTbCFX76XjOeRnbMrPosqIGSGQh3hz1MQ=="],
"nocache": ["nocache@3.0.4", "", {}, "sha512-WDD0bdg9mbq6F4mRxEYcPWwfA1vxd0mrvKOyxI7Xj/atfRHVeutzuWByG//jfm4uPzp0y4Kj051EORCBSQMycw=="],
@@ -1622,7 +1623,7 @@
"react-native-nitro-image": ["react-native-nitro-image@workspace:packages/react-native-nitro-image"],
- "react-native-nitro-modules": ["react-native-nitro-modules@0.35.5", "", { "peerDependencies": { "react": "*", "react-native": "*" } }, "sha512-aa03UzC5dLg5qFfyBkVK+JGSwHTjmK7jUZzyRz11r1Yk9C/nJTFe59EeHPxxNNTagkiwQTM6p3sySgD/TDRC7Q=="],
+ "react-native-nitro-modules": ["react-native-nitro-modules@0.35.7", "", { "peerDependencies": { "react": "*", "react-native": "*" } }, "sha512-3EiU27EmnxTlD3pAXR2OBWeDg7+9tdjKrNQOPX08rFX5hJPNIT/h1i2PjvTUieOypCGTEi9nW/SNBtK3F+4HYg=="],
"react-native-nitro-web-image": ["react-native-nitro-web-image@workspace:packages/react-native-nitro-web-image"],
@@ -1808,7 +1809,7 @@
"toidentifier": ["toidentifier@1.0.1", "", {}, "sha512-o5sSPKEkg/DIQNmH43V0/uerLrpzVedkUh8tGNvaeXpfpuwjKenlSox/2O/BTlZUtEe+JG7s5YhEz608PlAHRA=="],
- "ts-morph": ["ts-morph@27.0.2", "", { "dependencies": { "@ts-morph/common": "~0.28.1", "code-block-writer": "^13.0.3" } }, "sha512-fhUhgeljcrdZ+9DZND1De1029PrE+cMkIP7ooqkLRTrRLTqcki2AstsyJm0vRNbTbVCNJ0idGlbBrfqc7/nA8w=="],
+ "ts-morph": ["ts-morph@28.0.0", "", { "dependencies": { "@ts-morph/common": "~0.29.0", "code-block-writer": "^13.0.3" } }, "sha512-Wp3tnZ2bzwxyTZMtgWVzXDfm7lB1Drz+y9DmmYH/L702PQhPyVrp3pkou3yIz4qjS14GY9kcpmLiOOMvl8oG1g=="],
"tslib": ["tslib@2.8.1", "", {}, "sha512-oJFu94HQb+KVduSUQL7wnpmqnfmLsOA/nAh6b6EH0wCEoK0/mPeXU6c3wKDV83MkOuHPRHtSXKKU99IBazS/2w=="],
diff --git a/example/package.json b/example/package.json
index d47826e0..c20bb8cd 100644
--- a/example/package.json
+++ b/example/package.json
@@ -20,7 +20,7 @@
"react-native-harness": "^1.0.0-alpha.25",
"react-native-nitro-image": "workspace:*",
"react-native-nitro-web-image": "workspace:*",
- "react-native-nitro-modules": "0.35.5",
+ "react-native-nitro-modules": "0.35.7",
"react-native-safe-area-context": "^5.6.0",
"react-native-screens": "^4.14.1"
},
diff --git a/example/src/App.tsx b/example/src/App.tsx
index 2dff6a2c..bc6270ce 100644
--- a/example/src/App.tsx
+++ b/example/src/App.tsx
@@ -7,6 +7,7 @@
import { createBottomTabNavigator } from '@react-navigation/bottom-tabs'
import { createStaticNavigation } from '@react-navigation/native'
+import { CachePriorityTab } from './CachePriorityTab'
import { EmptyTab } from './EmptyTab'
import { FastImageTab } from './FastImageTab'
import { NitroImageTab } from './NitroImageTab'
@@ -17,6 +18,7 @@ const Tabs = createBottomTabNavigator({
Empty: EmptyTab,
FastImage: FastImageTab,
NitroImage: NitroImageTab,
+ CachePriority: CachePriorityTab,
},
})
const Navigation = createStaticNavigation(Tabs)
diff --git a/example/src/CachePriorityTab.tsx b/example/src/CachePriorityTab.tsx
new file mode 100644
index 00000000..0a56a2e1
--- /dev/null
+++ b/example/src/CachePriorityTab.tsx
@@ -0,0 +1,83 @@
+import { useCallback, useMemo, useState } from 'react'
+import { Button, FlatList, StyleSheet, Text, View } from 'react-native'
+import { NitroImage } from 'react-native-nitro-image'
+import { createImageURLs } from './createImageURLs'
+
+export function CachePriorityTab() {
+ const heroURL = useMemo(() => `https://picsum.photos/seed/hero/800`, [])
+ const thumbnailURLs = useMemo(() => createImageURLs(200), [])
+ const [heroNonce, setHeroNonce] = useState(0)
+
+ const thumbnailSources = useMemo(
+ () => thumbnailURLs.map((url) => ({ url })),
+ [thumbnailURLs],
+ )
+
+ const heroSource = useMemo(() => ({ url: heroURL }), [heroURL])
+
+ const renderItem = useCallback(
+ ({ item: source }: { item: { url: string } }) => (
+
+ ),
+ [],
+ )
+
+ return (
+
+ Hero (no cachePriority)
+
+
+
+ Remounted: {heroNonce} • check logcat: 1 SET, {heroNonce} HITs
+
+
+ Thumbnails (no cachePriority)
+ source.url}
+ renderItem={renderItem}
+ />
+
+ )
+}
+
+const styles = StyleSheet.create({
+ container: {
+ flex: 1,
+ },
+ buttonRow: {
+ paddingHorizontal: 12,
+ paddingVertical: 8,
+ },
+ label: {
+ paddingHorizontal: 12,
+ paddingVertical: 6,
+ fontWeight: '600',
+ },
+ hero: {
+ width: '100%',
+ aspectRatio: 16 / 9,
+ },
+ thumb: {
+ width: '25%',
+ aspectRatio: 1,
+ },
+})
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..56ec238f 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,8 @@ class HybridImageView(context: Context): HybridNitroImageViewSpec(), RecyclableV
field = value
}
+ override var cachePriority: Double? = null
+
override fun prepareForRecycle() {
onDisappear()
imageView.setImageBitmap(null)
diff --git a/packages/react-native-nitro-image/ios/HybridImageView.swift b/packages/react-native-nitro-image/ios/HybridImageView.swift
index 96f47848..324ed2a6 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 cachePriority: Double?
private func updateResizeMode() {
let mode = resizeMode ?? .cover
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..3926f54c 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
@@ -83,6 +83,15 @@ namespace margelo::nitro::image {
static const auto method = _javaPart->javaClassStatic()->getMethod /* recyclingKey */)>("setRecyclingKey");
method(_javaPart, recyclingKey.has_value() ? jni::make_jstring(recyclingKey.value()) : nullptr);
}
+ std::optional JHybridNitroImageViewSpec::getCachePriority() {
+ static const auto method = _javaPart->javaClassStatic()->getMethod()>("getCachePriority");
+ auto __result = method(_javaPart);
+ return __result != nullptr ? std::make_optional(__result->value()) : std::nullopt;
+ }
+ void JHybridNitroImageViewSpec::setCachePriority(std::optional cachePriority) {
+ static const auto method = _javaPart->javaClassStatic()->getMethod /* cachePriority */)>("setCachePriority");
+ method(_javaPart, cachePriority.has_value() ? jni::JDouble::valueOf(cachePriority.value()) : nullptr);
+ }
// Methods
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..5e417b92 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
@@ -56,6 +56,8 @@ namespace margelo::nitro::image {
void setResizeMode(std::optional resizeMode) override;
std::optional getRecyclingKey() override;
void setRecyclingKey(const std::optional& recyclingKey) override;
+ std::optional getCachePriority() override;
+ void setCachePriority(std::optional cachePriority) override;
public:
// Methods
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..104c9e2e 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
@@ -49,6 +49,10 @@ void JHybridNitroImageViewStateUpdater::updateViewProps(jni::alias_refsetRecyclingKey(props->recyclingKey.value);
props->recyclingKey.isDirty = false;
}
+ if (props->cachePriority.isDirty) {
+ hybridView->setCachePriority(props->cachePriority.value);
+ props->cachePriority.isDirty = false;
+ }
// Update hybridRef if it changed
if (props->hybridRef.isDirty) {
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..afa1379a 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
@@ -43,6 +43,12 @@ abstract class HybridNitroImageViewSpec: HybridView() {
@set:DoNotStrip
@set:Keep
abstract var recyclingKey: String?
+
+ @get:DoNotStrip
+ @get:Keep
+ @set:DoNotStrip
+ @set:Keep
+ abstract var cachePriority: Double?
// Methods
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..5b5c7756 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
@@ -94,6 +94,13 @@ namespace margelo::nitro::image {
inline void setRecyclingKey(const std::optional& recyclingKey) noexcept override {
_swiftPart.setRecyclingKey(recyclingKey);
}
+ inline std::optional getCachePriority() noexcept override {
+ auto __result = _swiftPart.getCachePriority();
+ return __result;
+ }
+ inline void setCachePriority(std::optional cachePriority) noexcept override {
+ _swiftPart.setCachePriority(cachePriority);
+ }
public:
// Methods
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 176e7b23..9655d5d4 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
@@ -94,6 +94,11 @@ - (void) updateProps:(const std::shared_ptr&)props
swiftPart.setRecyclingKey(newViewProps.recyclingKey.value);
newViewProps.recyclingKey.isDirty = false;
}
+ // cachePriority: optional
+ if (newViewProps.cachePriority.isDirty) {
+ swiftPart.setCachePriority(newViewProps.cachePriority.value);
+ newViewProps.cachePriority.isDirty = false;
+ }
swiftPart.afterUpdate();
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..fb745ca0 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
@@ -13,6 +13,7 @@ public protocol HybridNitroImageViewSpec_protocol: HybridObject, HybridView {
var image: Variant__any_HybridImageSpec___any_HybridImageLoaderSpec_? { get set }
var resizeMode: ResizeMode? { get set }
var recyclingKey: String? { get set }
+ var cachePriority: Double? { get set }
// Methods
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..480f1491 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
@@ -218,6 +218,30 @@ open class HybridNitroImageViewSpec_cxx {
}()
}
}
+
+ public final var cachePriority: bridge.std__optional_double_ {
+ @inline(__always)
+ get {
+ return { () -> bridge.std__optional_double_ in
+ if let __unwrappedValue = self.__implementation.cachePriority {
+ return bridge.create_std__optional_double_(__unwrappedValue)
+ } else {
+ return .init()
+ }
+ }()
+ }
+ @inline(__always)
+ set {
+ self.__implementation.cachePriority = { () -> Double? in
+ if bridge.has_value_std__optional_double_(newValue) {
+ let __unwrapped = bridge.get_std__optional_double_(newValue)
+ return __unwrapped
+ } else {
+ return nil
+ }
+ }()
+ }
+ }
// Methods
public final func getView() -> UnsafeMutableRawPointer {
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..c249f3a0 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,8 @@ namespace margelo::nitro::image {
prototype.registerHybridSetter("resizeMode", &HybridNitroImageViewSpec::setResizeMode);
prototype.registerHybridGetter("recyclingKey", &HybridNitroImageViewSpec::getRecyclingKey);
prototype.registerHybridSetter("recyclingKey", &HybridNitroImageViewSpec::setRecyclingKey);
+ prototype.registerHybridGetter("cachePriority", &HybridNitroImageViewSpec::getCachePriority);
+ prototype.registerHybridSetter("cachePriority", &HybridNitroImageViewSpec::setCachePriority);
});
}
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..c88f6325 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
@@ -61,6 +61,8 @@ namespace margelo::nitro::image {
virtual void setResizeMode(std::optional resizeMode) = 0;
virtual std::optional getRecyclingKey() = 0;
virtual void setRecyclingKey(const std::optional& recyclingKey) = 0;
+ virtual std::optional getCachePriority() = 0;
+ virtual void setCachePriority(std::optional cachePriority) = 0;
public:
// Methods
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..c428e7bb 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
@@ -56,6 +56,16 @@ namespace margelo::nitro::image::views {
throw std::runtime_error(std::string("NitroImageView.recyclingKey: ") + exc.what());
}
}()),
+ cachePriority([&]() -> CachedProp> {
+ try {
+ const react::RawValue* rawValue = rawProps.at("cachePriority", nullptr, nullptr);
+ if (rawValue == nullptr) return sourceProps.cachePriority;
+ const auto& [runtime, value] = (std::pair)*rawValue;
+ return CachedProp>::fromRawValue(*runtime, value, sourceProps.cachePriority);
+ } catch (const std::exception& exc) {
+ throw std::runtime_error(std::string("NitroImageView.cachePriority: ") + exc.what());
+ }
+ }()),
hybridRef([&]() -> CachedProp& /* ref */)>>> {
try {
const react::RawValue* rawValue = rawProps.at("hybridRef", nullptr, nullptr);
@@ -72,6 +82,7 @@ namespace margelo::nitro::image::views {
case hashString("image"): return true;
case hashString("resizeMode"): return true;
case hashString("recyclingKey"): return true;
+ case hashString("cachePriority"): return true;
case hashString("hybridRef"): return true;
default: return false;
}
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..32d12e8a 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
@@ -49,6 +49,7 @@ namespace margelo::nitro::image::views {
CachedProp, std::shared_ptr>>> image;
CachedProp> resizeMode;
CachedProp> recyclingKey;
+ CachedProp> cachePriority;
CachedProp& /* ref */)>>> hybridRef;
private:
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..da107cec 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
@@ -7,6 +7,7 @@
"image": true,
"resizeMode": true,
"recyclingKey": true,
+ "cachePriority": true,
"hybridRef": true
}
}
diff --git a/packages/react-native-nitro-image/package.json b/packages/react-native-nitro-image/package.json
index cfb2e634..55ba7235 100644
--- a/packages/react-native-nitro-image/package.json
+++ b/packages/react-native-nitro-image/package.json
@@ -58,10 +58,10 @@
},
"devDependencies": {
"@types/react": "^19.0.6",
- "nitrogen": "0.35.5",
+ "nitrogen": "0.35.7",
"react": "19.1.0",
"react-native": "0.81.0",
- "react-native-nitro-modules": "0.35.5",
+ "react-native-nitro-modules": "0.35.7",
"typescript": "5.8.3"
},
"peerDependencies": {
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..7b016b0f 100644
--- a/packages/react-native-nitro-image/src/specs/ImageView.nitro.ts
+++ b/packages/react-native-nitro-image/src/specs/ImageView.nitro.ts
@@ -55,6 +55,20 @@ export interface NativeNitroImageViewProps extends HybridViewProps {
* ```
*/
recyclingKey?: string
+ /**
+ * Hint for the memory cache about how important this image is to keep.
+ *
+ * When the cache is over its byte budget, entries are evicted lowest
+ * priority first; within the same priority the least-recently-used
+ * entry is evicted first.
+ *
+ * - `0`: low — evicted first (thumbnails, decorative)
+ * - `1`: normal — default
+ * - `2`: high — evicted last (heroes, avatars)
+ *
+ * @default 1
+ */
+ cachePriority?: number
}
export interface NativeNitroImageViewMethods extends HybridViewMethods {
diff --git a/packages/react-native-nitro-web-image/android/build.gradle b/packages/react-native-nitro-web-image/android/build.gradle
index 7270e0bd..65fb61b0 100644
--- a/packages/react-native-nitro-web-image/android/build.gradle
+++ b/packages/react-native-nitro-web-image/android/build.gradle
@@ -120,6 +120,7 @@ android {
}
}
}
+
}
repositories {
@@ -142,5 +143,7 @@ dependencies {
implementation("io.coil-kt.coil3:coil:3.3.0")
implementation("io.coil-kt.coil3:coil-network-okhttp:3.3.0")
implementation("io.coil-kt.coil3:coil-gif:3.3.0")
+ // Lock primitive used to match Coil's RealMemoryCache synchronization.
+ implementation("org.jetbrains.kotlinx:atomicfu:0.29.0")
}
diff --git a/packages/react-native-nitro-web-image/android/src/main/java/com/margelo/nitro/web/image/HybridWebImageFactory.kt b/packages/react-native-nitro-web-image/android/src/main/java/com/margelo/nitro/web/image/HybridWebImageFactory.kt
index e282f35b..e8ad51f6 100644
--- a/packages/react-native-nitro-web-image/android/src/main/java/com/margelo/nitro/web/image/HybridWebImageFactory.kt
+++ b/packages/react-native-nitro-web-image/android/src/main/java/com/margelo/nitro/web/image/HybridWebImageFactory.kt
@@ -2,6 +2,7 @@ package com.margelo.nitro.web.image
import androidx.annotation.Keep
import coil3.ImageLoader
+import coil3.memory.MemoryCache
import coil3.request.ImageRequest
import com.facebook.common.internal.DoNotStrip
import com.facebook.react.bridge.ReactApplicationContext
@@ -9,6 +10,7 @@ import com.margelo.nitro.NitroModules
import com.margelo.nitro.core.Promise
import com.margelo.nitro.image.HybridImageLoaderSpec
import com.margelo.nitro.image.HybridImageSpec
+import com.margelo.nitro.web.image.cache.PriorityMemoryCache
import com.margelo.nitro.web.image.extensions.loadImageAsync
@DoNotStrip
@@ -16,7 +18,14 @@ import com.margelo.nitro.web.image.extensions.loadImageAsync
class HybridWebImageFactory: HybridWebImageFactorySpec() {
private val context: ReactApplicationContext
get() = NitroModules.applicationContext ?: throw Error("No context - NitroModules.applicationContext was null!")
- private val imageLoader = ImageLoader(context)
+ private val imageLoader = ImageLoader.Builder(context)
+ .memoryCache {
+ // Set initialMaxSize to Coil default.
+ PriorityMemoryCache(
+ initialMaxSize = MemoryCache.Builder().maxSizePercent(context).build().maxSize,
+ )
+ }
+ .build()
override fun createWebImageLoader(
url: String,
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..c1768d49 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
@@ -8,6 +8,7 @@ import com.margelo.nitro.core.Promise
import com.margelo.nitro.image.HybridImageSpec
import com.margelo.nitro.image.HybridImageLoaderSpec
import com.margelo.nitro.image.HybridNitroImageViewSpec
+import com.margelo.nitro.web.image.cache.CACHE_PRIORITY_EXTRA
import com.margelo.nitro.web.image.extensions.applyOptions
import com.margelo.nitro.web.image.extensions.loadImageAsync
@@ -21,9 +22,11 @@ class HybridWebImageLoader(private val imageLoader: ImageLoader,
override fun requestImage(forView: HybridNitroImageViewSpec) {
val imageView = forView.view as? ImageView ?: return
+ val cachePriority = forView.cachePriority?.toInt() ?: 1
imageView.load(url, imageLoader) {
this.applyOptions(options)
+ memoryCacheKeyExtra(CACHE_PRIORITY_EXTRA, cachePriority.toString())
}
}
diff --git a/packages/react-native-nitro-web-image/android/src/main/java/com/margelo/nitro/web/image/cache/PriorityMemoryCache.kt b/packages/react-native-nitro-web-image/android/src/main/java/com/margelo/nitro/web/image/cache/PriorityMemoryCache.kt
new file mode 100644
index 00000000..0100d470
--- /dev/null
+++ b/packages/react-native-nitro-web-image/android/src/main/java/com/margelo/nitro/web/image/cache/PriorityMemoryCache.kt
@@ -0,0 +1,149 @@
+package com.margelo.nitro.web.image.cache
+
+import coil3.Image
+import coil3.memory.MemoryCache
+import coil3.memory.MemoryCache.Key
+import kotlinx.atomicfu.locks.SynchronizedObject
+import kotlinx.atomicfu.locks.synchronized
+
+const val CACHE_PRIORITY_EXTRA = "cachePriority"
+private const val BUCKET_COUNT = 3
+private const val DEFAULT_PRIORITY = 1
+
+
+class PriorityMemoryCache(
+ initialMaxSize: Long,
+) : MemoryCache {
+
+ private val lock = SynchronizedObject()
+ // `true` auto-moves the entry to the MRU tail.
+ private val buckets = Array(BUCKET_COUNT) { LinkedHashMap(16, 0.75f, true) }
+ private val keyToBucket = HashMap()
+ private var currentBytes = 0L
+ // Weak tier mirrors Coil's WeakMemoryCache. Catches images that are evicted
+ // from the strong tier but still strong-referenced by an in-flight consumer.
+ private val weakCache = WeakImageCache()
+
+ override val initialMaxSize: Long = initialMaxSize
+
+ override val size: Long
+ get() = synchronized(lock) {
+ currentBytes
+ }
+
+ override var maxSize: Long = initialMaxSize
+ get() = synchronized(lock) {
+ field
+ }
+ set(value) = synchronized(lock) {
+ field = value
+ trimToSize(value)
+ }
+
+ override val keys: Set
+ get() = synchronized(lock) {
+ HashSet(keyToBucket.keys) + weakCache.keys
+ }
+
+ override fun get(key: Key): MemoryCache.Value? = synchronized(lock) {
+ val bucketIndex = keyToBucket[key]
+ if (bucketIndex == null) {
+ // Strong miss, fall back to the weak tier (mirrors RealMemoryCache.get from Coil).
+ val weakHit = weakCache.get(key) ?: return null
+ if (!weakHit.image.shareable) weakCache.remove(key)
+ return weakHit
+ }
+ val cachedEntry = buckets[bucketIndex][key] ?: return null
+
+ // Unshareable images (e.g. animated GIFs) can't be safely held by two
+ // consumers at once, so hand the entry out and drop it from both tiers.
+ if (!cachedEntry.image.shareable) {
+ keyToBucket.remove(key)
+ buckets[bucketIndex].remove(key)
+ currentBytes -= cachedEntry.size
+ weakCache.remove(key)
+ }
+ return MemoryCache.Value(cachedEntry.image, cachedEntry.extras)
+ }
+
+ override fun set(key: Key, value: MemoryCache.Value) = synchronized(lock) {
+ val entryBytes = value.image.size
+ check(entryBytes >= 0) { "Image size must be non-negative: $entryBytes" }
+
+ // Replace any prior entry under this key; forward the displaced image to weak
+ evictFromStrongToWeak(key)
+
+ // Refuse oversized entries would otherwise evict the rest of the cache.
+ // Coil's RealStrongMemoryCache.set still hands oversized values to weak.
+ if (entryBytes > maxSize) {
+ weakCache.set(key, value.image, value.extras, entryBytes)
+ return@synchronized
+ }
+
+ val priority = parsePriority(key)
+ buckets[priority][key] = InternalValue(value.image, value.extras, entryBytes)
+ keyToBucket[key] = priority
+ currentBytes += entryBytes
+
+ trimToSize(maxSize)
+ }
+
+ override fun remove(key: Key): Boolean = synchronized(lock) {
+ val removedStrong = removeFromStrong(key)
+ val removedWeak = weakCache.remove(key)
+ return removedStrong || removedWeak
+ }
+
+ override fun trimToSize(size: Long) = synchronized(lock) {
+ while (currentBytes > size) {
+ if (!evictOne()) return@synchronized
+ }
+ }
+
+ override fun clear() = synchronized(lock) {
+ for (bucket in buckets) bucket.clear()
+ keyToBucket.clear()
+ currentBytes = 0L
+ weakCache.clear()
+ }
+
+ private fun evictOne(): Boolean {
+ for (priority in 0 until BUCKET_COUNT) {
+ val bucket = buckets[priority]
+ if (bucket.isEmpty()) continue
+ // Head of an access-order LinkedHashMap = least-recently-used entry.
+ val oldestKey = bucket.entries.iterator().next().key
+ evictFromStrongToWeak(oldestKey)
+ return true
+ }
+ return false
+ }
+
+ /** Pull an entry out of the strong tier and hand it to the weak tier. */
+ private fun evictFromStrongToWeak(key: Key) {
+ val bucketIndex = keyToBucket.remove(key) ?: return
+ val displaced = buckets[bucketIndex].remove(key) ?: return
+ currentBytes -= displaced.size
+ weakCache.set(key, displaced.image, displaced.extras, displaced.size)
+ }
+
+ /** Strong-tier-only removal. Caller is responsible for the weak tier. */
+ private fun removeFromStrong(key: Key): Boolean {
+ val bucketIndex = keyToBucket.remove(key) ?: return false
+ val value = buckets[bucketIndex].remove(key) ?: return false
+ currentBytes -= value.size
+ return true
+ }
+
+ private fun parsePriority(key: Key): Int {
+ val rawPriority = key.extras[CACHE_PRIORITY_EXTRA]?.toIntOrNull() ?: DEFAULT_PRIORITY
+ // Clamp out-of-range priorities to a valid bucket index.
+ return rawPriority.coerceIn(0, BUCKET_COUNT - 1)
+ }
+
+ private class InternalValue(
+ val image: Image,
+ val extras: Map,
+ val size: Long,
+ )
+}
diff --git a/packages/react-native-nitro-web-image/android/src/main/java/com/margelo/nitro/web/image/cache/WeakImageCache.kt b/packages/react-native-nitro-web-image/android/src/main/java/com/margelo/nitro/web/image/cache/WeakImageCache.kt
new file mode 100644
index 00000000..c85d0cbe
--- /dev/null
+++ b/packages/react-native-nitro-web-image/android/src/main/java/com/margelo/nitro/web/image/cache/WeakImageCache.kt
@@ -0,0 +1,119 @@
+package com.margelo.nitro.web.image.cache
+
+import coil3.Image
+import coil3.memory.MemoryCache.Key
+import coil3.memory.MemoryCache.Value
+import java.lang.ref.WeakReference
+
+/**
+ * Holds weak references to images that have been evicted from the strong cache.
+ *
+ * A port of coil3.memory.RealWeakMemoryCache:
+ * https://github.com/coil-kt/coil/blob/3.3.0/coil-core/src/commonMain/kotlin/coil3/memory/WeakMemoryCache.kt
+ */
+internal class WeakImageCache {
+
+ internal val cache = LinkedHashMap>()
+ private var operationsSinceCleanUp = 0
+
+ val keys: Set
+ get() = cache.keys.toSet()
+
+ fun get(key: Key): Value? {
+ val values = cache[key] ?: return null
+
+ // Find the first image that hasn't been collected.
+ var value: Value? = null
+ for (index in values.indices) {
+ val image = values[index].image.get()
+ if (image != null) {
+ value = Value(image, values[index].extras)
+ break
+ }
+ }
+
+ cleanUpIfNecessary()
+ return value
+ }
+
+ fun set(key: Key, image: Image, extras: Map, size: Long) {
+ val values = cache.getOrPut(key) { arrayListOf() }
+
+ // Insert the value into the list sorted descending by size.
+ val newValue = InternalValue(WeakReference(image), extras, size)
+ if (values.isEmpty()) {
+ values += newValue
+ } else {
+ for (index in values.indices) {
+ val value = values[index]
+ if (size >= value.size) {
+ if (value.image.get() === image) {
+ values[index] = newValue
+ } else {
+ values.add(index, newValue)
+ }
+ break
+ }
+ }
+ }
+
+ cleanUpIfNecessary()
+ }
+
+ fun remove(key: Key): Boolean {
+ return cache.remove(key) != null
+ }
+
+ fun clear() {
+ operationsSinceCleanUp = 0
+ cache.clear()
+ }
+
+ private fun cleanUpIfNecessary() {
+ if (operationsSinceCleanUp++ >= CLEAN_UP_INTERVAL) {
+ cleanUp()
+ }
+ }
+
+ /** Remove any dereferenced images from the cache. */
+ internal fun cleanUp() {
+ operationsSinceCleanUp = 0
+
+ // Remove all the values whose references have been collected.
+ val iterator = cache.values.iterator()
+ while (iterator.hasNext()) {
+ val list = iterator.next()
+
+ if (list.size <= 1) {
+ // Typically, the list will only contain 1 item. Handle this case in an optimal way here.
+ if (list.firstOrNull()?.image?.get() == null) {
+ iterator.remove()
+ }
+ } else {
+ // Iterate over the list of values and delete individual entries that have been collected.
+ var writeIndex = 0
+ for (readIndex in list.indices) {
+ if (list[readIndex].image.get() != null) {
+ list[writeIndex] = list[readIndex]
+ writeIndex++
+ }
+ }
+ while (list.size > writeIndex) list.removeAt(list.size - 1)
+
+ if (list.isEmpty()) {
+ iterator.remove()
+ }
+ }
+ }
+ }
+
+ internal class InternalValue(
+ val image: WeakReference,
+ val extras: Map,
+ val size: Long,
+ )
+
+ companion object {
+ private const val CLEAN_UP_INTERVAL = 10
+ }
+}
diff --git a/packages/react-native-nitro-web-image/ios/Cache/OrderedKeyMap.swift b/packages/react-native-nitro-web-image/ios/Cache/OrderedKeyMap.swift
new file mode 100644
index 00000000..135940f5
--- /dev/null
+++ b/packages/react-native-nitro-web-image/ios/Cache/OrderedKeyMap.swift
@@ -0,0 +1,73 @@
+//
+// OrderedKeyMap.swift
+// NitroWebImage
+//
+
+import Foundation
+
+/// Order-preserving map used as the LRU list within a single priority bucket.
+/// Head = LRU, tail = MRU.
+final class OrderedKeyMap {
+ private final class Node {
+ let key: Key
+ var value: Value
+ var prev: Node?
+ var next: Node?
+ init(key: Key, value: Value) {
+ self.key = key
+ self.value = value
+ }
+ }
+
+ private var head: Node?
+ private var tail: Node?
+ private var nodes: [Key: Node] = [:]
+
+ var isEmpty: Bool { nodes.isEmpty }
+
+ func append(_ key: Key, value: Value) {
+ let node = Node(key: key, value: value)
+ nodes[key] = node
+ linkAtTail(node)
+ }
+
+ func moveToTail(_ key: Key) -> Value? {
+ guard let node = nodes[key] else { return nil }
+ unlink(node)
+ linkAtTail(node)
+ return node.value
+ }
+
+ @discardableResult
+ func remove(_ key: Key) -> Value? {
+ guard let node = nodes.removeValue(forKey: key) else { return nil }
+ unlink(node)
+ return node.value
+ }
+
+ func popHead() -> (Key, Value)? {
+ guard let node = head else { return nil }
+ unlink(node)
+ nodes.removeValue(forKey: node.key)
+ return (node.key, node.value)
+ }
+
+ private func linkAtTail(_ node: Node) {
+ node.prev = tail
+ node.next = nil
+ tail?.next = node
+ tail = node
+ if head == nil { head = node }
+ }
+
+ private func unlink(_ node: Node) {
+ let prev = node.prev
+ let next = node.next
+ prev?.next = next
+ next?.prev = prev
+ if head === node { head = next }
+ if tail === node { tail = prev }
+ node.prev = nil
+ node.next = nil
+ }
+}
diff --git a/packages/react-native-nitro-web-image/ios/Cache/PriorityMemoryCache.swift b/packages/react-native-nitro-web-image/ios/Cache/PriorityMemoryCache.swift
new file mode 100644
index 00000000..15854f7c
--- /dev/null
+++ b/packages/react-native-nitro-web-image/ios/Cache/PriorityMemoryCache.swift
@@ -0,0 +1,139 @@
+//
+// PriorityMemoryCache.swift
+// NitroWebImage
+//
+
+import Foundation
+import SDWebImage
+import UIKit
+
+
+final class PriorityMemoryCache: NSObject, SDMemoryCacheProtocol {
+ static let cachePriorityExtra = "cachePriority"
+ static let prioritySeparator: Character = "|"
+
+ private static let bucketCount = 3
+ private static let defaultPriority = 1
+
+ private let lock = NSLock()
+ private var buckets: [OrderedKeyMap] =
+ (0.. Any? {
+ guard let key = key as? NSString else { return nil }
+ lock.lock()
+ defer { lock.unlock() }
+
+ guard let bucketIndex = keyToBucket[key] else { return nil }
+ return buckets[bucketIndex].moveToTail(key)?.object
+ }
+
+ func setObject(_ object: Any?, forKey key: Any) {
+ setObject(object, forKey: key, cost: 0)
+ }
+
+ func setObject(_ object: Any?, forKey key: Any, cost: UInt) {
+ guard let key = key as? NSString else { return }
+ lock.lock()
+ defer { lock.unlock() }
+
+ // Replace any prior entry under this key.
+ removeLocked(key)
+
+ guard let object = object else { return }
+
+ let maxBytes = UInt(_config.maxMemoryCost)
+ // Refuse oversized entries, would otherwise evict the rest of the cache.
+ if maxBytes > 0 && cost > maxBytes { return }
+
+ let priority = Self.parsePriority(key)
+ buckets[priority].append(key, value: InternalValue(object: object, cost: cost))
+ keyToBucket[key] = priority
+ currentBytes &+= cost
+
+ if maxBytes > 0 { trimToSizeLocked(maxBytes) }
+ }
+
+ func removeObject(forKey key: Any) {
+ guard let key = key as? NSString else { return }
+ lock.lock()
+ defer { lock.unlock() }
+ _ = removeLocked(key)
+ }
+
+ func removeAllObjects() {
+ lock.lock()
+ defer { lock.unlock() }
+ for index in 0.. Bool {
+ guard let bucketIndex = keyToBucket.removeValue(forKey: key) else { return false }
+ guard let value = buckets[bucketIndex].remove(key) else { return false }
+ currentBytes = currentBytes >= value.cost ? currentBytes - value.cost : 0
+ return true
+ }
+
+ private func trimToSizeLocked(_ target: UInt) {
+ while currentBytes > target {
+ if !evictOneLocked() { return }
+ }
+ }
+
+ private func evictOneLocked() -> Bool {
+ for priority in 0..= value.cost ? currentBytes - value.cost : 0
+ return true
+ }
+ return false
+ }
+
+ private static func parsePriority(_ key: NSString) -> Int {
+ let str = key as String
+ guard let sepIndex = str.firstIndex(of: prioritySeparator) else { return defaultPriority }
+ let prefix = str[str.startIndex.. Promise {
guard let url = URL(string: urlString) else {
throw RuntimeError.error(withMessage: "URL string \"\(urlString)\" is not a valid URL!")
diff --git a/packages/react-native-nitro-web-image/ios/HybridWebImageLoader.swift b/packages/react-native-nitro-web-image/ios/HybridWebImageLoader.swift
index e95e8986..98e1ca5d 100644
--- a/packages/react-native-nitro-web-image/ios/HybridWebImageLoader.swift
+++ b/packages/react-native-nitro-web-image/ios/HybridWebImageLoader.swift
@@ -32,10 +32,19 @@ class HybridWebImageLoader: HybridImageLoaderSpec {
}
func requestImage(forView view: (any HybridNitroImageViewSpec)) throws {
+ let cachePriority = Int(view.cachePriority ?? 1)
guard let view = view as? NativeImageView else { throw RuntimeError.error(withMessage: "Invalid view type!") }
let webImageOptions = options?.toSDWebImageOptions() ?? []
- let webImageContext = options?.toSDWebImageContext()
+ var webImageContext = options?.toSDWebImageContext() ?? [:]
+
+ // Bake cachePriority into the memory cache key via SDWebImage's
+ // CacheKeyFilter. Our PriorityMemoryCache parses the `"P|"` prefix
+ // to pick the bucket.
+ webImageContext[.cacheKeyFilter] = SDWebImageCacheKeyFilter { url in
+ "\(cachePriority)\(PriorityMemoryCache.prioritySeparator)\(url.absoluteString)"
+ }
+
view.imageView.sd_setImage(with: url,
placeholderImage: view.imageView.image,
options: webImageOptions,
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,
diff --git a/packages/react-native-nitro-web-image/package.json b/packages/react-native-nitro-web-image/package.json
index 7c6f28eb..9f704b77 100644
--- a/packages/react-native-nitro-web-image/package.json
+++ b/packages/react-native-nitro-web-image/package.json
@@ -57,10 +57,10 @@
},
"devDependencies": {
"@types/react": "^19.0.6",
- "nitrogen": "0.35.5",
+ "nitrogen": "0.35.7",
"react": "19.1.0",
"react-native": "0.81.0",
- "react-native-nitro-modules": "0.35.5",
+ "react-native-nitro-modules": "0.35.7",
"typescript": "5.8.3"
},
"peerDependencies": {