@depup/react-native-fast-image 8.6.3-depup.0 → 8.13.1-depup.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 +2 -2
- package/RNFastImage.podspec +5 -2
- package/android/build.gradle +13 -0
- package/android/src/main/AndroidManifestNew.xml +3 -0
- package/android/src/main/java/com/dylanvann/fastimage/FastImageCookieHandler.java +84 -0
- package/android/src/main/java/com/dylanvann/fastimage/FastImageEvent.java +45 -0
- package/android/src/main/java/com/dylanvann/fastimage/FastImageEvents.java +60 -0
- package/android/src/main/java/com/dylanvann/fastimage/FastImageGif.java +110 -0
- package/android/src/main/java/com/dylanvann/fastimage/FastImageOkHttpProgressGlideModule.java +65 -5
- package/android/src/main/java/com/dylanvann/fastimage/FastImageRequestListener.java +65 -22
- package/android/src/main/java/com/dylanvann/fastimage/FastImageShadowNode.java +21 -0
- package/android/src/main/java/com/dylanvann/fastimage/FastImageSource.java +21 -1
- package/android/src/main/java/com/dylanvann/fastimage/FastImageSourceSize.java +237 -0
- package/android/src/main/java/com/dylanvann/fastimage/FastImageViewConverter.java +14 -5
- package/android/src/main/java/com/dylanvann/fastimage/FastImageViewManager.java +66 -22
- package/android/src/main/java/com/dylanvann/fastimage/FastImageViewModule.java +130 -24
- package/android/src/main/java/com/dylanvann/fastimage/FastImageViewWithUrl.java +359 -39
- package/android/src/main/java/com/dylanvann/fastimage/FastImageWebGlideUrl.java +13 -0
- package/changes.json +1 -1
- package/dist/index.cjs.js +166 -130
- package/dist/index.cjs.js.flow +33 -2
- package/dist/index.d.ts +77 -22
- package/dist/index.js +141 -121
- package/dist/index.js.flow +33 -2
- package/ios/FastImage/FFFDownsampledImage.h +28 -0
- package/ios/FastImage/FFFDownsampledImage.m +169 -0
- package/ios/FastImage/FFFastImageSource.h +6 -0
- package/ios/FastImage/FFFastImageSource.m +13 -0
- package/ios/FastImage/FFFastImageView.h +17 -0
- package/ios/FastImage/FFFastImageView.m +498 -71
- package/ios/FastImage/FFFastImageViewManager.m +106 -8
- package/ios/FastImage/RCTConvert+FFFastImage.m +4 -1
- package/package.json +31 -43
- package/dist/index.d.ts.map +0 -1
- package/dist/index.test.d.ts +0 -2
- package/dist/index.test.d.ts.map +0 -1
|
@@ -1,23 +1,31 @@
|
|
|
1
1
|
package com.dylanvann.fastimage;
|
|
2
2
|
|
|
3
3
|
import static com.dylanvann.fastimage.FastImageRequestListener.REACT_ON_ERROR_EVENT;
|
|
4
|
+
import static com.dylanvann.fastimage.FastImageRequestListener.REACT_ON_LOAD_END_EVENT;
|
|
4
5
|
|
|
5
6
|
import android.annotation.SuppressLint;
|
|
6
7
|
import android.content.Context;
|
|
8
|
+
import android.graphics.drawable.BitmapDrawable;
|
|
7
9
|
import android.graphics.drawable.Drawable;
|
|
8
10
|
|
|
11
|
+
import androidx.annotation.NonNull;
|
|
9
12
|
import androidx.annotation.Nullable;
|
|
10
13
|
import androidx.appcompat.widget.AppCompatImageView;
|
|
14
|
+
import androidx.core.view.ViewCompat;
|
|
11
15
|
|
|
12
16
|
import com.bumptech.glide.RequestBuilder;
|
|
13
17
|
import com.bumptech.glide.RequestManager;
|
|
14
18
|
import com.bumptech.glide.load.model.GlideUrl;
|
|
19
|
+
import com.bumptech.glide.load.resource.bitmap.DownsampleStrategy;
|
|
20
|
+
import com.bumptech.glide.load.resource.gif.GifDrawable;
|
|
15
21
|
import com.bumptech.glide.request.Request;
|
|
22
|
+
import com.bumptech.glide.request.RequestOptions;
|
|
23
|
+
import com.bumptech.glide.request.target.DrawableImageViewTarget;
|
|
24
|
+
import com.bumptech.glide.request.target.SizeReadyCallback;
|
|
25
|
+
import com.bumptech.glide.request.transition.Transition;
|
|
16
26
|
import com.facebook.react.bridge.ReadableMap;
|
|
17
27
|
import com.facebook.react.bridge.WritableMap;
|
|
18
28
|
import com.facebook.react.bridge.WritableNativeMap;
|
|
19
|
-
import com.facebook.react.uimanager.ThemedReactContext;
|
|
20
|
-
import com.facebook.react.uimanager.events.RCTEventEmitter;
|
|
21
29
|
|
|
22
30
|
import java.util.ArrayList;
|
|
23
31
|
import java.util.Collections;
|
|
@@ -32,9 +40,13 @@ class FastImageViewWithUrl extends AppCompatImageView {
|
|
|
32
40
|
private Drawable mDefaultSource = null;
|
|
33
41
|
|
|
34
42
|
public GlideUrl glideUrl;
|
|
43
|
+
// Null when the view was created in a destroyed Activity (nothing loads).
|
|
44
|
+
@Nullable
|
|
45
|
+
final RequestManager requestManager;
|
|
35
46
|
|
|
36
|
-
public FastImageViewWithUrl(Context context) {
|
|
47
|
+
public FastImageViewWithUrl(Context context, @Nullable RequestManager requestManager) {
|
|
37
48
|
super(context);
|
|
49
|
+
this.requestManager = requestManager;
|
|
38
50
|
}
|
|
39
51
|
|
|
40
52
|
public void setSource(@Nullable ReadableMap source) {
|
|
@@ -47,8 +59,286 @@ class FastImageViewWithUrl extends AppCompatImageView {
|
|
|
47
59
|
mDefaultSource = source;
|
|
48
60
|
}
|
|
49
61
|
|
|
50
|
-
|
|
51
|
-
|
|
62
|
+
// Legacy architecture only (see FastImageShadowNode): the view's layout is
|
|
63
|
+
// 0×0 at its parent's origin, which React Native never applies. Lay it out
|
|
64
|
+
// at that size, as the New Architecture does, so Glide stops waiting for a
|
|
65
|
+
// size and loads it (#865).
|
|
66
|
+
void onZeroLayout() {
|
|
67
|
+
if (!ViewCompat.isLaidOut(this)) layout(0, 0, 0, 0);
|
|
68
|
+
}
|
|
69
|
+
|
|
70
|
+
// How many times GIFs play: -1 for the file's own loop count (the `loop`
|
|
71
|
+
// prop not set), 0 for forever, or a number of times.
|
|
72
|
+
private int mLoopCount = -1;
|
|
73
|
+
|
|
74
|
+
public void setLoopCount(int loopCount) {
|
|
75
|
+
if (loopCount == mLoopCount) return;
|
|
76
|
+
mLoopCount = loopCount;
|
|
77
|
+
// Apply it to the GIF that's showing, and play it again. Only this
|
|
78
|
+
// view's own animation: restarting one Glide shares throws while
|
|
79
|
+
// another view plays it.
|
|
80
|
+
if (mOwnGif != null && getDrawable() == mOwnGif) {
|
|
81
|
+
applyLoopCount(mOwnGif);
|
|
82
|
+
if (!mPaused) {
|
|
83
|
+
mOwnGif.stop();
|
|
84
|
+
mOwnGif.startFromFirstFrame();
|
|
85
|
+
}
|
|
86
|
+
}
|
|
87
|
+
}
|
|
88
|
+
|
|
89
|
+
// Pauses GIFs on the frame they're showing (the view's own animation).
|
|
90
|
+
private boolean mPaused = false;
|
|
91
|
+
|
|
92
|
+
public void setPaused(boolean paused) {
|
|
93
|
+
if (paused == mPaused) return;
|
|
94
|
+
mPaused = paused;
|
|
95
|
+
if (mOwnGif != null && getDrawable() == mOwnGif) {
|
|
96
|
+
if (paused) {
|
|
97
|
+
mOwnGif.stop();
|
|
98
|
+
} else {
|
|
99
|
+
// Continues the loop count, as on iOS.
|
|
100
|
+
FastImageGif.resume(mOwnGif);
|
|
101
|
+
}
|
|
102
|
+
}
|
|
103
|
+
}
|
|
104
|
+
|
|
105
|
+
// The GIF this view shows as its own animation (FastImageGif), recycled
|
|
106
|
+
// when the view stops showing it.
|
|
107
|
+
@Nullable
|
|
108
|
+
private GifDrawable mOwnGif;
|
|
109
|
+
|
|
110
|
+
// Shows each GIF as this view's own animation. Glide's target also starts
|
|
111
|
+
// and stops it with the Activity, as it does Glide's own GifDrawable.
|
|
112
|
+
private final class OwnGifTarget extends DrawableImageViewTarget {
|
|
113
|
+
OwnGifTarget() {
|
|
114
|
+
super(FastImageViewWithUrl.this);
|
|
115
|
+
}
|
|
116
|
+
|
|
117
|
+
@Override
|
|
118
|
+
public void onResourceReady(@NonNull Drawable resource, @Nullable Transition<? super Drawable> transition) {
|
|
119
|
+
if (resource instanceof BitmapDrawable) {
|
|
120
|
+
if (mPixelated) {
|
|
121
|
+
// Scaled by the view without filtering: sharp pixels.
|
|
122
|
+
resource = resource.mutate();
|
|
123
|
+
resource.setFilterBitmap(false);
|
|
124
|
+
}
|
|
125
|
+
}
|
|
126
|
+
if (resource instanceof GifDrawable) {
|
|
127
|
+
GifDrawable own = FastImageGif.copy(
|
|
128
|
+
getContext(), (GifDrawable) resource, mLoadingWidth, mLoadingHeight);
|
|
129
|
+
if (own != null) {
|
|
130
|
+
applyLoopCount(own);
|
|
131
|
+
super.onResourceReady(own, transition);
|
|
132
|
+
mOwnGif = own;
|
|
133
|
+
// The target starts it; paused, it waits on its first frame.
|
|
134
|
+
if (mPaused) own.stop();
|
|
135
|
+
return;
|
|
136
|
+
}
|
|
137
|
+
}
|
|
138
|
+
super.onResourceReady(resource, transition);
|
|
139
|
+
}
|
|
140
|
+
|
|
141
|
+
// The target starts animations again when the Activity does: not a
|
|
142
|
+
// paused one, and the view's own GIF continues its loop count (start()
|
|
143
|
+
// would count again).
|
|
144
|
+
@Override
|
|
145
|
+
public void onStart() {
|
|
146
|
+
if (mPaused) return;
|
|
147
|
+
if (mOwnGif != null && getDrawable() == mOwnGif) {
|
|
148
|
+
FastImageGif.resume(mOwnGif);
|
|
149
|
+
} else {
|
|
150
|
+
super.onStart();
|
|
151
|
+
}
|
|
152
|
+
}
|
|
153
|
+
|
|
154
|
+
@Override
|
|
155
|
+
protected void setResource(@Nullable Drawable resource) {
|
|
156
|
+
super.setResource(resource);
|
|
157
|
+
// Not shown anymore (replaced, cleared or failed): free its frames.
|
|
158
|
+
if (mOwnGif != null && mOwnGif != resource) {
|
|
159
|
+
mOwnGif.stop();
|
|
160
|
+
mOwnGif.recycle();
|
|
161
|
+
mOwnGif = null;
|
|
162
|
+
}
|
|
163
|
+
}
|
|
164
|
+
}
|
|
165
|
+
|
|
166
|
+
void applyLoopCount(GifDrawable gif) {
|
|
167
|
+
gif.setLoopCount(mLoopCount == -1 ? GifDrawable.LOOP_INTRINSIC
|
|
168
|
+
: mLoopCount == 0 ? GifDrawable.LOOP_FOREVER
|
|
169
|
+
: mLoopCount);
|
|
170
|
+
}
|
|
171
|
+
|
|
172
|
+
// Glide crops or fits the bitmap for the scale type when it loads, so a
|
|
173
|
+
// new resizeMode needs a reload to take effect (#762).
|
|
174
|
+
public void setResizeMode(ScaleType scaleType) {
|
|
175
|
+
if (scaleType == getScaleType()) return;
|
|
176
|
+
setScaleType(scaleType);
|
|
177
|
+
mNeedsReload = true;
|
|
178
|
+
}
|
|
179
|
+
|
|
180
|
+
// imageRendering="pixelated": no resampling by Glide, and drawn without
|
|
181
|
+
// filtering (sharp pixels). "smooth" is iOS only; here it's the same as
|
|
182
|
+
// "auto": Glide already decodes a large image at about the view's size, and
|
|
183
|
+
// averaging it properly would need a full-size decode. Part of the
|
|
184
|
+
// request, so a change reloads.
|
|
185
|
+
private boolean mPixelated = false;
|
|
186
|
+
|
|
187
|
+
public void setImageRendering(@Nullable String imageRendering) {
|
|
188
|
+
boolean pixelated = "pixelated".equals(imageRendering);
|
|
189
|
+
if (pixelated == mPixelated) return;
|
|
190
|
+
mPixelated = pixelated;
|
|
191
|
+
mNeedsReload = true;
|
|
192
|
+
}
|
|
193
|
+
|
|
194
|
+
// The scale type's options (what into() would apply), or none when
|
|
195
|
+
// pixelated.
|
|
196
|
+
private RequestOptions renderingOptions(Object model) {
|
|
197
|
+
if (mPixelated) {
|
|
198
|
+
// Decoded as it is, then scaled by the view without filtering (see
|
|
199
|
+
// OwnGifTarget).
|
|
200
|
+
return new RequestOptions()
|
|
201
|
+
.downsample(new FastImageSourceSize.Capture(DownsampleStrategy.NONE, String.valueOf(model)))
|
|
202
|
+
.dontTransform();
|
|
203
|
+
}
|
|
204
|
+
return FastImageSourceSize.scaleTypeOptions(getScaleType(), FastImageSourceSize.capture(getScaleType(), model));
|
|
205
|
+
}
|
|
206
|
+
|
|
207
|
+
// The request for the image the view shows once it has loaded, and the one
|
|
208
|
+
// loading. A new source starts with the shown one as a thumbnail, from the
|
|
209
|
+
// cache only, so the image stays until the new one has loaded instead of
|
|
210
|
+
// flashing blank (#747). This is Glide's safe way to do that: the previous
|
|
211
|
+
// bitmap can't be kept in the view itself, since Glide reuses it once its
|
|
212
|
+
// request is cleared.
|
|
213
|
+
@Nullable
|
|
214
|
+
private RequestBuilder<Drawable> mShownRequest;
|
|
215
|
+
@Nullable
|
|
216
|
+
private RequestBuilder<Drawable> mLoadingRequest;
|
|
217
|
+
// Counts loads, to tell whether a posted update is for the current one.
|
|
218
|
+
private int mLoadCount = 0;
|
|
219
|
+
// The size Glide loaded the shown image at, and is loading the loading
|
|
220
|
+
// one at (0 until it has one).
|
|
221
|
+
private int mShownWidth = 0;
|
|
222
|
+
private int mShownHeight = 0;
|
|
223
|
+
private int mLoadingWidth = 0;
|
|
224
|
+
private int mLoadingHeight = 0;
|
|
225
|
+
private boolean mResizeReloadPosted = false;
|
|
226
|
+
|
|
227
|
+
@Nullable
|
|
228
|
+
private String mRecyclingKey;
|
|
229
|
+
|
|
230
|
+
// When it changes, the next image doesn't replace the current one: the
|
|
231
|
+
// view clears first (for views reused for other content, like list rows).
|
|
232
|
+
void setRecyclingKey(@Nullable String recyclingKey) {
|
|
233
|
+
if (recyclingKey == null ? mRecyclingKey == null : recyclingKey.equals(mRecyclingKey)) return;
|
|
234
|
+
boolean changed = mRecyclingKey != null;
|
|
235
|
+
mRecyclingKey = recyclingKey;
|
|
236
|
+
if (changed) {
|
|
237
|
+
// No thumbnail of the current image, so Glide clears the view to
|
|
238
|
+
// defaultSource (or nothing) while the next one loads. Reload even
|
|
239
|
+
// if the source is the same.
|
|
240
|
+
mShownRequest = null;
|
|
241
|
+
mNeedsReload = true;
|
|
242
|
+
}
|
|
243
|
+
}
|
|
244
|
+
|
|
245
|
+
// The loading image loaded (FastImageRequestListener).
|
|
246
|
+
void onImageLoaded() {
|
|
247
|
+
mShownRequest = mLoadingRequest;
|
|
248
|
+
mShownWidth = mLoadingWidth;
|
|
249
|
+
mShownHeight = mLoadingHeight;
|
|
250
|
+
reloadIfResized();
|
|
251
|
+
}
|
|
252
|
+
|
|
253
|
+
// Glide crops or scales the image to the view's size when it loads, so if
|
|
254
|
+
// the view's size changes after that, load it again at the new size.
|
|
255
|
+
// Otherwise a view that gets taller shows a zoomed-in slice of it (#983).
|
|
256
|
+
@Override
|
|
257
|
+
protected void onSizeChanged(int w, int h, int oldw, int oldh) {
|
|
258
|
+
super.onSizeChanged(w, h, oldw, oldh);
|
|
259
|
+
reloadIfResized();
|
|
260
|
+
}
|
|
261
|
+
|
|
262
|
+
private void reloadIfResized() {
|
|
263
|
+
if (mResizeReloadPosted || !isResized()) return;
|
|
264
|
+
mResizeReloadPosted = true;
|
|
265
|
+
// After layout (once per frame), and not from within Glide's callback.
|
|
266
|
+
post(new Runnable() {
|
|
267
|
+
@Override
|
|
268
|
+
public void run() {
|
|
269
|
+
mResizeReloadPosted = false;
|
|
270
|
+
if (isResized()) reloadForSize();
|
|
271
|
+
}
|
|
272
|
+
});
|
|
273
|
+
}
|
|
274
|
+
|
|
275
|
+
// Whether the image loaded, at a size the view doesn't have now.
|
|
276
|
+
private boolean isResized() {
|
|
277
|
+
int width = getWidth() - getPaddingLeft() - getPaddingRight();
|
|
278
|
+
int height = getHeight() - getPaddingTop() - getPaddingBottom();
|
|
279
|
+
return requestManager != null && !mNeedsReload
|
|
280
|
+
&& mShownRequest != null && mShownRequest == mLoadingRequest
|
|
281
|
+
&& mShownWidth > 0 && mShownHeight > 0 && width > 0 && height > 0
|
|
282
|
+
&& (width != mShownWidth || height != mShownHeight);
|
|
283
|
+
}
|
|
284
|
+
|
|
285
|
+
// The same image at the view's new size. Meanwhile, and if that fails, the
|
|
286
|
+
// view shows the image at its old size, from the cache. It's the image
|
|
287
|
+
// that's already showing, so this sends no events.
|
|
288
|
+
@SuppressLint("CheckResult")
|
|
289
|
+
private void reloadForSize() {
|
|
290
|
+
RequestBuilder<Drawable> shown = mShownRequest;
|
|
291
|
+
RequestBuilder<Drawable> current = fromCache(shown);
|
|
292
|
+
mLoadCount++;
|
|
293
|
+
clearView(requestManager);
|
|
294
|
+
into(shown, shown.clone()
|
|
295
|
+
.thumbnail(current)
|
|
296
|
+
.error(current.clone())
|
|
297
|
+
.listener(new FastImageRequestListener(null, null, true, false)));
|
|
298
|
+
}
|
|
299
|
+
|
|
300
|
+
// The shown image, from the cache only (never loaded again), at the size
|
|
301
|
+
// it was loaded at: the key it's in Glide's memory cache under.
|
|
302
|
+
@SuppressLint("CheckResult")
|
|
303
|
+
private RequestBuilder<Drawable> fromCache(RequestBuilder<Drawable> shown) {
|
|
304
|
+
RequestBuilder<Drawable> request = shown.clone().onlyRetrieveFromCache(true);
|
|
305
|
+
if (mShownWidth > 0 && mShownHeight > 0) request = request.override(mShownWidth, mShownHeight);
|
|
306
|
+
return request;
|
|
307
|
+
}
|
|
308
|
+
|
|
309
|
+
// Starts loading the request (built by builder), recording the size Glide
|
|
310
|
+
// loads it at. The size callback is added first, so it has the size by
|
|
311
|
+
// the time the image loads, even from the memory cache.
|
|
312
|
+
private void into(RequestBuilder<Drawable> request, RequestBuilder<Drawable> builder) {
|
|
313
|
+
final int load = mLoadCount;
|
|
314
|
+
mLoadingRequest = request;
|
|
315
|
+
mLoadingWidth = 0;
|
|
316
|
+
mLoadingHeight = 0;
|
|
317
|
+
OwnGifTarget target = new OwnGifTarget();
|
|
318
|
+
target.getSize(new SizeReadyCallback() {
|
|
319
|
+
@Override
|
|
320
|
+
public void onSizeReady(int width, int height) {
|
|
321
|
+
if (load != mLoadCount) return;
|
|
322
|
+
mLoadingWidth = width;
|
|
323
|
+
mLoadingHeight = height;
|
|
324
|
+
}
|
|
325
|
+
});
|
|
326
|
+
builder.into(target);
|
|
327
|
+
}
|
|
328
|
+
|
|
329
|
+
// The loading image failed (FastImageRequestListener). Glide shows
|
|
330
|
+
// defaultSource then, but not over a thumbnail: show it here, as iOS does.
|
|
331
|
+
void onImageFailed(boolean hadThumbnail) {
|
|
332
|
+
mShownRequest = null;
|
|
333
|
+
if (!hadThumbnail) return;
|
|
334
|
+
final int load = mLoadCount;
|
|
335
|
+
// Not from within Glide's callback.
|
|
336
|
+
post(new Runnable() {
|
|
337
|
+
@Override
|
|
338
|
+
public void run() {
|
|
339
|
+
if (load == mLoadCount) setImageDrawable(mDefaultSource);
|
|
340
|
+
}
|
|
341
|
+
});
|
|
52
342
|
}
|
|
53
343
|
|
|
54
344
|
@SuppressLint("CheckResult")
|
|
@@ -58,18 +348,20 @@ class FastImageViewWithUrl extends AppCompatImageView {
|
|
|
58
348
|
@Nonnull Map<String, List<FastImageViewWithUrl>> viewsForUrlsMap) {
|
|
59
349
|
if (!mNeedsReload)
|
|
60
350
|
return;
|
|
351
|
+
// Only reload for changes that affect the request (source,
|
|
352
|
+
// defaultSource, resizeMode), not for every prop update.
|
|
353
|
+
mNeedsReload = false;
|
|
354
|
+
mLoadCount++;
|
|
355
|
+
RequestBuilder<Drawable> shownRequest = mShownRequest;
|
|
356
|
+
mShownRequest = null;
|
|
357
|
+
mLoadingRequest = null;
|
|
61
358
|
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
isNullOrEmpty(mSource.getString("uri"))) &&
|
|
65
|
-
mDefaultSource == null) {
|
|
66
|
-
|
|
359
|
+
// Nothing to show.
|
|
360
|
+
if (mSource == null && mDefaultSource == null) {
|
|
67
361
|
// Cancel existing requests.
|
|
68
362
|
clearView(requestManager);
|
|
69
363
|
|
|
70
|
-
|
|
71
|
-
FastImageOkHttpProgressGlideModule.forget(glideUrl.toStringUrl());
|
|
72
|
-
}
|
|
364
|
+
untrackUrl(viewsForUrlsMap);
|
|
73
365
|
|
|
74
366
|
// Clear the image.
|
|
75
367
|
setImageDrawable(null);
|
|
@@ -77,36 +369,41 @@ class FastImageViewWithUrl extends AppCompatImageView {
|
|
|
77
369
|
}
|
|
78
370
|
|
|
79
371
|
//final GlideUrl glideUrl = FastImageViewConverter.getGlideUrl(view.getContext(), mSource);
|
|
80
|
-
final FastImageSource imageSource = FastImageViewConverter.
|
|
372
|
+
final FastImageSource imageSource = FastImageViewConverter.hasUri(mSource)
|
|
373
|
+
? FastImageViewConverter.getImageSource(getContext(), mSource)
|
|
374
|
+
: null;
|
|
81
375
|
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
|
|
376
|
+
// A source without a uri (empty, missing or null), or one that can't be
|
|
377
|
+
// resolved: fire onError and onLoadEnd and show defaultSource, as on iOS
|
|
378
|
+
// (#1028, #945).
|
|
379
|
+
if (mSource != null && (imageSource == null || imageSource.getUri().toString().length() == 0)) {
|
|
86
380
|
WritableMap event = new WritableNativeMap();
|
|
87
|
-
event.putString("
|
|
88
|
-
|
|
381
|
+
event.putString("error", "Invalid source: " + mSource);
|
|
382
|
+
FastImageEvents.send(this, REACT_ON_ERROR_EVENT, event);
|
|
383
|
+
FastImageEvents.send(this, REACT_ON_LOAD_END_EVENT);
|
|
89
384
|
|
|
90
385
|
// Cancel existing requests.
|
|
91
386
|
clearView(requestManager);
|
|
92
387
|
|
|
93
|
-
|
|
94
|
-
|
|
95
|
-
}
|
|
96
|
-
// Clear the image.
|
|
97
|
-
setImageDrawable(null);
|
|
388
|
+
untrackUrl(viewsForUrlsMap);
|
|
389
|
+
setImageDrawable(mDefaultSource);
|
|
98
390
|
return;
|
|
99
391
|
}
|
|
100
392
|
|
|
101
393
|
// `imageSource` may be null and we still continue, if `defaultSource` is not null
|
|
102
394
|
final GlideUrl glideUrl = imageSource == null ? null : imageSource.getGlideUrl();
|
|
103
395
|
|
|
396
|
+
String key = glideUrl == null ? null : glideUrl.toStringUrl();
|
|
397
|
+
|
|
398
|
+
// Loading a different url: stop tracking the old one.
|
|
399
|
+
if (this.glideUrl != null && !this.glideUrl.toStringUrl().equals(key)) {
|
|
400
|
+
untrackUrl(viewsForUrlsMap);
|
|
401
|
+
}
|
|
402
|
+
|
|
104
403
|
// Cancel existing request.
|
|
105
404
|
this.glideUrl = glideUrl;
|
|
106
405
|
clearView(requestManager);
|
|
107
406
|
|
|
108
|
-
String key = glideUrl == null ? null : glideUrl.toStringUrl();
|
|
109
|
-
|
|
110
407
|
if (glideUrl != null) {
|
|
111
408
|
FastImageOkHttpProgressGlideModule.expect(key, manager);
|
|
112
409
|
List<FastImageViewWithUrl> viewsForKey = viewsForUrlsMap.get(key);
|
|
@@ -118,18 +415,14 @@ class FastImageViewWithUrl extends AppCompatImageView {
|
|
|
118
415
|
}
|
|
119
416
|
}
|
|
120
417
|
|
|
121
|
-
ThemedReactContext context = (ThemedReactContext) getContext();
|
|
122
418
|
if (imageSource != null) {
|
|
123
419
|
// This is an orphan even without a load/loadend when only loading a placeholder
|
|
124
|
-
|
|
125
|
-
int viewId = this.getId();
|
|
126
|
-
|
|
127
|
-
eventEmitter.receiveEvent(viewId,
|
|
128
|
-
FastImageViewManager.REACT_ON_LOAD_START_EVENT,
|
|
129
|
-
new WritableNativeMap());
|
|
420
|
+
FastImageEvents.send(this, FastImageViewManager.REACT_ON_LOAD_START_EVENT);
|
|
130
421
|
}
|
|
131
422
|
|
|
132
423
|
if (requestManager != null) {
|
|
424
|
+
// Records the image's own size when Glide decodes it, for onLoad.
|
|
425
|
+
Object model = imageSource == null ? null : imageSource.getSourceForLoad();
|
|
133
426
|
RequestBuilder<Drawable> builder =
|
|
134
427
|
requestManager
|
|
135
428
|
// This will make this work for remote and local images. e.g.
|
|
@@ -138,17 +431,44 @@ class FastImageViewWithUrl extends AppCompatImageView {
|
|
|
138
431
|
// - res:/
|
|
139
432
|
// - android.resource://
|
|
140
433
|
// - data:image/png;base64
|
|
141
|
-
.load(
|
|
434
|
+
.load(model)
|
|
142
435
|
.apply(FastImageViewConverter
|
|
143
|
-
.getOptions(
|
|
436
|
+
.getOptions(getContext(), imageSource, mSource)
|
|
144
437
|
.placeholder(mDefaultSource) // show until loaded
|
|
145
|
-
.fallback(mDefaultSource))
|
|
438
|
+
.fallback(mDefaultSource)) // null will not be treated as error
|
|
439
|
+
// What into() would apply for the scale type, with
|
|
440
|
+
// the size capture, for imageRendering.
|
|
441
|
+
.apply(renderingOptions(model));
|
|
442
|
+
RequestBuilder<Drawable> request = builder.clone();
|
|
443
|
+
|
|
444
|
+
boolean thumbnail = shownRequest != null && model != null;
|
|
445
|
+
if (thumbnail) {
|
|
446
|
+
builder = builder.thumbnail(fromCache(shownRequest));
|
|
447
|
+
}
|
|
146
448
|
|
|
147
449
|
if (key != null)
|
|
148
|
-
builder.listener(new FastImageRequestListener(key));
|
|
450
|
+
builder.listener(new FastImageRequestListener(key, imageSource, thumbnail, true));
|
|
149
451
|
|
|
150
|
-
|
|
452
|
+
into(request, builder);
|
|
453
|
+
}
|
|
454
|
+
}
|
|
455
|
+
|
|
456
|
+
// Removes this view from the list of views for its current url (used to send
|
|
457
|
+
// progress events), which otherwise kept it, and its Activity, alive after
|
|
458
|
+
// its source changed (#384). The url's progress listener is only forgotten
|
|
459
|
+
// when no other view uses it.
|
|
460
|
+
void untrackUrl(@Nonnull Map<String, List<FastImageViewWithUrl>> viewsForUrlsMap) {
|
|
461
|
+
if (glideUrl == null) return;
|
|
462
|
+
String key = glideUrl.toStringUrl();
|
|
463
|
+
List<FastImageViewWithUrl> viewsForKey = viewsForUrlsMap.get(key);
|
|
464
|
+
if (viewsForKey != null) {
|
|
465
|
+
viewsForKey.remove(this);
|
|
466
|
+
if (viewsForKey.isEmpty()) viewsForUrlsMap.remove(key);
|
|
467
|
+
}
|
|
468
|
+
if (viewsForKey == null || viewsForKey.isEmpty()) {
|
|
469
|
+
FastImageOkHttpProgressGlideModule.forget(key);
|
|
151
470
|
}
|
|
471
|
+
glideUrl = null;
|
|
152
472
|
}
|
|
153
473
|
|
|
154
474
|
public void clearView(@Nullable RequestManager requestManager) {
|
|
@@ -0,0 +1,13 @@
|
|
|
1
|
+
package com.dylanvann.fastimage;
|
|
2
|
+
|
|
3
|
+
import com.bumptech.glide.load.model.GlideUrl;
|
|
4
|
+
import com.bumptech.glide.load.model.Headers;
|
|
5
|
+
|
|
6
|
+
// A url loaded with `cache: 'web'`. Glide doesn't cache these, so they load
|
|
7
|
+
// through an OkHttp client with an HTTP cache (see
|
|
8
|
+
// FastImageOkHttpProgressGlideModule) to follow the server's caching headers.
|
|
9
|
+
class FastImageWebGlideUrl extends GlideUrl {
|
|
10
|
+
FastImageWebGlideUrl(String url, Headers headers) {
|
|
11
|
+
super(url, headers);
|
|
12
|
+
}
|
|
13
|
+
}
|
package/changes.json
CHANGED