react-native-fast-image 8.15.0 → 8.16.1

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(...)`.
@@ -332,6 +349,8 @@ for (const result of results) {
332
349
  }
333
350
  ```
334
351
 
352
+ Each source's `cache` applies, as for a view: with `web` the preload follows the HTTP cache, and with `cacheOnly` it doesn't download. A `cacheOnly` preload resolves `ok` only if the image is cached, and loads it from the disk cache into memory, so a view shows it at once.
353
+
335
354
  ### `FastImage.clearMemoryCache: () => Promise<void>`
336
355
 
337
356
  Clear all images from memory cache.
@@ -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
+ }
@@ -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
 
@@ -76,6 +76,7 @@ export type FastImageSource = {
76
76
  headers?: Object,
77
77
  priority?: Priorities,
78
78
  cache?: CacheControls,
79
+ cacheKey?: string,
79
80
  }
80
81
 
81
82
  export type FastImageProps = $ReadOnly<{|
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: {
@@ -76,6 +76,7 @@ export type FastImageSource = {
76
76
  headers?: Object,
77
77
  priority?: Priorities,
78
78
  cache?: CacheControls,
79
+ cacheKey?: string,
79
80
  }
80
81
 
81
82
  export type FastImageProps = $ReadOnly<{|
@@ -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,8 @@
1
1
  #import <Foundation/Foundation.h>
2
2
  #import <UIKit/UIKit.h>
3
3
  #import <SDWebImage/SDWebImageDownloaderRequestModifier.h>
4
+ #import <SDWebImage/SDWebImageCacheKeyFilter.h>
5
+ #import <SDWebImage/SDWebImageDefine.h>
4
6
 
5
7
  typedef NS_ENUM(NSInteger, FFFPriority) {
6
8
  FFFPriorityLow,
@@ -27,6 +29,8 @@ typedef NS_ENUM(NSInteger, FFFCacheControl) {
27
29
  @property (nonatomic) NSDictionary *headers;
28
30
  // cache control mode
29
31
  @property (nonatomic) FFFCacheControl cacheControl;
32
+ // The key to cache the image under instead of its url, or nil.
33
+ @property (nonatomic, copy) NSString *cacheKey;
30
34
 
31
35
  - (instancetype)initWithURL:(NSURL *)url
32
36
  priority:(FFFPriority)priority
@@ -36,4 +40,11 @@ typedef NS_ENUM(NSInteger, FFFCacheControl) {
36
40
  // Adds this source's headers to its image requests.
37
41
  - (SDWebImageDownloaderRequestModifier *)requestModifier;
38
42
 
43
+ // Caches the image under cacheKey, or nil without one.
44
+ - (SDWebImageCacheKeyFilter *)cacheKeyFilter;
45
+
46
+ // The load options for `cache`: web follows the HTTP cache, cacheOnly never
47
+ // downloads. For views and preload alike.
48
+ - (SDWebImageOptions)cacheOptions;
49
+
39
50
  @end
@@ -17,6 +17,31 @@
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
+
32
+ - (SDWebImageOptions)cacheOptions
33
+ {
34
+ switch (_cacheControl) {
35
+ case FFFCacheControlWeb:
36
+ return SDWebImageRefreshCached;
37
+ case FFFCacheControlCacheOnly:
38
+ return SDWebImageFromCacheOnly;
39
+ case FFFCacheControlImmutable:
40
+ return 0;
41
+ }
42
+ return 0;
43
+ }
44
+
20
45
  - (SDWebImageDownloaderRequestModifier *)requestModifier
21
46
  {
22
47
  NSDictionary* headers = _headers;
@@ -500,17 +500,7 @@ NSString *FFFErrorMessage(NSError *error)
500
500
  break;
501
501
  }
502
502
 
503
- switch (_source.cacheControl) {
504
- case FFFCacheControlWeb:
505
- options |= SDWebImageRefreshCached;
506
- break;
507
- case FFFCacheControlCacheOnly:
508
- options |= SDWebImageFromCacheOnly;
509
- break;
510
- case FFFCacheControlImmutable:
511
- break;
512
- }
513
- return options;
503
+ return options | [_source cacheOptions];
514
504
  }
515
505
 
516
506
  // Headers, and the size to decode at (see FFFDownsampledImage), which it
@@ -518,6 +508,7 @@ NSString *FFFErrorMessage(NSError *error)
518
508
  - (SDWebImageContext*) loadContext {
519
509
  SDWebImageMutableContext* context = [NSMutableDictionary dictionary];
520
510
  context[SDWebImageContextDownloadRequestModifier] = _source.requestModifier;
511
+ context[SDWebImageContextCacheKeyFilter] = _source.cacheKeyFilter;
521
512
  CGSize box = [self decodeBox];
522
513
  self.decodedBox = box;
523
514
  self.decodedCover = [self decodeCovers];
@@ -525,7 +516,8 @@ NSString *FFFErrorMessage(NSError *error)
525
516
  context[SDWebImageContextAnimatedImageClass] = [SDAnimatedImage class];
526
517
  return context;
527
518
  }
528
- [FFFDownsampledImage addToContext: context forURL: _source.url box: box cover: self.decodedCover];
519
+ NSString* key = _source.cacheKeyFilter ? _source.cacheKey : _source.url.absoluteString;
520
+ [FFFDownsampledImage addToContext: context forKey: key box: box cover: self.decodedCover];
529
521
  return context;
530
522
  }
531
523
 
@@ -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;
@@ -80,8 +89,9 @@ RCT_EXPORT_METHOD(preload:(nonnull NSArray<FFFastImageSource *> *)sources
80
89
  }
81
90
  };
82
91
  // With the prefetcher's options (low priority), but one source at a
83
- // time (not SDWebImagePrefetcher), to get each source's result and to
84
- // send its headers with its own request only.
92
+ // time (not SDWebImagePrefetcher), to get each source's result, to
93
+ // send its headers with its own request only, and to follow its
94
+ // `cache` (#406).
85
95
  SDWebImageOptions prefetcherOptions = [SDWebImagePrefetcher sharedImagePrefetcher].options;
86
96
 
87
97
  [sources enumerateObjectsUsingBlock:^(FFFastImageSource * _Nonnull source, NSUInteger idx, BOOL * _Nonnull stop) {
@@ -103,13 +113,14 @@ RCT_EXPORT_METHOD(preload:(nonnull NSArray<FFFastImageSource *> *)sources
103
113
  options |= SDWebImageHighPriority;
104
114
  }
105
115
  }
116
+ options |= [source cacheOptions];
106
117
  [FFFPendingPreloads addObject:[^{
107
118
  // Once per slot: SDWebImage calls this once with finished set
108
119
  // (a progressive load calls it more, without).
109
120
  __block BOOL done = NO;
110
121
  [[SDWebImageManager sharedManager] loadImageWithURL:source.url
111
122
  options:options
112
- context:@{SDWebImageContextDownloadRequestModifier: source.requestModifier}
123
+ context:[FFFPreloadContext(source) copy]
113
124
  progress:nil
114
125
  completed:^(UIImage *image, NSData *data, NSError *error, SDImageCacheType cacheType, BOOL finished, NSURL *imageURL) {
115
126
  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.15.0",
3
+ "version": "8.16.1",
4
4
  "description": "🚩 FastImage, performant React Native image component.",
5
5
  "keywords": [
6
6
  "cache",