react-native-fast-image 8.24.0 → 8.26.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 +30 -1
- package/android/src/main/java/com/dylanvann/fastimage/FastImageViewManager.java +11 -2
- package/android/src/main/java/com/dylanvann/fastimage/FastImageViewWithUrl.java +155 -7
- package/dist/constants.d.ts +1 -0
- package/dist/index.cjs.js +22 -6
- package/dist/index.cjs.js.flow +5 -1
- package/dist/index.cjs.web.js +80 -4
- package/dist/index.cjs.web.js.flow +5 -1
- package/dist/index.d.ts +15 -2
- package/dist/index.js +22 -6
- package/dist/index.js.flow +5 -1
- package/dist/index.web.js +81 -5
- package/dist/index.web.js.flow +5 -1
- package/ios/FastImage/FFFastImageSource.h +3 -0
- package/ios/FastImage/FFFastImageView.h +3 -0
- package/ios/FastImage/FFFastImageView.m +134 -22
- package/ios/FastImage/FFFastImageViewManager.m +1 -0
- package/ios/FastImage/RCTConvert+FFFastImage.h +1 -0
- package/ios/FastImage/RCTConvert+FFFastImage.m +2 -0
- package/package.json +1 -1
package/README.md
CHANGED
|
@@ -175,6 +175,33 @@ Whether the decoded image is kept in the memory cache. **Default: true.** With `
|
|
|
175
175
|
|
|
176
176
|
---
|
|
177
177
|
|
|
178
|
+
### Several sizes of an image
|
|
179
|
+
|
|
180
|
+
`source` can also be an array of the same image at different sizes, each with its `width` and `height` in pixels (times `scale`, if it has one). The view loads the one whose size is closest to its own, in pixels, so a small view downloads a small image:
|
|
181
|
+
|
|
182
|
+
```jsx
|
|
183
|
+
<FastImage
|
|
184
|
+
style={{ width: 120, height: 120 }}
|
|
185
|
+
source={[
|
|
186
|
+
{ uri: 'https://example.com/photo-200.jpg', width: 200, height: 200 },
|
|
187
|
+
{ uri: 'https://example.com/photo-800.jpg', width: 800, height: 800 },
|
|
188
|
+
{
|
|
189
|
+
uri: 'https://example.com/photo-2000.jpg',
|
|
190
|
+
width: 2000,
|
|
191
|
+
height: 2000,
|
|
192
|
+
},
|
|
193
|
+
]}
|
|
194
|
+
/>
|
|
195
|
+
```
|
|
196
|
+
|
|
197
|
+
- The view picks once it has been laid out. One that has no size (e.g. sized from `onLoad`) loads the largest.
|
|
198
|
+
- 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`.
|
|
199
|
+
- 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.
|
|
200
|
+
- `FastImage.preload`, `getCachePath` and `writeToCache` take one size: pass the one a view will show. An array fails with `{ ok: false, error }`.
|
|
201
|
+
- An array of one is the same as that source.
|
|
202
|
+
|
|
203
|
+
---
|
|
204
|
+
|
|
178
205
|
### `defaultSource?: number`
|
|
179
206
|
|
|
180
207
|
- An asset loaded with `require(...)`.
|
|
@@ -199,6 +226,7 @@ For views that get reused for other content, such as rows in FlashList or recycl
|
|
|
199
226
|
- `FastImage.resizeMode.cover` **(Default)** - Scale the image uniformly (maintain the image's aspect ratio) so that both dimensions (width and height) of the image will be equal to or larger than the corresponding dimension of the view (minus padding).
|
|
200
227
|
- `FastImage.resizeMode.stretch` - Scale width and height independently, This may change the aspect ratio of the src.
|
|
201
228
|
- `FastImage.resizeMode.center` - Do not scale the image, keep centered.
|
|
229
|
+
- `FastImage.resizeMode.repeat` - Repeat the image to cover the view, from its top-left corner, at the image's own size in pixels (a bundled image at its size in points), scaled down to fit if it's larger than the view. An animated image repeats its first frame, and `defaultSource` repeats too.
|
|
202
230
|
|
|
203
231
|
---
|
|
204
232
|
|
|
@@ -511,8 +539,9 @@ With [react-native-web](https://necolas.github.io/react-native-web/), FastImage
|
|
|
511
539
|
|
|
512
540
|
Support is minimal:
|
|
513
541
|
|
|
514
|
-
- Props that work: `source` (a `uri`,
|
|
542
|
+
- Props that work: `source` (a `uri`, a `require()`d image, or several sizes), `defaultSource`, `resizeMode`, `tintColor`, `blurRadius`, `style`, children, `onLoadStart`, `onLoad`, `onError`, `onLoadEnd`, and View props such as `testID`, accessibility props, `onLayout` and `pointerEvents`.
|
|
515
543
|
- 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`.
|
|
544
|
+
- Several sizes are shown with a lazily loaded `<img>` whose `srcset` lists them, with `sizes="auto, 100vw"`: the browser loads the one for the width the image is shown at, in device pixels (usually the smallest that's at least as wide). Browsers that don't support `sizes="auto"` use the viewport's width. `tintColor`, `defaultSource` and `resizeMode="repeat"` don't apply to them.
|
|
516
545
|
- `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' }`.
|
|
517
546
|
|
|
518
547
|
## Troubleshooting
|
|
@@ -14,6 +14,7 @@ import androidx.annotation.NonNull;
|
|
|
14
14
|
|
|
15
15
|
import com.bumptech.glide.Glide;
|
|
16
16
|
import com.bumptech.glide.RequestManager;
|
|
17
|
+
import com.facebook.react.bridge.ReadableArray;
|
|
17
18
|
import com.facebook.react.bridge.ReadableMap;
|
|
18
19
|
import com.facebook.react.bridge.WritableMap;
|
|
19
20
|
import com.facebook.react.bridge.WritableNativeMap;
|
|
@@ -69,6 +70,11 @@ class FastImageViewManager extends SimpleViewManager<FastImageViewWithUrl> imple
|
|
|
69
70
|
view.setSource(source);
|
|
70
71
|
}
|
|
71
72
|
|
|
73
|
+
@ReactProp(name = "sources")
|
|
74
|
+
public void setSources(FastImageViewWithUrl view, @Nullable ReadableArray sources) {
|
|
75
|
+
view.setSources(sources);
|
|
76
|
+
}
|
|
77
|
+
|
|
72
78
|
@ReactProp(name = "defaultSource")
|
|
73
79
|
public void setDefaultSource(FastImageViewWithUrl view, @Nullable String source) {
|
|
74
80
|
view.setDefaultSource(
|
|
@@ -127,8 +133,11 @@ class FastImageViewManager extends SimpleViewManager<FastImageViewWithUrl> imple
|
|
|
127
133
|
|
|
128
134
|
@ReactProp(name = "resizeMode")
|
|
129
135
|
public void setResizeMode(FastImageViewWithUrl view, String resizeMode) {
|
|
130
|
-
|
|
131
|
-
|
|
136
|
+
// repeat fills the view with the tiled image (see setImageDrawable).
|
|
137
|
+
boolean repeat = "repeat".equals(resizeMode);
|
|
138
|
+
final FastImageViewWithUrl.ScaleType scaleType =
|
|
139
|
+
repeat ? FastImageViewWithUrl.ScaleType.FIT_XY : FastImageViewConverter.getScaleType(resizeMode);
|
|
140
|
+
view.setResizeMode(scaleType, repeat);
|
|
132
141
|
}
|
|
133
142
|
|
|
134
143
|
@Override
|
|
@@ -12,6 +12,7 @@ import android.graphics.Canvas;
|
|
|
12
12
|
import android.graphics.Paint;
|
|
13
13
|
import android.graphics.PorterDuff;
|
|
14
14
|
import android.graphics.PorterDuffXfermode;
|
|
15
|
+
import android.graphics.Shader;
|
|
15
16
|
import android.graphics.drawable.BitmapDrawable;
|
|
16
17
|
import android.graphics.drawable.Drawable;
|
|
17
18
|
|
|
@@ -34,6 +35,7 @@ import com.bumptech.glide.request.target.SizeReadyCallback;
|
|
|
34
35
|
import com.bumptech.glide.request.transition.NoTransition;
|
|
35
36
|
import com.bumptech.glide.request.transition.Transition;
|
|
36
37
|
import com.bumptech.glide.request.transition.TransitionFactory;
|
|
38
|
+
import com.facebook.react.bridge.ReadableArray;
|
|
37
39
|
import com.facebook.react.bridge.ReadableMap;
|
|
38
40
|
import com.facebook.react.bridge.WritableMap;
|
|
39
41
|
import com.facebook.react.bridge.WritableNativeMap;
|
|
@@ -62,7 +64,89 @@ class FastImageViewWithUrl extends AppCompatImageView {
|
|
|
62
64
|
|
|
63
65
|
public void setSource(@Nullable ReadableMap source) {
|
|
64
66
|
mNeedsReload = true;
|
|
65
|
-
|
|
67
|
+
mPropSource = source;
|
|
68
|
+
if (mSources == null) mSource = source;
|
|
69
|
+
}
|
|
70
|
+
|
|
71
|
+
// The `source` prop. With several `sources`, mSource is the one picked for
|
|
72
|
+
// the view's size instead.
|
|
73
|
+
@Nullable
|
|
74
|
+
private ReadableMap mPropSource = null;
|
|
75
|
+
// Several sources (2 or more) of the same image at different sizes, or
|
|
76
|
+
// null: the view loads the one whose size is closest to its own.
|
|
77
|
+
@Nullable
|
|
78
|
+
private ReadableArray mSources = null;
|
|
79
|
+
// Several sources: the index of the one picked (-1 before one is), and
|
|
80
|
+
// whether the load waits for the view's size (after layout), or has
|
|
81
|
+
// waited (then it loads the largest if the view still has no size).
|
|
82
|
+
private int mSourceIndex = -1;
|
|
83
|
+
private boolean mWaitsForSize = false;
|
|
84
|
+
private boolean mWaitedForSize = false;
|
|
85
|
+
// Several sources: the next load switches to another one for a new view
|
|
86
|
+
// size. It's the same picture at another size, so it doesn't fade in.
|
|
87
|
+
private boolean mSwitchesSource = false;
|
|
88
|
+
// What the last update was called with, to load once the view has a size.
|
|
89
|
+
@Nullable
|
|
90
|
+
private FastImageViewManager mManager;
|
|
91
|
+
@Nullable
|
|
92
|
+
private Map<String, List<FastImageViewWithUrl>> mViewsForUrlsMap;
|
|
93
|
+
|
|
94
|
+
public void setSources(@Nullable ReadableArray sources) {
|
|
95
|
+
mNeedsReload = true;
|
|
96
|
+
mSources = sources != null && sources.size() > 1 ? sources : null;
|
|
97
|
+
mSourceIndex = -1;
|
|
98
|
+
if (mSources == null) mSource = mPropSource;
|
|
99
|
+
}
|
|
100
|
+
|
|
101
|
+
// Of several sources, the index of the one whose size in pixels (width ×
|
|
102
|
+
// height × scale²) is closest to the view's (by pixel count), or of the
|
|
103
|
+
// largest while the view has no size (e.g. one sized from onLoad).
|
|
104
|
+
private int sourceIndexForSize() {
|
|
105
|
+
ReadableArray sources = mSources;
|
|
106
|
+
if (sources == null) return -1;
|
|
107
|
+
double viewPixels = (double) getWidth() * getHeight();
|
|
108
|
+
int best = -1;
|
|
109
|
+
double bestFit = Double.MAX_VALUE;
|
|
110
|
+
for (int i = 0; i < sources.size(); i++) {
|
|
111
|
+
ReadableMap source = sources.getMap(i);
|
|
112
|
+
if (source == null) continue;
|
|
113
|
+
double scale = number(source, "scale", 1);
|
|
114
|
+
double pixels = number(source, "width", 0) * number(source, "height", 0) * scale * scale;
|
|
115
|
+
double fit = viewPixels > 0 ? Math.abs(1 - pixels / viewPixels) : -pixels;
|
|
116
|
+
if (best < 0 || fit < bestFit) {
|
|
117
|
+
best = i;
|
|
118
|
+
bestFit = fit;
|
|
119
|
+
}
|
|
120
|
+
}
|
|
121
|
+
return best;
|
|
122
|
+
}
|
|
123
|
+
|
|
124
|
+
// Picks the source for the view's size (several sources).
|
|
125
|
+
private void pickSource() {
|
|
126
|
+
mSourceIndex = sourceIndexForSize();
|
|
127
|
+
mSource = mSourceIndex >= 0 && mSources != null ? mSources.getMap(mSourceIndex) : null;
|
|
128
|
+
}
|
|
129
|
+
|
|
130
|
+
private static double number(ReadableMap map, String key, double fallback) {
|
|
131
|
+
return map.hasKey(key) && !map.isNull(key) ? map.getDouble(key) : fallback;
|
|
132
|
+
}
|
|
133
|
+
|
|
134
|
+
// Several sources: another one fits the view's new size better. Loads it
|
|
135
|
+
// (after layout), keeping the image showing until then, without a fade.
|
|
136
|
+
private boolean switchSourceIfResized() {
|
|
137
|
+
if (mSources == null || mNeedsReload || mWaitsForSize || getWidth() <= 0 || getHeight() <= 0
|
|
138
|
+
|| mManager == null || sourceIndexForSize() == mSourceIndex) {
|
|
139
|
+
return false;
|
|
140
|
+
}
|
|
141
|
+
mSwitchesSource = true;
|
|
142
|
+
mNeedsReload = true;
|
|
143
|
+
post(new Runnable() {
|
|
144
|
+
@Override
|
|
145
|
+
public void run() {
|
|
146
|
+
if (mNeedsReload && mManager != null) onAfterUpdate(mManager, requestManager, mViewsForUrlsMap);
|
|
147
|
+
}
|
|
148
|
+
});
|
|
149
|
+
return true;
|
|
66
150
|
}
|
|
67
151
|
|
|
68
152
|
public void setDefaultSource(@Nullable Drawable source) {
|
|
@@ -354,14 +438,41 @@ class FastImageViewWithUrl extends AppCompatImageView {
|
|
|
354
438
|
: mLoopCount);
|
|
355
439
|
}
|
|
356
440
|
|
|
441
|
+
// resizeMode repeat: the image (a GIF's first frame) repeated from the
|
|
442
|
+
// top-left at its own size, scaled down to fit the view if it's larger,
|
|
443
|
+
// as React Native's Image does. The view fills with it (FIT_XY).
|
|
444
|
+
private boolean mRepeat = false;
|
|
445
|
+
|
|
357
446
|
// Glide crops or fits the bitmap for the scale type when it loads, so a
|
|
358
447
|
// new resizeMode needs a reload to take effect (#762).
|
|
359
|
-
public void setResizeMode(ScaleType scaleType) {
|
|
360
|
-
if (scaleType == getScaleType()) return;
|
|
448
|
+
public void setResizeMode(ScaleType scaleType, boolean repeat) {
|
|
449
|
+
if (scaleType == getScaleType() && repeat == mRepeat) return;
|
|
361
450
|
setScaleType(scaleType);
|
|
451
|
+
mRepeat = repeat;
|
|
362
452
|
mNeedsReload = true;
|
|
363
453
|
}
|
|
364
454
|
|
|
455
|
+
// Repeats what the view shows (the loaded image, and defaultSource).
|
|
456
|
+
@Override
|
|
457
|
+
public void setImageDrawable(@Nullable Drawable drawable) {
|
|
458
|
+
super.setImageDrawable(mRepeat ? tiled(drawable) : drawable);
|
|
459
|
+
}
|
|
460
|
+
|
|
461
|
+
@Nullable
|
|
462
|
+
private Drawable tiled(@Nullable Drawable drawable) {
|
|
463
|
+
if (!(drawable instanceof BitmapDrawable)) return drawable;
|
|
464
|
+
BitmapDrawable bitmapDrawable = (BitmapDrawable) drawable;
|
|
465
|
+
if (bitmapDrawable.getTileModeX() == Shader.TileMode.REPEAT) return drawable;
|
|
466
|
+
Bitmap bitmap = bitmapDrawable.getBitmap();
|
|
467
|
+
if (bitmap == null) return drawable;
|
|
468
|
+
// A copy of the drawable (not of the bitmap): Glide's may be shown by
|
|
469
|
+
// other views too.
|
|
470
|
+
BitmapDrawable tiled = new BitmapDrawable(getResources(), bitmap);
|
|
471
|
+
tiled.setTileModeXY(Shader.TileMode.REPEAT, Shader.TileMode.REPEAT);
|
|
472
|
+
if (mPixelated) tiled.setFilterBitmap(false);
|
|
473
|
+
return tiled;
|
|
474
|
+
}
|
|
475
|
+
|
|
365
476
|
// imageRendering="pixelated": no resampling by Glide, and drawn without
|
|
366
477
|
// filtering (sharp pixels). "smooth" is iOS only; here it's the same as
|
|
367
478
|
// "auto": Glide already decodes a large image at about the view's size, and
|
|
@@ -391,9 +502,20 @@ class FastImageViewWithUrl extends AppCompatImageView {
|
|
|
391
502
|
// pixelated, and the blur. A blurred animated image shows its first frame,
|
|
392
503
|
// as on iOS.
|
|
393
504
|
private RequestOptions renderingOptions(Object model) {
|
|
394
|
-
FastImageBlur blur = mBlurRadius > 0 ? new FastImageBlur(mBlurRadius, getScaleType()) : null;
|
|
505
|
+
FastImageBlur blur = mBlurRadius > 0 ? new FastImageBlur(mBlurRadius, mRepeat ? null : getScaleType()) : null;
|
|
395
506
|
RequestOptions options;
|
|
396
|
-
if (
|
|
507
|
+
if (mRepeat) {
|
|
508
|
+
// Decoded at its own size, or scaled down to fit the view if it's
|
|
509
|
+
// larger (not cropped or fitted for the scale type), and a GIF as
|
|
510
|
+
// its first frame (tiled by setImageDrawable).
|
|
511
|
+
options = new RequestOptions()
|
|
512
|
+
.downsample(new FastImageSourceSize.Capture(
|
|
513
|
+
mPixelated ? DownsampleStrategy.NONE : DownsampleStrategy.CENTER_INSIDE,
|
|
514
|
+
String.valueOf(model)))
|
|
515
|
+
.dontTransform()
|
|
516
|
+
.dontAnimate();
|
|
517
|
+
if (blur != null) options = options.optionalTransform(blur);
|
|
518
|
+
} else if (mPixelated) {
|
|
397
519
|
// Decoded as it is, then scaled by the view without filtering (see
|
|
398
520
|
// OwnGifTarget).
|
|
399
521
|
options = new RequestOptions()
|
|
@@ -481,7 +603,7 @@ class FastImageViewWithUrl extends AppCompatImageView {
|
|
|
481
603
|
mFadeFrom = null;
|
|
482
604
|
invalidate();
|
|
483
605
|
}
|
|
484
|
-
reloadIfResized();
|
|
606
|
+
if (!switchSourceIfResized()) reloadIfResized();
|
|
485
607
|
}
|
|
486
608
|
|
|
487
609
|
private void reloadIfResized() {
|
|
@@ -632,16 +754,42 @@ class FastImageViewWithUrl extends AppCompatImageView {
|
|
|
632
754
|
@Nonnull FastImageViewManager manager,
|
|
633
755
|
@Nullable RequestManager requestManager,
|
|
634
756
|
@Nonnull Map<String, List<FastImageViewWithUrl>> viewsForUrlsMap) {
|
|
757
|
+
mManager = manager;
|
|
758
|
+
mViewsForUrlsMap = viewsForUrlsMap;
|
|
635
759
|
if (mBlurChanged && !mNeedsReload) {
|
|
636
760
|
mBlurChanged = false;
|
|
637
761
|
reblur();
|
|
638
762
|
}
|
|
639
763
|
if (!mNeedsReload)
|
|
640
764
|
return;
|
|
765
|
+
// Several sources: one is picked for the view's size, so a view that
|
|
766
|
+
// hasn't been laid out yet loads after layout (props and layout are
|
|
767
|
+
// applied in the same batch). A view that still has no size then
|
|
768
|
+
// (e.g. one sized from onLoad) loads the largest source.
|
|
769
|
+
if (mSources != null) {
|
|
770
|
+
if (mWaitsForSize) return;
|
|
771
|
+
if ((getWidth() <= 0 || getHeight() <= 0) && !mWaitedForSize) {
|
|
772
|
+
mWaitsForSize = true;
|
|
773
|
+
post(new Runnable() {
|
|
774
|
+
@Override
|
|
775
|
+
public void run() {
|
|
776
|
+
if (!mWaitsForSize) return;
|
|
777
|
+
mWaitsForSize = false;
|
|
778
|
+
mWaitedForSize = true;
|
|
779
|
+
onAfterUpdate(manager, requestManager, viewsForUrlsMap);
|
|
780
|
+
}
|
|
781
|
+
});
|
|
782
|
+
return;
|
|
783
|
+
}
|
|
784
|
+
mWaitedForSize = false;
|
|
785
|
+
pickSource();
|
|
786
|
+
}
|
|
641
787
|
// Only reload for changes that affect the request (source,
|
|
642
788
|
// defaultSource, resizeMode), not for every prop update.
|
|
643
789
|
mNeedsReload = false;
|
|
644
790
|
mBlurChanged = false;
|
|
791
|
+
boolean switching = mSwitchesSource;
|
|
792
|
+
mSwitchesSource = false;
|
|
645
793
|
boolean restarting = mRestarting;
|
|
646
794
|
mRestarting = false;
|
|
647
795
|
mLoadCount++;
|
|
@@ -752,7 +900,7 @@ class FastImageViewWithUrl extends AppCompatImageView {
|
|
|
752
900
|
// replaces it at once, as Glide, Coil and Fresco fade an image in
|
|
753
901
|
// from nothing and never between images.
|
|
754
902
|
boolean replacing = shownRequest != null && model != null;
|
|
755
|
-
if (mTransitionDuration > 0 && (!replacing || mTransitionBetweenImages)) {
|
|
903
|
+
if (mTransitionDuration > 0 && !switching && (!replacing || mTransitionBetweenImages)) {
|
|
756
904
|
builder = builder.transition(DrawableTransitionOptions.with(
|
|
757
905
|
new FadeFactory(mTransitionDuration, mTransitionSkipOnCacheHit)));
|
|
758
906
|
}
|
package/dist/constants.d.ts
CHANGED
package/dist/index.cjs.js
CHANGED
|
@@ -32,7 +32,8 @@ const resizeMode = {
|
|
|
32
32
|
contain: "contain",
|
|
33
33
|
cover: "cover",
|
|
34
34
|
stretch: "stretch",
|
|
35
|
-
center: "center"
|
|
35
|
+
center: "center",
|
|
36
|
+
repeat: "repeat"
|
|
36
37
|
};
|
|
37
38
|
const priority = {
|
|
38
39
|
low: "low",
|
|
@@ -124,7 +125,7 @@ function FastImageBase({ source, defaultSource, tintColor, onLoadStart, onProgre
|
|
|
124
125
|
};
|
|
125
126
|
const resolvedTintColor = tintColor != null ? tintColor : tintColorFromStyle(style);
|
|
126
127
|
if (fallback) {
|
|
127
|
-
const cleanedSource = typeof source === "number" ? source : withoutCache(source);
|
|
128
|
+
const cleanedSource = typeof source === "number" ? source : Array.isArray(source) ? source.map(withoutCache) : withoutCache(source);
|
|
128
129
|
const resolvedSource = react_native.Image.resolveAssetSource(cleanedSource);
|
|
129
130
|
return /* @__PURE__ */ react.default.createElement(react_native.View, {
|
|
130
131
|
style: [styles.imageContainer, style],
|
|
@@ -163,7 +164,9 @@ function FastImageBase({ source, defaultSource, tintColor, onLoadStart, onProgre
|
|
|
163
164
|
resizeMode
|
|
164
165
|
}), children);
|
|
165
166
|
}
|
|
166
|
-
const
|
|
167
|
+
const sources = Array.isArray(source) ? source.length === 1 ? void 0 : source : void 0;
|
|
168
|
+
const single = Array.isArray(source) ? source[0] : source;
|
|
169
|
+
const resolvedSource = sources ? void 0 : react_native.Image.resolveAssetSource(single);
|
|
167
170
|
const resolvedDefaultSource = resolveDefaultSource(defaultSource);
|
|
168
171
|
return /* @__PURE__ */ react.default.createElement(react_native.View, {
|
|
169
172
|
style: [styles.imageContainer, style],
|
|
@@ -176,6 +179,7 @@ function FastImageBase({ source, defaultSource, tintColor, onLoadStart, onProgre
|
|
|
176
179
|
...transitionProps(transition),
|
|
177
180
|
style: react_native.StyleSheet.absoluteFill,
|
|
178
181
|
source: resolvedSource,
|
|
182
|
+
sources,
|
|
179
183
|
defaultSource: resolvedDefaultSource,
|
|
180
184
|
onFastImageLoadStart: onLoadStart,
|
|
181
185
|
onFastImageProgress: withProgress(onProgress),
|
|
@@ -199,8 +203,14 @@ const FastImage = FastImageComponent;
|
|
|
199
203
|
FastImage.resizeMode = resizeMode;
|
|
200
204
|
FastImage.cacheControl = cacheControl;
|
|
201
205
|
FastImage.priority = priority;
|
|
202
|
-
|
|
206
|
+
const ONE_SOURCE = "Takes one source, not an array of sizes: pass the size to use";
|
|
207
|
+
FastImage.preload = (sources) => Promise.resolve(react_native.NativeModules.FastImageView.preload(sources.map((s) => s && !Array.isArray(s) ? s : {}))).then((results) => sources.map((source, i) => {
|
|
203
208
|
var _results$i;
|
|
209
|
+
if (Array.isArray(source)) return {
|
|
210
|
+
ok: false,
|
|
211
|
+
error: ONE_SOURCE,
|
|
212
|
+
uri: void 0
|
|
213
|
+
};
|
|
204
214
|
const uri = source ? source.uri : void 0;
|
|
205
215
|
const result = (_results$i = results === null || results === void 0 ? void 0 : results[i]) !== null && _results$i !== void 0 ? _results$i : noResult;
|
|
206
216
|
if (!result.ok) return {
|
|
@@ -219,8 +229,14 @@ FastImage.preload = (sources) => Promise.resolve(react_native.NativeModules.Fast
|
|
|
219
229
|
FastImage.clearMemoryCache = () => react_native.NativeModules.FastImageView.clearMemoryCache();
|
|
220
230
|
FastImage.clearDiskCache = () => react_native.NativeModules.FastImageView.clearDiskCache();
|
|
221
231
|
FastImage.configureCache = (limits = {}) => Promise.resolve(react_native.NativeModules.FastImageView.configureCache(limits));
|
|
222
|
-
FastImage.writeToCache = (source, file) =>
|
|
223
|
-
|
|
232
|
+
FastImage.writeToCache = (source, file) => Array.isArray(source) ? Promise.resolve({
|
|
233
|
+
ok: false,
|
|
234
|
+
error: ONE_SOURCE
|
|
235
|
+
}) : Promise.resolve(react_native.NativeModules.FastImageView.writeToCache(source || {}, file));
|
|
236
|
+
FastImage.getCachePath = (source) => Array.isArray(source) ? Promise.resolve({
|
|
237
|
+
ok: false,
|
|
238
|
+
error: ONE_SOURCE
|
|
239
|
+
}) : Promise.resolve(react_native.NativeModules.FastImageView.getCachePath(source || {}));
|
|
224
240
|
const styles = react_native.StyleSheet.create({
|
|
225
241
|
fallbackImage: {
|
|
226
242
|
position: "absolute",
|
package/dist/index.cjs.js.flow
CHANGED
|
@@ -33,6 +33,7 @@ export type ResizeMode = $ReadOnly<{|
|
|
|
33
33
|
cover: 'cover',
|
|
34
34
|
stretch: 'stretch',
|
|
35
35
|
center: 'center',
|
|
36
|
+
repeat: 'repeat',
|
|
36
37
|
|}>
|
|
37
38
|
|
|
38
39
|
export type Priority = $ReadOnly<{|
|
|
@@ -103,6 +104,9 @@ export type FastImageSource = {
|
|
|
103
104
|
cache?: CacheControls,
|
|
104
105
|
cacheKey?: string,
|
|
105
106
|
memoryCache?: boolean,
|
|
107
|
+
width?: number,
|
|
108
|
+
height?: number,
|
|
109
|
+
scale?: number,
|
|
106
110
|
}
|
|
107
111
|
|
|
108
112
|
export type FastImageProps = $ReadOnly<{|
|
|
@@ -113,7 +117,7 @@ export type FastImageProps = $ReadOnly<{|
|
|
|
113
117
|
onLoadStart?: ?() => void,
|
|
114
118
|
onProgress?: ?(event: OnProgressEvent) => void,
|
|
115
119
|
|
|
116
|
-
source?: ?(FastImageSource | number),
|
|
120
|
+
source?: ?(FastImageSource | Array<FastImageSource> | number),
|
|
117
121
|
defaultSource?: ?number,
|
|
118
122
|
|
|
119
123
|
tintColor?: number | string,
|
package/dist/index.cjs.web.js
CHANGED
|
@@ -32,7 +32,8 @@ const resizeMode = {
|
|
|
32
32
|
contain: "contain",
|
|
33
33
|
cover: "cover",
|
|
34
34
|
stretch: "stretch",
|
|
35
|
-
center: "center"
|
|
35
|
+
center: "center",
|
|
36
|
+
repeat: "repeat"
|
|
36
37
|
};
|
|
37
38
|
const priority = {
|
|
38
39
|
low: "low",
|
|
@@ -54,14 +55,27 @@ function FastImageBase({ source, defaultSource, tintColor, blurRadius, onLoadSta
|
|
|
54
55
|
const result = (0, react.useRef)(void 0);
|
|
55
56
|
const sent = (0, react.useRef)(Promise.resolve());
|
|
56
57
|
const image = (0, react.useRef)(null);
|
|
57
|
-
const
|
|
58
|
-
const
|
|
58
|
+
const sizes = Array.isArray(source) && source.length > 1 ? source : void 0;
|
|
59
|
+
const single = Array.isArray(source) ? source[0] : source;
|
|
60
|
+
const uri = typeof single === "object" && single ? single.uri : void 0;
|
|
61
|
+
const webSource = typeof single === "number" ? single : uri ? { uri } : void 0;
|
|
59
62
|
return /* @__PURE__ */ react.default.createElement(react_native.View, {
|
|
60
63
|
style: [styles.container, style],
|
|
61
64
|
onLayout,
|
|
62
65
|
pointerEvents,
|
|
63
66
|
ref: forwardedRef
|
|
64
|
-
}, /* @__PURE__ */ react.default.createElement(react_native.
|
|
67
|
+
}, sizes ? /* @__PURE__ */ react.default.createElement(react_native.View, {
|
|
68
|
+
...props,
|
|
69
|
+
style: styles.image
|
|
70
|
+
}, /* @__PURE__ */ react.default.createElement(ImageSizes, {
|
|
71
|
+
sources: sizes,
|
|
72
|
+
resizeMode: mode,
|
|
73
|
+
blurRadius,
|
|
74
|
+
onLoadStart,
|
|
75
|
+
onLoad,
|
|
76
|
+
onError,
|
|
77
|
+
onLoadEnd
|
|
78
|
+
})) : /* @__PURE__ */ react.default.createElement(react_native.Image, {
|
|
65
79
|
...props,
|
|
66
80
|
style: styles.image,
|
|
67
81
|
source: webSource,
|
|
@@ -107,6 +121,68 @@ function FastImageBase({ source, defaultSource, tintColor, blurRadius, onLoadSta
|
|
|
107
121
|
}
|
|
108
122
|
}), children);
|
|
109
123
|
}
|
|
124
|
+
function ImageSizes({ sources, resizeMode: mode, blurRadius, onLoadStart, onLoad, onError, onLoadEnd }) {
|
|
125
|
+
var _largest$uri, _sources$find;
|
|
126
|
+
const sized = sources.filter((source) => (source === null || source === void 0 ? void 0 : source.uri) && source.width).map((source) => {
|
|
127
|
+
var _source$width, _source$scale;
|
|
128
|
+
return {
|
|
129
|
+
uri: source.uri,
|
|
130
|
+
width: Math.round(((_source$width = source.width) !== null && _source$width !== void 0 ? _source$width : 0) * ((_source$scale = source.scale) !== null && _source$scale !== void 0 ? _source$scale : 1))
|
|
131
|
+
};
|
|
132
|
+
});
|
|
133
|
+
const largest = sized.reduce((best, source) => !best || source.width > best.width ? source : best, void 0);
|
|
134
|
+
const src = (_largest$uri = largest === null || largest === void 0 ? void 0 : largest.uri) !== null && _largest$uri !== void 0 ? _largest$uri : (_sources$find = sources.find((source) => source === null || source === void 0 ? void 0 : source.uri)) === null || _sources$find === void 0 ? void 0 : _sources$find.uri;
|
|
135
|
+
const srcSet = sized.length > 0 ? sized.map((source) => `${source.uri} ${source.width}w`).join(", ") : void 0;
|
|
136
|
+
(0, react.useEffect)(() => {
|
|
137
|
+
onLoadStart === null || onLoadStart === void 0 || onLoadStart();
|
|
138
|
+
}, [src, srcSet]);
|
|
139
|
+
return /* @__PURE__ */ react.default.createElement("img", {
|
|
140
|
+
src,
|
|
141
|
+
srcSet,
|
|
142
|
+
sizes: srcSet ? "auto, 100vw" : void 0,
|
|
143
|
+
loading: "lazy",
|
|
144
|
+
alt: "",
|
|
145
|
+
draggable: false,
|
|
146
|
+
style: {
|
|
147
|
+
position: "absolute",
|
|
148
|
+
width: "100%",
|
|
149
|
+
height: "100%",
|
|
150
|
+
objectFit: OBJECT_FIT[mode !== null && mode !== void 0 ? mode : "cover"],
|
|
151
|
+
filter: blurRadius ? `blur(${blurRadius}px)` : void 0
|
|
152
|
+
},
|
|
153
|
+
onLoad: (event) => {
|
|
154
|
+
var _picked$width;
|
|
155
|
+
const image = event.currentTarget;
|
|
156
|
+
const picked = srcSet ? sized.find((source) => new URL(source.uri, image.currentSrc).href === image.currentSrc) : void 0;
|
|
157
|
+
const loadedWidth = (_picked$width = picked === null || picked === void 0 ? void 0 : picked.width) !== null && _picked$width !== void 0 ? _picked$width : image.naturalWidth;
|
|
158
|
+
const loadedHeight = picked ? Math.round(image.naturalHeight * picked.width / image.naturalWidth) : image.naturalHeight;
|
|
159
|
+
onLoad === null || onLoad === void 0 || onLoad({ nativeEvent: {
|
|
160
|
+
width: loadedWidth,
|
|
161
|
+
height: loadedHeight
|
|
162
|
+
} });
|
|
163
|
+
onLoadEnd === null || onLoadEnd === void 0 || onLoadEnd({
|
|
164
|
+
ok: true,
|
|
165
|
+
width: loadedWidth,
|
|
166
|
+
height: loadedHeight
|
|
167
|
+
});
|
|
168
|
+
},
|
|
169
|
+
onError: () => {
|
|
170
|
+
const error = "Failed to load the image";
|
|
171
|
+
onError === null || onError === void 0 || onError({ nativeEvent: { error } });
|
|
172
|
+
onLoadEnd === null || onLoadEnd === void 0 || onLoadEnd({
|
|
173
|
+
ok: false,
|
|
174
|
+
error
|
|
175
|
+
});
|
|
176
|
+
}
|
|
177
|
+
});
|
|
178
|
+
}
|
|
179
|
+
const OBJECT_FIT = {
|
|
180
|
+
cover: "cover",
|
|
181
|
+
contain: "contain",
|
|
182
|
+
stretch: "fill",
|
|
183
|
+
center: "scale-down",
|
|
184
|
+
repeat: "cover"
|
|
185
|
+
};
|
|
110
186
|
const FastImageMemo = (0, react.memo)(FastImageBase);
|
|
111
187
|
const FastImageComponent = (0, react.forwardRef)((props, ref) => /* @__PURE__ */ react.default.createElement(FastImageMemo, {
|
|
112
188
|
forwardedRef: ref,
|
|
@@ -33,6 +33,7 @@ export type ResizeMode = $ReadOnly<{|
|
|
|
33
33
|
cover: 'cover',
|
|
34
34
|
stretch: 'stretch',
|
|
35
35
|
center: 'center',
|
|
36
|
+
repeat: 'repeat',
|
|
36
37
|
|}>
|
|
37
38
|
|
|
38
39
|
export type Priority = $ReadOnly<{|
|
|
@@ -103,6 +104,9 @@ export type FastImageSource = {
|
|
|
103
104
|
cache?: CacheControls,
|
|
104
105
|
cacheKey?: string,
|
|
105
106
|
memoryCache?: boolean,
|
|
107
|
+
width?: number,
|
|
108
|
+
height?: number,
|
|
109
|
+
scale?: number,
|
|
106
110
|
}
|
|
107
111
|
|
|
108
112
|
export type FastImageProps = $ReadOnly<{|
|
|
@@ -113,7 +117,7 @@ export type FastImageProps = $ReadOnly<{|
|
|
|
113
117
|
onLoadStart?: ?() => void,
|
|
114
118
|
onProgress?: ?(event: OnProgressEvent) => void,
|
|
115
119
|
|
|
116
|
-
source?: ?(FastImageSource | number),
|
|
120
|
+
source?: ?(FastImageSource | Array<FastImageSource> | number),
|
|
117
121
|
defaultSource?: ?number,
|
|
118
122
|
|
|
119
123
|
tintColor?: number | string,
|
package/dist/index.d.ts
CHANGED
|
@@ -2,7 +2,7 @@ import React from 'react';
|
|
|
2
2
|
import { LayoutChangeEvent, StyleProp, ViewStyle, ImageRequireSource, AccessibilityProps, ViewProps } from 'react-native';
|
|
3
3
|
import { cacheControl, priority, resizeMode } from './constants';
|
|
4
4
|
type ColorValue = NonNullable<ViewStyle['backgroundColor']>;
|
|
5
|
-
export type ResizeMode = 'contain' | 'cover' | 'stretch' | 'center';
|
|
5
|
+
export type ResizeMode = 'contain' | 'cover' | 'stretch' | 'center' | 'repeat';
|
|
6
6
|
export type Priority = 'low' | 'normal' | 'high';
|
|
7
7
|
export type Cache = 'immutable' | 'web' | 'cacheOnly';
|
|
8
8
|
export type Source = {
|
|
@@ -26,6 +26,14 @@ export type Source = {
|
|
|
26
26
|
* list scrolled back decodes them again.
|
|
27
27
|
*/
|
|
28
28
|
memoryCache?: boolean;
|
|
29
|
+
/**
|
|
30
|
+
* With several sources (`source` as an array), the image's size at this
|
|
31
|
+
* uri in pixels (width and height, times `scale` if given): the view
|
|
32
|
+
* loads the source closest to its own size.
|
|
33
|
+
*/
|
|
34
|
+
width?: number;
|
|
35
|
+
height?: number;
|
|
36
|
+
scale?: number;
|
|
29
37
|
};
|
|
30
38
|
export type Transition = {
|
|
31
39
|
/**
|
|
@@ -116,7 +124,12 @@ export interface ImageStyle extends ViewStyle {
|
|
|
116
124
|
opacity?: number;
|
|
117
125
|
}
|
|
118
126
|
export interface FastImageProps extends AccessibilityProps, ViewProps {
|
|
119
|
-
|
|
127
|
+
/**
|
|
128
|
+
* The image: a source, a `require()`d image, or several sources of the
|
|
129
|
+
* same image at different sizes (each with its `width` and `height` in
|
|
130
|
+
* pixels), of which the view loads the one closest to its size.
|
|
131
|
+
*/
|
|
132
|
+
source?: Source | Source[] | ImageRequireSource;
|
|
120
133
|
defaultSource?: ImageRequireSource;
|
|
121
134
|
resizeMode?: ResizeMode;
|
|
122
135
|
fallback?: boolean;
|
package/dist/index.js
CHANGED
|
@@ -5,7 +5,8 @@ const resizeMode = {
|
|
|
5
5
|
contain: "contain",
|
|
6
6
|
cover: "cover",
|
|
7
7
|
stretch: "stretch",
|
|
8
|
-
center: "center"
|
|
8
|
+
center: "center",
|
|
9
|
+
repeat: "repeat"
|
|
9
10
|
};
|
|
10
11
|
const priority = {
|
|
11
12
|
low: "low",
|
|
@@ -97,7 +98,7 @@ function FastImageBase({ source, defaultSource, tintColor, onLoadStart, onProgre
|
|
|
97
98
|
};
|
|
98
99
|
const resolvedTintColor = tintColor != null ? tintColor : tintColorFromStyle(style);
|
|
99
100
|
if (fallback) {
|
|
100
|
-
const cleanedSource = typeof source === "number" ? source : withoutCache(source);
|
|
101
|
+
const cleanedSource = typeof source === "number" ? source : Array.isArray(source) ? source.map(withoutCache) : withoutCache(source);
|
|
101
102
|
const resolvedSource = Image.resolveAssetSource(cleanedSource);
|
|
102
103
|
return /* @__PURE__ */ React.createElement(View, {
|
|
103
104
|
style: [styles.imageContainer, style],
|
|
@@ -136,7 +137,9 @@ function FastImageBase({ source, defaultSource, tintColor, onLoadStart, onProgre
|
|
|
136
137
|
resizeMode
|
|
137
138
|
}), children);
|
|
138
139
|
}
|
|
139
|
-
const
|
|
140
|
+
const sources = Array.isArray(source) ? source.length === 1 ? void 0 : source : void 0;
|
|
141
|
+
const single = Array.isArray(source) ? source[0] : source;
|
|
142
|
+
const resolvedSource = sources ? void 0 : Image.resolveAssetSource(single);
|
|
140
143
|
const resolvedDefaultSource = resolveDefaultSource(defaultSource);
|
|
141
144
|
return /* @__PURE__ */ React.createElement(View, {
|
|
142
145
|
style: [styles.imageContainer, style],
|
|
@@ -149,6 +152,7 @@ function FastImageBase({ source, defaultSource, tintColor, onLoadStart, onProgre
|
|
|
149
152
|
...transitionProps(transition),
|
|
150
153
|
style: StyleSheet.absoluteFill,
|
|
151
154
|
source: resolvedSource,
|
|
155
|
+
sources,
|
|
152
156
|
defaultSource: resolvedDefaultSource,
|
|
153
157
|
onFastImageLoadStart: onLoadStart,
|
|
154
158
|
onFastImageProgress: withProgress(onProgress),
|
|
@@ -172,8 +176,14 @@ const FastImage = FastImageComponent;
|
|
|
172
176
|
FastImage.resizeMode = resizeMode;
|
|
173
177
|
FastImage.cacheControl = cacheControl;
|
|
174
178
|
FastImage.priority = priority;
|
|
175
|
-
|
|
179
|
+
const ONE_SOURCE = "Takes one source, not an array of sizes: pass the size to use";
|
|
180
|
+
FastImage.preload = (sources) => Promise.resolve(NativeModules.FastImageView.preload(sources.map((s) => s && !Array.isArray(s) ? s : {}))).then((results) => sources.map((source, i) => {
|
|
176
181
|
var _results$i;
|
|
182
|
+
if (Array.isArray(source)) return {
|
|
183
|
+
ok: false,
|
|
184
|
+
error: ONE_SOURCE,
|
|
185
|
+
uri: void 0
|
|
186
|
+
};
|
|
177
187
|
const uri = source ? source.uri : void 0;
|
|
178
188
|
const result = (_results$i = results === null || results === void 0 ? void 0 : results[i]) !== null && _results$i !== void 0 ? _results$i : noResult;
|
|
179
189
|
if (!result.ok) return {
|
|
@@ -192,8 +202,14 @@ FastImage.preload = (sources) => Promise.resolve(NativeModules.FastImageView.pre
|
|
|
192
202
|
FastImage.clearMemoryCache = () => NativeModules.FastImageView.clearMemoryCache();
|
|
193
203
|
FastImage.clearDiskCache = () => NativeModules.FastImageView.clearDiskCache();
|
|
194
204
|
FastImage.configureCache = (limits = {}) => Promise.resolve(NativeModules.FastImageView.configureCache(limits));
|
|
195
|
-
FastImage.writeToCache = (source, file) =>
|
|
196
|
-
|
|
205
|
+
FastImage.writeToCache = (source, file) => Array.isArray(source) ? Promise.resolve({
|
|
206
|
+
ok: false,
|
|
207
|
+
error: ONE_SOURCE
|
|
208
|
+
}) : Promise.resolve(NativeModules.FastImageView.writeToCache(source || {}, file));
|
|
209
|
+
FastImage.getCachePath = (source) => Array.isArray(source) ? Promise.resolve({
|
|
210
|
+
ok: false,
|
|
211
|
+
error: ONE_SOURCE
|
|
212
|
+
}) : Promise.resolve(NativeModules.FastImageView.getCachePath(source || {}));
|
|
197
213
|
const styles = StyleSheet.create({
|
|
198
214
|
fallbackImage: {
|
|
199
215
|
position: "absolute",
|
package/dist/index.js.flow
CHANGED
|
@@ -33,6 +33,7 @@ export type ResizeMode = $ReadOnly<{|
|
|
|
33
33
|
cover: 'cover',
|
|
34
34
|
stretch: 'stretch',
|
|
35
35
|
center: 'center',
|
|
36
|
+
repeat: 'repeat',
|
|
36
37
|
|}>
|
|
37
38
|
|
|
38
39
|
export type Priority = $ReadOnly<{|
|
|
@@ -103,6 +104,9 @@ export type FastImageSource = {
|
|
|
103
104
|
cache?: CacheControls,
|
|
104
105
|
cacheKey?: string,
|
|
105
106
|
memoryCache?: boolean,
|
|
107
|
+
width?: number,
|
|
108
|
+
height?: number,
|
|
109
|
+
scale?: number,
|
|
106
110
|
}
|
|
107
111
|
|
|
108
112
|
export type FastImageProps = $ReadOnly<{|
|
|
@@ -113,7 +117,7 @@ export type FastImageProps = $ReadOnly<{|
|
|
|
113
117
|
onLoadStart?: ?() => void,
|
|
114
118
|
onProgress?: ?(event: OnProgressEvent) => void,
|
|
115
119
|
|
|
116
|
-
source?: ?(FastImageSource | number),
|
|
120
|
+
source?: ?(FastImageSource | Array<FastImageSource> | number),
|
|
117
121
|
defaultSource?: ?number,
|
|
118
122
|
|
|
119
123
|
tintColor?: number | string,
|
package/dist/index.web.js
CHANGED
|
@@ -1,11 +1,12 @@
|
|
|
1
|
-
import React, { forwardRef, memo, useRef } from "react";
|
|
1
|
+
import React, { forwardRef, memo, useEffect, useRef } from "react";
|
|
2
2
|
import { Image, StyleSheet, View } from "react-native";
|
|
3
3
|
//#region src/constants.ts
|
|
4
4
|
const resizeMode = {
|
|
5
5
|
contain: "contain",
|
|
6
6
|
cover: "cover",
|
|
7
7
|
stretch: "stretch",
|
|
8
|
-
center: "center"
|
|
8
|
+
center: "center",
|
|
9
|
+
repeat: "repeat"
|
|
9
10
|
};
|
|
10
11
|
const priority = {
|
|
11
12
|
low: "low",
|
|
@@ -27,14 +28,27 @@ function FastImageBase({ source, defaultSource, tintColor, blurRadius, onLoadSta
|
|
|
27
28
|
const result = useRef(void 0);
|
|
28
29
|
const sent = useRef(Promise.resolve());
|
|
29
30
|
const image = useRef(null);
|
|
30
|
-
const
|
|
31
|
-
const
|
|
31
|
+
const sizes = Array.isArray(source) && source.length > 1 ? source : void 0;
|
|
32
|
+
const single = Array.isArray(source) ? source[0] : source;
|
|
33
|
+
const uri = typeof single === "object" && single ? single.uri : void 0;
|
|
34
|
+
const webSource = typeof single === "number" ? single : uri ? { uri } : void 0;
|
|
32
35
|
return /* @__PURE__ */ React.createElement(View, {
|
|
33
36
|
style: [styles.container, style],
|
|
34
37
|
onLayout,
|
|
35
38
|
pointerEvents,
|
|
36
39
|
ref: forwardedRef
|
|
37
|
-
}, /* @__PURE__ */ React.createElement(
|
|
40
|
+
}, sizes ? /* @__PURE__ */ React.createElement(View, {
|
|
41
|
+
...props,
|
|
42
|
+
style: styles.image
|
|
43
|
+
}, /* @__PURE__ */ React.createElement(ImageSizes, {
|
|
44
|
+
sources: sizes,
|
|
45
|
+
resizeMode: mode,
|
|
46
|
+
blurRadius,
|
|
47
|
+
onLoadStart,
|
|
48
|
+
onLoad,
|
|
49
|
+
onError,
|
|
50
|
+
onLoadEnd
|
|
51
|
+
})) : /* @__PURE__ */ React.createElement(Image, {
|
|
38
52
|
...props,
|
|
39
53
|
style: styles.image,
|
|
40
54
|
source: webSource,
|
|
@@ -80,6 +94,68 @@ function FastImageBase({ source, defaultSource, tintColor, blurRadius, onLoadSta
|
|
|
80
94
|
}
|
|
81
95
|
}), children);
|
|
82
96
|
}
|
|
97
|
+
function ImageSizes({ sources, resizeMode: mode, blurRadius, onLoadStart, onLoad, onError, onLoadEnd }) {
|
|
98
|
+
var _largest$uri, _sources$find;
|
|
99
|
+
const sized = sources.filter((source) => (source === null || source === void 0 ? void 0 : source.uri) && source.width).map((source) => {
|
|
100
|
+
var _source$width, _source$scale;
|
|
101
|
+
return {
|
|
102
|
+
uri: source.uri,
|
|
103
|
+
width: Math.round(((_source$width = source.width) !== null && _source$width !== void 0 ? _source$width : 0) * ((_source$scale = source.scale) !== null && _source$scale !== void 0 ? _source$scale : 1))
|
|
104
|
+
};
|
|
105
|
+
});
|
|
106
|
+
const largest = sized.reduce((best, source) => !best || source.width > best.width ? source : best, void 0);
|
|
107
|
+
const src = (_largest$uri = largest === null || largest === void 0 ? void 0 : largest.uri) !== null && _largest$uri !== void 0 ? _largest$uri : (_sources$find = sources.find((source) => source === null || source === void 0 ? void 0 : source.uri)) === null || _sources$find === void 0 ? void 0 : _sources$find.uri;
|
|
108
|
+
const srcSet = sized.length > 0 ? sized.map((source) => `${source.uri} ${source.width}w`).join(", ") : void 0;
|
|
109
|
+
useEffect(() => {
|
|
110
|
+
onLoadStart === null || onLoadStart === void 0 || onLoadStart();
|
|
111
|
+
}, [src, srcSet]);
|
|
112
|
+
return /* @__PURE__ */ React.createElement("img", {
|
|
113
|
+
src,
|
|
114
|
+
srcSet,
|
|
115
|
+
sizes: srcSet ? "auto, 100vw" : void 0,
|
|
116
|
+
loading: "lazy",
|
|
117
|
+
alt: "",
|
|
118
|
+
draggable: false,
|
|
119
|
+
style: {
|
|
120
|
+
position: "absolute",
|
|
121
|
+
width: "100%",
|
|
122
|
+
height: "100%",
|
|
123
|
+
objectFit: OBJECT_FIT[mode !== null && mode !== void 0 ? mode : "cover"],
|
|
124
|
+
filter: blurRadius ? `blur(${blurRadius}px)` : void 0
|
|
125
|
+
},
|
|
126
|
+
onLoad: (event) => {
|
|
127
|
+
var _picked$width;
|
|
128
|
+
const image = event.currentTarget;
|
|
129
|
+
const picked = srcSet ? sized.find((source) => new URL(source.uri, image.currentSrc).href === image.currentSrc) : void 0;
|
|
130
|
+
const loadedWidth = (_picked$width = picked === null || picked === void 0 ? void 0 : picked.width) !== null && _picked$width !== void 0 ? _picked$width : image.naturalWidth;
|
|
131
|
+
const loadedHeight = picked ? Math.round(image.naturalHeight * picked.width / image.naturalWidth) : image.naturalHeight;
|
|
132
|
+
onLoad === null || onLoad === void 0 || onLoad({ nativeEvent: {
|
|
133
|
+
width: loadedWidth,
|
|
134
|
+
height: loadedHeight
|
|
135
|
+
} });
|
|
136
|
+
onLoadEnd === null || onLoadEnd === void 0 || onLoadEnd({
|
|
137
|
+
ok: true,
|
|
138
|
+
width: loadedWidth,
|
|
139
|
+
height: loadedHeight
|
|
140
|
+
});
|
|
141
|
+
},
|
|
142
|
+
onError: () => {
|
|
143
|
+
const error = "Failed to load the image";
|
|
144
|
+
onError === null || onError === void 0 || onError({ nativeEvent: { error } });
|
|
145
|
+
onLoadEnd === null || onLoadEnd === void 0 || onLoadEnd({
|
|
146
|
+
ok: false,
|
|
147
|
+
error
|
|
148
|
+
});
|
|
149
|
+
}
|
|
150
|
+
});
|
|
151
|
+
}
|
|
152
|
+
const OBJECT_FIT = {
|
|
153
|
+
cover: "cover",
|
|
154
|
+
contain: "contain",
|
|
155
|
+
stretch: "fill",
|
|
156
|
+
center: "scale-down",
|
|
157
|
+
repeat: "cover"
|
|
158
|
+
};
|
|
83
159
|
const FastImageMemo = memo(FastImageBase);
|
|
84
160
|
const FastImageComponent = forwardRef((props, ref) => /* @__PURE__ */ React.createElement(FastImageMemo, {
|
|
85
161
|
forwardedRef: ref,
|
package/dist/index.web.js.flow
CHANGED
|
@@ -33,6 +33,7 @@ export type ResizeMode = $ReadOnly<{|
|
|
|
33
33
|
cover: 'cover',
|
|
34
34
|
stretch: 'stretch',
|
|
35
35
|
center: 'center',
|
|
36
|
+
repeat: 'repeat',
|
|
36
37
|
|}>
|
|
37
38
|
|
|
38
39
|
export type Priority = $ReadOnly<{|
|
|
@@ -103,6 +104,9 @@ export type FastImageSource = {
|
|
|
103
104
|
cache?: CacheControls,
|
|
104
105
|
cacheKey?: string,
|
|
105
106
|
memoryCache?: boolean,
|
|
107
|
+
width?: number,
|
|
108
|
+
height?: number,
|
|
109
|
+
scale?: number,
|
|
106
110
|
}
|
|
107
111
|
|
|
108
112
|
export type FastImageProps = $ReadOnly<{|
|
|
@@ -113,7 +117,7 @@ export type FastImageProps = $ReadOnly<{|
|
|
|
113
117
|
onLoadStart?: ?() => void,
|
|
114
118
|
onProgress?: ?(event: OnProgressEvent) => void,
|
|
115
119
|
|
|
116
|
-
source?: ?(FastImageSource | number),
|
|
120
|
+
source?: ?(FastImageSource | Array<FastImageSource> | number),
|
|
117
121
|
defaultSource?: ?number,
|
|
118
122
|
|
|
119
123
|
tintColor?: number | string,
|
|
@@ -32,6 +32,9 @@ 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
|
+
// With several sources, the image's size at this url in pixels (width ×
|
|
36
|
+
// height × scale²), to pick the one closest to the view's size; 0 if unknown.
|
|
37
|
+
@property (nonatomic) CGFloat pixelCount;
|
|
35
38
|
// Whether the decoded image is kept in the memory cache (`memoryCache`,
|
|
36
39
|
// default YES). Without it, it's only kept on disk: views don't leave it in
|
|
37
40
|
// memory, and preload doesn't decode it.
|
|
@@ -17,6 +17,9 @@
|
|
|
17
17
|
@property (nonatomic, copy) RCTDirectEventBlock onFastImageLoadEnd;
|
|
18
18
|
@property (nonatomic, assign) RCTResizeMode resizeMode;
|
|
19
19
|
@property (nonatomic, strong) FFFastImageSource *source;
|
|
20
|
+
// Several sources (2 or more) of the same image at different sizes: the view
|
|
21
|
+
// loads the one whose size is closest to its own.
|
|
22
|
+
@property (nonatomic, copy) NSArray<FFFastImageSource *> *sources;
|
|
20
23
|
@property (nonatomic, strong) UIImage *defaultSource;
|
|
21
24
|
@property (nonatomic, strong) UIColor *imageColor;
|
|
22
25
|
// When it changes, the next image doesn't replace the current one: the view
|
|
@@ -24,6 +24,8 @@
|
|
|
24
24
|
@property(nonatomic, assign) NSUInteger imageCount;
|
|
25
25
|
// The view's size when the image showing was blurred.
|
|
26
26
|
@property(nonatomic, assign) CGSize blurredForSize;
|
|
27
|
+
// The view's size when the image showing was tiled (resizeMode repeat).
|
|
28
|
+
@property(nonatomic, assign) CGSize tiledForSize;
|
|
27
29
|
// The blurred image showing (untinted), so a tint change can re-show it.
|
|
28
30
|
@property(nonatomic, strong) UIImage* blurredImage;
|
|
29
31
|
// The image being blurred fades in once it's blurred (transition).
|
|
@@ -39,8 +41,17 @@
|
|
|
39
41
|
// Whether the view shows an image that loaded (not defaultSource or nothing).
|
|
40
42
|
// A new source then keeps it until the new image has loaded (see reloadImage).
|
|
41
43
|
@property(nonatomic, assign) BOOL showsLoadedImage;
|
|
42
|
-
// downsample: a load waits for the view's size (see
|
|
44
|
+
// downsample, or several sources: a load waits for the view's size (see
|
|
45
|
+
// didSetProps).
|
|
43
46
|
@property(nonatomic, assign) BOOL waitsForSize;
|
|
47
|
+
// The `source` prop. With several `sources`, _source is the one picked for
|
|
48
|
+
// the view's size instead.
|
|
49
|
+
@property(nonatomic, strong) FFFastImageSource* propSource;
|
|
50
|
+
// Several sources: the next load switches to another one for a new view
|
|
51
|
+
// size, and the one after it is loading. It's the same picture at another
|
|
52
|
+
// size, so it doesn't fade in.
|
|
53
|
+
@property(nonatomic, assign) BOOL switchesSource;
|
|
54
|
+
@property(nonatomic, assign) BOOL loadsSwitchedSource;
|
|
44
55
|
// The size (in pixels) the image showing or loading was decoded for, and
|
|
45
56
|
// whether it covers it; zero for a full-size image.
|
|
46
57
|
@property(nonatomic, assign) CGSize decodedBox;
|
|
@@ -130,7 +141,8 @@ static UIImage* FFFBlurredImage(UIImage* image, CGFloat scale, CGFloat radius, B
|
|
|
130
141
|
// the default), and from the disk cache too with 'all'. Downloads, local
|
|
131
142
|
// files and bundled images fade.
|
|
132
143
|
- (BOOL) fadesImageFromCache: (SDImageCacheType)cacheType {
|
|
133
|
-
if (self.transitionDuration <= 0 ||
|
|
144
|
+
if (self.transitionDuration <= 0 || self.loadsSwitchedSource ||
|
|
145
|
+
([self displaysLoadedImage] && !self.transitionBetweenImages)) {
|
|
134
146
|
return NO;
|
|
135
147
|
}
|
|
136
148
|
NSString* skip = self.transitionSkipOnCacheHit;
|
|
@@ -211,11 +223,14 @@ static UIImage* FFFBlurredImage(UIImage* image, CGFloat scale, CGFloat radius, B
|
|
|
211
223
|
|
|
212
224
|
- (void) setResizeMode: (RCTResizeMode)resizeMode {
|
|
213
225
|
if (_resizeMode != resizeMode) {
|
|
226
|
+
BOOL repeated = _resizeMode == RCTResizeModeRepeat;
|
|
214
227
|
_resizeMode = resizeMode;
|
|
215
228
|
[self updateContentMode];
|
|
216
|
-
// The blur is made for the size the image is shown at
|
|
217
|
-
|
|
218
|
-
|
|
229
|
+
// The blur is made for the size the image is shown at, and repeat
|
|
230
|
+
// shows the image tiled: show it again.
|
|
231
|
+
UIImage* image = self.originalImage ?: super.image;
|
|
232
|
+
if ([self blurs: self.originalImage] || ((repeated || resizeMode == RCTResizeModeRepeat) && image)) {
|
|
233
|
+
[self setImage: image];
|
|
219
234
|
}
|
|
220
235
|
}
|
|
221
236
|
}
|
|
@@ -227,7 +242,10 @@ static UIImage* FFFBlurredImage(UIImage* image, CGFloat scale, CGFloat radius, B
|
|
|
227
242
|
// view's size.
|
|
228
243
|
- (void) updateContentMode {
|
|
229
244
|
UIViewContentMode contentMode = (UIViewContentMode) _resizeMode;
|
|
230
|
-
if (_resizeMode ==
|
|
245
|
+
if (_resizeMode == RCTResizeModeRepeat) {
|
|
246
|
+
// The tiled image (see tiledImage:) fills the view.
|
|
247
|
+
contentMode = UIViewContentModeScaleToFill;
|
|
248
|
+
} else if (_resizeMode == RCTResizeModeCenter) {
|
|
231
249
|
CGSize imageSize = super.image.size;
|
|
232
250
|
CGSize viewSize = self.bounds.size;
|
|
233
251
|
if (imageSize.width > viewSize.width || imageSize.height > viewSize.height) {
|
|
@@ -243,11 +261,12 @@ static UIImage* FFFBlurredImage(UIImage* image, CGFloat scale, CGFloat radius, B
|
|
|
243
261
|
[super layoutSubviews];
|
|
244
262
|
[self updateContentMode];
|
|
245
263
|
[self blurAgainIfResized];
|
|
264
|
+
[self tileAgainIfResized];
|
|
246
265
|
if (self.waitsForSize) {
|
|
247
266
|
if ([self hasSize]) {
|
|
248
267
|
[self reloadImage];
|
|
249
268
|
}
|
|
250
|
-
} else {
|
|
269
|
+
} else if (![self switchSourceIfResized]) {
|
|
251
270
|
[self reloadIfResized];
|
|
252
271
|
}
|
|
253
272
|
}
|
|
@@ -320,7 +339,8 @@ static UIImage* FFFBlurredImage(UIImage* image, CGFloat scale, CGFloat radius, B
|
|
|
320
339
|
[self blurImage: image];
|
|
321
340
|
return;
|
|
322
341
|
}
|
|
323
|
-
|
|
342
|
+
// Kept to tint, or tile (resizeMode repeat), it again.
|
|
343
|
+
self.originalImage = image && (self.imageColor || _resizeMode == RCTResizeModeRepeat) ? image : nil;
|
|
324
344
|
self.blurredImage = nil;
|
|
325
345
|
self.blursOverNothing = NO;
|
|
326
346
|
// The blur was removed before it was ready: the fade it was waiting for
|
|
@@ -342,7 +362,9 @@ static UIImage* FFFBlurredImage(UIImage* image, CGFloat scale, CGFloat radius, B
|
|
|
342
362
|
// template is), or, before SDWebImage 5.20, the first frame shows.
|
|
343
363
|
- (void) showImage: (UIImage*)image {
|
|
344
364
|
UIColor* tint = image ? self.imageColor : nil;
|
|
345
|
-
|
|
365
|
+
// resizeMode repeat tiles an animated image's first frame (tiledImage:).
|
|
366
|
+
BOOL repeats = image && _resizeMode == RCTResizeModeRepeat;
|
|
367
|
+
BOOL animated = !repeats && [image conformsToProtocol: @protocol(SDAnimatedImage)] && [(id<SDAnimatedImage>)image animatedImageFrameCount] > 1;
|
|
346
368
|
self.tintColor = tint;
|
|
347
369
|
if ([self tintsFrames]) {
|
|
348
370
|
[self setValue: tint && animated ? [SDImageTintTransformer transformerWithColor: tint] : nil forKey: @"animationTransformer"];
|
|
@@ -356,17 +378,54 @@ static UIImage* FFFBlurredImage(UIImage* image, CGFloat scale, CGFloat radius, B
|
|
|
356
378
|
if (changed) {
|
|
357
379
|
[self.player seekToFrameAtIndex: 0 loopCount: 0];
|
|
358
380
|
}
|
|
359
|
-
} else if (tint) {
|
|
360
|
-
if (animated) {
|
|
361
|
-
image = [UIImage imageWithCGImage: image.CGImage scale: image.scale orientation: image.imageOrientation];
|
|
362
|
-
}
|
|
363
|
-
super.image = [image imageWithRenderingMode: UIImageRenderingModeAlwaysTemplate];
|
|
364
381
|
} else {
|
|
382
|
+
if (tint) {
|
|
383
|
+
if (animated) {
|
|
384
|
+
image = [UIImage imageWithCGImage: image.CGImage scale: image.scale orientation: image.imageOrientation];
|
|
385
|
+
}
|
|
386
|
+
image = [image imageWithRenderingMode: UIImageRenderingModeAlwaysTemplate];
|
|
387
|
+
}
|
|
388
|
+
if (repeats) {
|
|
389
|
+
image = [self tiledImage: image];
|
|
390
|
+
self.tiledForSize = self.bounds.size;
|
|
391
|
+
}
|
|
365
392
|
super.image = image;
|
|
366
393
|
}
|
|
367
394
|
[self updateContentMode];
|
|
368
395
|
}
|
|
369
396
|
|
|
397
|
+
// resizeMode repeat: the image repeated from the top-left at its own size in
|
|
398
|
+
// pixels (a scaled image, e.g. a bundled @3x one, at its size in points), an
|
|
399
|
+
// animated image's first frame, scaled down to fit the view if it's larger,
|
|
400
|
+
// as React Native's Image does (and Android here).
|
|
401
|
+
- (UIImage*) tiledImage: (UIImage*)image {
|
|
402
|
+
CGImageRef cgImage = image.CGImage;
|
|
403
|
+
if (!cgImage) {
|
|
404
|
+
return image;
|
|
405
|
+
}
|
|
406
|
+
CGFloat scale = image.scale > 1 ? image.scale : (self.window.screen.scale ?: [UIScreen mainScreen].scale);
|
|
407
|
+
CGSize size = CGSizeMake(image.size.width * image.scale / scale, image.size.height * image.scale / scale);
|
|
408
|
+
CGSize view = self.bounds.size;
|
|
409
|
+
if (view.width > 0 && view.height > 0 && (size.width > view.width || size.height > view.height)) {
|
|
410
|
+
scale *= MAX(size.width / view.width, size.height / view.height);
|
|
411
|
+
}
|
|
412
|
+
UIImage* still = [[UIImage imageWithCGImage: cgImage scale: scale orientation: image.imageOrientation]
|
|
413
|
+
imageWithRenderingMode: image.renderingMode];
|
|
414
|
+
return [still resizableImageWithCapInsets: UIEdgeInsetsZero resizingMode: UIImageResizingModeTile];
|
|
415
|
+
}
|
|
416
|
+
|
|
417
|
+
// The view's size changed since the image was tiled, which scales an image
|
|
418
|
+
// larger than the view down to fit it: tile it again.
|
|
419
|
+
- (void) tileAgainIfResized {
|
|
420
|
+
if (_resizeMode != RCTResizeModeRepeat || CGSizeEqualToSize(self.bounds.size, self.tiledForSize)) {
|
|
421
|
+
return;
|
|
422
|
+
}
|
|
423
|
+
UIImage* image = [self blurs: self.originalImage] ? self.blurredImage : self.originalImage;
|
|
424
|
+
if (image) {
|
|
425
|
+
[self showImage: image];
|
|
426
|
+
}
|
|
427
|
+
}
|
|
428
|
+
|
|
370
429
|
- (void) setBlurRadius: (CGFloat)blurRadius {
|
|
371
430
|
if (_blurRadius == blurRadius) {
|
|
372
431
|
return;
|
|
@@ -594,12 +653,59 @@ NSString *FFFErrorMessage(NSError *error)
|
|
|
594
653
|
}
|
|
595
654
|
|
|
596
655
|
- (void) setSource: (FFFastImageSource*)source {
|
|
597
|
-
if (
|
|
598
|
-
|
|
656
|
+
if (self.propSource != source) {
|
|
657
|
+
self.propSource = source;
|
|
658
|
+
if (![self picksSource]) {
|
|
659
|
+
_source = source;
|
|
660
|
+
}
|
|
661
|
+
_needsReload = YES;
|
|
662
|
+
}
|
|
663
|
+
}
|
|
664
|
+
|
|
665
|
+
- (void) setSources: (NSArray<FFFastImageSource*>*)sources {
|
|
666
|
+
if (_sources != sources) {
|
|
667
|
+
_sources = [sources copy];
|
|
668
|
+
if (![self picksSource]) {
|
|
669
|
+
_source = self.propSource;
|
|
670
|
+
}
|
|
599
671
|
_needsReload = YES;
|
|
600
672
|
}
|
|
601
673
|
}
|
|
602
674
|
|
|
675
|
+
// Whether the view picks one of several sources for its size.
|
|
676
|
+
- (BOOL) picksSource {
|
|
677
|
+
return _sources.count > 1;
|
|
678
|
+
}
|
|
679
|
+
|
|
680
|
+
// Of several sources, the one whose size in pixels is closest to the view's
|
|
681
|
+
// (by pixel count), or the largest while the view has no size (e.g. one sized
|
|
682
|
+
// from onLoad).
|
|
683
|
+
- (FFFastImageSource*) sourceForSize {
|
|
684
|
+
CGFloat scale = self.window.screen.scale ?: [UIScreen mainScreen].scale;
|
|
685
|
+
CGFloat viewPixels = self.bounds.size.width * self.bounds.size.height * scale * scale;
|
|
686
|
+
FFFastImageSource* best = nil;
|
|
687
|
+
CGFloat bestFit = CGFLOAT_MAX;
|
|
688
|
+
for (FFFastImageSource* source in _sources) {
|
|
689
|
+
CGFloat fit = viewPixels > 0 ? ABS(1 - source.pixelCount / viewPixels) : -source.pixelCount;
|
|
690
|
+
if (!best || fit < bestFit) {
|
|
691
|
+
best = source;
|
|
692
|
+
bestFit = fit;
|
|
693
|
+
}
|
|
694
|
+
}
|
|
695
|
+
return best;
|
|
696
|
+
}
|
|
697
|
+
|
|
698
|
+
// Several sources: another one fits the view's new size better. Loads it,
|
|
699
|
+
// keeping the image showing until then, without a fade (see switchesSource).
|
|
700
|
+
- (BOOL) switchSourceIfResized {
|
|
701
|
+
if (![self picksSource] || _needsReload || ![self hasSize] || [self sourceForSize] == _source) {
|
|
702
|
+
return NO;
|
|
703
|
+
}
|
|
704
|
+
self.switchesSource = YES;
|
|
705
|
+
[self reloadImage];
|
|
706
|
+
return YES;
|
|
707
|
+
}
|
|
708
|
+
|
|
603
709
|
- (void) setRecyclingKey: (NSString*)recyclingKey {
|
|
604
710
|
if (_recyclingKey == recyclingKey || [_recyclingKey isEqualToString: recyclingKey]) {
|
|
605
711
|
return;
|
|
@@ -625,12 +731,13 @@ NSString *FFFErrorMessage(NSError *error)
|
|
|
625
731
|
|
|
626
732
|
- (void) didSetProps: (NSArray<NSString*>*)changedProps {
|
|
627
733
|
if (_needsReload) {
|
|
628
|
-
// With downsample on, the image is decoded for the view's size,
|
|
629
|
-
//
|
|
630
|
-
//
|
|
631
|
-
//
|
|
632
|
-
//
|
|
633
|
-
|
|
734
|
+
// With downsample on, the image is decoded for the view's size, and
|
|
735
|
+
// with several sources one is picked for it, so a view that hasn't
|
|
736
|
+
// been laid out yet loads once it has. Props and layout are applied
|
|
737
|
+
// in the same update, so that's before the next frame (in
|
|
738
|
+
// layoutSubviews). A view that still has no size then (e.g. one sized
|
|
739
|
+
// from onLoad) loads at full size, or the largest source.
|
|
740
|
+
if (([self downsamples] || [self picksSource]) && ![self hasSize]) {
|
|
634
741
|
if (!self.waitsForSize) {
|
|
635
742
|
self.waitsForSize = YES;
|
|
636
743
|
__weak typeof(self) weakSelf = self;
|
|
@@ -717,6 +824,11 @@ NSString *FFFErrorMessage(NSError *error)
|
|
|
717
824
|
- (void) reloadImage {
|
|
718
825
|
_needsReload = NO;
|
|
719
826
|
self.waitsForSize = NO;
|
|
827
|
+
self.loadsSwitchedSource = self.switchesSource;
|
|
828
|
+
self.switchesSource = NO;
|
|
829
|
+
if ([self picksSource]) {
|
|
830
|
+
_source = [self sourceForSize];
|
|
831
|
+
}
|
|
720
832
|
self.decodedBox = CGSizeZero;
|
|
721
833
|
// The previous load, if it's still running, is for a source the view no
|
|
722
834
|
// longer shows: cancel it (a new download would, but a data uri or no
|
|
@@ -101,6 +101,7 @@ RCT_EXPORT_MODULE(FastImageView)
|
|
|
101
101
|
}
|
|
102
102
|
|
|
103
103
|
RCT_EXPORT_VIEW_PROPERTY(source, FFFastImageSource)
|
|
104
|
+
RCT_EXPORT_VIEW_PROPERTY(sources, FFFastImageSourceArray)
|
|
104
105
|
RCT_EXPORT_VIEW_PROPERTY(defaultSource, UIImage)
|
|
105
106
|
RCT_EXPORT_VIEW_PROPERTY(resizeMode, RCTResizeMode)
|
|
106
107
|
RCT_EXPORT_VIEW_PROPERTY(recyclingKey, NSString)
|
|
@@ -51,6 +51,8 @@ RCT_ENUM_CONVERTER(FFFCacheControl, (@{
|
|
|
51
51
|
imageSource.cacheKey = cacheKey.length > 0 ? cacheKey : nil;
|
|
52
52
|
id memoryCache = json[@"memoryCache"];
|
|
53
53
|
imageSource.memoryCache = memoryCache && memoryCache != (id)kCFNull ? [self BOOL:memoryCache] : YES;
|
|
54
|
+
CGFloat scale = json[@"scale"] && json[@"scale"] != (id)kCFNull ? [self CGFloat:json[@"scale"]] : 1;
|
|
55
|
+
imageSource.pixelCount = [self CGFloat:json[@"width"]] * [self CGFloat:json[@"height"]] * scale * scale;
|
|
54
56
|
|
|
55
57
|
return imageSource;
|
|
56
58
|
}
|