react-native-fast-image 9.1.0 → 9.2.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 +3 -3
- package/android/build.gradle +12 -0
- package/android/src/main/java/com/dylanvann/fastimage/FastImageOkHttpProgressGlideModule.java +23 -156
- package/android/src/main/java/com/dylanvann/fastimage/FastImageProgressListener.java +4 -0
- package/android/src/main/java/com/dylanvann/fastimage/FastImageRequestListener.java +1 -4
- package/android/src/main/java/com/dylanvann/fastimage/FastImageSharedDownloads.java +961 -196
- package/android/src/main/java/com/dylanvann/fastimage/FastImageViewManager.java +128 -28
- package/android/src/main/java/com/dylanvann/fastimage/FastImageViewModule.java +9 -6
- package/android/src/main/java/com/dylanvann/fastimage/FastImageViewWithUrl.java +98 -63
- package/dist/index.d.ts +6 -2
- package/package.json +1 -1
package/README.md
CHANGED
|
@@ -387,7 +387,7 @@ onLoad={e => console.log(e.nativeEvent.width, e.nativeEvent.height)}
|
|
|
387
387
|
|
|
388
388
|
**Type:** `(event: OnErrorEvent) => void`
|
|
389
389
|
|
|
390
|
-
Called on an image fetching error, with a message describing it (e.g. the HTTP status code).
|
|
390
|
+
Called on an image fetching error, with a message describing it (e.g. the HTTP status code). On iOS and Android, a download that gets nothing from the server for 15 seconds fails too (on Android, unless the app's OkHttp client has timeouts of its own).
|
|
391
391
|
|
|
392
392
|
```jsx
|
|
393
393
|
onError={e => console.log(e.nativeEvent.error)}
|
|
@@ -571,7 +571,7 @@ const { ok } = await FastImage.getCachePath({
|
|
|
571
571
|
})
|
|
572
572
|
```
|
|
573
573
|
|
|
574
|
-
- The source's `headers`, `cacheKey` and `priority` apply, as for a view. Downloads wait in the same queue as `preload`'s, so they don't hold up the images on screen.
|
|
574
|
+
- The source's `headers`, `cacheKey` and `priority` apply, as for a view. Downloads wait in the same queue as `preload`'s, so they don't hold up the images on screen. If a view or a preload is downloading the same source, `getCachePath` shares that download instead of downloading the image again (on Android, except with `cache: 'web'`).
|
|
575
575
|
- The file belongs to the cache, which can remove it at any time: copy it to keep it.
|
|
576
576
|
- Its name may not have an image extension (on Android it ends in `.0` or `.1`): copy it with one for APIs that need it.
|
|
577
577
|
- A `file://` source is its own path. Other local images (`require()` in a release build, `content://`) have no file to give.
|
|
@@ -765,7 +765,7 @@ How the image fades in: see [`transition`](#transition).
|
|
|
765
765
|
- `'all'`: skips it for images from the memory or disk cache too, so images that download fade in, and local files and bundled images usually only the first time (the disk cache usually keeps them too).
|
|
766
766
|
- `'none'`: always fades.
|
|
767
767
|
|
|
768
|
-
Downloads, local files and bundled images (`require()`) fade in.
|
|
768
|
+
Downloads, local files and bundled images (`require()`) fade in. On Android, a `cache: 'web'` image from its HTTP cache, without a request, counts as one from the memory cache (these images aren't kept there).
|
|
769
769
|
|
|
770
770
|
<!-- api:types end -->
|
|
771
771
|
|
package/android/build.gradle
CHANGED
|
@@ -109,3 +109,15 @@ dependencies {
|
|
|
109
109
|
// Animated PNGs (see FastImageApng).
|
|
110
110
|
implementation 'com.github.penfeizhou.android.animation:apng:3.0.5'
|
|
111
111
|
}
|
|
112
|
+
|
|
113
|
+
// Unit tests of the downloads (src/test, with Robolectric, which gives them
|
|
114
|
+
// Android's classes), run from an example app's android folder:
|
|
115
|
+
// `./gradlew :react-native-fast-image:testDebugUnitTest`. The npm package
|
|
116
|
+
// doesn't include src/test, so apps don't get these.
|
|
117
|
+
if (file('src/test').exists()) {
|
|
118
|
+
android.testOptions.unitTests.includeAndroidResources = true
|
|
119
|
+
dependencies {
|
|
120
|
+
testImplementation 'junit:junit:4.13.2'
|
|
121
|
+
testImplementation 'org.robolectric:robolectric:4.15.1'
|
|
122
|
+
}
|
|
123
|
+
}
|
package/android/src/main/java/com/dylanvann/fastimage/FastImageOkHttpProgressGlideModule.java
CHANGED
|
@@ -10,7 +10,6 @@ import com.bumptech.glide.annotation.GlideModule;
|
|
|
10
10
|
import com.bumptech.glide.load.ImageHeaderParser;
|
|
11
11
|
import com.bumptech.glide.load.ImageHeaderParserUtils;
|
|
12
12
|
import com.bumptech.glide.load.engine.bitmap_recycle.ArrayPool;
|
|
13
|
-
import com.bumptech.glide.integration.okhttp3.OkHttpUrlLoader;
|
|
14
13
|
import com.bumptech.glide.load.Options;
|
|
15
14
|
import com.bumptech.glide.load.model.GlideUrl;
|
|
16
15
|
import com.bumptech.glide.load.model.ModelLoader;
|
|
@@ -23,10 +22,8 @@ import com.facebook.react.modules.network.OkHttpClientProvider;
|
|
|
23
22
|
import java.io.File;
|
|
24
23
|
import java.io.IOException;
|
|
25
24
|
import java.io.InputStream;
|
|
26
|
-
import java.util.HashMap;
|
|
27
25
|
import java.util.Locale;
|
|
28
|
-
import java.util.
|
|
29
|
-
import java.util.WeakHashMap;
|
|
26
|
+
import java.util.concurrent.TimeUnit;
|
|
30
27
|
|
|
31
28
|
import okhttp3.Cache;
|
|
32
29
|
import okhttp3.HttpUrl;
|
|
@@ -34,20 +31,18 @@ import okhttp3.Interceptor;
|
|
|
34
31
|
import okhttp3.JavaNetCookieJar;
|
|
35
32
|
import okhttp3.MediaType;
|
|
36
33
|
import okhttp3.OkHttpClient;
|
|
37
|
-
import okhttp3.Request;
|
|
38
34
|
import okhttp3.Response;
|
|
39
35
|
import okhttp3.ResponseBody;
|
|
40
36
|
import okio.Buffer;
|
|
41
37
|
import okio.BufferedSource;
|
|
42
|
-
import okio.ForwardingSource;
|
|
43
|
-
import okio.Okio;
|
|
44
|
-
import okio.Source;
|
|
45
38
|
|
|
46
39
|
@GlideModule
|
|
47
40
|
public class FastImageOkHttpProgressGlideModule extends LibraryGlideModule {
|
|
48
41
|
|
|
49
|
-
private static final DispatchingProgressListener progressListener = new DispatchingProgressListener();
|
|
50
42
|
private static final long WEB_CACHE_SIZE = 50 * 1024 * 1024;
|
|
43
|
+
// How long a download can get nothing from the server (see
|
|
44
|
+
// registerComponents).
|
|
45
|
+
private static final long TIMEOUT_SECONDS = 15;
|
|
51
46
|
// The HTTP cache of `cache: 'web'` images, once Glide has set up.
|
|
52
47
|
@Nullable
|
|
53
48
|
private static Cache webCache;
|
|
@@ -67,11 +62,7 @@ public class FastImageOkHttpProgressGlideModule extends LibraryGlideModule {
|
|
|
67
62
|
static void downloadToWebCache(GlideUrl url) throws IOException {
|
|
68
63
|
OkHttpClient client = webClient;
|
|
69
64
|
if (client == null) throw new IOException("Glide isn't set up");
|
|
70
|
-
|
|
71
|
-
for (Map.Entry<String, String> header : url.getHeaders().entrySet()) {
|
|
72
|
-
request.addHeader(header.getKey(), header.getValue());
|
|
73
|
-
}
|
|
74
|
-
try (Response response = client.newCall(request.build()).execute()) {
|
|
65
|
+
try (Response response = client.newCall(FastImageSharedDownloads.request(url)).execute()) {
|
|
75
66
|
if (!response.isSuccessful()) {
|
|
76
67
|
// As Glide's HttpException reports it.
|
|
77
68
|
throw new IOException(response.message() + ", status code: " + response.code());
|
|
@@ -107,7 +98,6 @@ public class FastImageOkHttpProgressGlideModule extends LibraryGlideModule {
|
|
|
107
98
|
OkHttpClient sharedClient = OkHttpClientProvider.getOkHttpClient();
|
|
108
99
|
OkHttpClient.Builder builder = sharedClient
|
|
109
100
|
.newBuilder()
|
|
110
|
-
.addInterceptor(createInterceptor(progressListener))
|
|
111
101
|
// A network interceptor, so it runs before the HTTP cache of
|
|
112
102
|
// `web` images stores the response (checking it reads it).
|
|
113
103
|
.addNetworkInterceptor(createNonImageInterceptor(registry, glide.getArrayPool()));
|
|
@@ -118,6 +108,15 @@ public class FastImageOkHttpProgressGlideModule extends LibraryGlideModule {
|
|
|
118
108
|
if (sharedClient.cookieJar() instanceof CookieJarContainer) {
|
|
119
109
|
builder.cookieJar(new JavaNetCookieJar(new FastImageCookieHandler()));
|
|
120
110
|
}
|
|
111
|
+
// React Native's shared client has no timeouts, so a download that
|
|
112
|
+
// stopped (e.g. on a connection that died) never ended, and every
|
|
113
|
+
// request sharing it waited. As on iOS (SDWebImage's 15 s), one that
|
|
114
|
+
// gets nothing for TIMEOUT_SECONDS fails: connecting, or waiting for
|
|
115
|
+
// the response or the next of its bytes. Timeouts the app gave the
|
|
116
|
+
// client stay. No call timeout: a large image on a slow link can take
|
|
117
|
+
// longer than any.
|
|
118
|
+
if (sharedClient.connectTimeoutMillis() == 0) builder.connectTimeout(TIMEOUT_SECONDS, TimeUnit.SECONDS);
|
|
119
|
+
if (sharedClient.readTimeoutMillis() == 0) builder.readTimeout(TIMEOUT_SECONDS, TimeUnit.SECONDS);
|
|
121
120
|
OkHttpClient client = builder.build();
|
|
122
121
|
// `cache: 'web'` skips Glide's caches and relies on HTTP caching, so
|
|
123
122
|
// those urls get a client with an HTTP cache (#280): one of their own,
|
|
@@ -127,6 +126,7 @@ public class FastImageOkHttpProgressGlideModule extends LibraryGlideModule {
|
|
|
127
126
|
// store them twice).
|
|
128
127
|
webCache = new Cache(new File(context.getCacheDir(), "fast-image-http-cache"), WEB_CACHE_SIZE);
|
|
129
128
|
webClient = client.newBuilder().cache(webCache).build();
|
|
129
|
+
FastImageSharedDownloads.setDirectory(new File(context.getCacheDir(), "fast-image-downloads"));
|
|
130
130
|
registry.prepend(FastImageUrl.class, InputStream.class, new UrlLoaderFactory(client, webClient));
|
|
131
131
|
FastImageSvg.register(registry, glide.getBitmapPool());
|
|
132
132
|
FastImageAnimated.register(context, glide, registry);
|
|
@@ -138,12 +138,12 @@ public class FastImageOkHttpProgressGlideModule extends LibraryGlideModule {
|
|
|
138
138
|
FastImageGlide.registered(glide);
|
|
139
139
|
}
|
|
140
140
|
|
|
141
|
-
// Loads FastImage's remote images (FastImageUrls)
|
|
142
|
-
//
|
|
143
|
-
//
|
|
144
|
-
//
|
|
145
|
-
//
|
|
146
|
-
//
|
|
141
|
+
// Loads FastImage's remote images (FastImageUrls), with one download of an
|
|
142
|
+
// image at a time, shared by its requests (FastImageSharedDownloads):
|
|
143
|
+
// `web` ones (FastImageWebGlideUrls) with the client with the HTTP cache,
|
|
144
|
+
// others with the other client. FastImageUrl isn't a GlideUrl, so Glide
|
|
145
|
+
// doesn't also give these to other GlideUrl loaders, which it would try
|
|
146
|
+
// after a failed load (e.g. a 404, requested again).
|
|
147
147
|
private static class UrlLoaderFactory implements ModelLoaderFactory<FastImageUrl, InputStream> {
|
|
148
148
|
private final OkHttpClient client;
|
|
149
149
|
private final OkHttpClient webClient;
|
|
@@ -156,16 +156,11 @@ public class FastImageOkHttpProgressGlideModule extends LibraryGlideModule {
|
|
|
156
156
|
@NonNull
|
|
157
157
|
@Override
|
|
158
158
|
public ModelLoader<FastImageUrl, InputStream> build(@NonNull MultiModelLoaderFactory multiFactory) {
|
|
159
|
-
final OkHttpUrlLoader loader = new OkHttpUrlLoader(client);
|
|
160
|
-
final OkHttpUrlLoader webLoader = new OkHttpUrlLoader(webClient);
|
|
161
159
|
return new ModelLoader<FastImageUrl, InputStream>() {
|
|
162
160
|
@Override
|
|
163
161
|
public LoadData<InputStream> buildLoadData(@NonNull FastImageUrl model, int width, int height, @NonNull Options options) {
|
|
164
|
-
|
|
165
|
-
|
|
166
|
-
}
|
|
167
|
-
LoadData<InputStream> data = loader.buildLoadData(model.url, width, height, options);
|
|
168
|
-
return data == null ? null : FastImageSharedDownloads.share(data, model.url, options);
|
|
162
|
+
return FastImageSharedDownloads.loadData(
|
|
163
|
+
model.url, model.url instanceof FastImageWebGlideUrl ? webClient : client);
|
|
169
164
|
}
|
|
170
165
|
|
|
171
166
|
@Override
|
|
@@ -227,132 +222,4 @@ public class FastImageOkHttpProgressGlideModule extends LibraryGlideModule {
|
|
|
227
222
|
|| subtype.equalsIgnoreCase("xhtml+xml")
|
|
228
223
|
|| subtype.equalsIgnoreCase("javascript");
|
|
229
224
|
}
|
|
230
|
-
|
|
231
|
-
private static Interceptor createInterceptor(final ResponseProgressListener listener) {
|
|
232
|
-
return new Interceptor() {
|
|
233
|
-
@Override
|
|
234
|
-
public Response intercept(Chain chain) throws IOException {
|
|
235
|
-
Request request = chain.request();
|
|
236
|
-
Response response = chain.proceed(request);
|
|
237
|
-
final String key = request.url().toString();
|
|
238
|
-
return response
|
|
239
|
-
.newBuilder()
|
|
240
|
-
.body(new OkHttpProgressResponseBody(key, response.body(), listener))
|
|
241
|
-
.build();
|
|
242
|
-
}
|
|
243
|
-
};
|
|
244
|
-
}
|
|
245
|
-
|
|
246
|
-
static void forget(String key) {
|
|
247
|
-
progressListener.forget(key);
|
|
248
|
-
}
|
|
249
|
-
|
|
250
|
-
static void expect(String key, FastImageProgressListener listener) {
|
|
251
|
-
progressListener.expect(key, listener);
|
|
252
|
-
}
|
|
253
|
-
|
|
254
|
-
private interface ResponseProgressListener {
|
|
255
|
-
void update(String key, long bytesRead, long contentLength);
|
|
256
|
-
}
|
|
257
|
-
|
|
258
|
-
private static class DispatchingProgressListener implements ResponseProgressListener {
|
|
259
|
-
private final Map<String, FastImageProgressListener> LISTENERS = new WeakHashMap<>();
|
|
260
|
-
private final Map<String, Long> PROGRESSES = new HashMap<>();
|
|
261
|
-
|
|
262
|
-
void forget(String key) {
|
|
263
|
-
LISTENERS.remove(key);
|
|
264
|
-
PROGRESSES.remove(key);
|
|
265
|
-
}
|
|
266
|
-
|
|
267
|
-
void expect(String key, FastImageProgressListener listener) {
|
|
268
|
-
LISTENERS.put(key, listener);
|
|
269
|
-
}
|
|
270
|
-
|
|
271
|
-
@Override
|
|
272
|
-
public void update(final String key, final long bytesRead, final long contentLength) {
|
|
273
|
-
final FastImageProgressListener listener = LISTENERS.get(key);
|
|
274
|
-
// Without a Content-Length the total is unknown (-1), and a
|
|
275
|
-
// percentage can't be worked out from it, so don't send those. (It
|
|
276
|
-
// also looked like the last update, which stopped all updates.)
|
|
277
|
-
if (listener == null || contentLength <= 0) {
|
|
278
|
-
return;
|
|
279
|
-
}
|
|
280
|
-
if (contentLength <= bytesRead) {
|
|
281
|
-
forget(key);
|
|
282
|
-
}
|
|
283
|
-
if (needsDispatch(key, bytesRead, contentLength, listener.getGranularityPercentage())) {
|
|
284
|
-
listener.onProgress(key, bytesRead, contentLength);
|
|
285
|
-
}
|
|
286
|
-
}
|
|
287
|
-
|
|
288
|
-
private boolean needsDispatch(String key, long current, long total, float granularity) {
|
|
289
|
-
if (granularity == 0 || current == 0 || total == current) {
|
|
290
|
-
return true;
|
|
291
|
-
}
|
|
292
|
-
float percent = 100f * current / total;
|
|
293
|
-
long currentProgress = (long) (percent / granularity);
|
|
294
|
-
Long lastProgress = PROGRESSES.get(key);
|
|
295
|
-
if (lastProgress == null || currentProgress != lastProgress) {
|
|
296
|
-
PROGRESSES.put(key, currentProgress);
|
|
297
|
-
return true;
|
|
298
|
-
} else {
|
|
299
|
-
return false;
|
|
300
|
-
}
|
|
301
|
-
}
|
|
302
|
-
}
|
|
303
|
-
|
|
304
|
-
private static class OkHttpProgressResponseBody extends ResponseBody {
|
|
305
|
-
private final String key;
|
|
306
|
-
private final ResponseBody responseBody;
|
|
307
|
-
private final ResponseProgressListener progressListener;
|
|
308
|
-
private BufferedSource bufferedSource;
|
|
309
|
-
|
|
310
|
-
OkHttpProgressResponseBody(
|
|
311
|
-
String key,
|
|
312
|
-
ResponseBody responseBody,
|
|
313
|
-
ResponseProgressListener progressListener
|
|
314
|
-
) {
|
|
315
|
-
this.key = key;
|
|
316
|
-
this.responseBody = responseBody;
|
|
317
|
-
this.progressListener = progressListener;
|
|
318
|
-
}
|
|
319
|
-
|
|
320
|
-
@Override
|
|
321
|
-
public MediaType contentType() {
|
|
322
|
-
return responseBody.contentType();
|
|
323
|
-
}
|
|
324
|
-
|
|
325
|
-
@Override
|
|
326
|
-
public long contentLength() {
|
|
327
|
-
return responseBody.contentLength();
|
|
328
|
-
}
|
|
329
|
-
|
|
330
|
-
@Override
|
|
331
|
-
public BufferedSource source() {
|
|
332
|
-
if (bufferedSource == null) {
|
|
333
|
-
bufferedSource = Okio.buffer(source(responseBody.source()));
|
|
334
|
-
}
|
|
335
|
-
return bufferedSource;
|
|
336
|
-
}
|
|
337
|
-
|
|
338
|
-
private Source source(Source source) {
|
|
339
|
-
return new ForwardingSource(source) {
|
|
340
|
-
long totalBytesRead = 0L;
|
|
341
|
-
|
|
342
|
-
@Override
|
|
343
|
-
public long read(Buffer sink, long byteCount) throws IOException {
|
|
344
|
-
long bytesRead = super.read(sink, byteCount);
|
|
345
|
-
long fullLength = responseBody.contentLength();
|
|
346
|
-
if (bytesRead == -1) {
|
|
347
|
-
// this source is exhausted
|
|
348
|
-
totalBytesRead = fullLength;
|
|
349
|
-
} else {
|
|
350
|
-
totalBytesRead += bytesRead;
|
|
351
|
-
}
|
|
352
|
-
progressListener.update(key, totalBytesRead, fullLength);
|
|
353
|
-
return bytesRead;
|
|
354
|
-
}
|
|
355
|
-
};
|
|
356
|
-
}
|
|
357
|
-
}
|
|
358
225
|
}
|
|
@@ -1,5 +1,9 @@
|
|
|
1
1
|
package com.dylanvann.fastimage;
|
|
2
2
|
|
|
3
|
+
// No longer used: FastImage sends progress through FastImageViewManager's
|
|
4
|
+
// onDownloadProgress. Kept, as a public type, so code that refers to it still
|
|
5
|
+
// compiles.
|
|
6
|
+
@Deprecated
|
|
3
7
|
public interface FastImageProgressListener {
|
|
4
8
|
|
|
5
9
|
void onProgress(String key, long bytesRead, long expectedLength);
|
|
@@ -16,7 +16,6 @@ public class FastImageRequestListener implements RequestListener<Drawable> {
|
|
|
16
16
|
static final String REACT_ON_ERROR_EVENT = "onFastImageError";
|
|
17
17
|
static final String REACT_ON_LOAD_EVENT = "onFastImageLoad";
|
|
18
18
|
static final String REACT_ON_LOAD_END_EVENT = "onFastImageLoadEnd";
|
|
19
|
-
private final String key;
|
|
20
19
|
private final FastImageSource source;
|
|
21
20
|
// Whether the request shows the previous image as a thumbnail meanwhile.
|
|
22
21
|
private final boolean thumbnail;
|
|
@@ -24,8 +23,7 @@ public class FastImageRequestListener implements RequestListener<Drawable> {
|
|
|
24
23
|
// view shows it at its old size if that fails.
|
|
25
24
|
private final boolean events;
|
|
26
25
|
|
|
27
|
-
FastImageRequestListener(
|
|
28
|
-
this.key = key;
|
|
26
|
+
FastImageRequestListener(FastImageSource source, boolean thumbnail, boolean events) {
|
|
29
27
|
this.source = source;
|
|
30
28
|
this.thumbnail = thumbnail;
|
|
31
29
|
this.events = events;
|
|
@@ -39,7 +37,6 @@ public class FastImageRequestListener implements RequestListener<Drawable> {
|
|
|
39
37
|
}
|
|
40
38
|
return false;
|
|
41
39
|
}
|
|
42
|
-
FastImageOkHttpProgressGlideModule.forget(key);
|
|
43
40
|
if (!(target instanceof ImageViewTarget)) {
|
|
44
41
|
return false;
|
|
45
42
|
}
|