react-native-fast-image 8.14.0 → 8.16.0

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
@@ -146,6 +146,23 @@ Indicates the load order priority of an image. Images with `FastImage.priority.h
146
146
 
147
147
  ---
148
148
 
149
+ ### `source.cacheKey?: string`
150
+
151
+ The key the image is cached under, instead of its uri. Use it for urls that change while the image stays the same, e.g. signed urls with a token or an expiry. Use something that identifies the image, and change it when the image changes (e.g. include a version or the time it was updated), or the old image keeps showing:
152
+
153
+ ```jsx
154
+ <FastImage
155
+ source={{
156
+ uri: signedUrl,
157
+ cacheKey: `avatar-${user.id}-${user.avatarUpdatedAt}`,
158
+ }}
159
+ />
160
+ ```
161
+
162
+ Not used with `cache: 'web'`, which follows the HTTP cache (keyed by url).
163
+
164
+ ---
165
+
149
166
  ### `defaultSource?: number`
150
167
 
151
168
  - An asset loaded with `require(...)`.
@@ -242,9 +259,19 @@ e.g. `onError={e => console.log(e.nativeEvent.error)}`
242
259
 
243
260
  ---
244
261
 
245
- ### `onLoadEnd?: () => void`
262
+ ### `onLoadEnd?: (result) => void`
263
+
264
+ Called when the image finishes loading, whether it was successful or an error, with the result: `{ ok: true, width, height }` (the image's size, as `onLoad` gets it) or `{ ok: false, error }` (as `onError` gets it). TypeScript makes you check `ok` before reading the size or the error.
246
265
 
247
- Called when the image finishes loading, whether it was successful or an error.
266
+ ```jsx
267
+ <FastImage
268
+ source={{ uri }}
269
+ onLoadEnd={(result) => {
270
+ if (result.ok) setAspectRatio(result.width / result.height)
271
+ else setFailed(result.error)
272
+ }}
273
+ />
274
+ ```
248
275
 
249
276
  ---
250
277
 
@@ -26,6 +26,24 @@ final class FastImageEvents {
26
26
  send(view, name, Arguments.createMap());
27
27
  }
28
28
 
29
+ // onLoadEnd with the load's result: ok with the image's size, as onLoad
30
+ // sent it.
31
+ static void sendLoadEnd(View view, int width, int height) {
32
+ WritableMap event = Arguments.createMap();
33
+ event.putBoolean("ok", true);
34
+ event.putInt("width", width);
35
+ event.putInt("height", height);
36
+ send(view, FastImageRequestListener.REACT_ON_LOAD_END_EVENT, event);
37
+ }
38
+
39
+ // onLoadEnd with the load's result: not ok, with the error onError sent.
40
+ static void sendLoadEnd(View view, String error) {
41
+ WritableMap event = Arguments.createMap();
42
+ event.putBoolean("ok", false);
43
+ event.putString("error", error);
44
+ send(view, FastImageRequestListener.REACT_ON_LOAD_END_EVENT, event);
45
+ }
46
+
29
47
  static void send(View view, String name, WritableMap data) {
30
48
  ReactContext context = FastImageViewManager.getReactContext(view.getContext());
31
49
  if (context == null) return;
@@ -0,0 +1,37 @@
1
+ package com.dylanvann.fastimage;
2
+
3
+ import androidx.annotation.NonNull;
4
+ import androidx.annotation.Nullable;
5
+
6
+ import com.bumptech.glide.load.model.GlideUrl;
7
+ import com.bumptech.glide.load.model.Headers;
8
+
9
+ // A url cached under the source's `cacheKey` instead of the url. Glide keys
10
+ // its disk cache by getCacheKey(), and its memory cache by equals() and
11
+ // hashCode(), which also compare the headers: here they only use the key, so
12
+ // a url whose token or headers change still hits both caches. The request
13
+ // still uses the url and headers.
14
+ class FastImageKeyedGlideUrl extends GlideUrl {
15
+ private final String mCacheKey;
16
+
17
+ FastImageKeyedGlideUrl(String url, Headers headers, @NonNull String cacheKey) {
18
+ super(url, headers);
19
+ mCacheKey = cacheKey;
20
+ }
21
+
22
+ @Override
23
+ public String getCacheKey() {
24
+ return mCacheKey;
25
+ }
26
+
27
+ @Override
28
+ public boolean equals(@Nullable Object other) {
29
+ return other instanceof FastImageKeyedGlideUrl
30
+ && mCacheKey.equals(((FastImageKeyedGlideUrl) other).mCacheKey);
31
+ }
32
+
33
+ @Override
34
+ public int hashCode() {
35
+ return mCacheKey.hashCode();
36
+ }
37
+ }
@@ -41,10 +41,11 @@ public class FastImageRequestListener implements RequestListener<Drawable> {
41
41
  }
42
42
  FastImageViewWithUrl view = (FastImageViewWithUrl) ((ImageViewTarget) target).getView();
43
43
  view.onImageFailed(thumbnail);
44
+ String error = errorMessage(e);
44
45
  WritableMap event = new WritableNativeMap();
45
- event.putString("error", errorMessage(e));
46
+ event.putString("error", error);
46
47
  FastImageEvents.send(view, REACT_ON_ERROR_EVENT, event);
47
- FastImageEvents.send(view, REACT_ON_LOAD_END_EVENT);
48
+ FastImageEvents.sendLoadEnd(view, error);
48
49
  return false;
49
50
  }
50
51
 
@@ -98,6 +99,6 @@ public class FastImageRequestListener implements RequestListener<Drawable> {
98
99
  event.putInt("width", size[0]);
99
100
  event.putInt("height", size[1]);
100
101
  FastImageEvents.send(view, REACT_ON_LOAD_EVENT, event);
101
- FastImageEvents.send(view, REACT_ON_LOAD_END_EVENT);
102
+ FastImageEvents.sendLoadEnd(view, size[0], size[1]);
102
103
  }
103
104
  }
@@ -124,9 +124,23 @@ public class FastImageSource extends ImageSource {
124
124
  mWebCache = webCache;
125
125
  }
126
126
 
127
+ // `cacheKey`: the key to cache the image under instead of its url.
128
+ @Nullable
129
+ private String mCacheKey = null;
130
+
131
+ void setCacheKey(@Nullable String cacheKey) {
132
+ mCacheKey = cacheKey == null || cacheKey.isEmpty() ? null : cacheKey;
133
+ }
134
+
127
135
  public GlideUrl getGlideUrl() {
128
- return mWebCache
129
- ? new FastImageWebGlideUrl(getUri().toString(), getHeaders())
130
- : new GlideUrl(getUri().toString(), getHeaders());
136
+ String url = getUri().toString();
137
+ if (mWebCache) {
138
+ // Follows the HTTP cache, which is keyed by url.
139
+ return new FastImageWebGlideUrl(url, getHeaders());
140
+ }
141
+ if (mCacheKey != null) {
142
+ return new FastImageKeyedGlideUrl(url, getHeaders(), mCacheKey);
143
+ }
144
+ return new GlideUrl(url, getHeaders());
131
145
  }
132
146
  }
@@ -19,6 +19,7 @@ import com.facebook.react.bridge.JSApplicationIllegalArgumentException;
19
19
  import com.facebook.react.bridge.NoSuchKeyException;
20
20
  import com.facebook.react.bridge.ReadableMap;
21
21
  import com.facebook.react.bridge.ReadableMapKeySetIterator;
22
+ import com.facebook.react.bridge.ReadableType;
22
23
 
23
24
  import java.util.HashMap;
24
25
  import java.util.Map;
@@ -63,6 +64,9 @@ class FastImageViewConverter {
63
64
  if (source == null) return null;
64
65
  FastImageSource imageSource = new FastImageSource(context, source.getString("uri"), getHeaders(source));
65
66
  imageSource.setWebCache(getCacheControl(source) == FastImageCacheControl.WEB);
67
+ if (source.hasKey("cacheKey") && source.getType("cacheKey") == ReadableType.String) {
68
+ imageSource.setCacheKey(source.getString("cacheKey"));
69
+ }
66
70
  return imageSource;
67
71
  }
68
72
 
@@ -1,7 +1,6 @@
1
1
  package com.dylanvann.fastimage;
2
2
 
3
3
  import static com.dylanvann.fastimage.FastImageRequestListener.REACT_ON_ERROR_EVENT;
4
- import static com.dylanvann.fastimage.FastImageRequestListener.REACT_ON_LOAD_END_EVENT;
5
4
 
6
5
  import android.annotation.SuppressLint;
7
6
  import android.content.Context;
@@ -376,10 +375,11 @@ class FastImageViewWithUrl extends AppCompatImageView {
376
375
  // resolved: fire onError and onLoadEnd and show defaultSource, as on iOS
377
376
  // (#1028, #945).
378
377
  if (mSource != null && (imageSource == null || imageSource.getUri().toString().length() == 0)) {
378
+ String error = "Invalid source: " + mSource;
379
379
  WritableMap event = new WritableNativeMap();
380
- event.putString("error", "Invalid source: " + mSource);
380
+ event.putString("error", error);
381
381
  FastImageEvents.send(this, REACT_ON_ERROR_EVENT, event);
382
- FastImageEvents.send(this, REACT_ON_LOAD_END_EVENT);
382
+ FastImageEvents.sendLoadEnd(this, error);
383
383
 
384
384
  // Cancel existing requests.
385
385
  clearView(requestManager);
package/dist/index.cjs.js CHANGED
@@ -73,12 +73,24 @@ function loopCount(loop) {
73
73
  if (loop === false) return 1;
74
74
  return Number.isFinite(loop) ? Math.max(1, Math.floor(loop)) : 0;
75
75
  }
76
+ function loadResult(event) {
77
+ var _event$width, _event$height, _event$error;
78
+ return event.ok ? {
79
+ ok: true,
80
+ width: (_event$width = event.width) !== null && _event$width !== void 0 ? _event$width : 0,
81
+ height: (_event$height = event.height) !== null && _event$height !== void 0 ? _event$height : 0
82
+ } : {
83
+ ok: false,
84
+ error: (_event$error = event.error) !== null && _event$error !== void 0 ? _event$error : "Failed to load the image"
85
+ };
86
+ }
76
87
  function withoutCache(source) {
77
88
  const { cache: _cache, ...rest } = source || {};
78
89
  return rest;
79
90
  }
80
91
  function FastImageBase({ source, defaultSource, tintColor, onLoadStart, onProgress, onLoad, onError, onLoadEnd, style, fallback, children, resizeMode = "cover", loop, forwardedRef, onLayout, pointerEvents, ...viewProps }) {
81
92
  const { onClick, ...props } = viewProps;
93
+ const fallbackResult = (0, react.useRef)(void 0);
82
94
  const wrapperProps = {
83
95
  onLayout,
84
96
  onClick,
@@ -103,9 +115,29 @@ function FastImageBase({ source, defaultSource, tintColor, onLoadStart, onProgre
103
115
  defaultSource,
104
116
  onLoadStart,
105
117
  onProgress,
106
- onLoad,
107
- onError,
108
- onLoadEnd,
118
+ onLoad: onLoadEnd ? (event) => {
119
+ const { width, height } = event.nativeEvent.source;
120
+ fallbackResult.current = {
121
+ ok: true,
122
+ width,
123
+ height
124
+ };
125
+ onLoad === null || onLoad === void 0 || onLoad(event);
126
+ } : onLoad,
127
+ onError: onLoadEnd ? (event) => {
128
+ fallbackResult.current = {
129
+ ok: false,
130
+ error: String(event.nativeEvent.error)
131
+ };
132
+ onError === null || onError === void 0 || onError(event);
133
+ } : onError,
134
+ onLoadEnd: onLoadEnd && (() => {
135
+ var _fallbackResult$curre;
136
+ return onLoadEnd((_fallbackResult$curre = fallbackResult.current) !== null && _fallbackResult$curre !== void 0 ? _fallbackResult$curre : {
137
+ ok: false,
138
+ error: "Failed to load the image"
139
+ });
140
+ }),
109
141
  resizeMode
110
142
  }), children);
111
143
  }
@@ -126,7 +158,7 @@ function FastImageBase({ source, defaultSource, tintColor, onLoadStart, onProgre
126
158
  onFastImageProgress: onProgress,
127
159
  onFastImageLoad: onLoad,
128
160
  onFastImageError: onError,
129
- onFastImageLoadEnd: onLoadEnd,
161
+ onFastImageLoadEnd: onLoadEnd && ((event) => onLoadEnd(loadResult(event.nativeEvent))),
130
162
  resizeMode
131
163
  }), children);
132
164
  }
@@ -49,6 +49,10 @@ export type ResizeModes = $Values<ResizeMode>
49
49
  export type Priorities = $Values<Priority>
50
50
  export type CacheControls = $Values<CacheControl>
51
51
 
52
+ export type LoadResult =
53
+ | {| ok: true, width: number, height: number |}
54
+ | {| ok: false, error: string |}
55
+
52
56
  export type PreloadSuccess = {|
53
57
  uri: string,
54
58
  ok: true,
@@ -72,13 +76,14 @@ export type FastImageSource = {
72
76
  headers?: Object,
73
77
  priority?: Priorities,
74
78
  cache?: CacheControls,
79
+ cacheKey?: string,
75
80
  }
76
81
 
77
82
  export type FastImageProps = $ReadOnly<{|
78
83
  ...ViewProps,
79
84
  onError?: ?(event: OnErrorEvent) => void,
80
85
  onLoad?: ?(event: OnLoadEvent) => void,
81
- onLoadEnd?: ?() => void,
86
+ onLoadEnd?: ?(result: LoadResult) => void,
82
87
  onLoadStart?: ?() => void,
83
88
  onProgress?: ?(event: OnProgressEvent) => void,
84
89
 
package/dist/index.d.ts CHANGED
@@ -27,6 +27,12 @@ export type Source = {
27
27
  };
28
28
  priority?: Priority;
29
29
  cache?: Cache;
30
+ /**
31
+ * The key the image is cached under, instead of its uri. For urls that
32
+ * change while the image stays the same, e.g. signed urls with a token or
33
+ * an expiry: use something stable, like the image's id.
34
+ */
35
+ cacheKey?: string;
30
36
  };
31
37
  export interface OnLoadEvent {
32
38
  nativeEvent: {
@@ -40,6 +46,14 @@ export interface OnErrorEvent {
40
46
  error: string;
41
47
  };
42
48
  }
49
+ export type LoadResult = {
50
+ ok: true;
51
+ width: number;
52
+ height: number;
53
+ } | {
54
+ ok: false;
55
+ error: string;
56
+ };
43
57
  export interface OnProgressEvent {
44
58
  nativeEvent: {
45
59
  loaded: number;
@@ -105,7 +119,11 @@ export interface FastImageProps extends AccessibilityProps, ViewProps {
105
119
  onProgress?(event: OnProgressEvent): void;
106
120
  onLoad?(event: OnLoadEvent): void;
107
121
  onError?(event: OnErrorEvent): void;
108
- onLoadEnd?(): void;
122
+ /**
123
+ * Called once the image has loaded or failed to, with the result: `ok` and
124
+ * the image's size, or not `ok` and the error.
125
+ */
126
+ onLoadEnd?(result: LoadResult): void;
109
127
  /**
110
128
  * onLayout function
111
129
  *
package/dist/index.js CHANGED
@@ -1,4 +1,4 @@
1
- import React, { forwardRef, memo } from "react";
1
+ import React, { forwardRef, memo, useRef } from "react";
2
2
  import { Image, NativeModules, Platform, StyleSheet, View, requireNativeComponent } from "react-native";
3
3
  //#region src/index.tsx
4
4
  const resizeMode = {
@@ -46,12 +46,24 @@ function loopCount(loop) {
46
46
  if (loop === false) return 1;
47
47
  return Number.isFinite(loop) ? Math.max(1, Math.floor(loop)) : 0;
48
48
  }
49
+ function loadResult(event) {
50
+ var _event$width, _event$height, _event$error;
51
+ return event.ok ? {
52
+ ok: true,
53
+ width: (_event$width = event.width) !== null && _event$width !== void 0 ? _event$width : 0,
54
+ height: (_event$height = event.height) !== null && _event$height !== void 0 ? _event$height : 0
55
+ } : {
56
+ ok: false,
57
+ error: (_event$error = event.error) !== null && _event$error !== void 0 ? _event$error : "Failed to load the image"
58
+ };
59
+ }
49
60
  function withoutCache(source) {
50
61
  const { cache: _cache, ...rest } = source || {};
51
62
  return rest;
52
63
  }
53
64
  function FastImageBase({ source, defaultSource, tintColor, onLoadStart, onProgress, onLoad, onError, onLoadEnd, style, fallback, children, resizeMode = "cover", loop, forwardedRef, onLayout, pointerEvents, ...viewProps }) {
54
65
  const { onClick, ...props } = viewProps;
66
+ const fallbackResult = useRef(void 0);
55
67
  const wrapperProps = {
56
68
  onLayout,
57
69
  onClick,
@@ -76,9 +88,29 @@ function FastImageBase({ source, defaultSource, tintColor, onLoadStart, onProgre
76
88
  defaultSource,
77
89
  onLoadStart,
78
90
  onProgress,
79
- onLoad,
80
- onError,
81
- onLoadEnd,
91
+ onLoad: onLoadEnd ? (event) => {
92
+ const { width, height } = event.nativeEvent.source;
93
+ fallbackResult.current = {
94
+ ok: true,
95
+ width,
96
+ height
97
+ };
98
+ onLoad === null || onLoad === void 0 || onLoad(event);
99
+ } : onLoad,
100
+ onError: onLoadEnd ? (event) => {
101
+ fallbackResult.current = {
102
+ ok: false,
103
+ error: String(event.nativeEvent.error)
104
+ };
105
+ onError === null || onError === void 0 || onError(event);
106
+ } : onError,
107
+ onLoadEnd: onLoadEnd && (() => {
108
+ var _fallbackResult$curre;
109
+ return onLoadEnd((_fallbackResult$curre = fallbackResult.current) !== null && _fallbackResult$curre !== void 0 ? _fallbackResult$curre : {
110
+ ok: false,
111
+ error: "Failed to load the image"
112
+ });
113
+ }),
82
114
  resizeMode
83
115
  }), children);
84
116
  }
@@ -99,7 +131,7 @@ function FastImageBase({ source, defaultSource, tintColor, onLoadStart, onProgre
99
131
  onFastImageProgress: onProgress,
100
132
  onFastImageLoad: onLoad,
101
133
  onFastImageError: onError,
102
- onFastImageLoadEnd: onLoadEnd,
134
+ onFastImageLoadEnd: onLoadEnd && ((event) => onLoadEnd(loadResult(event.nativeEvent))),
103
135
  resizeMode
104
136
  }), children);
105
137
  }
@@ -49,6 +49,10 @@ export type ResizeModes = $Values<ResizeMode>
49
49
  export type Priorities = $Values<Priority>
50
50
  export type CacheControls = $Values<CacheControl>
51
51
 
52
+ export type LoadResult =
53
+ | {| ok: true, width: number, height: number |}
54
+ | {| ok: false, error: string |}
55
+
52
56
  export type PreloadSuccess = {|
53
57
  uri: string,
54
58
  ok: true,
@@ -72,13 +76,14 @@ export type FastImageSource = {
72
76
  headers?: Object,
73
77
  priority?: Priorities,
74
78
  cache?: CacheControls,
79
+ cacheKey?: string,
75
80
  }
76
81
 
77
82
  export type FastImageProps = $ReadOnly<{|
78
83
  ...ViewProps,
79
84
  onError?: ?(event: OnErrorEvent) => void,
80
85
  onLoad?: ?(event: OnLoadEvent) => void,
81
- onLoadEnd?: ?() => void,
86
+ onLoadEnd?: ?(result: LoadResult) => void,
82
87
  onLoadStart?: ?() => void,
83
88
  onProgress?: ?(event: OnProgressEvent) => void,
84
89
 
@@ -14,8 +14,8 @@
14
14
  + (NSURL*) loadURLForURL: (NSURL*)url;
15
15
 
16
16
  // Sets a load's context to decode the image for a box (in pixels) to cover
17
- // or fit in.
18
- + (void) addToContext: (SDWebImageMutableContext*)context forURL: (NSURL*)url box: (CGSize)box cover: (BOOL)cover;
17
+ // or fit in. `key` is the source's cache key (its url, or its cacheKey).
18
+ + (void) addToContext: (SDWebImageMutableContext*)context forKey: (NSString*)key box: (CGSize)box cover: (BOOL)cover;
19
19
 
20
20
  // The size of the full image (in points, like UIImage's size) that an image
21
21
  // loaded this way was decoded from, for onLoad, or zero for other images.
@@ -100,11 +100,11 @@ static CGSize FFFPixelSize(NSData* data) {
100
100
  return components.URL ?: url;
101
101
  }
102
102
 
103
- + (void) addToContext: (SDWebImageMutableContext*)context forURL: (NSURL*)url box: (CGSize)box cover: (BOOL)cover {
103
+ + (void) addToContext: (SDWebImageMutableContext*)context forKey: (NSString*)key box: (CGSize)box cover: (BOOL)cover {
104
104
  context[SDWebImageContextAnimatedImageClass] = [FFFDownsampledImage class];
105
- // Cached under the url itself (see loadURLForURL:), so the disk cache
106
- // keeps one download for every size and for loads that aren't downsampled.
107
- NSString* key = url.absoluteString;
105
+ // Cached under the source's own key (not the url from loadURLForURL:),
106
+ // so the disk cache keeps one download for every size and for loads
107
+ // that aren't downsampled.
108
108
  context[SDWebImageContextCacheKeyFilter] = [SDWebImageCacheKeyFilter cacheKeyFilterWithBlock: ^NSString* (NSURL* _Nonnull loadURL) {
109
109
  return key;
110
110
  }];
@@ -1,6 +1,7 @@
1
1
  #import <Foundation/Foundation.h>
2
2
  #import <UIKit/UIKit.h>
3
3
  #import <SDWebImage/SDWebImageDownloaderRequestModifier.h>
4
+ #import <SDWebImage/SDWebImageCacheKeyFilter.h>
4
5
 
5
6
  typedef NS_ENUM(NSInteger, FFFPriority) {
6
7
  FFFPriorityLow,
@@ -27,6 +28,8 @@ typedef NS_ENUM(NSInteger, FFFCacheControl) {
27
28
  @property (nonatomic) NSDictionary *headers;
28
29
  // cache control mode
29
30
  @property (nonatomic) FFFCacheControl cacheControl;
31
+ // The key to cache the image under instead of its url, or nil.
32
+ @property (nonatomic, copy) NSString *cacheKey;
30
33
 
31
34
  - (instancetype)initWithURL:(NSURL *)url
32
35
  priority:(FFFPriority)priority
@@ -36,4 +39,7 @@ typedef NS_ENUM(NSInteger, FFFCacheControl) {
36
39
  // Adds this source's headers to its image requests.
37
40
  - (SDWebImageDownloaderRequestModifier *)requestModifier;
38
41
 
42
+ // Caches the image under cacheKey, or nil without one.
43
+ - (SDWebImageCacheKeyFilter *)cacheKeyFilter;
44
+
39
45
  @end
@@ -17,6 +17,18 @@
17
17
  return self;
18
18
  }
19
19
 
20
+ - (SDWebImageCacheKeyFilter *)cacheKeyFilter
21
+ {
22
+ NSString* cacheKey = _cacheKey;
23
+ // `cache: 'web'` follows the HTTP cache, which is keyed by url.
24
+ if (cacheKey.length == 0 || _cacheControl == FFFCacheControlWeb) {
25
+ return nil;
26
+ }
27
+ return [SDWebImageCacheKeyFilter cacheKeyFilterWithBlock: ^NSString* _Nullable (NSURL* _Nonnull url) {
28
+ return cacheKey;
29
+ }];
30
+ }
31
+
20
32
  - (SDWebImageDownloaderRequestModifier *)requestModifier
21
33
  {
22
34
  NSDictionary* headers = _headers;
@@ -161,7 +161,7 @@ static CFTimeInterval FFFEnteredBackgroundAt = 0;
161
161
  - (void) setOnFastImageLoadEnd: (RCTDirectEventBlock)onFastImageLoadEnd {
162
162
  _onFastImageLoadEnd = onFastImageLoadEnd;
163
163
  if (self.hasCompleted && _onFastImageLoadEnd) {
164
- _onFastImageLoadEnd(@{});
164
+ _onFastImageLoadEnd([self loadEndEvent: YES]);
165
165
  }
166
166
  }
167
167
 
@@ -284,6 +284,14 @@ NSString *FFFErrorMessage(NSError *error)
284
284
  }
285
285
  }
286
286
 
287
+ // onLoadEnd's event: the load's result, as onLoad or onError sent it (ok: its
288
+ // size, or not ok: the error).
289
+ - (NSDictionary*) loadEndEvent: (BOOL)ok {
290
+ NSMutableDictionary* event = [NSMutableDictionary dictionaryWithDictionary: (ok ? self.onLoadEvent : self.onErrorEvent) ?: @{}];
291
+ event[@"ok"] = @(ok);
292
+ return event;
293
+ }
294
+
287
295
  - (void) setSource: (FFFastImageSource*)source {
288
296
  if (_source != source) {
289
297
  _source = source;
@@ -429,7 +437,7 @@ NSString *FFFErrorMessage(NSError *error)
429
437
  self.showsLoadedImage = NO;
430
438
  [self sendOnError: @"Failed to decode the image"];
431
439
  if (self.onFastImageLoadEnd) {
432
- self.onFastImageLoadEnd(@{});
440
+ self.onFastImageLoadEnd([self loadEndEvent: NO]);
433
441
  }
434
442
  return;
435
443
  }
@@ -445,7 +453,7 @@ NSString *FFFErrorMessage(NSError *error)
445
453
  [self sendOnLoad: image];
446
454
 
447
455
  if (self.onFastImageLoadEnd) {
448
- self.onFastImageLoadEnd(@{});
456
+ self.onFastImageLoadEnd([self loadEndEvent: YES]);
449
457
  }
450
458
  return;
451
459
  }
@@ -510,6 +518,7 @@ NSString *FFFErrorMessage(NSError *error)
510
518
  - (SDWebImageContext*) loadContext {
511
519
  SDWebImageMutableContext* context = [NSMutableDictionary dictionary];
512
520
  context[SDWebImageContextDownloadRequestModifier] = _source.requestModifier;
521
+ context[SDWebImageContextCacheKeyFilter] = _source.cacheKeyFilter;
513
522
  CGSize box = [self decodeBox];
514
523
  self.decodedBox = box;
515
524
  self.decodedCover = [self decodeCovers];
@@ -517,7 +526,8 @@ NSString *FFFErrorMessage(NSError *error)
517
526
  context[SDWebImageContextAnimatedImageClass] = [SDAnimatedImage class];
518
527
  return context;
519
528
  }
520
- [FFFDownsampledImage addToContext: context forURL: _source.url box: box cover: self.decodedCover];
529
+ NSString* key = _source.cacheKeyFilter ? _source.cacheKey : _source.url.absoluteString;
530
+ [FFFDownsampledImage addToContext: context forKey: key box: box cover: self.decodedCover];
521
531
  return context;
522
532
  }
523
533
 
@@ -621,14 +631,14 @@ NSString *FFFErrorMessage(NSError *error)
621
631
  weakSelf.showsLoadedImage = NO;
622
632
  [weakSelf sendOnError: FFFErrorMessage(error)];
623
633
  if (weakSelf.onFastImageLoadEnd) {
624
- weakSelf.onFastImageLoadEnd(@{});
634
+ weakSelf.onFastImageLoadEnd([weakSelf loadEndEvent: NO]);
625
635
  }
626
636
  } else {
627
637
  weakSelf.hasCompleted = YES;
628
638
  weakSelf.showsLoadedImage = YES;
629
639
  [weakSelf sendOnLoad: image];
630
640
  if (weakSelf.onFastImageLoadEnd) {
631
- weakSelf.onFastImageLoadEnd(@{});
641
+ weakSelf.onFastImageLoadEnd([weakSelf loadEndEvent: YES]);
632
642
  }
633
643
  }
634
644
  }];
@@ -37,6 +37,15 @@ RCT_REMAP_VIEW_PROPERTY(tintColor, imageColor, UIColor)
37
37
  static NSMutableArray<dispatch_block_t> *FFFPendingPreloads;
38
38
  static NSUInteger FFFPreloadsInFlight;
39
39
 
40
+ // A preload's context: the source's headers and cache key.
41
+ static SDWebImageMutableContext *FFFPreloadContext(FFFastImageSource *source)
42
+ {
43
+ SDWebImageMutableContext *context = [NSMutableDictionary dictionary];
44
+ context[SDWebImageContextDownloadRequestModifier] = source.requestModifier;
45
+ context[SDWebImageContextCacheKeyFilter] = source.cacheKeyFilter;
46
+ return context;
47
+ }
48
+
40
49
  static NSUInteger FFFPreloadLimit(void)
41
50
  {
42
51
  NSUInteger limit = [SDWebImagePrefetcher sharedImagePrefetcher].maxConcurrentPrefetchCount;
@@ -109,7 +118,7 @@ RCT_EXPORT_METHOD(preload:(nonnull NSArray<FFFastImageSource *> *)sources
109
118
  __block BOOL done = NO;
110
119
  [[SDWebImageManager sharedManager] loadImageWithURL:source.url
111
120
  options:options
112
- context:@{SDWebImageContextDownloadRequestModifier: source.requestModifier}
121
+ context:[FFFPreloadContext(source) copy]
113
122
  progress:nil
114
123
  completed:^(UIImage *image, NSData *data, NSError *error, SDImageCacheType cacheType, BOOL finished, NSURL *imageURL) {
115
124
  if (!finished || done) {
@@ -47,6 +47,8 @@ RCT_ENUM_CONVERTER(FFFCacheControl, (@{
47
47
  FFFastImageSource *imageSource = [[FFFastImageSource alloc] initWithURL:uri priority:priority headers:headers cacheControl:cacheControl];
48
48
  id priorityJSON = json[@"priority"];
49
49
  imageSource.hasPriority = priorityJSON && priorityJSON != (id)kCFNull;
50
+ NSString *cacheKey = [self NSString:json[@"cacheKey"]];
51
+ imageSource.cacheKey = cacheKey.length > 0 ? cacheKey : nil;
50
52
 
51
53
  return imageSource;
52
54
  }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "react-native-fast-image",
3
- "version": "8.14.0",
3
+ "version": "8.16.0",
4
4
  "description": "🚩 FastImage, performant React Native image component.",
5
5
  "keywords": [
6
6
  "cache",