react-native-fast-image 8.21.0 → 8.23.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 +31 -0
- package/android/src/main/java/com/dylanvann/fastimage/FastImageViewManager.java +15 -0
- package/android/src/main/java/com/dylanvann/fastimage/FastImageViewWithUrl.java +199 -3
- package/dist/constants.d.ts +16 -0
- package/dist/index.cjs.js +18 -2
- package/dist/index.cjs.js.flow +7 -0
- package/dist/index.cjs.web.js +193 -0
- package/dist/index.cjs.web.js.flow +154 -0
- package/dist/index.d.ts +37 -16
- package/dist/index.js +18 -2
- package/dist/index.js.flow +7 -0
- package/dist/index.web.d.ts +6 -0
- package/dist/index.web.js +165 -0
- package/dist/index.web.js.flow +154 -0
- package/ios/FastImage/FFFastImageView.h +6 -0
- package/ios/FastImage/FFFastImageView.m +107 -7
- package/ios/FastImage/FFFastImageViewManager.m +3 -0
- package/package.json +6 -4
package/README.md
CHANGED
|
@@ -212,6 +212,27 @@ Pauses an animated image (GIF, and animated WebP on iOS) on the frame it's showi
|
|
|
212
212
|
|
|
213
213
|
---
|
|
214
214
|
|
|
215
|
+
### `transition?: boolean | number | Transition`
|
|
216
|
+
|
|
217
|
+
Fades the image in when it loads. Off by default. `true` uses the platform's usual fade, a number is the duration in milliseconds, or pass an object:
|
|
218
|
+
|
|
219
|
+
- `duration`: milliseconds; 0 is no fade. Defaults to the platform's usual length: 300 ms on Android, 250 ms on iOS.
|
|
220
|
+
- `betweenImages` (default `false`): also fade between images. By default an image fades in when it appears over nothing (or over `defaultSource`), and a new `source` replaces the image that's showing at once, once it has loaded. With `true`, the new image cross-dissolves from the one showing (for a gallery or an avatar that changes, say).
|
|
221
|
+
- `skipOnCacheHit`: which images show at once, without the fade:
|
|
222
|
+
- `'memory'` (default): images from the memory cache, so a list scrolled back up shows images it already loaded at once. Images from the disk cache fade in.
|
|
223
|
+
- `'all'`: images from the memory or disk cache: images that download fade in, and local files and bundled images usually only the first time they load (the disk cache usually keeps them too).
|
|
224
|
+
- `'none'`: every image fades in.
|
|
225
|
+
|
|
226
|
+
Downloads, local files (`file://`, `content://`) and bundled images (`require()`) fade in. In lists that reuse views (e.g. FlashList), set `recyclingKey`, so a reused view starts empty and its image fades in, instead of showing the previous item's image until it loads.
|
|
227
|
+
|
|
228
|
+
```jsx
|
|
229
|
+
<FastImage source={{ uri }} transition />
|
|
230
|
+
<FastImage source={{ uri }} transition={500} />
|
|
231
|
+
<FastImage source={{ uri }} transition={{ betweenImages: true, skipOnCacheHit: 'none' }} />
|
|
232
|
+
```
|
|
233
|
+
|
|
234
|
+
---
|
|
235
|
+
|
|
215
236
|
### `downsample?: boolean`
|
|
216
237
|
|
|
217
238
|
iOS only. Decodes a large image at about the size it's shown at, instead of at full size, so it takes much less memory.
|
|
@@ -465,6 +486,16 @@ On Android, if your app has its own `AppGlideModule` (see [Are you using Glide a
|
|
|
465
486
|
|
|
466
487
|
Images with `cache: 'web'` are kept in their own HTTP cache instead, up to 50 MB on each platform, which these limits don't change.
|
|
467
488
|
|
|
489
|
+
## Web
|
|
490
|
+
|
|
491
|
+
With [react-native-web](https://necolas.github.io/react-native-web/), FastImage shows images with the web's `Image`. Bundlers pick the web version through the package's `browser` field, or the `.web.js` files next to the native ones.
|
|
492
|
+
|
|
493
|
+
Support is minimal:
|
|
494
|
+
|
|
495
|
+
- Props that work: `source` (a `uri`, or a `require()`d image), `defaultSource`, `resizeMode`, `tintColor`, `blurRadius`, `style`, children, `onLoadStart`, `onLoad`, `onError`, `onLoadEnd`, and View props such as `testID`, accessibility props, `onLayout` and `pointerEvents`.
|
|
496
|
+
- Ignored: `source.headers` (a browser can't send them for an image), `source.priority`, `source.cache`, `source.cacheKey`, `source.memoryCache`, `recyclingKey`, `loop`, `imageRendering`, `paused`, `transition`, `downsample`, `fallback` and `onProgress`.
|
|
497
|
+
- `FastImage.preload` loads the images into the browser's cache and resolves with a result per source. `clearMemoryCache`, `clearDiskCache` and `configureCache` resolve without doing anything (the browser manages its cache), and `getCachePath` and `writeToCache` resolve with `{ ok: false, error: 'Not supported on the web' }`.
|
|
498
|
+
|
|
468
499
|
## Troubleshooting
|
|
469
500
|
|
|
470
501
|
If you have any problems using this library try the steps in [troubleshooting](docs/troubleshooting.md) and see if they fix it.
|
|
@@ -105,6 +105,21 @@ class FastImageViewManager extends SimpleViewManager<FastImageViewWithUrl> imple
|
|
|
105
105
|
view.setBlurRadius(PixelUtil.toPixelFromDIP(blurRadius));
|
|
106
106
|
}
|
|
107
107
|
|
|
108
|
+
@ReactProp(name = "transitionDuration")
|
|
109
|
+
public void setTransitionDuration(FastImageViewWithUrl view, int transitionDuration) {
|
|
110
|
+
view.setTransitionDuration(transitionDuration);
|
|
111
|
+
}
|
|
112
|
+
|
|
113
|
+
@ReactProp(name = "transitionBetweenImages")
|
|
114
|
+
public void setTransitionBetweenImages(FastImageViewWithUrl view, boolean betweenImages) {
|
|
115
|
+
view.setTransitionBetweenImages(betweenImages);
|
|
116
|
+
}
|
|
117
|
+
|
|
118
|
+
@ReactProp(name = "transitionSkipOnCacheHit")
|
|
119
|
+
public void setTransitionSkipOnCacheHit(FastImageViewWithUrl view, @Nullable String skipOnCacheHit) {
|
|
120
|
+
view.setTransitionSkipOnCacheHit(skipOnCacheHit);
|
|
121
|
+
}
|
|
122
|
+
|
|
108
123
|
@ReactProp(name = "paused")
|
|
109
124
|
public void setPaused(FastImageViewWithUrl view, boolean paused) {
|
|
110
125
|
view.setPaused(paused);
|
|
@@ -2,9 +2,16 @@ package com.dylanvann.fastimage;
|
|
|
2
2
|
|
|
3
3
|
import static com.dylanvann.fastimage.FastImageRequestListener.REACT_ON_ERROR_EVENT;
|
|
4
4
|
|
|
5
|
+
import android.animation.Animator;
|
|
6
|
+
import android.animation.AnimatorListenerAdapter;
|
|
7
|
+
import android.animation.ValueAnimator;
|
|
5
8
|
import android.annotation.SuppressLint;
|
|
6
9
|
import android.content.Context;
|
|
7
10
|
import android.graphics.Bitmap;
|
|
11
|
+
import android.graphics.Canvas;
|
|
12
|
+
import android.graphics.Paint;
|
|
13
|
+
import android.graphics.PorterDuff;
|
|
14
|
+
import android.graphics.PorterDuffXfermode;
|
|
8
15
|
import android.graphics.drawable.BitmapDrawable;
|
|
9
16
|
import android.graphics.drawable.Drawable;
|
|
10
17
|
|
|
@@ -13,15 +20,20 @@ import androidx.annotation.Nullable;
|
|
|
13
20
|
import androidx.appcompat.widget.AppCompatImageView;
|
|
14
21
|
import androidx.core.view.ViewCompat;
|
|
15
22
|
|
|
23
|
+
import com.bumptech.glide.GenericTransitionOptions;
|
|
16
24
|
import com.bumptech.glide.RequestBuilder;
|
|
17
25
|
import com.bumptech.glide.RequestManager;
|
|
26
|
+
import com.bumptech.glide.load.DataSource;
|
|
18
27
|
import com.bumptech.glide.load.model.GlideUrl;
|
|
19
28
|
import com.bumptech.glide.load.resource.bitmap.DownsampleStrategy;
|
|
29
|
+
import com.bumptech.glide.load.resource.drawable.DrawableTransitionOptions;
|
|
20
30
|
import com.bumptech.glide.load.resource.gif.GifDrawable;
|
|
21
31
|
import com.bumptech.glide.request.RequestOptions;
|
|
22
32
|
import com.bumptech.glide.request.target.DrawableImageViewTarget;
|
|
23
33
|
import com.bumptech.glide.request.target.SizeReadyCallback;
|
|
34
|
+
import com.bumptech.glide.request.transition.NoTransition;
|
|
24
35
|
import com.bumptech.glide.request.transition.Transition;
|
|
36
|
+
import com.bumptech.glide.request.transition.TransitionFactory;
|
|
25
37
|
import com.facebook.react.bridge.ReadableMap;
|
|
26
38
|
import com.facebook.react.bridge.WritableMap;
|
|
27
39
|
import com.facebook.react.bridge.WritableNativeMap;
|
|
@@ -88,6 +100,168 @@ class FastImageViewWithUrl extends AppCompatImageView {
|
|
|
88
100
|
// Pauses GIFs on the frame they're showing (the view's own animation).
|
|
89
101
|
private boolean mPaused = false;
|
|
90
102
|
|
|
103
|
+
// The `transition` prop (from the next load): how long a loaded image
|
|
104
|
+
// takes to fade in, in milliseconds (0 for no fade), whether it also fades
|
|
105
|
+
// over a loaded image (a new source), and which cache hits show at once
|
|
106
|
+
// (none, memory or all).
|
|
107
|
+
private int mTransitionDuration = 0;
|
|
108
|
+
private boolean mTransitionBetweenImages = false;
|
|
109
|
+
private String mTransitionSkipOnCacheHit = "memory";
|
|
110
|
+
|
|
111
|
+
public void setTransitionDuration(int transitionDuration) {
|
|
112
|
+
mTransitionDuration = Math.max(0, transitionDuration);
|
|
113
|
+
}
|
|
114
|
+
|
|
115
|
+
public void setTransitionBetweenImages(boolean betweenImages) {
|
|
116
|
+
mTransitionBetweenImages = betweenImages;
|
|
117
|
+
}
|
|
118
|
+
|
|
119
|
+
public void setTransitionSkipOnCacheHit(@Nullable String skipOnCacheHit) {
|
|
120
|
+
mTransitionSkipOnCacheHit = skipOnCacheHit == null ? "memory" : skipOnCacheHit;
|
|
121
|
+
}
|
|
122
|
+
|
|
123
|
+
// Fades a loaded image in, unless it's from a cache that skipOnCacheHit
|
|
124
|
+
// skips, as Glide's and Coil's cross-fades and Fresco decide: from the
|
|
125
|
+
// memory cache it shows at once (by default), and from the disk cache
|
|
126
|
+
// too with 'all'. Downloads, local files and bundled images fade.
|
|
127
|
+
private final class FadeFactory implements TransitionFactory<Drawable> {
|
|
128
|
+
private final int duration;
|
|
129
|
+
private final String skipOnCacheHit;
|
|
130
|
+
|
|
131
|
+
FadeFactory(int duration, String skipOnCacheHit) {
|
|
132
|
+
this.duration = duration;
|
|
133
|
+
this.skipOnCacheHit = skipOnCacheHit;
|
|
134
|
+
}
|
|
135
|
+
|
|
136
|
+
@Override
|
|
137
|
+
public Transition<Drawable> build(DataSource dataSource, boolean isFirstResource) {
|
|
138
|
+
if (skips(dataSource)) return NoTransition.get();
|
|
139
|
+
return new Transition<Drawable>() {
|
|
140
|
+
@Override
|
|
141
|
+
public boolean transition(Drawable current, ViewAdapter adapter) {
|
|
142
|
+
startFade(duration);
|
|
143
|
+
// The target shows the image as usual.
|
|
144
|
+
return false;
|
|
145
|
+
}
|
|
146
|
+
};
|
|
147
|
+
}
|
|
148
|
+
|
|
149
|
+
private boolean skips(DataSource dataSource) {
|
|
150
|
+
if (skipOnCacheHit.equals("none")) return false;
|
|
151
|
+
if (dataSource == DataSource.MEMORY_CACHE) return true;
|
|
152
|
+
// Glide also keeps a local file's or bundled image's decoded
|
|
153
|
+
// image in its disk cache (at the size it was decoded at, and not
|
|
154
|
+
// for GIFs), so with 'all' they usually only fade the first time.
|
|
155
|
+
boolean disk = dataSource == DataSource.DATA_DISK_CACHE || dataSource == DataSource.RESOURCE_DISK_CACHE;
|
|
156
|
+
return disk && skipOnCacheHit.equals("all");
|
|
157
|
+
}
|
|
158
|
+
}
|
|
159
|
+
|
|
160
|
+
// A fade in progress: what the view showed (null if nothing), drawn
|
|
161
|
+
// fading out while the view's image fades in, as a cross-dissolve on iOS.
|
|
162
|
+
// Drawn here rather than with Glide's DrawableCrossFadeTransition, whose
|
|
163
|
+
// TransitionDrawable stretches both images to one size and stays in the
|
|
164
|
+
// view after the fade, still drawing (and animating) the previous image.
|
|
165
|
+
@Nullable
|
|
166
|
+
private ValueAnimator mFade;
|
|
167
|
+
@Nullable
|
|
168
|
+
private Bitmap mFadeFrom;
|
|
169
|
+
private float mFadeProgress = 1f;
|
|
170
|
+
private final Paint mFadeFromPaint = new Paint(Paint.FILTER_BITMAP_FLAG);
|
|
171
|
+
private final Paint mFadeInPaint = new Paint();
|
|
172
|
+
|
|
173
|
+
{
|
|
174
|
+
// The two add up to a cross-dissolve: from * (1 - t) + image * t.
|
|
175
|
+
mFadeInPaint.setXfermode(new PorterDuffXfermode(PorterDuff.Mode.ADD));
|
|
176
|
+
}
|
|
177
|
+
|
|
178
|
+
private void startFade(int duration) {
|
|
179
|
+
int width = getWidth();
|
|
180
|
+
int height = getHeight();
|
|
181
|
+
if (width <= 0 || height <= 0) return;
|
|
182
|
+
// What the view shows now, including a fade in progress.
|
|
183
|
+
Bitmap from = null;
|
|
184
|
+
if (getDrawable() != null) {
|
|
185
|
+
try {
|
|
186
|
+
from = Bitmap.createBitmap(width, height, Bitmap.Config.ARGB_8888);
|
|
187
|
+
onDraw(new Canvas(from));
|
|
188
|
+
} catch (OutOfMemoryError | RuntimeException e) {
|
|
189
|
+
// Out of memory, or a hardware bitmap (Android 8+; an app's
|
|
190
|
+
// Glide module can enable them), which a software canvas
|
|
191
|
+
// can't draw: fade in over nothing instead.
|
|
192
|
+
if (from != null) from.recycle();
|
|
193
|
+
from = null;
|
|
194
|
+
}
|
|
195
|
+
}
|
|
196
|
+
endFade();
|
|
197
|
+
mFadeFrom = from;
|
|
198
|
+
mFadeProgress = 0f;
|
|
199
|
+
final ValueAnimator fade = ValueAnimator.ofFloat(0f, 1f);
|
|
200
|
+
fade.setDuration(duration);
|
|
201
|
+
fade.addUpdateListener(new ValueAnimator.AnimatorUpdateListener() {
|
|
202
|
+
@Override
|
|
203
|
+
public void onAnimationUpdate(ValueAnimator animation) {
|
|
204
|
+
mFadeProgress = (float) animation.getAnimatedValue();
|
|
205
|
+
invalidate();
|
|
206
|
+
}
|
|
207
|
+
});
|
|
208
|
+
fade.addListener(new AnimatorListenerAdapter() {
|
|
209
|
+
@Override
|
|
210
|
+
public void onAnimationEnd(Animator animation) {
|
|
211
|
+
if (mFade == fade) endFade();
|
|
212
|
+
}
|
|
213
|
+
});
|
|
214
|
+
mFade = fade;
|
|
215
|
+
fade.start();
|
|
216
|
+
}
|
|
217
|
+
|
|
218
|
+
// Shows the image on its own (the fade ends, another starts, or the view
|
|
219
|
+
// is detached).
|
|
220
|
+
private void endFade() {
|
|
221
|
+
ValueAnimator fade = mFade;
|
|
222
|
+
mFade = null;
|
|
223
|
+
if (fade != null) fade.cancel();
|
|
224
|
+
if (mFadeFrom != null) {
|
|
225
|
+
mFadeFrom.recycle();
|
|
226
|
+
mFadeFrom = null;
|
|
227
|
+
}
|
|
228
|
+
mFadeProgress = 1f;
|
|
229
|
+
invalidate();
|
|
230
|
+
}
|
|
231
|
+
|
|
232
|
+
@SuppressWarnings("deprecation")
|
|
233
|
+
@Override
|
|
234
|
+
protected void onDraw(Canvas canvas) {
|
|
235
|
+
if (mFade == null) {
|
|
236
|
+
super.onDraw(canvas);
|
|
237
|
+
return;
|
|
238
|
+
}
|
|
239
|
+
int width = getWidth();
|
|
240
|
+
int height = getHeight();
|
|
241
|
+
int alpha = Math.round(255 * mFadeProgress);
|
|
242
|
+
if (mFadeFrom == null) {
|
|
243
|
+
// Fading in over nothing: one layer.
|
|
244
|
+
int count = canvas.saveLayerAlpha(0, 0, width, height, alpha, Canvas.ALL_SAVE_FLAG);
|
|
245
|
+
super.onDraw(canvas);
|
|
246
|
+
canvas.restoreToCount(count);
|
|
247
|
+
return;
|
|
248
|
+
}
|
|
249
|
+
// Canvas.ALL_SAVE_FLAG: the saveLayer without flags is API 21+.
|
|
250
|
+
int count = canvas.saveLayer(0, 0, width, height, null, Canvas.ALL_SAVE_FLAG);
|
|
251
|
+
mFadeFromPaint.setAlpha(255 - alpha);
|
|
252
|
+
canvas.drawBitmap(mFadeFrom, 0, 0, mFadeFromPaint);
|
|
253
|
+
mFadeInPaint.setAlpha(alpha);
|
|
254
|
+
canvas.saveLayer(0, 0, width, height, mFadeInPaint, Canvas.ALL_SAVE_FLAG);
|
|
255
|
+
super.onDraw(canvas);
|
|
256
|
+
canvas.restoreToCount(count);
|
|
257
|
+
}
|
|
258
|
+
|
|
259
|
+
@Override
|
|
260
|
+
protected void onDetachedFromWindow() {
|
|
261
|
+
endFade();
|
|
262
|
+
super.onDetachedFromWindow();
|
|
263
|
+
}
|
|
264
|
+
|
|
91
265
|
public void setPaused(boolean paused) {
|
|
92
266
|
if (paused == mPaused) return;
|
|
93
267
|
mPaused = paused;
|
|
@@ -277,9 +451,10 @@ class FastImageViewWithUrl extends AppCompatImageView {
|
|
|
277
451
|
mRecyclingKey = recyclingKey;
|
|
278
452
|
if (changed) {
|
|
279
453
|
// No thumbnail of the current image, so Glide clears the view to
|
|
280
|
-
// defaultSource (or nothing) while the next one loads
|
|
281
|
-
// if the source is the same.
|
|
454
|
+
// defaultSource (or nothing) while the next one loads, nor a fade
|
|
455
|
+
// from it. Reload even if the source is the same.
|
|
282
456
|
mShownRequest = null;
|
|
457
|
+
endFade();
|
|
283
458
|
mNeedsReload = true;
|
|
284
459
|
}
|
|
285
460
|
}
|
|
@@ -299,6 +474,13 @@ class FastImageViewWithUrl extends AppCompatImageView {
|
|
|
299
474
|
@Override
|
|
300
475
|
protected void onSizeChanged(int w, int h, int oldw, int oldh) {
|
|
301
476
|
super.onSizeChanged(w, h, oldw, oldh);
|
|
477
|
+
// During a fade, what the view showed was drawn at the old size: the
|
|
478
|
+
// image fades in over nothing instead.
|
|
479
|
+
if (mFadeFrom != null && (w != mFadeFrom.getWidth() || h != mFadeFrom.getHeight())) {
|
|
480
|
+
mFadeFrom.recycle();
|
|
481
|
+
mFadeFrom = null;
|
|
482
|
+
invalidate();
|
|
483
|
+
}
|
|
302
484
|
reloadIfResized();
|
|
303
485
|
}
|
|
304
486
|
|
|
@@ -395,7 +577,9 @@ class FastImageViewWithUrl extends AppCompatImageView {
|
|
|
395
577
|
// it was loaded at: the key it's in Glide's memory cache under.
|
|
396
578
|
@SuppressLint("CheckResult")
|
|
397
579
|
private RequestBuilder<Drawable> fromCache(RequestBuilder<Drawable> shown) {
|
|
398
|
-
|
|
580
|
+
// Never fades: it's the image the view showed.
|
|
581
|
+
RequestBuilder<Drawable> request = shown.clone().onlyRetrieveFromCache(true)
|
|
582
|
+
.transition(GenericTransitionOptions.<Drawable>withNoTransition());
|
|
399
583
|
if (mShownWidth > 0 && mShownHeight > 0) request = request.override(mShownWidth, mShownHeight);
|
|
400
584
|
return request;
|
|
401
585
|
}
|
|
@@ -461,6 +645,8 @@ class FastImageViewWithUrl extends AppCompatImageView {
|
|
|
461
645
|
boolean restarting = mRestarting;
|
|
462
646
|
mRestarting = false;
|
|
463
647
|
mLoadCount++;
|
|
648
|
+
// A fade in progress carries on, as on iOS: into the next image if it
|
|
649
|
+
// shows at once, and a next image that fades starts from it.
|
|
464
650
|
RequestBuilder<Drawable> shownRequest = mShownRequest;
|
|
465
651
|
mShownRequest = null;
|
|
466
652
|
mLoadingRequest = null;
|
|
@@ -560,6 +746,16 @@ class FastImageViewWithUrl extends AppCompatImageView {
|
|
|
560
746
|
|
|
561
747
|
if (model == null) meanwhile = null;
|
|
562
748
|
boolean thumbnail = shownRequest != null && model != null && meanwhile == null;
|
|
749
|
+
// After the clone: loading the image that's showing again (at a
|
|
750
|
+
// new size, or with another blur) doesn't fade it in again. Over a
|
|
751
|
+
// loaded image (a new source) only with betweenImages: otherwise it
|
|
752
|
+
// replaces it at once, as Glide, Coil and Fresco fade an image in
|
|
753
|
+
// from nothing and never between images.
|
|
754
|
+
boolean replacing = shownRequest != null && model != null;
|
|
755
|
+
if (mTransitionDuration > 0 && (!replacing || mTransitionBetweenImages)) {
|
|
756
|
+
builder = builder.transition(DrawableTransitionOptions.with(
|
|
757
|
+
new FadeFactory(mTransitionDuration, mTransitionSkipOnCacheHit)));
|
|
758
|
+
}
|
|
563
759
|
if (thumbnail) {
|
|
564
760
|
builder = builder.thumbnail(fromCache(shownRequest));
|
|
565
761
|
}
|
|
@@ -0,0 +1,16 @@
|
|
|
1
|
+
export declare const resizeMode: {
|
|
2
|
+
readonly contain: 'contain';
|
|
3
|
+
readonly cover: 'cover';
|
|
4
|
+
readonly stretch: 'stretch';
|
|
5
|
+
readonly center: 'center';
|
|
6
|
+
};
|
|
7
|
+
export declare const priority: {
|
|
8
|
+
readonly low: 'low';
|
|
9
|
+
readonly normal: 'normal';
|
|
10
|
+
readonly high: 'high';
|
|
11
|
+
};
|
|
12
|
+
export declare const cacheControl: {
|
|
13
|
+
readonly immutable: 'immutable';
|
|
14
|
+
readonly web: 'web';
|
|
15
|
+
readonly cacheOnly: 'cacheOnly';
|
|
16
|
+
};
|
package/dist/index.cjs.js
CHANGED
|
@@ -27,7 +27,7 @@ var __toESM = (mod, isNodeMode, target) => (target = mod != null ? __create(__ge
|
|
|
27
27
|
let react = require("react");
|
|
28
28
|
react = __toESM(react);
|
|
29
29
|
let react_native = require("react-native");
|
|
30
|
-
//#region src/
|
|
30
|
+
//#region src/constants.ts
|
|
31
31
|
const resizeMode = {
|
|
32
32
|
contain: "contain",
|
|
33
33
|
cover: "cover",
|
|
@@ -44,6 +44,8 @@ const cacheControl = {
|
|
|
44
44
|
web: "web",
|
|
45
45
|
cacheOnly: "cacheOnly"
|
|
46
46
|
};
|
|
47
|
+
//#endregion
|
|
48
|
+
//#region src/index.tsx
|
|
47
49
|
const resolveDefaultSource = (defaultSource) => {
|
|
48
50
|
if (!defaultSource) return null;
|
|
49
51
|
if (react_native.Platform.OS === "android") {
|
|
@@ -91,11 +93,24 @@ function withProgress(onProgress) {
|
|
|
91
93
|
onProgress(event);
|
|
92
94
|
});
|
|
93
95
|
}
|
|
96
|
+
const DEFAULT_FADE_MS = react_native.Platform.OS === "ios" ? 250 : 300;
|
|
97
|
+
function transitionProps(transition) {
|
|
98
|
+
var _transition$duration;
|
|
99
|
+
const { duration, betweenImages, skipOnCacheHit } = typeof transition === "number" ? { duration: transition } : typeof transition === "object" && transition ? {
|
|
100
|
+
...transition,
|
|
101
|
+
duration: (_transition$duration = transition.duration) !== null && _transition$duration !== void 0 ? _transition$duration : DEFAULT_FADE_MS
|
|
102
|
+
} : { duration: transition ? DEFAULT_FADE_MS : 0 };
|
|
103
|
+
return {
|
|
104
|
+
transitionDuration: typeof duration === "number" && duration > 0 ? duration : 0,
|
|
105
|
+
transitionBetweenImages: !!betweenImages,
|
|
106
|
+
transitionSkipOnCacheHit: skipOnCacheHit || "memory"
|
|
107
|
+
};
|
|
108
|
+
}
|
|
94
109
|
function withoutCache(source) {
|
|
95
110
|
const { cache: _cache, ...rest } = source || {};
|
|
96
111
|
return rest;
|
|
97
112
|
}
|
|
98
|
-
function FastImageBase({ source, defaultSource, tintColor, onLoadStart, onProgress, onLoad, onError, onLoadEnd, style, fallback, children, resizeMode = "cover", loop, forwardedRef, onLayout, pointerEvents, ...viewProps }) {
|
|
113
|
+
function FastImageBase({ source, defaultSource, tintColor, onLoadStart, onProgress, onLoad, onError, onLoadEnd, style, fallback, children, resizeMode = "cover", loop, transition, forwardedRef, onLayout, pointerEvents, ...viewProps }) {
|
|
99
114
|
const { onClick, ...props } = viewProps;
|
|
100
115
|
const fallbackResult = (0, react.useRef)(void 0);
|
|
101
116
|
const wrapperProps = {
|
|
@@ -158,6 +173,7 @@ function FastImageBase({ source, defaultSource, tintColor, onLoadStart, onProgre
|
|
|
158
173
|
...imageProps,
|
|
159
174
|
tintColor: resolvedTintColor,
|
|
160
175
|
loopCount: loopCount(loop),
|
|
176
|
+
...transitionProps(transition),
|
|
161
177
|
style: react_native.StyleSheet.absoluteFill,
|
|
162
178
|
source: resolvedSource,
|
|
163
179
|
defaultSource: resolvedDefaultSource,
|
package/dist/index.cjs.js.flow
CHANGED
|
@@ -72,6 +72,12 @@ export type CachePathResult =
|
|
|
72
72
|
| {| ok: true, path: string |}
|
|
73
73
|
| {| ok: false, error: string |}
|
|
74
74
|
|
|
75
|
+
export type Transition = $ReadOnly<{|
|
|
76
|
+
duration?: ?number,
|
|
77
|
+
betweenImages?: ?boolean,
|
|
78
|
+
skipOnCacheHit?: ?('none' | 'memory' | 'all'),
|
|
79
|
+
|}>
|
|
80
|
+
|
|
75
81
|
export type PreloadSuccess = {|
|
|
76
82
|
uri: string,
|
|
77
83
|
ok: true,
|
|
@@ -117,6 +123,7 @@ export type FastImageProps = $ReadOnly<{|
|
|
|
117
123
|
loop?: ?(boolean | number),
|
|
118
124
|
imageRendering?: ?('auto' | 'smooth' | 'pixelated'),
|
|
119
125
|
paused?: ?boolean,
|
|
126
|
+
transition?: ?(boolean | number | Transition),
|
|
120
127
|
downsample?: ?boolean,
|
|
121
128
|
blurRadius?: ?number,
|
|
122
129
|
testID?: ?string,
|
|
@@ -0,0 +1,193 @@
|
|
|
1
|
+
Object.defineProperties(exports, {
|
|
2
|
+
__esModule: { value: true },
|
|
3
|
+
[Symbol.toStringTag]: { value: "Module" }
|
|
4
|
+
});
|
|
5
|
+
//#region \0rolldown/runtime.js
|
|
6
|
+
var __create = Object.create;
|
|
7
|
+
var __defProp = Object.defineProperty;
|
|
8
|
+
var __getOwnPropDesc = Object.getOwnPropertyDescriptor;
|
|
9
|
+
var __getOwnPropNames = Object.getOwnPropertyNames;
|
|
10
|
+
var __getProtoOf = Object.getPrototypeOf;
|
|
11
|
+
var __hasOwnProp = Object.prototype.hasOwnProperty;
|
|
12
|
+
var __copyProps = (to, from, except, desc) => {
|
|
13
|
+
if (from && typeof from === "object" || typeof from === "function") for (var keys = __getOwnPropNames(from), i = 0, n = keys.length, key; i < n; i++) {
|
|
14
|
+
key = keys[i];
|
|
15
|
+
if (!__hasOwnProp.call(to, key) && key !== except) __defProp(to, key, {
|
|
16
|
+
get: ((k) => from[k]).bind(null, key),
|
|
17
|
+
enumerable: !(desc = __getOwnPropDesc(from, key)) || desc.enumerable
|
|
18
|
+
});
|
|
19
|
+
}
|
|
20
|
+
return to;
|
|
21
|
+
};
|
|
22
|
+
var __toESM = (mod, isNodeMode, target) => (target = mod != null ? __create(__getProtoOf(mod)) : {}, __copyProps(isNodeMode || !mod || !mod.__esModule || !__hasOwnProp.call(mod, "default") ? __defProp(target, "default", {
|
|
23
|
+
value: mod,
|
|
24
|
+
enumerable: true
|
|
25
|
+
}) : target, mod));
|
|
26
|
+
//#endregion
|
|
27
|
+
let react = require("react");
|
|
28
|
+
react = __toESM(react);
|
|
29
|
+
let react_native = require("react-native");
|
|
30
|
+
//#region src/constants.ts
|
|
31
|
+
const resizeMode = {
|
|
32
|
+
contain: "contain",
|
|
33
|
+
cover: "cover",
|
|
34
|
+
stretch: "stretch",
|
|
35
|
+
center: "center"
|
|
36
|
+
};
|
|
37
|
+
const priority = {
|
|
38
|
+
low: "low",
|
|
39
|
+
normal: "normal",
|
|
40
|
+
high: "high"
|
|
41
|
+
};
|
|
42
|
+
const cacheControl = {
|
|
43
|
+
immutable: "immutable",
|
|
44
|
+
web: "web",
|
|
45
|
+
cacheOnly: "cacheOnly"
|
|
46
|
+
};
|
|
47
|
+
//#endregion
|
|
48
|
+
//#region src/index.web.tsx
|
|
49
|
+
const notSupported = {
|
|
50
|
+
ok: false,
|
|
51
|
+
error: "Not supported on the web"
|
|
52
|
+
};
|
|
53
|
+
function FastImageBase({ source, defaultSource, tintColor, blurRadius, onLoadStart, onLoad, onError, onLoadEnd, style, children, resizeMode: mode = "cover", forwardedRef, onLayout, pointerEvents, fallback: _fallback, recyclingKey: _recyclingKey, loop: _loop, imageRendering: _imageRendering, paused: _paused, transition: _transition, downsample: _downsample, onProgress: _onProgress, ...props }) {
|
|
54
|
+
const result = (0, react.useRef)(void 0);
|
|
55
|
+
const sent = (0, react.useRef)(Promise.resolve());
|
|
56
|
+
const image = (0, react.useRef)(null);
|
|
57
|
+
const uri = typeof source === "object" && source ? source.uri : void 0;
|
|
58
|
+
const webSource = typeof source === "number" ? source : uri ? { uri } : void 0;
|
|
59
|
+
return /* @__PURE__ */ react.default.createElement(react_native.View, {
|
|
60
|
+
style: [styles.container, style],
|
|
61
|
+
onLayout,
|
|
62
|
+
pointerEvents,
|
|
63
|
+
ref: forwardedRef
|
|
64
|
+
}, /* @__PURE__ */ react.default.createElement(react_native.Image, {
|
|
65
|
+
...props,
|
|
66
|
+
style: styles.image,
|
|
67
|
+
source: webSource,
|
|
68
|
+
defaultSource,
|
|
69
|
+
resizeMode: mode,
|
|
70
|
+
tintColor,
|
|
71
|
+
blurRadius,
|
|
72
|
+
ref: image,
|
|
73
|
+
onLoadStart,
|
|
74
|
+
onLoad: (event) => {
|
|
75
|
+
var _event$nativeEvent;
|
|
76
|
+
sent.current = loadedSize(event === null || event === void 0 || (_event$nativeEvent = event.nativeEvent) === null || _event$nativeEvent === void 0 ? void 0 : _event$nativeEvent.target, image.current, uri).then(({ width, height }) => {
|
|
77
|
+
result.current = {
|
|
78
|
+
ok: true,
|
|
79
|
+
width,
|
|
80
|
+
height
|
|
81
|
+
};
|
|
82
|
+
onLoad === null || onLoad === void 0 || onLoad({ nativeEvent: {
|
|
83
|
+
width,
|
|
84
|
+
height
|
|
85
|
+
} });
|
|
86
|
+
});
|
|
87
|
+
},
|
|
88
|
+
onError: (event) => {
|
|
89
|
+
var _event$nativeEvent$er, _event$nativeEvent2;
|
|
90
|
+
const error = String((_event$nativeEvent$er = event === null || event === void 0 || (_event$nativeEvent2 = event.nativeEvent) === null || _event$nativeEvent2 === void 0 ? void 0 : _event$nativeEvent2.error) !== null && _event$nativeEvent$er !== void 0 ? _event$nativeEvent$er : "Failed to load the image");
|
|
91
|
+
result.current = {
|
|
92
|
+
ok: false,
|
|
93
|
+
error
|
|
94
|
+
};
|
|
95
|
+
sent.current = Promise.resolve();
|
|
96
|
+
onError === null || onError === void 0 || onError({ nativeEvent: { error } });
|
|
97
|
+
},
|
|
98
|
+
onLoadEnd: () => {
|
|
99
|
+
sent.current.then(() => {
|
|
100
|
+
var _result$current;
|
|
101
|
+
onLoadEnd === null || onLoadEnd === void 0 || onLoadEnd((_result$current = result.current) !== null && _result$current !== void 0 ? _result$current : {
|
|
102
|
+
ok: false,
|
|
103
|
+
error: "Failed to load the image"
|
|
104
|
+
});
|
|
105
|
+
result.current = void 0;
|
|
106
|
+
});
|
|
107
|
+
}
|
|
108
|
+
}), children);
|
|
109
|
+
}
|
|
110
|
+
const FastImageMemo = (0, react.memo)(FastImageBase);
|
|
111
|
+
const FastImageComponent = (0, react.forwardRef)((props, ref) => /* @__PURE__ */ react.default.createElement(FastImageMemo, {
|
|
112
|
+
forwardedRef: ref,
|
|
113
|
+
...props
|
|
114
|
+
}));
|
|
115
|
+
FastImageComponent.displayName = "FastImage";
|
|
116
|
+
async function loadedSize(loaded, view, uri) {
|
|
117
|
+
var _view$querySelector;
|
|
118
|
+
if (loaded === null || loaded === void 0 ? void 0 : loaded.naturalWidth) return {
|
|
119
|
+
width: loaded.naturalWidth,
|
|
120
|
+
height: loaded.naturalHeight
|
|
121
|
+
};
|
|
122
|
+
await new Promise((resolve) => setTimeout(resolve, 0));
|
|
123
|
+
const shown = view === null || view === void 0 || (_view$querySelector = view.querySelector) === null || _view$querySelector === void 0 ? void 0 : _view$querySelector.call(view, "img");
|
|
124
|
+
if (shown === null || shown === void 0 ? void 0 : shown.naturalWidth) return {
|
|
125
|
+
width: shown.naturalWidth,
|
|
126
|
+
height: shown.naturalHeight
|
|
127
|
+
};
|
|
128
|
+
const src = uri !== null && uri !== void 0 ? uri : shown === null || shown === void 0 ? void 0 : shown.src;
|
|
129
|
+
return src ? getSize(src) : {
|
|
130
|
+
width: 0,
|
|
131
|
+
height: 0
|
|
132
|
+
};
|
|
133
|
+
}
|
|
134
|
+
function getSize(uri) {
|
|
135
|
+
return new Promise((resolve) => {
|
|
136
|
+
react_native.Image.getSize(uri, (width, height) => resolve({
|
|
137
|
+
width,
|
|
138
|
+
height
|
|
139
|
+
}), () => resolve({
|
|
140
|
+
width: 0,
|
|
141
|
+
height: 0
|
|
142
|
+
}));
|
|
143
|
+
});
|
|
144
|
+
}
|
|
145
|
+
const FastImage = FastImageComponent;
|
|
146
|
+
FastImage.resizeMode = resizeMode;
|
|
147
|
+
FastImage.cacheControl = cacheControl;
|
|
148
|
+
FastImage.priority = priority;
|
|
149
|
+
FastImage.preload = (sources) => Promise.all(sources.map((source) => {
|
|
150
|
+
const uri = source ? source.uri : void 0;
|
|
151
|
+
if (typeof uri !== "string" || !uri) return Promise.resolve({
|
|
152
|
+
ok: false,
|
|
153
|
+
error: "Invalid source: no uri",
|
|
154
|
+
uri
|
|
155
|
+
});
|
|
156
|
+
return react_native.Image.prefetch(uri).then(() => getSize(uri).then((size) => ({
|
|
157
|
+
ok: true,
|
|
158
|
+
uri,
|
|
159
|
+
...size
|
|
160
|
+
})), () => ({
|
|
161
|
+
ok: false,
|
|
162
|
+
error: "Failed to load the image",
|
|
163
|
+
uri
|
|
164
|
+
}));
|
|
165
|
+
}));
|
|
166
|
+
FastImage.clearMemoryCache = () => Promise.resolve();
|
|
167
|
+
FastImage.clearDiskCache = () => Promise.resolve();
|
|
168
|
+
FastImage.configureCache = () => Promise.resolve({});
|
|
169
|
+
FastImage.writeToCache = () => Promise.resolve(notSupported);
|
|
170
|
+
FastImage.getCachePath = () => Promise.resolve(notSupported);
|
|
171
|
+
const styles = react_native.StyleSheet.create({
|
|
172
|
+
container: { overflow: "hidden" },
|
|
173
|
+
image: {
|
|
174
|
+
position: "absolute",
|
|
175
|
+
top: 0,
|
|
176
|
+
left: 0,
|
|
177
|
+
width: "100%",
|
|
178
|
+
height: "100%"
|
|
179
|
+
}
|
|
180
|
+
});
|
|
181
|
+
const FastImageWithRef = FastImageComponent;
|
|
182
|
+
const FastImageBackground = (0, react.forwardRef)(({ style, imageStyle, imageRef, children, ...props }, ref) => /* @__PURE__ */ react.default.createElement(react_native.View, {
|
|
183
|
+
style,
|
|
184
|
+
ref
|
|
185
|
+
}, /* @__PURE__ */ react.default.createElement(FastImageWithRef, {
|
|
186
|
+
...props,
|
|
187
|
+
style: [react_native.StyleSheet.absoluteFill, imageStyle],
|
|
188
|
+
ref: imageRef
|
|
189
|
+
}), children));
|
|
190
|
+
FastImageBackground.displayName = "FastImageBackground";
|
|
191
|
+
//#endregion
|
|
192
|
+
exports.FastImageBackground = FastImageBackground;
|
|
193
|
+
exports.default = FastImage;
|