react-native-fast-image 10.4.0 → 10.6.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 +17 -6
- package/android/src/main/java/com/fastimage/FastImageViewConverter.java +1 -1
- package/android/src/main/java/com/fastimage/FastImageViewManager.java +18 -12
- package/android/src/main/java/com/fastimage/FastImageViewWithUrl.java +231 -32
- package/dist/index.cjs.js +15 -27
- package/dist/index.cjs.js.flow +9 -2
- package/dist/index.cjs.web.js +41 -8
- package/dist/index.cjs.web.js.flow +9 -2
- package/dist/index.d.ts +15 -5
- package/dist/index.js +15 -27
- package/dist/index.js.flow +9 -2
- package/dist/index.web.js +41 -8
- package/dist/index.web.js.flow +9 -2
- package/dist/placeholders.d.ts +2 -1
- package/dist/specs/FastImageViewNativeComponent.d.ts +0 -2
- package/ios/FastImage/FFFThumbHash.h +3 -0
- package/ios/FastImage/FFFThumbHash.m +5 -1
- package/ios/FastImage/FFFastImageView.h +13 -7
- package/ios/FastImage/FFFastImageView.m +187 -33
- package/ios/FastImage/FFFastImageViewComponentView.mm +78 -13
- package/package.json +1 -1
- package/src/index.js.flow +9 -2
- package/src/index.tsx +59 -71
- package/src/index.web.tsx +78 -15
- package/src/placeholders.ts +26 -9
- package/src/specs/FastImageViewNativeComponent.ts +10 -11
package/README.md
CHANGED
|
@@ -26,7 +26,7 @@ React Native's `Image` component handles image caching like browsers for the mos
|
|
|
26
26
|
- **Responsive images**: give several sizes, and the one that fits the view loads ([`source`](#source)).
|
|
27
27
|
- **Animated images** (GIF, WebP, APNG and AVIF) that can [loop](#loop) and [pause](#paused).
|
|
28
28
|
- **Fades** in and between images ([`transition`](#transition)).
|
|
29
|
-
- **Placeholders**: a blurred preview from a [ThumbHash](#sourcethumbhash) or a [tiny image](#sourceblurdatauri) while an image loads,
|
|
29
|
+
- **Placeholders**: a blurred preview from a [ThumbHash](#sourcethumbhash) or a [tiny image](#sourceblurdatauri) while an image loads, a bundled image, or a url, e.g. a thumbnail that's already cached ([`placeholder`](#placeholder)).
|
|
30
30
|
- [**SVG**](docs/formats.md#svg-images) and [**photo library**](#photo-library-images-ios-and-tvos) images.
|
|
31
31
|
- [**`FastImageBackground`**](#fastimagebackground) for content over an image.
|
|
32
32
|
- **Expo and web support**, with a [config plugin](#expo-config-plugin) for Expo.
|
|
@@ -107,7 +107,7 @@ It's a single native view, which also takes React Native's View props, such as a
|
|
|
107
107
|
|
|
108
108
|
#### `source`
|
|
109
109
|
|
|
110
|
-
**Type:** `number | Source | Source[]`
|
|
110
|
+
**Type:** `number | Source | (number | Source)[]`
|
|
111
111
|
|
|
112
112
|
Source for the remote image to load: a `Source`, a `require()`d image, or an array of the same image at different sizes.
|
|
113
113
|
|
|
@@ -129,6 +129,7 @@ When `source` changes, the image that's showing stays until the new one has load
|
|
|
129
129
|
- When the view's size changes so that another size fits better, it loads that one (with its load events), and keeps showing the current image until then, without a `transition`.
|
|
130
130
|
- Each size is cached separately. A `cacheKey` applies to its own entry: give each size its own key (or none), or the sizes would replace each other in the cache.
|
|
131
131
|
- `FastImage.preload`, `getCachePath` and `writeToCache` take one size: pass the one a view will show. An array fails with `{ ok: false, error }`.
|
|
132
|
+
- A `require()`d image can be one of the sizes: its size is the file's.
|
|
132
133
|
- An array of one is the same as that source.
|
|
133
134
|
|
|
134
135
|
---
|
|
@@ -266,7 +267,7 @@ With several sources, the scale `width` and `height` are multiplied by.
|
|
|
266
267
|
|
|
267
268
|
#### `placeholder`
|
|
268
269
|
|
|
269
|
-
**Type:** `string | number | SourcePlaceholder`
|
|
270
|
+
**Type:** `string | number | PlaceholderSource | SourcePlaceholder`
|
|
270
271
|
|
|
271
272
|
A placeholder shown while the image loads, and if it fails to load:
|
|
272
273
|
|
|
@@ -275,13 +276,15 @@ A placeholder shown while the image loads, and if it fails to load:
|
|
|
275
276
|
- The name of an image bundled with the app (an iOS asset catalog image or an Android drawable): `placeholder="gray"`.
|
|
276
277
|
- A [ThumbHash](#sourcethumbhash), shown blurred: `placeholder={{ thumbhash }}`.
|
|
277
278
|
- A tiny image as a data uri, shown blurred: `placeholder={{ blurDataUri }}`.
|
|
279
|
+
- A url, loaded and cached like a source: `placeholder="https://example.com/placeholder.png"`, or a source with `headers` or a `cacheKey`: `placeholder={{ uri, headers }}`.
|
|
280
|
+
- A url only shown if it's already cached, e.g. a list's thumbnail while the full-size image loads: `placeholder={{ uri, cache: 'cacheOnly' }}`.
|
|
278
281
|
|
|
279
|
-
A source's own `thumbhash` or `blurDataUri` is shown instead, if it has one.
|
|
282
|
+
A source's own `thumbhash` or `blurDataUri` is shown instead, if it has one; a url placeholder shows over it once it has loaded.
|
|
280
283
|
|
|
281
284
|
Notes:
|
|
282
285
|
|
|
283
|
-
- A url isn't loaded: it's ignored, with a warning in development.
|
|
284
286
|
- On Android, a `require()`d image doesn't show in debug builds, where the dev server serves it. Release builds show it.
|
|
287
|
+
- On the web, a url loads like any image, also with `'cacheOnly'`: a browser can't look only in its cache.
|
|
285
288
|
|
|
286
289
|
---
|
|
287
290
|
|
|
@@ -289,7 +292,7 @@ Notes:
|
|
|
289
292
|
|
|
290
293
|
**Type:** `number`
|
|
291
294
|
|
|
292
|
-
**Deprecated:** Use `placeholder`, which does the same.
|
|
295
|
+
**Deprecated:** Use `placeholder`, which does the same. With both, `placeholder` shows.
|
|
293
296
|
|
|
294
297
|
---
|
|
295
298
|
|
|
@@ -844,6 +847,14 @@ Where the image is in the view: see [`objectPosition`](#objectposition).
|
|
|
844
847
|
|
|
845
848
|
---
|
|
846
849
|
|
|
850
|
+
### `PlaceholderSource`
|
|
851
|
+
|
|
852
|
+
**Type:** `Pick<Source, 'uri' | 'headers' | 'cacheKey' | 'cache'>`
|
|
853
|
+
|
|
854
|
+
A url as `placeholder`, loaded like a source (see `placeholder`).
|
|
855
|
+
|
|
856
|
+
---
|
|
857
|
+
|
|
847
858
|
### `PositionLength`
|
|
848
859
|
|
|
849
860
|
**Type:** `` number | `${number}%` ``
|
|
@@ -27,7 +27,7 @@ import java.util.Map;
|
|
|
27
27
|
import javax.annotation.Nullable;
|
|
28
28
|
|
|
29
29
|
class FastImageViewConverter {
|
|
30
|
-
|
|
30
|
+
static final Drawable TRANSPARENT_DRAWABLE = new ColorDrawable(Color.TRANSPARENT);
|
|
31
31
|
|
|
32
32
|
private static final Map<String, FastImageCacheControl> FAST_IMAGE_CACHE_CONTROL_MAP =
|
|
33
33
|
new HashMap<String, FastImageCacheControl>() {{
|
|
@@ -21,6 +21,7 @@ import com.bumptech.glide.RequestManager;
|
|
|
21
21
|
import com.facebook.react.bridge.ColorPropConverter;
|
|
22
22
|
import com.facebook.react.bridge.Dynamic;
|
|
23
23
|
import com.facebook.react.bridge.DynamicFromObject;
|
|
24
|
+
import com.facebook.react.bridge.JavaOnlyArray;
|
|
24
25
|
import com.facebook.react.bridge.ReadableArray;
|
|
25
26
|
import com.facebook.react.bridge.ReadableMap;
|
|
26
27
|
import com.facebook.react.bridge.ReadableType;
|
|
@@ -307,17 +308,27 @@ class FastImageViewManager extends SimpleViewManager<FastImageViewWithUrl>
|
|
|
307
308
|
|
|
308
309
|
@Override
|
|
309
310
|
public void setSource(FastImageViewWithUrl view, Dynamic source) {
|
|
310
|
-
|
|
311
|
+
// One source, or several (an array).
|
|
312
|
+
view.setSource(source.isNull() ? null
|
|
313
|
+
: source.getType() == ReadableType.Array ? source.asArray()
|
|
314
|
+
: JavaOnlyArray.of(source.asMap()));
|
|
311
315
|
}
|
|
312
316
|
|
|
313
|
-
|
|
314
|
-
|
|
315
|
-
|
|
316
|
-
}
|
|
317
|
-
|
|
318
|
-
// The `placeholder` prop: the resolved asset's uri, or a data uri.
|
|
317
|
+
// The `placeholder` prop: a thumbhash or blurDataUri, or a url to load
|
|
318
|
+
// apart from the image, as a source (a map), or the resolved asset's uri
|
|
319
|
+
// or a data uri (a string).
|
|
319
320
|
@Override
|
|
320
321
|
public void setPlaceholder(FastImageViewWithUrl view, Dynamic source) {
|
|
322
|
+
if (source.getType() == ReadableType.Map) {
|
|
323
|
+
ReadableMap map = source.asMap();
|
|
324
|
+
boolean hash = map.hasKey("thumbhash") || map.hasKey("blurDataUri");
|
|
325
|
+
view.setPlaceholder(null);
|
|
326
|
+
view.setPlaceholderHash(hash ? FastImageViewWithUrl.sourcePlaceholder(map) : null);
|
|
327
|
+
view.setPlaceholderSource(hash ? null : map);
|
|
328
|
+
return;
|
|
329
|
+
}
|
|
330
|
+
view.setPlaceholderHash(null);
|
|
331
|
+
view.setPlaceholderSource(null);
|
|
321
332
|
String uri = source.isNull() ? null : source.asString();
|
|
322
333
|
if (uri != null && FastImageThumbHash.isDataUri(uri)) {
|
|
323
334
|
Bitmap bitmap = FastImageThumbHash.dataUri(uri);
|
|
@@ -344,11 +355,6 @@ class FastImageViewManager extends SimpleViewManager<FastImageViewWithUrl>
|
|
|
344
355
|
view.setRecyclingKey(recyclingKey);
|
|
345
356
|
}
|
|
346
357
|
|
|
347
|
-
@Override
|
|
348
|
-
public void setSourcePlaceholders(FastImageViewWithUrl view, @Nullable ReadableArray placeholders) {
|
|
349
|
-
view.setSourcePlaceholders(placeholders);
|
|
350
|
-
}
|
|
351
|
-
|
|
352
358
|
@Override
|
|
353
359
|
public void setLoopCount(FastImageViewWithUrl view, int loopCount) {
|
|
354
360
|
view.setLoopCount(loopCount);
|
|
@@ -30,6 +30,7 @@ import androidx.annotation.RequiresApi;
|
|
|
30
30
|
import androidx.appcompat.widget.AppCompatImageView;
|
|
31
31
|
|
|
32
32
|
import com.bumptech.glide.GenericTransitionOptions;
|
|
33
|
+
import com.bumptech.glide.Priority;
|
|
33
34
|
import com.bumptech.glide.RequestBuilder;
|
|
34
35
|
import com.bumptech.glide.RequestManager;
|
|
35
36
|
import com.bumptech.glide.load.DataSource;
|
|
@@ -39,6 +40,7 @@ import com.bumptech.glide.load.resource.drawable.DrawableTransitionOptions;
|
|
|
39
40
|
import com.bumptech.glide.load.resource.gif.GifDrawable;
|
|
40
41
|
import com.bumptech.glide.request.Request;
|
|
41
42
|
import com.bumptech.glide.request.RequestOptions;
|
|
43
|
+
import com.bumptech.glide.request.target.CustomTarget;
|
|
42
44
|
import com.bumptech.glide.request.target.DrawableImageViewTarget;
|
|
43
45
|
import com.bumptech.glide.request.target.SizeReadyCallback;
|
|
44
46
|
import com.bumptech.glide.request.transition.NoTransition;
|
|
@@ -46,6 +48,7 @@ import com.bumptech.glide.request.transition.Transition;
|
|
|
46
48
|
import com.bumptech.glide.request.transition.TransitionFactory;
|
|
47
49
|
import com.facebook.react.bridge.ReadableArray;
|
|
48
50
|
import com.facebook.react.bridge.ReadableMap;
|
|
51
|
+
import com.facebook.react.bridge.ReadableType;
|
|
49
52
|
import com.facebook.react.bridge.WritableMap;
|
|
50
53
|
import com.facebook.react.bridge.WritableNativeMap;
|
|
51
54
|
import com.facebook.react.modules.i18nmanager.I18nUtil;
|
|
@@ -56,6 +59,10 @@ import com.facebook.react.uimanager.PointerEvents;
|
|
|
56
59
|
import com.facebook.react.uimanager.ReactPointerEventsView;
|
|
57
60
|
import com.facebook.react.uimanager.style.LogicalEdge;
|
|
58
61
|
|
|
62
|
+
import java.util.ArrayList;
|
|
63
|
+
import java.util.HashMap;
|
|
64
|
+
import java.util.List;
|
|
65
|
+
import java.util.Objects;
|
|
59
66
|
import javax.annotation.Nonnull;
|
|
60
67
|
|
|
61
68
|
class FastImageViewWithUrl extends AppCompatImageView implements ReactPointerEventsView, ReactHitSlopView {
|
|
@@ -211,16 +218,72 @@ class FastImageViewWithUrl extends AppCompatImageView implements ReactPointerEve
|
|
|
211
218
|
return false;
|
|
212
219
|
}
|
|
213
220
|
|
|
214
|
-
|
|
215
|
-
|
|
216
|
-
|
|
217
|
-
|
|
221
|
+
// The `source` prop: one source, or several (sizes of one image), each
|
|
222
|
+
// with its thumbhash or blurDataUri if it has one. Only a change besides
|
|
223
|
+
// those loads again: a new thumbhash for the same image (e.g. one that
|
|
224
|
+
// arrives later) shows at once (see updatePlaceholder).
|
|
225
|
+
public void setSource(@Nullable ReadableArray sources) {
|
|
226
|
+
List<Object> key = sourceKey(sources);
|
|
227
|
+
if (!mHasSource || !Objects.equals(key, mSourceKey)) {
|
|
228
|
+
mHasSource = true;
|
|
229
|
+
mSourceKey = key;
|
|
230
|
+
mNeedsReload = true;
|
|
231
|
+
mSources = sources != null && sources.size() > 1 ? sources : null;
|
|
232
|
+
mSourceIndex = -1;
|
|
233
|
+
if (mSources == null) mSource = sources != null && sources.size() == 1 ? map(sources, 0) : null;
|
|
234
|
+
}
|
|
235
|
+
mSourcePlaceholders = new ArrayList<>();
|
|
236
|
+
for (int i = 0; sources != null && i < sources.size(); i++) {
|
|
237
|
+
mSourcePlaceholders.add(sourcePlaceholder(map(sources, i)));
|
|
238
|
+
}
|
|
239
|
+
updatePlaceholder();
|
|
240
|
+
}
|
|
241
|
+
|
|
242
|
+
// The sources without their thumbhashes and blurDataUris, to compare.
|
|
243
|
+
private boolean mHasSource = false;
|
|
244
|
+
@Nullable
|
|
245
|
+
private List<Object> mSourceKey = null;
|
|
246
|
+
|
|
247
|
+
@Nullable
|
|
248
|
+
private static List<Object> sourceKey(@Nullable ReadableArray sources) {
|
|
249
|
+
if (sources == null) return null;
|
|
250
|
+
List<Object> key = new ArrayList<>();
|
|
251
|
+
for (int i = 0; i < sources.size(); i++) {
|
|
252
|
+
ReadableMap source = map(sources, i);
|
|
253
|
+
key.add(source == null ? null : withoutPlaceholders(source));
|
|
254
|
+
}
|
|
255
|
+
return key;
|
|
256
|
+
}
|
|
257
|
+
|
|
258
|
+
// A source without its thumbhash and blurDataUri.
|
|
259
|
+
private static HashMap<String, Object> withoutPlaceholders(ReadableMap source) {
|
|
260
|
+
HashMap<String, Object> map = source.toHashMap();
|
|
261
|
+
map.remove("thumbhash");
|
|
262
|
+
map.remove("blurDataUri");
|
|
263
|
+
return map;
|
|
264
|
+
}
|
|
265
|
+
|
|
266
|
+
@Nullable
|
|
267
|
+
private static ReadableMap map(ReadableArray array, int index) {
|
|
268
|
+
return array.getType(index) == ReadableType.Map ? array.getMap(index) : null;
|
|
218
269
|
}
|
|
219
270
|
|
|
220
|
-
//
|
|
221
|
-
//
|
|
271
|
+
// A source's (or `placeholder`'s) ThumbHash, or its blurDataUri if it's a
|
|
272
|
+
// data uri (JS warns about another), or null.
|
|
222
273
|
@Nullable
|
|
223
|
-
|
|
274
|
+
static String sourcePlaceholder(@Nullable ReadableMap source) {
|
|
275
|
+
if (source == null) return null;
|
|
276
|
+
String thumbhash = string(source, "thumbhash");
|
|
277
|
+
if (thumbhash != null && !thumbhash.isEmpty()) return thumbhash;
|
|
278
|
+
String blurDataUri = string(source, "blurDataUri");
|
|
279
|
+
return blurDataUri != null && FastImageThumbHash.isDataUri(blurDataUri) ? blurDataUri : null;
|
|
280
|
+
}
|
|
281
|
+
|
|
282
|
+
@Nullable
|
|
283
|
+
private static String string(ReadableMap map, String key) {
|
|
284
|
+
return map.hasKey(key) && map.getType(key) == ReadableType.String ? map.getString(key) : null;
|
|
285
|
+
}
|
|
286
|
+
|
|
224
287
|
// Several sources (2 or more) of the same image at different sizes, or
|
|
225
288
|
// null: the view loads the one whose size is closest to its own.
|
|
226
289
|
@Nullable
|
|
@@ -238,13 +301,6 @@ class FastImageViewWithUrl extends AppCompatImageView implements ReactPointerEve
|
|
|
238
301
|
@Nullable
|
|
239
302
|
private FastImageViewManager mManager;
|
|
240
303
|
|
|
241
|
-
public void setSources(@Nullable ReadableArray sources) {
|
|
242
|
-
mNeedsReload = true;
|
|
243
|
-
mSources = sources != null && sources.size() > 1 ? sources : null;
|
|
244
|
-
mSourceIndex = -1;
|
|
245
|
-
if (mSources == null) mSource = mPropSource;
|
|
246
|
-
}
|
|
247
|
-
|
|
248
304
|
// The box the image is drawn in: the view inside its padding, which is the
|
|
249
305
|
// border widths (see updateBorderPadding).
|
|
250
306
|
private int contentWidth() {
|
|
@@ -308,17 +364,20 @@ class FastImageViewWithUrl extends AppCompatImageView implements ReactPointerEve
|
|
|
308
364
|
return true;
|
|
309
365
|
}
|
|
310
366
|
|
|
367
|
+
// Shows at once, as a new source placeholder does (see updatePlaceholder),
|
|
368
|
+
// without loading the image again.
|
|
311
369
|
public void setPlaceholder(@Nullable Drawable placeholder) {
|
|
312
|
-
mNeedsReload = true;
|
|
313
370
|
mPlaceholder = placeholder;
|
|
371
|
+
updatePlaceholder();
|
|
314
372
|
}
|
|
315
373
|
|
|
316
|
-
// The
|
|
317
|
-
// for
|
|
318
|
-
|
|
319
|
-
//
|
|
374
|
+
// The sources' ThumbHashes or blurDataUris, by the source's index (null
|
|
375
|
+
// for none): the picked source's shows instead of the placeholder prop.
|
|
376
|
+
private List<String> mSourcePlaceholders = new ArrayList<>();
|
|
377
|
+
// `placeholder` as a thumbhash or blurDataUri: shows for the sources
|
|
378
|
+
// without their own.
|
|
320
379
|
@Nullable
|
|
321
|
-
private
|
|
380
|
+
private String mPlaceholderHash = null;
|
|
322
381
|
// What shows while the image loads and if it fails: the picked source's
|
|
323
382
|
// ThumbHash or blurDataUri decoded (FastImageThumbHash), or the placeholder
|
|
324
383
|
// prop. FastImage's own drawable either way, which Glide doesn't recycle.
|
|
@@ -333,18 +392,129 @@ class FastImageViewWithUrl extends AppCompatImageView implements ReactPointerEve
|
|
|
333
392
|
@Nullable
|
|
334
393
|
private String mInvalidPlaceholder = null;
|
|
335
394
|
|
|
336
|
-
public void
|
|
337
|
-
|
|
395
|
+
public void setPlaceholderHash(@Nullable String hash) {
|
|
396
|
+
if (Objects.equals(hash, mPlaceholderHash)) return;
|
|
397
|
+
mPlaceholderHash = hash;
|
|
398
|
+
updatePlaceholder();
|
|
399
|
+
}
|
|
400
|
+
|
|
401
|
+
// `placeholder` as a url (a source): loaded apart from the image, at the
|
|
402
|
+
// most urgent priority, as it only helps if it comes before the image (or
|
|
403
|
+
// only from Glide's caches, with cache cacheOnly).
|
|
404
|
+
// Once it has, a copy shows instead of the other placeholders: FastImage's
|
|
405
|
+
// own drawable, which Glide doesn't recycle, so a fade can still draw it
|
|
406
|
+
// after its request is cleared. A failure shows nothing more.
|
|
407
|
+
@Nullable
|
|
408
|
+
private ReadableMap mPlaceholderSource = null;
|
|
409
|
+
@Nullable
|
|
410
|
+
private Drawable mUrlPlaceholder = null;
|
|
411
|
+
@Nullable
|
|
412
|
+
private CustomTarget<Drawable> mUrlPlaceholderTarget = null;
|
|
413
|
+
|
|
414
|
+
// Its load waits for the view's size (see startUrlPlaceholderLoad).
|
|
415
|
+
private boolean mUrlPlaceholderWaitsForSize = false;
|
|
416
|
+
|
|
417
|
+
public void setPlaceholderSource(@Nullable ReadableMap source) {
|
|
418
|
+
if (source == null && mPlaceholderSource == null) return;
|
|
419
|
+
mPlaceholderSource = source;
|
|
420
|
+
clearUrlPlaceholderTarget();
|
|
421
|
+
mUrlPlaceholderWaitsForSize = false;
|
|
422
|
+
mUrlPlaceholder = null;
|
|
338
423
|
updatePlaceholder();
|
|
424
|
+
scheduleUrlPlaceholderLoad();
|
|
425
|
+
}
|
|
426
|
+
|
|
427
|
+
// Starts placeholderSource's load once the current callbacks have run: an
|
|
428
|
+
// image from the memory cache has shown by then, and needs none.
|
|
429
|
+
private void scheduleUrlPlaceholderLoad() {
|
|
430
|
+
post(new Runnable() {
|
|
431
|
+
@Override
|
|
432
|
+
public void run() {
|
|
433
|
+
startUrlPlaceholderLoad();
|
|
434
|
+
}
|
|
435
|
+
});
|
|
436
|
+
}
|
|
437
|
+
|
|
438
|
+
// Loads placeholderSource's image while the image loads (or after it
|
|
439
|
+
// failed), at the view's size, once it has one: not if the image showed,
|
|
440
|
+
// or it's already loaded or loading.
|
|
441
|
+
private void startUrlPlaceholderLoad() {
|
|
442
|
+
if (requestManager == null || mDropped || mUrlPlaceholder != null || mUrlPlaceholderTarget != null
|
|
443
|
+
|| !FastImageViewConverter.hasUri(mPlaceholderSource)) return;
|
|
444
|
+
if (mLoadEnded && mShownRequest != null) return;
|
|
445
|
+
if (getWidth() <= 0 || getHeight() <= 0) {
|
|
446
|
+
mUrlPlaceholderWaitsForSize = true;
|
|
447
|
+
return;
|
|
448
|
+
}
|
|
449
|
+
FastImageSource imageSource = FastImageViewConverter.getImageSource(getContext(), mPlaceholderSource);
|
|
450
|
+
if (imageSource == null) return;
|
|
451
|
+
CustomTarget<Drawable> target = new CustomTarget<Drawable>(getWidth(), getHeight()) {
|
|
452
|
+
@Override
|
|
453
|
+
public void onResourceReady(@NonNull Drawable resource, @Nullable Transition<? super Drawable> transition) {
|
|
454
|
+
if (this != mUrlPlaceholderTarget) return;
|
|
455
|
+
mUrlPlaceholder = ownCopy(resource);
|
|
456
|
+
updatePlaceholder();
|
|
457
|
+
// With the copy, Glide's can go back to its pools (a load
|
|
458
|
+
// can't be cleared from its own callback).
|
|
459
|
+
final CustomTarget<Drawable> done = this;
|
|
460
|
+
post(new Runnable() {
|
|
461
|
+
@Override
|
|
462
|
+
public void run() {
|
|
463
|
+
if (requestManager != null) requestManager.clear(done);
|
|
464
|
+
}
|
|
465
|
+
});
|
|
466
|
+
}
|
|
467
|
+
|
|
468
|
+
@Override
|
|
469
|
+
public void onLoadCleared(@Nullable Drawable placeholder) {
|
|
470
|
+
}
|
|
471
|
+
};
|
|
472
|
+
mUrlPlaceholderTarget = target;
|
|
473
|
+
requestManager
|
|
474
|
+
.load(imageSource.getSourceForLoad())
|
|
475
|
+
.apply(FastImageViewConverter.getOptions(getContext(), imageSource, mPlaceholderSource)
|
|
476
|
+
.priority(Priority.IMMEDIATE)
|
|
477
|
+
// Copied on a software canvas (see ownCopy).
|
|
478
|
+
.disallowHardwareConfig())
|
|
479
|
+
.into(target);
|
|
339
480
|
}
|
|
340
481
|
|
|
341
|
-
|
|
482
|
+
private void clearUrlPlaceholderTarget() {
|
|
483
|
+
if (mUrlPlaceholderTarget != null && requestManager != null) requestManager.clear(mUrlPlaceholderTarget);
|
|
484
|
+
mUrlPlaceholderTarget = null;
|
|
485
|
+
}
|
|
486
|
+
|
|
487
|
+
// A bitmap of the drawable (an animated one's first frame), scaled up
|
|
488
|
+
// smoothly as the other placeholders are, or null for one with no size.
|
|
489
|
+
@Nullable
|
|
490
|
+
private Drawable ownCopy(Drawable drawable) {
|
|
491
|
+
int width = drawable.getIntrinsicWidth();
|
|
492
|
+
int height = drawable.getIntrinsicHeight();
|
|
493
|
+
if (width <= 0 || height <= 0) return null;
|
|
494
|
+
try {
|
|
495
|
+
Bitmap bitmap = Bitmap.createBitmap(width, height, Bitmap.Config.ARGB_8888);
|
|
496
|
+
Canvas canvas = new Canvas(bitmap);
|
|
497
|
+
Drawable copy = drawable.getConstantState() != null ? drawable.getConstantState().newDrawable().mutate() : drawable;
|
|
498
|
+
copy.setBounds(0, 0, width, height);
|
|
499
|
+
copy.draw(canvas);
|
|
500
|
+
BitmapDrawable own = new BitmapDrawable(getResources(), bitmap);
|
|
501
|
+
own.setFilterBitmap(true);
|
|
502
|
+
return own;
|
|
503
|
+
} catch (OutOfMemoryError | IllegalStateException e) {
|
|
504
|
+
// As startFade: out of memory, or a hardware bitmap after all.
|
|
505
|
+
return null;
|
|
506
|
+
}
|
|
507
|
+
}
|
|
508
|
+
|
|
509
|
+
// The picked source's ThumbHash or blurDataUri, or `placeholder`'s, or
|
|
510
|
+
// null.
|
|
342
511
|
@Nullable
|
|
343
512
|
private String pickedSourcePlaceholder() {
|
|
344
513
|
int index = mSources == null ? 0 : mSourceIndex;
|
|
345
|
-
|
|
346
|
-
|
|
347
|
-
|
|
514
|
+
// None until one is picked.
|
|
515
|
+
if (index < 0) return null;
|
|
516
|
+
String key = index < mSourcePlaceholders.size() ? mSourcePlaceholders.get(index) : null;
|
|
517
|
+
return key != null ? key : mPlaceholderHash;
|
|
348
518
|
}
|
|
349
519
|
|
|
350
520
|
// Sets mShownPlaceholder for the picked source. A view showing the previous
|
|
@@ -372,10 +542,16 @@ class FastImageViewWithUrl extends AppCompatImageView implements ReactPointerEve
|
|
|
372
542
|
mInvalidPlaceholder = key;
|
|
373
543
|
}
|
|
374
544
|
}
|
|
545
|
+
// placeholderSource's image, once it has loaded, shows instead.
|
|
546
|
+
if (mUrlPlaceholder != null) placeholder = mUrlPlaceholder;
|
|
375
547
|
if (placeholder == mShownPlaceholder) return;
|
|
376
548
|
boolean showing = mShowsPlaceholder;
|
|
377
549
|
mShownPlaceholder = placeholder;
|
|
378
|
-
|
|
550
|
+
// Also one that arrives while the view shows nothing (no placeholder
|
|
551
|
+
// while the image loads, or after it failed): never over an image.
|
|
552
|
+
Drawable shown = getDrawable();
|
|
553
|
+
boolean showsNothing = shown == null || shown == FastImageViewConverter.TRANSPARENT_DRAWABLE;
|
|
554
|
+
if (showing || (placeholder != null && showsNothing)) setImageDrawable(placeholder);
|
|
379
555
|
}
|
|
380
556
|
|
|
381
557
|
// The events JS has a handler for (the handledEvents prop): the view only
|
|
@@ -449,6 +625,7 @@ class FastImageViewWithUrl extends AppCompatImageView implements ReactPointerEve
|
|
|
449
625
|
void drop() {
|
|
450
626
|
mDropped = true;
|
|
451
627
|
clearView(requestManager);
|
|
628
|
+
clearUrlPlaceholderTarget();
|
|
452
629
|
endProgress();
|
|
453
630
|
}
|
|
454
631
|
|
|
@@ -1183,10 +1360,11 @@ class FastImageViewWithUrl extends AppCompatImageView implements ReactPointerEve
|
|
|
1183
1360
|
setImageMatrix(matrix);
|
|
1184
1361
|
return;
|
|
1185
1362
|
}
|
|
1186
|
-
// A source's placeholder (a ThumbHash or a blurDataUri)
|
|
1187
|
-
// image whose size isn't
|
|
1188
|
-
// show an image at its own
|
|
1189
|
-
|
|
1363
|
+
// A source's placeholder (a ThumbHash or a blurDataUri), or
|
|
1364
|
+
// placeholderSource's image, stands for an image whose size isn't
|
|
1365
|
+
// known yet: with scale-down and none (which show an image at its own
|
|
1366
|
+
// size), it's shown as with contain.
|
|
1367
|
+
boolean hashFits = drawable == mShownPlaceholder && (mPlaceholderKey != null || drawable == mUrlPlaceholder);
|
|
1190
1368
|
if (mLayoutScaleType == ScaleType.CENTER_CROP || mLayoutScaleType == ScaleType.FIT_CENTER || hashFits) {
|
|
1191
1369
|
float x = viewWidth / drawableWidth;
|
|
1192
1370
|
float y = viewHeight / drawableHeight;
|
|
@@ -1352,6 +1530,17 @@ class FastImageViewWithUrl extends AppCompatImageView implements ReactPointerEve
|
|
|
1352
1530
|
// The loading image loaded (FastImageRequestListener).
|
|
1353
1531
|
void onImageLoaded() {
|
|
1354
1532
|
mLoadEnded = true;
|
|
1533
|
+
// The image came first: its placeholder isn't needed (not from
|
|
1534
|
+
// within Glide's callback).
|
|
1535
|
+
if (mUrlPlaceholder == null) {
|
|
1536
|
+
mUrlPlaceholderWaitsForSize = false;
|
|
1537
|
+
post(new Runnable() {
|
|
1538
|
+
@Override
|
|
1539
|
+
public void run() {
|
|
1540
|
+
if (mUrlPlaceholder == null && mLoadEnded && mShownRequest != null) clearUrlPlaceholderTarget();
|
|
1541
|
+
}
|
|
1542
|
+
});
|
|
1543
|
+
}
|
|
1355
1544
|
endProgress();
|
|
1356
1545
|
// Before the view shows it (the target does once this returns).
|
|
1357
1546
|
applyObjectFit();
|
|
@@ -1380,6 +1569,10 @@ class FastImageViewWithUrl extends AppCompatImageView implements ReactPointerEve
|
|
|
1380
1569
|
invalidate();
|
|
1381
1570
|
}
|
|
1382
1571
|
if (!switchSourceIfResized()) reloadIfResized();
|
|
1572
|
+
if (mUrlPlaceholderWaitsForSize && w > 0 && h > 0) {
|
|
1573
|
+
mUrlPlaceholderWaitsForSize = false;
|
|
1574
|
+
scheduleUrlPlaceholderLoad();
|
|
1575
|
+
}
|
|
1383
1576
|
}
|
|
1384
1577
|
|
|
1385
1578
|
private void reloadIfResized() {
|
|
@@ -1508,6 +1701,7 @@ class FastImageViewWithUrl extends AppCompatImageView implements ReactPointerEve
|
|
|
1508
1701
|
}
|
|
1509
1702
|
});
|
|
1510
1703
|
builder.into(target);
|
|
1704
|
+
if (!quiet) scheduleUrlPlaceholderLoad();
|
|
1511
1705
|
}
|
|
1512
1706
|
|
|
1513
1707
|
// Loading the image that's showing again (at a new size, or with another
|
|
@@ -1524,6 +1718,8 @@ class FastImageViewWithUrl extends AppCompatImageView implements ReactPointerEve
|
|
|
1524
1718
|
endProgress();
|
|
1525
1719
|
applyObjectFit();
|
|
1526
1720
|
mShownRequest = null;
|
|
1721
|
+
// Shows instead of the other placeholders once it loads.
|
|
1722
|
+
scheduleUrlPlaceholderLoad();
|
|
1527
1723
|
final int load = mLoadCount;
|
|
1528
1724
|
// Not from within Glide's callback. Without a thumbnail Glide shows the
|
|
1529
1725
|
// placeholder the request started with: a source placeholder that changed since
|
|
@@ -1605,6 +1801,7 @@ class FastImageViewWithUrl extends AppCompatImageView implements ReactPointerEve
|
|
|
1605
1801
|
|
|
1606
1802
|
// Clear the image.
|
|
1607
1803
|
setImageDrawable(null);
|
|
1804
|
+
scheduleUrlPlaceholderLoad();
|
|
1608
1805
|
return;
|
|
1609
1806
|
}
|
|
1610
1807
|
|
|
@@ -1620,7 +1817,8 @@ class FastImageViewWithUrl extends AppCompatImageView implements ReactPointerEve
|
|
|
1620
1817
|
// resolved: fire onError and onLoadEnd and show the placeholder prop,
|
|
1621
1818
|
// as on iOS (#1028, #945).
|
|
1622
1819
|
if (mSource != null && (imageSource == null || imageSource.getUri().toString().length() == 0)) {
|
|
1623
|
-
|
|
1820
|
+
// Without its thumbhash or blurDataUri (a data uri can be long).
|
|
1821
|
+
String error = "Invalid source: " + withoutPlaceholders(mSource);
|
|
1624
1822
|
WritableMap event = new WritableNativeMap();
|
|
1625
1823
|
event.putString("error", error);
|
|
1626
1824
|
FastImageEvents.send(this, REACT_ON_ERROR_EVENT, event);
|
|
@@ -1632,6 +1830,7 @@ class FastImageViewWithUrl extends AppCompatImageView implements ReactPointerEve
|
|
|
1632
1830
|
|
|
1633
1831
|
endProgress();
|
|
1634
1832
|
setImageDrawable(mShownPlaceholder);
|
|
1833
|
+
scheduleUrlPlaceholderLoad();
|
|
1635
1834
|
return;
|
|
1636
1835
|
}
|
|
1637
1836
|
|
package/dist/index.cjs.js
CHANGED
|
@@ -261,8 +261,14 @@ var FastImageViewNativeComponent_default = (0, react_native.codegenNativeCompone
|
|
|
261
261
|
var NativeFastImageModule_default = react_native.TurboModuleRegistry.get("FastImageModule");
|
|
262
262
|
//#endregion
|
|
263
263
|
//#region src/placeholders.ts
|
|
264
|
+
const SCHEME = /^[a-z][a-z\d+.-]*:/i;
|
|
264
265
|
function isImageName(value) {
|
|
265
|
-
return
|
|
266
|
+
return !SCHEME.test(value) && !value.startsWith("/");
|
|
267
|
+
}
|
|
268
|
+
function placeholderSource(value) {
|
|
269
|
+
const uri = typeof value === "string" ? value : value && typeof value === "object" ? value.uri : void 0;
|
|
270
|
+
if (typeof uri !== "string" || !/^https?:/i.test(uri)) return void 0;
|
|
271
|
+
return typeof value === "string" ? { uri } : value;
|
|
266
272
|
}
|
|
267
273
|
function hasSourcePlaceholder(value) {
|
|
268
274
|
return !!value && typeof value === "object" && ("thumbhash" in value || "blurDataUri" in value);
|
|
@@ -270,20 +276,10 @@ function hasSourcePlaceholder(value) {
|
|
|
270
276
|
function dataUri(value) {
|
|
271
277
|
if (typeof value !== "string" || value === "") return void 0;
|
|
272
278
|
if (/^data:/i.test(value)) return value;
|
|
273
|
-
if (typeof __DEV__ !== "undefined" && __DEV__) warnOnce("placeholder-url", `react-native-fast-image: \`
|
|
279
|
+
if (typeof __DEV__ !== "undefined" && __DEV__) warnOnce("placeholder-url", `react-native-fast-image: \`blurDataUri\` takes a data uri (data:image/…): "${value.slice(0, 60)}" is ignored. For a placeholder loaded from a url, use \`placeholder\`.`, "warn");
|
|
274
280
|
}
|
|
275
281
|
//#endregion
|
|
276
282
|
//#region src/index.tsx
|
|
277
|
-
function withoutPlaceholders(source) {
|
|
278
|
-
if (!hasSourcePlaceholder(source)) return source;
|
|
279
|
-
const { thumbhash: _thumbhash, blurDataUri: _blurDataUri, ...rest } = source;
|
|
280
|
-
return rest;
|
|
281
|
-
}
|
|
282
|
-
function sourcePlaceholder(source) {
|
|
283
|
-
if (!source || typeof source !== "object") return "";
|
|
284
|
-
const { thumbhash, blurDataUri } = source;
|
|
285
|
-
return thumbhash || dataUri(blurDataUri) || "";
|
|
286
|
-
}
|
|
287
283
|
const resolvePlaceholder = (placeholder) => {
|
|
288
284
|
if (typeof placeholder === "string") {
|
|
289
285
|
if (isImageName(placeholder)) return react_native.Platform.OS === "android" ? placeholder : { uri: placeholder };
|
|
@@ -348,25 +344,19 @@ function builtInTransition() {
|
|
|
348
344
|
}
|
|
349
345
|
let defaultTransition;
|
|
350
346
|
function FastImageBase({ source, placeholder, defaultSource, tintColor, onLoadStart, onProgress, onLoad, onError, onLoadEnd, style, children, objectFit, objectPosition, loop, transition, loading: _loading, sizes: _sizes, forwardedRef, ...viewProps }) {
|
|
351
|
-
var _props$recyclingKey, _defaultTransition;
|
|
347
|
+
var _placeholderSource, _props$recyclingKey, _defaultTransition;
|
|
352
348
|
const { fallback, resizeMode: _resizeMode, ...props } = viewProps;
|
|
353
349
|
if (__DEV__ && fallback !== void 0) warnOnce("fallback", "react-native-fast-image: `fallback` was removed in 10.0, and does nothing: images always load natively. Remove it, or use React Native's Image for those images.", "warn");
|
|
354
350
|
if (__DEV__) warnIfChildren(children);
|
|
355
351
|
const resolvedTintColor = tintColor != null ? tintColor : fromStyle(style, "tintColor");
|
|
356
352
|
const fit = resolveObjectFit(objectFit, style);
|
|
357
353
|
const position = objectPositionOrCentered(objectPosition);
|
|
358
|
-
const
|
|
359
|
-
|
|
360
|
-
const
|
|
361
|
-
|
|
362
|
-
|
|
363
|
-
|
|
364
|
-
resolvedSource: sources ? void 0 : react_native.Image.resolveAssetSource(withoutPlaceholders(single)),
|
|
365
|
-
sources: (sources === null || sources === void 0 ? void 0 : sources.some(hasSourcePlaceholder)) ? sources.map(withoutPlaceholders) : sources,
|
|
366
|
-
sourcePlaceholders: placeholders.some(Boolean) ? placeholders : void 0
|
|
367
|
-
};
|
|
368
|
-
}, [source, shared]);
|
|
369
|
-
const resolvedPlaceholder = resolvePlaceholder(hasSourcePlaceholder(placeholder) ? defaultSource : placeholder !== null && placeholder !== void 0 ? placeholder : defaultSource);
|
|
354
|
+
const resolvedSource = (0, react.useMemo)(() => {
|
|
355
|
+
for (const entry of [source].flat()) dataUri(entry === null || entry === void 0 ? void 0 : entry.blurDataUri);
|
|
356
|
+
const single = Array.isArray(source) && source.length === 1 ? source[0] : source;
|
|
357
|
+
return Array.isArray(single) ? single.map((entry) => react_native.Image.resolveAssetSource(entry)) : react_native.Image.resolveAssetSource(single);
|
|
358
|
+
}, [source]);
|
|
359
|
+
const resolvedPlaceholder = hasSourcePlaceholder(placeholder) ? placeholder.thumbhash || dataUri(placeholder.blurDataUri) ? placeholder : resolvePlaceholder(defaultSource) : (_placeholderSource = placeholderSource(placeholder)) !== null && _placeholderSource !== void 0 ? _placeholderSource : resolvePlaceholder(placeholder !== null && placeholder !== void 0 ? placeholder : defaultSource);
|
|
370
360
|
return /* @__PURE__ */ react.default.createElement(FastImageViewNativeComponent_default, {
|
|
371
361
|
...props,
|
|
372
362
|
ref: forwardedRef,
|
|
@@ -375,8 +365,6 @@ function FastImageBase({ source, placeholder, defaultSource, tintColor, onLoadSt
|
|
|
375
365
|
loopCount: loopCount(loop),
|
|
376
366
|
...resolveTransition(transition, (_defaultTransition = defaultTransition) !== null && _defaultTransition !== void 0 ? _defaultTransition : builtInTransition()),
|
|
377
367
|
source: resolvedSource,
|
|
378
|
-
sources,
|
|
379
|
-
sourcePlaceholders,
|
|
380
368
|
placeholder: resolvedPlaceholder,
|
|
381
369
|
onFastImageLoadStart: onLoadStart,
|
|
382
370
|
onFastImageProgress: onProgress,
|
package/dist/index.cjs.js.flow
CHANGED
|
@@ -110,6 +110,13 @@ export type PreloadResult = PreloadSuccess | PreloadFailure
|
|
|
110
110
|
export type PreloadFn = (
|
|
111
111
|
sources: Array<FastImageSource>,
|
|
112
112
|
) => Promise<Array<PreloadResult>>
|
|
113
|
+
export type PlaceholderSource = {
|
|
114
|
+
uri?: string,
|
|
115
|
+
headers?: Object,
|
|
116
|
+
cacheKey?: string,
|
|
117
|
+
cache?: CacheControls,
|
|
118
|
+
}
|
|
119
|
+
|
|
113
120
|
export type SourcePlaceholder = {
|
|
114
121
|
thumbhash?: string,
|
|
115
122
|
blurDataUri?: string,
|
|
@@ -138,8 +145,8 @@ export type FastImageProps = $ReadOnly<{|
|
|
|
138
145
|
onLoadStart?: ?() => void,
|
|
139
146
|
onProgress?: ?(event: OnProgressEvent) => void,
|
|
140
147
|
|
|
141
|
-
source?: ?(FastImageSource | Array<FastImageSource> | number),
|
|
142
|
-
placeholder?: ?(number | string | SourcePlaceholder),
|
|
148
|
+
source?: ?(FastImageSource | Array<FastImageSource | number> | number),
|
|
149
|
+
placeholder?: ?(number | string | SourcePlaceholder | PlaceholderSource),
|
|
143
150
|
// Deprecated: use placeholder, which is the same.
|
|
144
151
|
defaultSource?: ?number,
|
|
145
152
|
|