react-native-nitro-image-pipeline 0.3.4 → 0.3.5

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/README.md CHANGED
@@ -85,7 +85,7 @@ await NitroImagePipeline.preLoadImages([
85
85
  const blurred = await NitroImagePipeline.gaussianBlur(image, 10);
86
86
 
87
87
  // Clear the image cache
88
- NitroImagePipeline.clearCache();
88
+ await NitroImagePipeline.clearCache();
89
89
  ```
90
90
 
91
91
  ## API Reference
@@ -114,7 +114,7 @@ Applies a Gaussian blur to an existing `Image` object. Returns `Promise<Image>`.
114
114
 
115
115
  ### `clearCache()`
116
116
 
117
- Removes all cached images from memory and disk.
117
+ Removes all cached images from memory and disk. Returns `Promise<void>` that resolves once both caches are cleared.
118
118
 
119
119
  ## Credits
120
120
 
@@ -22,6 +22,11 @@ import com.margelo.nitro.core.Promise
22
22
  import com.margelo.nitro.image.HybridImage
23
23
  import com.margelo.nitro.image.HybridImageSpec
24
24
  import com.margelo.nitro.nitroimagepipeline.transform.BlurTransformation
25
+ import kotlinx.coroutines.Dispatchers
26
+ import kotlinx.coroutines.async
27
+ import kotlinx.coroutines.awaitAll
28
+ import kotlinx.coroutines.coroutineScope
29
+ import kotlinx.coroutines.withContext
25
30
  import okhttp3.OkHttpClient
26
31
  import okio.Path.Companion.toOkioPath
27
32
  import org.chromium.net.CronetEngine
@@ -30,29 +35,8 @@ class HybridNitroImagePipeline : HybridNitroImagePipelineSpec() {
30
35
  private val context
31
36
  get() = NitroModules.applicationContext as Context
32
37
 
33
- private val okHttpClient: OkHttpClient by lazy {
34
- val builder = OkHttpClient.Builder()
35
- try {
36
- val cronetEngine = CronetEngine.Builder(context).build()
37
- builder.addInterceptor(CronetInterceptor.newBuilder(cronetEngine).build())
38
- } catch (_: Exception) {
39
- // Cronet unavailable (no GMS or unsupported device) — use plain OkHttp
40
- }
41
- builder.build()
42
- }
43
-
44
- private val imageLoader: ImageLoader by lazy {
45
- ImageLoader.Builder(context)
46
- .components { add(OkHttpNetworkFetcherFactory(callFactory = okHttpClient)) }
47
- .memoryCache { MemoryCache.Builder().maxSizePercent(context, 0.25).build() }
48
- .diskCache {
49
- DiskCache.Builder()
50
- .directory(context.cacheDir.resolve("nitro_image_cache").toOkioPath())
51
- .maxSizeBytes(256L * 1024 * 1024)
52
- .build()
53
- }
54
- .build()
55
- }
38
+ private val imageLoader: ImageLoader
39
+ get() = getOrCreateImageLoader(context)
56
40
 
57
41
  override fun loadImage(url: String, options: Options?): Promise<HybridImageSpec> = Promise.async {
58
42
  val blur = options?.blur?.toFloat() ?: 0f
@@ -61,11 +45,6 @@ class HybridNitroImagePipeline : HybridNitroImagePipelineSpec() {
61
45
  if (blur > 0f) add(BlurTransformation(context, blur))
62
46
  if (cornerRadius > 0f) add(RoundedCornersTransformation(cornerRadius))
63
47
  }
64
- val memoryCacheKey = buildString {
65
- append(url)
66
- if (blur > 0f) append("_blur$blur")
67
- if (cornerRadius > 0f) append("_corner$cornerRadius")
68
- }
69
48
  val request =
70
49
  ImageRequest.Builder(context)
71
50
  .data(url)
@@ -87,7 +66,6 @@ class HybridNitroImagePipeline : HybridNitroImagePipelineSpec() {
87
66
  }
88
67
  }
89
68
  .allowHardware(true)
90
- .memoryCacheKey(memoryCacheKey)
91
69
  .transformations(transformations)
92
70
  .build()
93
71
 
@@ -111,10 +89,17 @@ class HybridNitroImagePipeline : HybridNitroImagePipelineSpec() {
111
89
  }
112
90
 
113
91
  override fun preLoadImages(urls: Array<String>): Promise<Unit> = Promise.async {
114
- for (url in urls) {
115
- val request = ImageRequest.Builder(context).data(url).build()
116
- imageLoader.execute(request)
92
+ coroutineScope {
93
+ urls
94
+ .map { url ->
95
+ async {
96
+ val request = ImageRequest.Builder(context).data(url).build()
97
+ imageLoader.execute(request)
98
+ }
99
+ }
100
+ .awaitAll()
117
101
  }
102
+ Unit
118
103
  }
119
104
 
120
105
  override fun gaussianBlur(image: HybridImageSpec, radius: Double): Promise<HybridImageSpec> =
@@ -126,8 +111,48 @@ class HybridNitroImagePipeline : HybridNitroImagePipelineSpec() {
126
111
  HybridImage(blurred)
127
112
  }
128
113
 
129
- override fun clearCache() {
114
+ override fun clearCache(): Promise<Unit> = Promise.async {
130
115
  imageLoader.memoryCache?.clear()
131
- imageLoader.diskCache?.clear()
116
+ // DiskCache.clear() does file I/O; keep it off the JS thread but await
117
+ // completion so callers can rely on the cache being empty, and so an
118
+ // IOException rejects the promise instead of crashing the process.
119
+ withContext(Dispatchers.IO) { imageLoader.diskCache?.clear() }
120
+ }
121
+
122
+ companion object {
123
+ // Coil requires a single DiskCache instance per directory, so the loader
124
+ // must be shared across all HybridNitroImagePipeline instances.
125
+ @Volatile private var sharedImageLoader: ImageLoader? = null
126
+
127
+ private fun getOrCreateImageLoader(context: Context): ImageLoader =
128
+ sharedImageLoader
129
+ ?: synchronized(this) {
130
+ sharedImageLoader ?: createImageLoader(context).also { sharedImageLoader = it }
131
+ }
132
+
133
+ private fun createImageLoader(context: Context): ImageLoader {
134
+ val okHttpClient =
135
+ OkHttpClient.Builder()
136
+ .apply {
137
+ try {
138
+ val cronetEngine = CronetEngine.Builder(context).build()
139
+ addInterceptor(CronetInterceptor.newBuilder(cronetEngine).build())
140
+ } catch (_: Exception) {
141
+ // Cronet unavailable (no GMS or unsupported device) — use plain OkHttp
142
+ }
143
+ }
144
+ .build()
145
+
146
+ return ImageLoader.Builder(context)
147
+ .components { add(OkHttpNetworkFetcherFactory(callFactory = okHttpClient)) }
148
+ .memoryCache { MemoryCache.Builder().maxSizePercent(context, 0.25).build() }
149
+ .diskCache {
150
+ DiskCache.Builder()
151
+ .directory(context.cacheDir.resolve("nitro_image_cache").toOkioPath())
152
+ .maxSizeBytes(256L * 1024 * 1024)
153
+ .build()
154
+ }
155
+ .build()
156
+ }
132
157
  }
133
158
  }
@@ -16,9 +16,44 @@ import CoreImage
16
16
  private class HybridImage: HybridImageSpec, NativeImage {
17
17
  let uiImage: UIImage
18
18
 
19
+ // PNG encoding is expensive; encode once and reuse for both
20
+ // toArrayBuffer() and toBase64(). Guarded by a lock (lazy var is not
21
+ // thread-safe) and dropped on memory pressure so a JS-retained image
22
+ // doesn't pin its encoding forever.
23
+ private let pngLock = NSLock()
24
+ private var cachedPngData: Data?
25
+ private var memoryWarningObserver: (any NSObjectProtocol)?
26
+
19
27
  init(uiImage: UIImage) {
20
28
  self.uiImage = uiImage
21
29
  super.init()
30
+ memoryWarningObserver = NotificationCenter.default.addObserver(
31
+ forName: UIApplication.didReceiveMemoryWarningNotification,
32
+ object: nil,
33
+ queue: nil
34
+ ) { [weak self] _ in
35
+ guard let self else { return }
36
+ self.pngLock.lock()
37
+ self.cachedPngData = nil
38
+ self.pngLock.unlock()
39
+ }
40
+ }
41
+
42
+ deinit {
43
+ if let memoryWarningObserver {
44
+ NotificationCenter.default.removeObserver(memoryWarningObserver)
45
+ }
46
+ }
47
+
48
+ private func pngData() -> Data? {
49
+ pngLock.lock()
50
+ defer { pngLock.unlock() }
51
+ if let cachedPngData {
52
+ return cachedPngData
53
+ }
54
+ let data = uiImage.pngData()
55
+ cachedPngData = data
56
+ return data
22
57
  }
23
58
 
24
59
  var width: Double {
@@ -30,14 +65,14 @@ private class HybridImage: HybridImageSpec, NativeImage {
30
65
  }
31
66
 
32
67
  func toArrayBuffer() throws -> ArrayBuffer {
33
- guard let data = uiImage.pngData() else {
68
+ guard let data = pngData() else {
34
69
  throw RuntimeError.error(withMessage: "Failed to encode image to PNG")
35
70
  }
36
71
  return try ArrayBuffer.copy(data: data)
37
72
  }
38
73
 
39
74
  func toBase64() throws -> String {
40
- guard let data = uiImage.pngData() else {
75
+ guard let data = pngData() else {
41
76
  throw RuntimeError.error(withMessage: "Failed to encode image")
42
77
  }
43
78
 
@@ -50,14 +85,34 @@ private class HybridImage: HybridImageSpec, NativeImage {
50
85
  }
51
86
 
52
87
  class HybridNitroImagePipeline: HybridNitroImagePipelineSpec {
53
- private let prefetcher = ImagePrefetcher()
54
-
55
- override init() {
56
- ImagePipeline.shared = ImagePipeline(configuration: .withDataCache)
57
- }
88
+ // One shared pipeline (instead of mutating ImagePipeline.shared) so the
89
+ // prefetcher and loadImage read/write the exact same caches, other Nuke
90
+ // users in the host app are left untouched, and repeated instantiation
91
+ // (e.g. Metro reloads) never puts two DataCache instances on the same
92
+ // directory.
93
+ private static let sharedPipeline: ImagePipeline = {
94
+ var configuration = ImagePipeline.Configuration.withDataCache
95
+ // Store processed (blurred/rounded) variants on disk in addition to
96
+ // the original download, so they survive memory eviction and
97
+ // restarts without dropping the original for other variants.
98
+ configuration.dataCachePolicy = .storeAll
99
+ return ImagePipeline(configuration: configuration)
100
+ }()
101
+
102
+ private static let sharedPrefetcher = ImagePrefetcher(pipeline: sharedPipeline)
103
+
104
+ // CIContext is expensive to create; Apple recommends reusing one.
105
+ private static let ciContext = CIContext()
106
+
107
+ private var pipeline: ImagePipeline { Self.sharedPipeline }
108
+ private var prefetcher: ImagePrefetcher { Self.sharedPrefetcher }
58
109
 
59
110
  func loadImage(url: String, options: Options?) throws -> Promise<any HybridImageSpec> {
60
111
  return Promise.async {
112
+ guard let imageUrl = URL(string: url) else {
113
+ throw RuntimeError.error(withMessage: "Invalid URL: \(url)")
114
+ }
115
+
61
116
  let cacheOptions: ImageRequest.Options = switch options?.cache {
62
117
  case .memory: [.disableDiskCache]
63
118
  case .disk: [.disableMemoryCache]
@@ -74,12 +129,12 @@ class HybridNitroImagePipeline: HybridNitroImagePipelineSpec {
74
129
  }
75
130
 
76
131
  let imgRequest = ImageRequest(
77
- url: URL(string: url),
132
+ url: imageUrl,
78
133
  processors: processors,
79
134
  options: cacheOptions
80
135
  )
81
136
 
82
- let image = try await ImagePipeline.shared.image(for: imgRequest)
137
+ let image = try await self.pipeline.image(for: imgRequest)
83
138
  return HybridImage(uiImage: image)
84
139
  }
85
140
  }
@@ -101,8 +156,10 @@ class HybridNitroImagePipeline: HybridNitroImagePipelineSpec {
101
156
  }
102
157
  }
103
158
 
104
- func clearCache() throws {
105
- ImagePipeline.shared.cache.removeAll()
159
+ func clearCache() throws -> Promise<Void> {
160
+ return Promise.async {
161
+ self.pipeline.cache.removeAll()
162
+ }
106
163
  }
107
164
 
108
165
  func gaussianBlur(image: any HybridImageSpec, radius: Double) throws -> Promise<any HybridImageSpec> {
@@ -113,17 +170,20 @@ class HybridNitroImagePipeline: HybridNitroImagePipelineSpec {
113
170
 
114
171
  let uiImage = nativeImage.uiImage
115
172
 
116
- let ciImage = CIImage(image: uiImage)
173
+ guard let ciImage = CIImage(image: uiImage) else {
174
+ throw RuntimeError.error(withMessage: "Failed to read image data")
175
+ }
117
176
 
118
177
  let filter = CIFilter(name: "CIGaussianBlur")!
119
178
  filter.setValue(ciImage, forKey: kCIInputImageKey)
120
179
  filter.setValue(radius, forKey: kCIInputRadiusKey)
121
180
 
122
- let context = CIContext()
123
- let output = filter.outputImage
124
- let cgImage = context.createCGImage(output!, from: ciImage!.extent)
181
+ guard let output = filter.outputImage,
182
+ let cgImage = Self.ciContext.createCGImage(output, from: ciImage.extent) else {
183
+ throw RuntimeError.error(withMessage: "Failed to apply gaussian blur")
184
+ }
125
185
 
126
- return HybridImage(uiImage: UIImage(cgImage: cgImage!))
186
+ return HybridImage(uiImage: UIImage(cgImage: cgImage))
127
187
  }
128
188
  }
129
189
 
@@ -19,32 +19,53 @@ const NitroImagePipeline = exports.NitroImagePipeline = _reactNativeNitroModules
19
19
  function useImage({
20
20
  url,
21
21
  blur = 0,
22
- cornerRadius = 0
22
+ cornerRadius = 0,
23
+ cache
23
24
  }) {
24
25
  const [image, setImage] = (0, _react.useState)({
25
26
  image: undefined,
26
27
  error: undefined
27
28
  });
29
+ const loadedUrlRef = (0, _react.useRef)(url);
28
30
  (0, _react.useEffect)(() => {
31
+ let cancelled = false;
32
+ // Only reset to the loading state when the URL changes; for same-URL
33
+ // param tweaks (blur/cornerRadius/cache) keep showing the current image
34
+ // until the new variant resolves, to avoid flashing empty.
35
+ if (loadedUrlRef.current !== url) {
36
+ loadedUrlRef.current = url;
37
+ setImage({
38
+ image: undefined,
39
+ error: undefined
40
+ });
41
+ }
29
42
  (async () => {
30
43
  try {
31
44
  const result = await NitroImagePipeline.loadImage(url, {
32
45
  blur,
33
- cornerRadius
34
- });
35
- setImage({
36
- image: result,
37
- error: undefined
46
+ cornerRadius,
47
+ cache
38
48
  });
49
+ if (!cancelled) {
50
+ setImage({
51
+ image: result,
52
+ error: undefined
53
+ });
54
+ }
39
55
  } catch (e) {
40
56
  const error = e instanceof Error ? e : new Error(`${e}`);
41
- setImage({
42
- image: undefined,
43
- error: error
44
- });
57
+ if (!cancelled) {
58
+ setImage({
59
+ image: undefined,
60
+ error: error
61
+ });
62
+ }
45
63
  }
46
64
  })();
47
- }, [url, blur]);
65
+ return () => {
66
+ cancelled = true;
67
+ };
68
+ }, [url, blur, cornerRadius, cache]);
48
69
  return image;
49
70
  }
50
71
  //# sourceMappingURL=index.js.map
@@ -1 +1 @@
1
- {"version":3,"names":["_react","require","_reactNativeNitroModules","NitroImagePipeline","exports","NitroModules","createHybridObject","useImage","url","blur","cornerRadius","image","setImage","useState","undefined","error","useEffect","result","loadImage","e","Error"],"sourceRoot":"../../src","sources":["index.ts"],"mappings":";;;;;;;AAAA,IAAAA,MAAA,GAAAC,OAAA;AAEA,IAAAC,wBAAA,GAAAD,OAAA;AAIO,MAAME,kBAAkB,GAAAC,OAAA,CAAAD,kBAAA,GAC7BE,qCAAY,CAACC,kBAAkB,CAAyB,oBAAoB,CAAC;AAmB/E;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACO,SAASC,QAAQA,CAAC;EACvBC,GAAG;EACHC,IAAI,GAAG,CAAC;EACRC,YAAY,GAAG;AAKjB,CAAC,EAAU;EACT,MAAM,CAACC,KAAK,EAAEC,QAAQ,CAAC,GAAG,IAAAC,eAAQ,EAAS;IACzCF,KAAK,EAAEG,SAAS;IAChBC,KAAK,EAAED;EACT,CAAC,CAAC;EAEF,IAAAE,gBAAS,EAAC,MAAM;IACd,CAAC,YAAY;MACX,IAAI;QACF,MAAMC,MAAM,GAAG,MAAMd,kBAAkB,CAACe,SAAS,CAACV,GAAG,EAAE;UACrDC,IAAI;UACJC;QACF,CAAC,CAAC;QAEFE,QAAQ,CAAC;UAAED,KAAK,EAAEM,MAAM;UAAEF,KAAK,EAAED;QAAU,CAAC,CAAC;MAC/C,CAAC,CAAC,OAAOK,CAAC,EAAE;QACV,MAAMJ,KAAK,GAAGI,CAAC,YAAYC,KAAK,GAAGD,CAAC,GAAG,IAAIC,KAAK,CAAC,GAAGD,CAAC,EAAE,CAAC;QACxDP,QAAQ,CAAC;UAAED,KAAK,EAAEG,SAAS;UAAEC,KAAK,EAAEA;QAAM,CAAC,CAAC;MAC9C;IACF,CAAC,EAAE,CAAC;EACN,CAAC,EAAE,CAACP,GAAG,EAAEC,IAAI,CAAC,CAAC;EAEf,OAAOE,KAAK;AACd","ignoreList":[]}
1
+ {"version":3,"names":["_react","require","_reactNativeNitroModules","NitroImagePipeline","exports","NitroModules","createHybridObject","useImage","url","blur","cornerRadius","cache","image","setImage","useState","undefined","error","loadedUrlRef","useRef","useEffect","cancelled","current","result","loadImage","e","Error"],"sourceRoot":"../../src","sources":["index.ts"],"mappings":";;;;;;;AAAA,IAAAA,MAAA,GAAAC,OAAA;AAEA,IAAAC,wBAAA,GAAAD,OAAA;AAUO,MAAME,kBAAkB,GAAAC,OAAA,CAAAD,kBAAA,GAC7BE,qCAAY,CAACC,kBAAkB,CAAyB,oBAAoB,CAAC;AAmB/E;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACO,SAASC,QAAQA,CAAC;EACvBC,GAAG;EACHC,IAAI,GAAG,CAAC;EACRC,YAAY,GAAG,CAAC;EAChBC;AAMF,CAAC,EAAU;EACT,MAAM,CAACC,KAAK,EAAEC,QAAQ,CAAC,GAAG,IAAAC,eAAQ,EAAS;IACzCF,KAAK,EAAEG,SAAS;IAChBC,KAAK,EAAED;EACT,CAAC,CAAC;EACF,MAAME,YAAY,GAAG,IAAAC,aAAM,EAACV,GAAG,CAAC;EAEhC,IAAAW,gBAAS,EAAC,MAAM;IACd,IAAIC,SAAS,GAAG,KAAK;IACrB;IACA;IACA;IACA,IAAIH,YAAY,CAACI,OAAO,KAAKb,GAAG,EAAE;MAChCS,YAAY,CAACI,OAAO,GAAGb,GAAG;MAC1BK,QAAQ,CAAC;QAAED,KAAK,EAAEG,SAAS;QAAEC,KAAK,EAAED;MAAU,CAAC,CAAC;IAClD;IAEA,CAAC,YAAY;MACX,IAAI;QACF,MAAMO,MAAM,GAAG,MAAMnB,kBAAkB,CAACoB,SAAS,CAACf,GAAG,EAAE;UACrDC,IAAI;UACJC,YAAY;UACZC;QACF,CAAC,CAAC;QAEF,IAAI,CAACS,SAAS,EAAE;UACdP,QAAQ,CAAC;YAAED,KAAK,EAAEU,MAAM;YAAEN,KAAK,EAAED;UAAU,CAAC,CAAC;QAC/C;MACF,CAAC,CAAC,OAAOS,CAAC,EAAE;QACV,MAAMR,KAAK,GAAGQ,CAAC,YAAYC,KAAK,GAAGD,CAAC,GAAG,IAAIC,KAAK,CAAC,GAAGD,CAAC,EAAE,CAAC;QACxD,IAAI,CAACJ,SAAS,EAAE;UACdP,QAAQ,CAAC;YAAED,KAAK,EAAEG,SAAS;YAAEC,KAAK,EAAEA;UAAM,CAAC,CAAC;QAC9C;MACF;IACF,CAAC,EAAE,CAAC;IAEJ,OAAO,MAAM;MACXI,SAAS,GAAG,IAAI;IAClB,CAAC;EACH,CAAC,EAAE,CAACZ,GAAG,EAAEC,IAAI,EAAEC,YAAY,EAAEC,KAAK,CAAC,CAAC;EAEpC,OAAOC,KAAK;AACd","ignoreList":[]}
@@ -1,6 +1,6 @@
1
1
  "use strict";
2
2
 
3
- import { useEffect, useState } from 'react';
3
+ import { useEffect, useRef, useState } from 'react';
4
4
  import { NitroModules } from 'react-native-nitro-modules';
5
5
  export const NitroImagePipeline = NitroModules.createHybridObject('NitroImagePipeline');
6
6
  /**
@@ -14,32 +14,53 @@ export const NitroImagePipeline = NitroModules.createHybridObject('NitroImagePip
14
14
  export function useImage({
15
15
  url,
16
16
  blur = 0,
17
- cornerRadius = 0
17
+ cornerRadius = 0,
18
+ cache
18
19
  }) {
19
20
  const [image, setImage] = useState({
20
21
  image: undefined,
21
22
  error: undefined
22
23
  });
24
+ const loadedUrlRef = useRef(url);
23
25
  useEffect(() => {
26
+ let cancelled = false;
27
+ // Only reset to the loading state when the URL changes; for same-URL
28
+ // param tweaks (blur/cornerRadius/cache) keep showing the current image
29
+ // until the new variant resolves, to avoid flashing empty.
30
+ if (loadedUrlRef.current !== url) {
31
+ loadedUrlRef.current = url;
32
+ setImage({
33
+ image: undefined,
34
+ error: undefined
35
+ });
36
+ }
24
37
  (async () => {
25
38
  try {
26
39
  const result = await NitroImagePipeline.loadImage(url, {
27
40
  blur,
28
- cornerRadius
29
- });
30
- setImage({
31
- image: result,
32
- error: undefined
41
+ cornerRadius,
42
+ cache
33
43
  });
44
+ if (!cancelled) {
45
+ setImage({
46
+ image: result,
47
+ error: undefined
48
+ });
49
+ }
34
50
  } catch (e) {
35
51
  const error = e instanceof Error ? e : new Error(`${e}`);
36
- setImage({
37
- image: undefined,
38
- error: error
39
- });
52
+ if (!cancelled) {
53
+ setImage({
54
+ image: undefined,
55
+ error: error
56
+ });
57
+ }
40
58
  }
41
59
  })();
42
- }, [url, blur]);
60
+ return () => {
61
+ cancelled = true;
62
+ };
63
+ }, [url, blur, cornerRadius, cache]);
43
64
  return image;
44
65
  }
45
66
  //# sourceMappingURL=index.js.map
@@ -1 +1 @@
1
- {"version":3,"names":["useEffect","useState","NitroModules","NitroImagePipeline","createHybridObject","useImage","url","blur","cornerRadius","image","setImage","undefined","error","result","loadImage","e","Error"],"sourceRoot":"../../src","sources":["index.ts"],"mappings":";;AAAA,SAASA,SAAS,EAAEC,QAAQ,QAAQ,OAAO;AAE3C,SAASC,YAAY,QAAQ,4BAA4B;AAIzD,OAAO,MAAMC,kBAAkB,GAC7BD,YAAY,CAACE,kBAAkB,CAAyB,oBAAoB,CAAC;AAmB/E;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA,OAAO,SAASC,QAAQA,CAAC;EACvBC,GAAG;EACHC,IAAI,GAAG,CAAC;EACRC,YAAY,GAAG;AAKjB,CAAC,EAAU;EACT,MAAM,CAACC,KAAK,EAAEC,QAAQ,CAAC,GAAGT,QAAQ,CAAS;IACzCQ,KAAK,EAAEE,SAAS;IAChBC,KAAK,EAAED;EACT,CAAC,CAAC;EAEFX,SAAS,CAAC,MAAM;IACd,CAAC,YAAY;MACX,IAAI;QACF,MAAMa,MAAM,GAAG,MAAMV,kBAAkB,CAACW,SAAS,CAACR,GAAG,EAAE;UACrDC,IAAI;UACJC;QACF,CAAC,CAAC;QAEFE,QAAQ,CAAC;UAAED,KAAK,EAAEI,MAAM;UAAED,KAAK,EAAED;QAAU,CAAC,CAAC;MAC/C,CAAC,CAAC,OAAOI,CAAC,EAAE;QACV,MAAMH,KAAK,GAAGG,CAAC,YAAYC,KAAK,GAAGD,CAAC,GAAG,IAAIC,KAAK,CAAC,GAAGD,CAAC,EAAE,CAAC;QACxDL,QAAQ,CAAC;UAAED,KAAK,EAAEE,SAAS;UAAEC,KAAK,EAAEA;QAAM,CAAC,CAAC;MAC9C;IACF,CAAC,EAAE,CAAC;EACN,CAAC,EAAE,CAACN,GAAG,EAAEC,IAAI,CAAC,CAAC;EAEf,OAAOE,KAAK;AACd","ignoreList":[]}
1
+ {"version":3,"names":["useEffect","useRef","useState","NitroModules","NitroImagePipeline","createHybridObject","useImage","url","blur","cornerRadius","cache","image","setImage","undefined","error","loadedUrlRef","cancelled","current","result","loadImage","e","Error"],"sourceRoot":"../../src","sources":["index.ts"],"mappings":";;AAAA,SAASA,SAAS,EAAEC,MAAM,EAAEC,QAAQ,QAAQ,OAAO;AAEnD,SAASC,YAAY,QAAQ,4BAA4B;AAUzD,OAAO,MAAMC,kBAAkB,GAC7BD,YAAY,CAACE,kBAAkB,CAAyB,oBAAoB,CAAC;AAmB/E;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA,OAAO,SAASC,QAAQA,CAAC;EACvBC,GAAG;EACHC,IAAI,GAAG,CAAC;EACRC,YAAY,GAAG,CAAC;EAChBC;AAMF,CAAC,EAAU;EACT,MAAM,CAACC,KAAK,EAAEC,QAAQ,CAAC,GAAGV,QAAQ,CAAS;IACzCS,KAAK,EAAEE,SAAS;IAChBC,KAAK,EAAED;EACT,CAAC,CAAC;EACF,MAAME,YAAY,GAAGd,MAAM,CAACM,GAAG,CAAC;EAEhCP,SAAS,CAAC,MAAM;IACd,IAAIgB,SAAS,GAAG,KAAK;IACrB;IACA;IACA;IACA,IAAID,YAAY,CAACE,OAAO,KAAKV,GAAG,EAAE;MAChCQ,YAAY,CAACE,OAAO,GAAGV,GAAG;MAC1BK,QAAQ,CAAC;QAAED,KAAK,EAAEE,SAAS;QAAEC,KAAK,EAAED;MAAU,CAAC,CAAC;IAClD;IAEA,CAAC,YAAY;MACX,IAAI;QACF,MAAMK,MAAM,GAAG,MAAMd,kBAAkB,CAACe,SAAS,CAACZ,GAAG,EAAE;UACrDC,IAAI;UACJC,YAAY;UACZC;QACF,CAAC,CAAC;QAEF,IAAI,CAACM,SAAS,EAAE;UACdJ,QAAQ,CAAC;YAAED,KAAK,EAAEO,MAAM;YAAEJ,KAAK,EAAED;UAAU,CAAC,CAAC;QAC/C;MACF,CAAC,CAAC,OAAOO,CAAC,EAAE;QACV,MAAMN,KAAK,GAAGM,CAAC,YAAYC,KAAK,GAAGD,CAAC,GAAG,IAAIC,KAAK,CAAC,GAAGD,CAAC,EAAE,CAAC;QACxD,IAAI,CAACJ,SAAS,EAAE;UACdJ,QAAQ,CAAC;YAAED,KAAK,EAAEE,SAAS;YAAEC,KAAK,EAAEA;UAAM,CAAC,CAAC;QAC9C;MACF;IACF,CAAC,EAAE,CAAC;IAEJ,OAAO,MAAM;MACXE,SAAS,GAAG,IAAI;IAClB,CAAC;EACH,CAAC,EAAE,CAACT,GAAG,EAAEC,IAAI,EAAEC,YAAY,EAAEC,KAAK,CAAC,CAAC;EAEpC,OAAOC,KAAK;AACd","ignoreList":[]}
@@ -1,5 +1,6 @@
1
1
  import type { Image } from 'react-native-nitro-image';
2
- import type { NitroImagePipeline as NitroImagePipelineSpec } from './specs/nitro-image-toolkit.nitro';
2
+ import type { CacheOption, NitroImagePipeline as NitroImagePipelineSpec, Options } from './specs/nitro-image-toolkit.nitro';
3
+ export type { CacheOption, Options };
3
4
  export declare const NitroImagePipeline: NitroImagePipelineSpec;
4
5
  type Result = {
5
6
  image: undefined;
@@ -19,10 +20,10 @@ type Result = {
19
20
  * const { image, error } = useImage({ filePath: '/tmp/image.jpg' })
20
21
  * ```
21
22
  */
22
- export declare function useImage({ url, blur, cornerRadius, }: {
23
+ export declare function useImage({ url, blur, cornerRadius, cache, }: {
23
24
  url: string;
24
25
  blur?: number;
25
26
  cornerRadius?: number;
27
+ cache?: CacheOption;
26
28
  }): Result;
27
- export {};
28
29
  //# sourceMappingURL=index.d.ts.map
@@ -1 +1 @@
1
- {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../../src/index.ts"],"names":[],"mappings":"AACA,OAAO,KAAK,EAAE,KAAK,EAAE,MAAM,0BAA0B,CAAC;AAGtD,OAAO,KAAK,EAAE,kBAAkB,IAAI,sBAAsB,EAAE,MAAM,mCAAmC,CAAC;AAEtG,eAAO,MAAM,kBAAkB,wBACgD,CAAC;AAEhF,KAAK,MAAM,GAEP;IACE,KAAK,EAAE,SAAS,CAAC;IACjB,KAAK,EAAE,SAAS,CAAC;CAClB,GAED;IACE,KAAK,EAAE,KAAK,CAAC;IACb,KAAK,EAAE,SAAS,CAAC;CAClB,GAED;IACE,KAAK,EAAE,SAAS,CAAC;IACjB,KAAK,EAAE,KAAK,CAAC;CACd,CAAC;AAEN;;;;;;;GAOG;AACH,wBAAgB,QAAQ,CAAC,EACvB,GAAG,EACH,IAAQ,EACR,YAAgB,GACjB,EAAE;IACD,GAAG,EAAE,MAAM,CAAC;IACZ,IAAI,CAAC,EAAE,MAAM,CAAC;IACd,YAAY,CAAC,EAAE,MAAM,CAAC;CACvB,GAAG,MAAM,CAuBT"}
1
+ {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../../src/index.ts"],"names":[],"mappings":"AACA,OAAO,KAAK,EAAE,KAAK,EAAE,MAAM,0BAA0B,CAAC;AAGtD,OAAO,KAAK,EACV,WAAW,EACX,kBAAkB,IAAI,sBAAsB,EAC5C,OAAO,EACR,MAAM,mCAAmC,CAAC;AAE3C,YAAY,EAAE,WAAW,EAAE,OAAO,EAAE,CAAC;AAErC,eAAO,MAAM,kBAAkB,wBACgD,CAAC;AAEhF,KAAK,MAAM,GAEP;IACE,KAAK,EAAE,SAAS,CAAC;IACjB,KAAK,EAAE,SAAS,CAAC;CAClB,GAED;IACE,KAAK,EAAE,KAAK,CAAC;IACb,KAAK,EAAE,SAAS,CAAC;CAClB,GAED;IACE,KAAK,EAAE,SAAS,CAAC;IACjB,KAAK,EAAE,KAAK,CAAC;CACd,CAAC;AAEN;;;;;;;GAOG;AACH,wBAAgB,QAAQ,CAAC,EACvB,GAAG,EACH,IAAQ,EACR,YAAgB,EAChB,KAAK,GACN,EAAE;IACD,GAAG,EAAE,MAAM,CAAC;IACZ,IAAI,CAAC,EAAE,MAAM,CAAC;IACd,YAAY,CAAC,EAAE,MAAM,CAAC;IACtB,KAAK,CAAC,EAAE,WAAW,CAAC;CACrB,GAAG,MAAM,CA0CT"}
@@ -1,7 +1,7 @@
1
1
  import type { Image } from 'react-native-nitro-image';
2
2
  import type { HybridObject } from 'react-native-nitro-modules';
3
- type CacheOption = 'memory' | 'disk' | 'none';
4
- type Options = {
3
+ export type CacheOption = 'memory' | 'disk' | 'none';
4
+ export type Options = {
5
5
  blur?: number;
6
6
  cache?: CacheOption;
7
7
  cornerRadius?: number;
@@ -14,7 +14,6 @@ export interface NitroImagePipeline extends HybridObject<{
14
14
  preLoadImage(url: string): Promise<void>;
15
15
  preLoadImages(urls: string[]): Promise<void>;
16
16
  gaussianBlur(image: Image, radius: number): Promise<Image>;
17
- clearCache(): void;
17
+ clearCache(): Promise<void>;
18
18
  }
19
- export {};
20
19
  //# sourceMappingURL=nitro-image-toolkit.nitro.d.ts.map
@@ -1 +1 @@
1
- {"version":3,"file":"nitro-image-toolkit.nitro.d.ts","sourceRoot":"","sources":["../../../../src/specs/nitro-image-toolkit.nitro.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,KAAK,EAAE,MAAM,0BAA0B,CAAC;AACtD,OAAO,KAAK,EAAE,YAAY,EAAE,MAAM,4BAA4B,CAAC;AAE/D,KAAK,WAAW,GAAG,QAAQ,GAAG,MAAM,GAAG,MAAM,CAAC;AAC9C,KAAK,OAAO,GAAG;IAAE,IAAI,CAAC,EAAE,MAAM,CAAC;IAAC,KAAK,CAAC,EAAE,WAAW,CAAC;IAAC,YAAY,CAAC,EAAE,MAAM,CAAA;CAAE,CAAC;AAE7E,MAAM,WAAW,kBACf,SAAQ,YAAY,CAAC;IAAE,GAAG,EAAE,OAAO,CAAC;IAAC,OAAO,EAAE,QAAQ,CAAA;CAAE,CAAC;IACzD,SAAS,CAAC,GAAG,EAAE,MAAM,EAAE,OAAO,CAAC,EAAE,OAAO,GAAG,OAAO,CAAC,KAAK,CAAC,CAAC;IAC1D,YAAY,CAAC,GAAG,EAAE,MAAM,GAAG,OAAO,CAAC,IAAI,CAAC,CAAC;IACzC,aAAa,CAAC,IAAI,EAAE,MAAM,EAAE,GAAG,OAAO,CAAC,IAAI,CAAC,CAAC;IAC7C,YAAY,CAAC,KAAK,EAAE,KAAK,EAAE,MAAM,EAAE,MAAM,GAAG,OAAO,CAAC,KAAK,CAAC,CAAC;IAG3D,UAAU,IAAI,IAAI,CAAC;CACpB"}
1
+ {"version":3,"file":"nitro-image-toolkit.nitro.d.ts","sourceRoot":"","sources":["../../../../src/specs/nitro-image-toolkit.nitro.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,KAAK,EAAE,MAAM,0BAA0B,CAAC;AACtD,OAAO,KAAK,EAAE,YAAY,EAAE,MAAM,4BAA4B,CAAC;AAE/D,MAAM,MAAM,WAAW,GAAG,QAAQ,GAAG,MAAM,GAAG,MAAM,CAAC;AACrD,MAAM,MAAM,OAAO,GAAG;IACpB,IAAI,CAAC,EAAE,MAAM,CAAC;IACd,KAAK,CAAC,EAAE,WAAW,CAAC;IACpB,YAAY,CAAC,EAAE,MAAM,CAAC;CACvB,CAAC;AAEF,MAAM,WAAW,kBACf,SAAQ,YAAY,CAAC;IAAE,GAAG,EAAE,OAAO,CAAC;IAAC,OAAO,EAAE,QAAQ,CAAA;CAAE,CAAC;IACzD,SAAS,CAAC,GAAG,EAAE,MAAM,EAAE,OAAO,CAAC,EAAE,OAAO,GAAG,OAAO,CAAC,KAAK,CAAC,CAAC;IAC1D,YAAY,CAAC,GAAG,EAAE,MAAM,GAAG,OAAO,CAAC,IAAI,CAAC,CAAC;IACzC,aAAa,CAAC,IAAI,EAAE,MAAM,EAAE,GAAG,OAAO,CAAC,IAAI,CAAC,CAAC;IAC7C,YAAY,CAAC,KAAK,EAAE,KAAK,EAAE,MAAM,EAAE,MAAM,GAAG,OAAO,CAAC,KAAK,CAAC,CAAC;IAG3D,UAAU,IAAI,OAAO,CAAC,IAAI,CAAC,CAAC;CAC7B"}
@@ -132,9 +132,20 @@ namespace margelo::nitro::nitroimagepipeline {
132
132
  return __promise;
133
133
  }();
134
134
  }
135
- void JHybridNitroImagePipelineSpec::clearCache() {
136
- static const auto method = _javaPart->javaClassStatic()->getMethod<void()>("clearCache");
137
- method(_javaPart);
135
+ std::shared_ptr<Promise<void>> JHybridNitroImagePipelineSpec::clearCache() {
136
+ static const auto method = _javaPart->javaClassStatic()->getMethod<jni::local_ref<JPromise::javaobject>()>("clearCache");
137
+ auto __result = method(_javaPart);
138
+ return [&]() {
139
+ auto __promise = Promise<void>::create();
140
+ __result->cthis()->addOnResolvedListener([=](const jni::alias_ref<jni::JObject>& /* unit */) {
141
+ __promise->resolve();
142
+ });
143
+ __result->cthis()->addOnRejectedListener([=](const jni::alias_ref<jni::JThrowable>& __throwable) {
144
+ jni::JniException __jniError(__throwable);
145
+ __promise->reject(std::make_exception_ptr(__jniError));
146
+ });
147
+ return __promise;
148
+ }();
138
149
  }
139
150
 
140
151
  } // namespace margelo::nitro::nitroimagepipeline
@@ -58,7 +58,7 @@ namespace margelo::nitro::nitroimagepipeline {
58
58
  std::shared_ptr<Promise<void>> preLoadImage(const std::string& url) override;
59
59
  std::shared_ptr<Promise<void>> preLoadImages(const std::vector<std::string>& urls) override;
60
60
  std::shared_ptr<Promise<std::shared_ptr<margelo::nitro::image::HybridImageSpec>>> gaussianBlur(const std::shared_ptr<margelo::nitro::image::HybridImageSpec>& image, double radius) override;
61
- void clearCache() override;
61
+ std::shared_ptr<Promise<void>> clearCache() override;
62
62
 
63
63
  private:
64
64
  jni::global_ref<JHybridNitroImagePipelineSpec::JavaPart> _javaPart;
@@ -49,7 +49,7 @@ abstract class HybridNitroImagePipelineSpec: HybridObject() {
49
49
 
50
50
  @DoNotStrip
51
51
  @Keep
52
- abstract fun clearCache(): Unit
52
+ abstract fun clearCache(): Promise<Unit>
53
53
 
54
54
  // Default implementation of `HybridObject.toString()`
55
55
  override fun toString(): String {
@@ -231,14 +231,5 @@ namespace margelo::nitro::nitroimagepipeline::bridge::swift {
231
231
  inline Result_std__shared_ptr_Promise_void___ create_Result_std__shared_ptr_Promise_void___(const std::exception_ptr& error) noexcept {
232
232
  return Result<std::shared_ptr<Promise<void>>>::withError(error);
233
233
  }
234
-
235
- // pragma MARK: Result<void>
236
- using Result_void_ = Result<void>;
237
- inline Result_void_ create_Result_void_() noexcept {
238
- return Result<void>::withValue();
239
- }
240
- inline Result_void_ create_Result_void_(const std::exception_ptr& error) noexcept {
241
- return Result<void>::withError(error);
242
- }
243
234
 
244
235
  } // namespace margelo::nitro::nitroimagepipeline::bridge::swift
@@ -110,11 +110,13 @@ namespace margelo::nitro::nitroimagepipeline {
110
110
  auto __value = std::move(__result.value());
111
111
  return __value;
112
112
  }
113
- inline void clearCache() override {
113
+ inline std::shared_ptr<Promise<void>> clearCache() override {
114
114
  auto __result = _swiftPart.clearCache();
115
115
  if (__result.hasError()) [[unlikely]] {
116
116
  std::rethrow_exception(__result.error());
117
117
  }
118
+ auto __value = std::move(__result.value());
119
+ return __value;
118
120
  }
119
121
 
120
122
  private:
@@ -18,7 +18,7 @@ public protocol HybridNitroImagePipelineSpec_protocol: HybridObject {
18
18
  func preLoadImage(url: String) throws -> Promise<Void>
19
19
  func preLoadImages(urls: [String]) throws -> Promise<Void>
20
20
  func gaussianBlur(image: (any HybridImageSpec), radius: Double) throws -> Promise<(any HybridImageSpec)>
21
- func clearCache() throws -> Void
21
+ func clearCache() throws -> Promise<Void>
22
22
  }
23
23
 
24
24
  public extension HybridNitroImagePipelineSpec_protocol {
@@ -212,13 +212,21 @@ open class HybridNitroImagePipelineSpec_cxx {
212
212
  }
213
213
 
214
214
  @inline(__always)
215
- public final func clearCache() -> bridge.Result_void_ {
215
+ public final func clearCache() -> bridge.Result_std__shared_ptr_Promise_void___ {
216
216
  do {
217
- try self.__implementation.clearCache()
218
- return bridge.create_Result_void_()
217
+ let __result = try self.__implementation.clearCache()
218
+ let __resultCpp = { () -> bridge.std__shared_ptr_Promise_void__ in
219
+ let __promise = bridge.create_std__shared_ptr_Promise_void__()
220
+ let __promiseHolder = bridge.wrap_std__shared_ptr_Promise_void__(__promise)
221
+ __result
222
+ .then({ __result in __promiseHolder.resolve() })
223
+ .catch({ __error in __promiseHolder.reject(__error.toCpp()) })
224
+ return __promise
225
+ }()
226
+ return bridge.create_Result_std__shared_ptr_Promise_void___(__resultCpp)
219
227
  } catch (let __error) {
220
228
  let __exceptionPtr = __error.toCpp()
221
- return bridge.create_Result_void_(__exceptionPtr)
229
+ return bridge.create_Result_std__shared_ptr_Promise_void___(__exceptionPtr)
222
230
  }
223
231
  }
224
232
  }
@@ -61,7 +61,7 @@ namespace margelo::nitro::nitroimagepipeline {
61
61
  virtual std::shared_ptr<Promise<void>> preLoadImage(const std::string& url) = 0;
62
62
  virtual std::shared_ptr<Promise<void>> preLoadImages(const std::vector<std::string>& urls) = 0;
63
63
  virtual std::shared_ptr<Promise<std::shared_ptr<margelo::nitro::image::HybridImageSpec>>> gaussianBlur(const std::shared_ptr<margelo::nitro::image::HybridImageSpec>& image, double radius) = 0;
64
- virtual void clearCache() = 0;
64
+ virtual std::shared_ptr<Promise<void>> clearCache() = 0;
65
65
 
66
66
  protected:
67
67
  // Hybrid Setup
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "react-native-nitro-image-pipeline",
3
- "version": "0.3.4",
3
+ "version": "0.3.5",
4
4
  "description": "High-performance image loading, caching, and processing for React Native, built with Nitro Modules",
5
5
  "main": "./lib/commonjs/index.js",
6
6
  "module": "./lib/module/index.js",
package/src/index.ts CHANGED
@@ -1,8 +1,14 @@
1
- import { useEffect, useState } from 'react';
1
+ import { useEffect, useRef, useState } from 'react';
2
2
  import type { Image } from 'react-native-nitro-image';
3
3
  import { NitroModules } from 'react-native-nitro-modules';
4
4
 
5
- import type { NitroImagePipeline as NitroImagePipelineSpec } from './specs/nitro-image-toolkit.nitro';
5
+ import type {
6
+ CacheOption,
7
+ NitroImagePipeline as NitroImagePipelineSpec,
8
+ Options,
9
+ } from './specs/nitro-image-toolkit.nitro';
10
+
11
+ export type { CacheOption, Options };
6
12
 
7
13
  export const NitroImagePipeline =
8
14
  NitroModules.createHybridObject<NitroImagePipelineSpec>('NitroImagePipeline');
@@ -36,31 +42,52 @@ export function useImage({
36
42
  url,
37
43
  blur = 0,
38
44
  cornerRadius = 0,
45
+ cache,
39
46
  }: {
40
47
  url: string;
41
48
  blur?: number;
42
49
  cornerRadius?: number;
50
+ cache?: CacheOption;
43
51
  }): Result {
44
52
  const [image, setImage] = useState<Result>({
45
53
  image: undefined,
46
54
  error: undefined,
47
55
  });
56
+ const loadedUrlRef = useRef(url);
48
57
 
49
58
  useEffect(() => {
59
+ let cancelled = false;
60
+ // Only reset to the loading state when the URL changes; for same-URL
61
+ // param tweaks (blur/cornerRadius/cache) keep showing the current image
62
+ // until the new variant resolves, to avoid flashing empty.
63
+ if (loadedUrlRef.current !== url) {
64
+ loadedUrlRef.current = url;
65
+ setImage({ image: undefined, error: undefined });
66
+ }
67
+
50
68
  (async () => {
51
69
  try {
52
70
  const result = await NitroImagePipeline.loadImage(url, {
53
71
  blur,
54
72
  cornerRadius,
73
+ cache,
55
74
  });
56
75
 
57
- setImage({ image: result, error: undefined });
76
+ if (!cancelled) {
77
+ setImage({ image: result, error: undefined });
78
+ }
58
79
  } catch (e) {
59
80
  const error = e instanceof Error ? e : new Error(`${e}`);
60
- setImage({ image: undefined, error: error });
81
+ if (!cancelled) {
82
+ setImage({ image: undefined, error: error });
83
+ }
61
84
  }
62
85
  })();
63
- }, [url, blur]);
86
+
87
+ return () => {
88
+ cancelled = true;
89
+ };
90
+ }, [url, blur, cornerRadius, cache]);
64
91
 
65
92
  return image;
66
93
  }
@@ -1,8 +1,12 @@
1
1
  import type { Image } from 'react-native-nitro-image';
2
2
  import type { HybridObject } from 'react-native-nitro-modules';
3
3
 
4
- type CacheOption = 'memory' | 'disk' | 'none';
5
- type Options = { blur?: number; cache?: CacheOption; cornerRadius?: number };
4
+ export type CacheOption = 'memory' | 'disk' | 'none';
5
+ export type Options = {
6
+ blur?: number;
7
+ cache?: CacheOption;
8
+ cornerRadius?: number;
9
+ };
6
10
 
7
11
  export interface NitroImagePipeline
8
12
  extends HybridObject<{ ios: 'swift'; android: 'kotlin' }> {
@@ -12,5 +16,5 @@ export interface NitroImagePipeline
12
16
  gaussianBlur(image: Image, radius: number): Promise<Image>;
13
17
  // Future: brightness, saturation, tint, etc.
14
18
 
15
- clearCache(): void;
19
+ clearCache(): Promise<void>;
16
20
  }