react-native-fast-image 8.16.2 → 8.17.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 +12 -0
- package/android/src/main/java/com/dylanvann/fastimage/FastImageSource.java +21 -0
- package/android/src/main/java/com/dylanvann/fastimage/FastImageViewConverter.java +5 -1
- package/android/src/main/java/com/dylanvann/fastimage/FastImageViewModule.java +103 -12
- package/android/src/main/java/com/dylanvann/fastimage/FastImageViewWithUrl.java +50 -10
- package/dist/index.cjs.js.flow +1 -0
- package/dist/index.d.ts +8 -0
- package/dist/index.js.flow +1 -0
- package/ios/FastImage/FFFastImageSource.h +4 -0
- package/ios/FastImage/FFFastImageSource.m +1 -0
- package/ios/FastImage/FFFastImageView.m +9 -0
- package/ios/FastImage/FFFastImageViewManager.m +11 -2
- package/ios/FastImage/RCTConvert+FFFastImage.m +2 -0
- package/package.json +1 -1
package/README.md
CHANGED
|
@@ -163,6 +163,12 @@ Not used with `cache: 'web'`, which follows the HTTP cache (keyed by url).
|
|
|
163
163
|
|
|
164
164
|
---
|
|
165
165
|
|
|
166
|
+
### `source.memoryCache?: boolean`
|
|
167
|
+
|
|
168
|
+
Whether the decoded image is kept in the memory cache. **Default: true.** With `false` it's only kept on disk: a view doesn't leave it in memory once it stops showing it, and `FastImage.preload` downloads it without decoding it. Use it for large images that are shown once or rarely, like a full-screen photo: a decoded photo can take tens of MB of memory. Images shown again, like a list scrolled back, are decoded from disk again.
|
|
169
|
+
|
|
170
|
+
---
|
|
171
|
+
|
|
166
172
|
### `defaultSource?: number`
|
|
167
173
|
|
|
168
174
|
- An asset loaded with `require(...)`.
|
|
@@ -351,6 +357,12 @@ for (const result of results) {
|
|
|
351
357
|
|
|
352
358
|
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
359
|
|
|
360
|
+
A source with `memoryCache: false` is only downloaded to the disk cache, without being decoded into memory, and is decoded when it's shown. Use it to preload many images, or large ones, e.g. the next pages of a feed: a decoded photo can take tens of MB of memory. Other sources are also kept decoded in memory, so they show at once.
|
|
361
|
+
|
|
362
|
+
```js
|
|
363
|
+
await FastImage.preload(photos.map((uri) => ({ uri, memoryCache: false })))
|
|
364
|
+
```
|
|
365
|
+
|
|
354
366
|
### `FastImage.clearMemoryCache: () => Promise<void>`
|
|
355
367
|
|
|
356
368
|
Clear all images from memory cache.
|
|
@@ -124,6 +124,27 @@ public class FastImageSource extends ImageSource {
|
|
|
124
124
|
mWebCache = webCache;
|
|
125
125
|
}
|
|
126
126
|
|
|
127
|
+
boolean isWebCache() {
|
|
128
|
+
return mWebCache;
|
|
129
|
+
}
|
|
130
|
+
|
|
131
|
+
// `memoryCache`: whether the decoded image is kept in the memory cache.
|
|
132
|
+
private boolean mMemoryCache = true;
|
|
133
|
+
|
|
134
|
+
void setMemoryCache(boolean memoryCache) {
|
|
135
|
+
mMemoryCache = memoryCache;
|
|
136
|
+
}
|
|
137
|
+
|
|
138
|
+
boolean isMemoryCache() {
|
|
139
|
+
return mMemoryCache;
|
|
140
|
+
}
|
|
141
|
+
|
|
142
|
+
// An http(s) url.
|
|
143
|
+
boolean isRemote() {
|
|
144
|
+
String scheme = mUri == null ? null : mUri.getScheme();
|
|
145
|
+
return "http".equalsIgnoreCase(scheme) || "https".equalsIgnoreCase(scheme);
|
|
146
|
+
}
|
|
147
|
+
|
|
127
148
|
// `cacheKey`: the key to cache the image under instead of its url.
|
|
128
149
|
@Nullable
|
|
129
150
|
private String mCacheKey = null;
|
|
@@ -64,6 +64,9 @@ class FastImageViewConverter {
|
|
|
64
64
|
if (source == null) return null;
|
|
65
65
|
FastImageSource imageSource = new FastImageSource(context, source.getString("uri"), getHeaders(source));
|
|
66
66
|
imageSource.setWebCache(getCacheControl(source) == FastImageCacheControl.WEB);
|
|
67
|
+
if (source.hasKey("memoryCache") && source.getType("memoryCache") == ReadableType.Boolean) {
|
|
68
|
+
imageSource.setMemoryCache(source.getBoolean("memoryCache"));
|
|
69
|
+
}
|
|
67
70
|
if (source.hasKey("cacheKey") && source.getType("cacheKey") == ReadableType.String) {
|
|
68
71
|
imageSource.setCacheKey(source.getString("cacheKey"));
|
|
69
72
|
}
|
|
@@ -116,7 +119,8 @@ class FastImageViewConverter {
|
|
|
116
119
|
RequestOptions options = new RequestOptions()
|
|
117
120
|
.diskCacheStrategy(diskCacheStrategy)
|
|
118
121
|
.onlyRetrieveFromCache(onlyFromCache)
|
|
119
|
-
.
|
|
122
|
+
// memoryCache false: only kept on disk.
|
|
123
|
+
.skipMemoryCache(skipMemoryCache || (imageSource != null && !imageSource.isMemoryCache()))
|
|
120
124
|
.priority(priority)
|
|
121
125
|
.placeholder(TRANSPARENT_DRAWABLE);
|
|
122
126
|
|
|
@@ -1,6 +1,8 @@
|
|
|
1
1
|
package com.dylanvann.fastimage;
|
|
2
2
|
|
|
3
3
|
import android.app.Activity;
|
|
4
|
+
import android.content.Context;
|
|
5
|
+
import android.graphics.BitmapFactory;
|
|
4
6
|
import android.graphics.drawable.Drawable;
|
|
5
7
|
|
|
6
8
|
import androidx.annotation.NonNull;
|
|
@@ -9,6 +11,8 @@ import androidx.annotation.Nullable;
|
|
|
9
11
|
import com.bumptech.glide.Glide;
|
|
10
12
|
import com.bumptech.glide.Priority;
|
|
11
13
|
import com.bumptech.glide.load.DataSource;
|
|
14
|
+
import com.bumptech.glide.load.ImageHeaderParser;
|
|
15
|
+
import com.bumptech.glide.load.ImageHeaderParserUtils;
|
|
12
16
|
import com.bumptech.glide.load.engine.GlideException;
|
|
13
17
|
import com.bumptech.glide.request.RequestListener;
|
|
14
18
|
import com.bumptech.glide.request.RequestOptions;
|
|
@@ -24,8 +28,13 @@ import com.facebook.react.bridge.UiThreadUtil;
|
|
|
24
28
|
import com.facebook.react.bridge.WritableArray;
|
|
25
29
|
import com.facebook.react.bridge.WritableMap;
|
|
26
30
|
|
|
31
|
+
import java.io.File;
|
|
32
|
+
import java.io.FileInputStream;
|
|
27
33
|
import java.io.IOException;
|
|
34
|
+
import java.io.InputStream;
|
|
28
35
|
import java.util.ArrayDeque;
|
|
36
|
+
import java.util.concurrent.Executor;
|
|
37
|
+
import java.util.concurrent.Executors;
|
|
29
38
|
|
|
30
39
|
class FastImageViewModule extends ReactContextBaseJavaModule {
|
|
31
40
|
|
|
@@ -63,8 +72,44 @@ class FastImageViewModule extends ReactContextBaseJavaModule {
|
|
|
63
72
|
}
|
|
64
73
|
}
|
|
65
74
|
|
|
75
|
+
// Reads the size of images preloaded to disk only, off the UI thread.
|
|
76
|
+
private static final Executor sizeExecutor = Executors.newSingleThreadExecutor();
|
|
77
|
+
|
|
78
|
+
// The image's size from its file's header, as a decoded image has it (with
|
|
79
|
+
// its EXIF orientation), or null if it can't be read.
|
|
80
|
+
@Nullable
|
|
81
|
+
private static int[] imageSize(Context context, File file) {
|
|
82
|
+
BitmapFactory.Options bounds = new BitmapFactory.Options();
|
|
83
|
+
bounds.inJustDecodeBounds = true;
|
|
84
|
+
BitmapFactory.decodeFile(file.getPath(), bounds);
|
|
85
|
+
if (bounds.outWidth <= 0 || bounds.outHeight <= 0) return null;
|
|
86
|
+
int orientation = ImageHeaderParser.UNKNOWN_ORIENTATION;
|
|
87
|
+
Glide glide = Glide.get(context);
|
|
88
|
+
try (InputStream stream = new FileInputStream(file)) {
|
|
89
|
+
orientation = ImageHeaderParserUtils.getOrientation(
|
|
90
|
+
glide.getRegistry().getImageHeaderParsers(), stream, glide.getArrayPool());
|
|
91
|
+
} catch (IOException e) {
|
|
92
|
+
// Taken as not rotated.
|
|
93
|
+
}
|
|
94
|
+
// 5 to 8 turn the image by 90 degrees.
|
|
95
|
+
boolean transposed = orientation >= 5 && orientation <= 8;
|
|
96
|
+
return transposed
|
|
97
|
+
? new int[] {bounds.outHeight, bounds.outWidth}
|
|
98
|
+
: new int[] {bounds.outWidth, bounds.outHeight};
|
|
99
|
+
}
|
|
100
|
+
|
|
101
|
+
private static WritableMap success(int width, int height) {
|
|
102
|
+
WritableMap result = Arguments.createMap();
|
|
103
|
+
result.putBoolean("ok", true);
|
|
104
|
+
result.putInt("width", width);
|
|
105
|
+
result.putInt("height", height);
|
|
106
|
+
return result;
|
|
107
|
+
}
|
|
108
|
+
|
|
66
109
|
// Resolves with a result per source, in order, once all have loaded or
|
|
67
110
|
// failed: { ok, width, height } or { ok: false, error }. Never rejects.
|
|
111
|
+
// A remote source with memoryCache false is only downloaded to the disk
|
|
112
|
+
// cache, without decoding it (its size comes from the header).
|
|
68
113
|
@ReactMethod
|
|
69
114
|
public void preload(final ReadableArray sources, final Promise promise) {
|
|
70
115
|
final ReactApplicationContext context = getReactApplicationContext();
|
|
@@ -111,6 +156,58 @@ class FastImageViewModule extends ReactContextBaseJavaModule {
|
|
|
111
156
|
final RequestOptions preloadOptions = source.hasKey("priority") && !source.isNull("priority")
|
|
112
157
|
? options
|
|
113
158
|
: options.priority(Priority.LOW);
|
|
159
|
+
// A source's result, which frees its slot.
|
|
160
|
+
final ResultCallback done = new ResultCallback() {
|
|
161
|
+
@Override
|
|
162
|
+
public void run(WritableMap result) {
|
|
163
|
+
results[index] = result;
|
|
164
|
+
preloadsInFlight--;
|
|
165
|
+
finishOne.run();
|
|
166
|
+
startPendingPreloads();
|
|
167
|
+
}
|
|
168
|
+
};
|
|
169
|
+
// Remote images (web ones are in the HTTP cache instead,
|
|
170
|
+
// and local ones are already on disk).
|
|
171
|
+
if (!imageSource.isMemoryCache() && !imageSource.isWebCache() && imageSource.isRemote()) {
|
|
172
|
+
pendingPreloads.add(new Runnable() {
|
|
173
|
+
@Override
|
|
174
|
+
public void run() {
|
|
175
|
+
Glide
|
|
176
|
+
.with(context)
|
|
177
|
+
.asFile()
|
|
178
|
+
.load(imageSource.getSourceForLoad())
|
|
179
|
+
.apply(preloadOptions)
|
|
180
|
+
.listener(new RequestListener<File>() {
|
|
181
|
+
@Override
|
|
182
|
+
public boolean onLoadFailed(@Nullable GlideException e, Object model, Target<File> target, boolean isFirstResource) {
|
|
183
|
+
done.run(failure(FastImageRequestListener.errorMessage(e)));
|
|
184
|
+
return false;
|
|
185
|
+
}
|
|
186
|
+
|
|
187
|
+
@Override
|
|
188
|
+
public boolean onResourceReady(final File file, Object model, Target<File> target, DataSource dataSource, boolean isFirstResource) {
|
|
189
|
+
sizeExecutor.execute(new Runnable() {
|
|
190
|
+
@Override
|
|
191
|
+
public void run() {
|
|
192
|
+
final int[] size = imageSize(context, file);
|
|
193
|
+
UiThreadUtil.runOnUiThread(new Runnable() {
|
|
194
|
+
@Override
|
|
195
|
+
public void run() {
|
|
196
|
+
done.run(size != null
|
|
197
|
+
? success(size[0], size[1])
|
|
198
|
+
: failure("Failed to read the image's size"));
|
|
199
|
+
}
|
|
200
|
+
});
|
|
201
|
+
}
|
|
202
|
+
});
|
|
203
|
+
return false;
|
|
204
|
+
}
|
|
205
|
+
})
|
|
206
|
+
.preload();
|
|
207
|
+
}
|
|
208
|
+
});
|
|
209
|
+
continue;
|
|
210
|
+
}
|
|
114
211
|
pendingPreloads.add(new Runnable() {
|
|
115
212
|
@Override
|
|
116
213
|
public void run() {
|
|
@@ -123,10 +220,7 @@ class FastImageViewModule extends ReactContextBaseJavaModule {
|
|
|
123
220
|
.listener(new RequestListener<Drawable>() {
|
|
124
221
|
@Override
|
|
125
222
|
public boolean onLoadFailed(@Nullable GlideException e, Object model, Target<Drawable> target, boolean isFirstResource) {
|
|
126
|
-
|
|
127
|
-
preloadsInFlight--;
|
|
128
|
-
finishOne.run();
|
|
129
|
-
startPendingPreloads();
|
|
223
|
+
done.run(failure(FastImageRequestListener.errorMessage(e)));
|
|
130
224
|
return false;
|
|
131
225
|
}
|
|
132
226
|
|
|
@@ -134,14 +228,7 @@ class FastImageViewModule extends ReactContextBaseJavaModule {
|
|
|
134
228
|
public boolean onResourceReady(Drawable resource, Object model, Target<Drawable> target, DataSource dataSource, boolean isFirstResource) {
|
|
135
229
|
// Preloaded at its original size, so this is
|
|
136
230
|
// the image's own size.
|
|
137
|
-
|
|
138
|
-
result.putBoolean("ok", true);
|
|
139
|
-
result.putInt("width", resource.getIntrinsicWidth());
|
|
140
|
-
result.putInt("height", resource.getIntrinsicHeight());
|
|
141
|
-
results[index] = result;
|
|
142
|
-
preloadsInFlight--;
|
|
143
|
-
finishOne.run();
|
|
144
|
-
startPendingPreloads();
|
|
231
|
+
done.run(success(resource.getIntrinsicWidth(), resource.getIntrinsicHeight()));
|
|
145
232
|
return false;
|
|
146
233
|
}
|
|
147
234
|
})
|
|
@@ -158,6 +245,10 @@ class FastImageViewModule extends ReactContextBaseJavaModule {
|
|
|
158
245
|
});
|
|
159
246
|
}
|
|
160
247
|
|
|
248
|
+
private interface ResultCallback {
|
|
249
|
+
void run(WritableMap result);
|
|
250
|
+
}
|
|
251
|
+
|
|
161
252
|
private static WritableMap failure(String error) {
|
|
162
253
|
WritableMap result = Arguments.createMap();
|
|
163
254
|
result.putBoolean("ok", false);
|
|
@@ -4,6 +4,7 @@ import static com.dylanvann.fastimage.FastImageRequestListener.REACT_ON_ERROR_EV
|
|
|
4
4
|
|
|
5
5
|
import android.annotation.SuppressLint;
|
|
6
6
|
import android.content.Context;
|
|
7
|
+
import android.graphics.Bitmap;
|
|
7
8
|
import android.graphics.drawable.BitmapDrawable;
|
|
8
9
|
import android.graphics.drawable.Drawable;
|
|
9
10
|
|
|
@@ -108,8 +109,20 @@ class FastImageViewWithUrl extends AppCompatImageView {
|
|
|
108
109
|
// Shows each GIF as this view's own animation. Glide's target also starts
|
|
109
110
|
// and stops it with the Activity, as it does Glide's own GifDrawable.
|
|
110
111
|
private final class OwnGifTarget extends DrawableImageViewTarget {
|
|
111
|
-
|
|
112
|
+
// Shown instead of the placeholder while loading (see shownCopy).
|
|
113
|
+
@Nullable
|
|
114
|
+
private Drawable mMeanwhile;
|
|
115
|
+
|
|
116
|
+
OwnGifTarget(@Nullable Drawable meanwhile) {
|
|
112
117
|
super(FastImageViewWithUrl.this);
|
|
118
|
+
mMeanwhile = meanwhile;
|
|
119
|
+
}
|
|
120
|
+
|
|
121
|
+
@Override
|
|
122
|
+
public void onLoadStarted(@Nullable Drawable placeholder) {
|
|
123
|
+
Drawable meanwhile = mMeanwhile;
|
|
124
|
+
mMeanwhile = null;
|
|
125
|
+
super.onLoadStarted(meanwhile != null ? meanwhile : placeholder);
|
|
113
126
|
}
|
|
114
127
|
|
|
115
128
|
@Override
|
|
@@ -287,12 +300,35 @@ class FastImageViewWithUrl extends AppCompatImageView {
|
|
|
287
300
|
private void reloadForSize() {
|
|
288
301
|
RequestBuilder<Drawable> shown = mShownRequest;
|
|
289
302
|
RequestBuilder<Drawable> current = fromCache(shown);
|
|
303
|
+
Drawable meanwhile = shownCopy(shown);
|
|
290
304
|
mLoadCount++;
|
|
291
305
|
clearView(requestManager);
|
|
292
|
-
|
|
293
|
-
.thumbnail(current)
|
|
306
|
+
RequestBuilder<Drawable> builder = shown.clone()
|
|
294
307
|
.error(current.clone())
|
|
295
|
-
.listener(new FastImageRequestListener(null, null, true, false))
|
|
308
|
+
.listener(new FastImageRequestListener(null, null, true, false));
|
|
309
|
+
if (meanwhile == null) builder = builder.thumbnail(current);
|
|
310
|
+
into(shown, builder, meanwhile);
|
|
311
|
+
}
|
|
312
|
+
|
|
313
|
+
// A copy of the image showing, for the view to show while the next one
|
|
314
|
+
// loads, when it isn't in Glide's memory cache (source.memoryCache false):
|
|
315
|
+
// a thumbnail of it would come from the disk, after the view has cleared,
|
|
316
|
+
// and its own bitmap is reused once its request is cleared. Only the view
|
|
317
|
+
// keeps the copy, until the next image replaces it. Null when the image
|
|
318
|
+
// is in the memory cache, or isn't a bitmap (a GIF).
|
|
319
|
+
@Nullable
|
|
320
|
+
private Drawable shownCopy(RequestBuilder<Drawable> shown) {
|
|
321
|
+
if (shown.isMemoryCacheable()) return null;
|
|
322
|
+
Drawable drawable = getDrawable();
|
|
323
|
+
if (!(drawable instanceof BitmapDrawable)) return null;
|
|
324
|
+
Bitmap bitmap = ((BitmapDrawable) drawable).getBitmap();
|
|
325
|
+
if (bitmap == null || bitmap.isRecycled()) return null;
|
|
326
|
+
Bitmap.Config config = bitmap.getConfig();
|
|
327
|
+
Bitmap copy = bitmap.copy(config != null ? config : Bitmap.Config.ARGB_8888, false);
|
|
328
|
+
if (copy == null) return null;
|
|
329
|
+
BitmapDrawable result = new BitmapDrawable(getResources(), copy);
|
|
330
|
+
if (mPixelated) result.setFilterBitmap(false);
|
|
331
|
+
return result;
|
|
296
332
|
}
|
|
297
333
|
|
|
298
334
|
// The shown image, from the cache only (never loaded again), at the size
|
|
@@ -304,15 +340,15 @@ class FastImageViewWithUrl extends AppCompatImageView {
|
|
|
304
340
|
return request;
|
|
305
341
|
}
|
|
306
342
|
|
|
307
|
-
// Starts loading the request (built by builder),
|
|
308
|
-
// loads it at. The size callback is added first, so it has the size by
|
|
343
|
+
// Starts loading the request (built by builder), showing meanwhile (if
|
|
344
|
+
// not null) until it loads, and recording the size Glide loads it at. The size callback is added first, so it has the size by
|
|
309
345
|
// the time the image loads, even from the memory cache.
|
|
310
|
-
private void into(RequestBuilder<Drawable> request, RequestBuilder<Drawable> builder) {
|
|
346
|
+
private void into(RequestBuilder<Drawable> request, RequestBuilder<Drawable> builder, @Nullable Drawable meanwhile) {
|
|
311
347
|
final int load = mLoadCount;
|
|
312
348
|
mLoadingRequest = request;
|
|
313
349
|
mLoadingWidth = 0;
|
|
314
350
|
mLoadingHeight = 0;
|
|
315
|
-
OwnGifTarget target = new OwnGifTarget();
|
|
351
|
+
OwnGifTarget target = new OwnGifTarget(meanwhile);
|
|
316
352
|
target.getSize(new SizeReadyCallback() {
|
|
317
353
|
@Override
|
|
318
354
|
public void onSizeReady(int width, int height) {
|
|
@@ -399,6 +435,9 @@ class FastImageViewWithUrl extends AppCompatImageView {
|
|
|
399
435
|
untrackUrl(viewsForUrlsMap);
|
|
400
436
|
}
|
|
401
437
|
|
|
438
|
+
// Before the view clears (see shownCopy).
|
|
439
|
+
Drawable meanwhile = shownRequest != null && imageSource != null ? shownCopy(shownRequest) : null;
|
|
440
|
+
|
|
402
441
|
// Cancel existing request.
|
|
403
442
|
this.glideUrl = glideUrl;
|
|
404
443
|
clearView(requestManager);
|
|
@@ -440,7 +479,8 @@ class FastImageViewWithUrl extends AppCompatImageView {
|
|
|
440
479
|
.apply(renderingOptions(model));
|
|
441
480
|
RequestBuilder<Drawable> request = builder.clone();
|
|
442
481
|
|
|
443
|
-
|
|
482
|
+
if (model == null) meanwhile = null;
|
|
483
|
+
boolean thumbnail = shownRequest != null && model != null && meanwhile == null;
|
|
444
484
|
if (thumbnail) {
|
|
445
485
|
builder = builder.thumbnail(fromCache(shownRequest));
|
|
446
486
|
}
|
|
@@ -448,7 +488,7 @@ class FastImageViewWithUrl extends AppCompatImageView {
|
|
|
448
488
|
if (key != null)
|
|
449
489
|
builder.listener(new FastImageRequestListener(key, imageSource, thumbnail, true));
|
|
450
490
|
|
|
451
|
-
into(request, builder);
|
|
491
|
+
into(request, builder, meanwhile);
|
|
452
492
|
}
|
|
453
493
|
}
|
|
454
494
|
|
package/dist/index.cjs.js.flow
CHANGED
package/dist/index.d.ts
CHANGED
|
@@ -33,6 +33,14 @@ export type Source = {
|
|
|
33
33
|
* an expiry: use something stable, like the image's id.
|
|
34
34
|
*/
|
|
35
35
|
cacheKey?: string;
|
|
36
|
+
/**
|
|
37
|
+
* Whether the decoded image is kept in the memory cache (default true).
|
|
38
|
+
* With false it's only kept on disk: a view doesn't leave it in memory
|
|
39
|
+
* once it stops showing it, and `FastImage.preload` downloads it without
|
|
40
|
+
* decoding it. For large images shown once, like a full-screen photo; a
|
|
41
|
+
* list scrolled back decodes them again.
|
|
42
|
+
*/
|
|
43
|
+
memoryCache?: boolean;
|
|
36
44
|
};
|
|
37
45
|
export interface OnLoadEvent {
|
|
38
46
|
nativeEvent: {
|
package/dist/index.js.flow
CHANGED
|
@@ -32,6 +32,10 @@ typedef NS_ENUM(NSInteger, FFFCacheControl) {
|
|
|
32
32
|
@property (nonatomic) FFFCacheControl cacheControl;
|
|
33
33
|
// The key to cache the image under instead of its url, or nil.
|
|
34
34
|
@property (nonatomic, copy) NSString *cacheKey;
|
|
35
|
+
// Whether the decoded image is kept in the memory cache (`memoryCache`,
|
|
36
|
+
// default YES). Without it, it's only kept on disk: views don't leave it in
|
|
37
|
+
// memory, and preload doesn't decode it.
|
|
38
|
+
@property (nonatomic) BOOL memoryCache;
|
|
35
39
|
|
|
36
40
|
- (instancetype)initWithURL:(NSURL *)url
|
|
37
41
|
priority:(FFFPriority)priority
|
|
@@ -515,10 +515,19 @@ NSString *FFFErrorMessage(NSError *error)
|
|
|
515
515
|
self.decodedCover = [self decodeCovers];
|
|
516
516
|
if (CGSizeEqualToSize(box, CGSizeZero)) {
|
|
517
517
|
context[SDWebImageContextAnimatedImageClass] = [SDAnimatedImage class];
|
|
518
|
+
if (!_source.memoryCache) {
|
|
519
|
+
// Only on disk, also when it comes from there.
|
|
520
|
+
context[SDWebImageContextStoreCacheType] = @(SDImageCacheTypeDisk);
|
|
521
|
+
}
|
|
518
522
|
return context;
|
|
519
523
|
}
|
|
520
524
|
NSString* key = _source.cacheKeyFilter ? _source.cacheKey : _source.url.absoluteString;
|
|
521
525
|
[FFFDownsampledImage addToContext: context forKey: key box: box cover: self.decodedCover];
|
|
526
|
+
if (!_source.memoryCache) {
|
|
527
|
+
// The smaller copy is only kept in memory, so it isn't kept (the
|
|
528
|
+
// downloaded file is still on disk).
|
|
529
|
+
context[SDWebImageContextStoreCacheType] = @(SDImageCacheTypeNone);
|
|
530
|
+
}
|
|
522
531
|
return context;
|
|
523
532
|
}
|
|
524
533
|
|
|
@@ -69,7 +69,9 @@ static void FFFStartPendingPreloads(void)
|
|
|
69
69
|
}
|
|
70
70
|
|
|
71
71
|
// Resolves with a result per source, in order, once all have loaded or failed:
|
|
72
|
-
// { ok, width, height } or { ok: false, error }. Never rejects.
|
|
72
|
+
// { ok, width, height } or { ok: false, error }. Never rejects. A source with
|
|
73
|
+
// memoryCache false is only stored on disk, without decoding it (its size
|
|
74
|
+
// comes from the header).
|
|
73
75
|
RCT_EXPORT_METHOD(preload:(nonnull NSArray<FFFastImageSource *> *)sources
|
|
74
76
|
resolve:(RCTPromiseResolveBlock)resolve
|
|
75
77
|
reject:(__unused RCTPromiseRejectBlock)reject)
|
|
@@ -115,13 +117,20 @@ RCT_EXPORT_METHOD(preload:(nonnull NSArray<FFFastImageSource *> *)sources
|
|
|
115
117
|
}
|
|
116
118
|
}
|
|
117
119
|
options |= [source cacheOptions];
|
|
120
|
+
SDWebImageMutableContext *context = FFFPreloadContext(source);
|
|
121
|
+
if (!source.memoryCache) {
|
|
122
|
+
// Not decoded (only its header is read, for the size), and not
|
|
123
|
+
// kept in memory, also when it comes from the disk cache.
|
|
124
|
+
options |= SDWebImageAvoidDecodeImage;
|
|
125
|
+
context[SDWebImageContextStoreCacheType] = @(SDImageCacheTypeDisk);
|
|
126
|
+
}
|
|
118
127
|
[FFFPendingPreloads addObject:[^{
|
|
119
128
|
// Once per slot: SDWebImage calls this once with finished set
|
|
120
129
|
// (a progressive load calls it more, without).
|
|
121
130
|
__block BOOL done = NO;
|
|
122
131
|
[[SDWebImageManager sharedManager] loadImageWithURL:source.url
|
|
123
132
|
options:options
|
|
124
|
-
context:[
|
|
133
|
+
context:[context copy]
|
|
125
134
|
progress:nil
|
|
126
135
|
completed:^(UIImage *image, NSData *data, NSError *error, SDImageCacheType cacheType, BOOL finished, NSURL *imageURL) {
|
|
127
136
|
if (!finished || done) {
|
|
@@ -49,6 +49,8 @@ RCT_ENUM_CONVERTER(FFFCacheControl, (@{
|
|
|
49
49
|
imageSource.hasPriority = priorityJSON && priorityJSON != (id)kCFNull;
|
|
50
50
|
NSString *cacheKey = [self NSString:json[@"cacheKey"]];
|
|
51
51
|
imageSource.cacheKey = cacheKey.length > 0 ? cacheKey : nil;
|
|
52
|
+
id memoryCache = json[@"memoryCache"];
|
|
53
|
+
imageSource.memoryCache = memoryCache && memoryCache != (id)kCFNull ? [self BOOL:memoryCache] : YES;
|
|
52
54
|
|
|
53
55
|
return imageSource;
|
|
54
56
|
}
|