react-native-fast-image 9.3.6 → 10.0.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.
Files changed (76) hide show
  1. package/README.md +87 -61
  2. package/RNFastImage.podspec +14 -9
  3. package/android/build.gradle +21 -47
  4. package/android/consumer-rules.pro +7 -7
  5. package/android/src/main/AndroidManifest.xml +1 -2
  6. package/android/src/main/java/com/{dylanvann/fastimage → fastimage}/FastImageAnimatable.java +1 -1
  7. package/android/src/main/java/com/{dylanvann/fastimage → fastimage}/FastImageAnimated.java +2 -2
  8. package/android/src/main/java/com/{dylanvann/fastimage → fastimage}/FastImageApng.java +2 -2
  9. package/android/src/main/java/com/{dylanvann/fastimage → fastimage}/FastImageBlur.java +4 -6
  10. package/android/src/main/java/com/{dylanvann/fastimage → fastimage}/FastImageCacheControl.java +1 -1
  11. package/android/src/main/java/com/{dylanvann/fastimage → fastimage}/FastImageCacheLimits.java +4 -1
  12. package/android/src/main/java/com/{dylanvann/fastimage → fastimage}/FastImageCacheWrite.java +1 -1
  13. package/android/src/main/java/com/fastimage/FastImageCrop.java +92 -0
  14. package/android/src/main/java/com/{dylanvann/fastimage → fastimage}/FastImageEvent.java +2 -3
  15. package/android/src/main/java/com/fastimage/FastImageEvents.java +74 -0
  16. package/android/src/main/java/com/{dylanvann/fastimage → fastimage}/FastImageGif.java +1 -1
  17. package/android/src/main/java/com/{dylanvann/fastimage → fastimage}/FastImageGlide.java +3 -3
  18. package/android/src/main/java/com/{dylanvann/fastimage → fastimage}/FastImageGlideModule.java +1 -1
  19. package/android/src/main/java/com/{dylanvann/fastimage → fastimage}/FastImageKeyedGlideUrl.java +1 -1
  20. package/android/src/main/java/com/{dylanvann/fastimage → fastimage}/FastImageOkHttpProgressGlideModule.java +36 -9
  21. package/android/src/main/java/com/{dylanvann/fastimage → fastimage}/FastImageRequestListener.java +6 -1
  22. package/android/src/main/java/com/{dylanvann/fastimage → fastimage}/FastImageSharedDownloads.java +1 -1
  23. package/android/src/main/java/com/{dylanvann/fastimage → fastimage}/FastImageSource.java +1 -1
  24. package/android/src/main/java/com/{dylanvann/fastimage → fastimage}/FastImageSourceSize.java +12 -2
  25. package/android/src/main/java/com/{dylanvann/fastimage → fastimage}/FastImageSvg.java +3 -3
  26. package/android/src/main/java/com/{dylanvann/fastimage → fastimage}/FastImageSvgRenderer.java +1 -1
  27. package/android/src/main/java/com/{dylanvann/fastimage → fastimage}/FastImageUrl.java +1 -1
  28. package/android/src/main/java/com/{dylanvann/fastimage → fastimage}/FastImageViewConverter.java +7 -8
  29. package/android/src/main/java/com/fastimage/FastImageViewManager.java +591 -0
  30. package/android/src/main/java/com/{dylanvann/fastimage → fastimage}/FastImageViewModule.java +31 -28
  31. package/android/src/main/java/com/fastimage/FastImageViewPackage.java +51 -0
  32. package/android/src/main/java/com/{dylanvann/fastimage → fastimage}/FastImageViewWithUrl.java +362 -129
  33. package/android/src/main/java/com/{dylanvann/fastimage → fastimage}/FastImageWebGlideUrl.java +1 -1
  34. package/app.plugin.js +2 -7
  35. package/dist/index.cjs.js +276 -157
  36. package/dist/index.cjs.js.flow +33 -17
  37. package/dist/index.cjs.web.js +211 -61
  38. package/dist/index.cjs.web.js.flow +33 -17
  39. package/dist/index.d.ts +83 -58
  40. package/dist/index.js +278 -159
  41. package/dist/index.js.flow +33 -17
  42. package/dist/index.web.js +212 -62
  43. package/dist/index.web.js.flow +33 -17
  44. package/dist/objectFit.d.ts +2 -2
  45. package/dist/objectPosition.d.ts +42 -0
  46. package/dist/specs/FastImageViewNativeComponent.d.ts +48 -0
  47. package/dist/specs/NativeFastImageModule.d.ts +11 -0
  48. package/dist/warnings.d.ts +3 -0
  49. package/ios/FastImage/FFFDownsampledImage.h +4 -8
  50. package/ios/FastImage/FFFDownsampledImage.m +0 -34
  51. package/ios/FastImage/FFFastImageModule.h +8 -0
  52. package/ios/FastImage/{FFFastImageViewManager.m → FFFastImageModule.mm} +70 -59
  53. package/ios/FastImage/FFFastImageView.h +36 -7
  54. package/ios/FastImage/FFFastImageView.m +201 -164
  55. package/ios/FastImage/FFFastImageViewComponentView.h +11 -0
  56. package/ios/FastImage/FFFastImageViewComponentView.mm +325 -0
  57. package/ios/FastImage/RCTConvert+FFFastImage.h +1 -1
  58. package/ios/FastImage/RCTConvert+FFFastImage.m +7 -10
  59. package/package.json +28 -9
  60. package/src/constants.ts +28 -0
  61. package/src/index.js.flow +179 -0
  62. package/src/index.tsx +1103 -0
  63. package/src/index.web.tsx +548 -0
  64. package/src/objectFit.ts +48 -0
  65. package/src/objectPosition.ts +243 -0
  66. package/src/specs/FastImageViewNativeComponent.ts +83 -0
  67. package/src/specs/NativeFastImageModule.ts +35 -0
  68. package/src/warnings.ts +37 -0
  69. package/android/src/main/AndroidManifestNew.xml +0 -3
  70. package/android/src/main/java/com/dylanvann/fastimage/FastImageCookieHandler.java +0 -63
  71. package/android/src/main/java/com/dylanvann/fastimage/FastImageEvents.java +0 -49
  72. package/android/src/main/java/com/dylanvann/fastimage/FastImageProgressListener.java +0 -18
  73. package/android/src/main/java/com/dylanvann/fastimage/FastImageShadowNode.java +0 -21
  74. package/android/src/main/java/com/dylanvann/fastimage/FastImageViewManager.java +0 -379
  75. package/android/src/main/java/com/dylanvann/fastimage/FastImageViewPackage.java +0 -27
  76. package/ios/FastImage/FFFastImageViewManager.h +0 -5
@@ -0,0 +1,591 @@
1
+ package com.fastimage;
2
+
3
+ import static com.fastimage.FastImageRequestListener.REACT_ON_ERROR_EVENT;
4
+ import static com.fastimage.FastImageRequestListener.REACT_ON_LOAD_END_EVENT;
5
+ import static com.fastimage.FastImageRequestListener.REACT_ON_LOAD_EVENT;
6
+
7
+ import android.app.Activity;
8
+ import android.content.Context;
9
+ import android.content.ContextWrapper;
10
+ import android.graphics.PorterDuff;
11
+ import android.util.Log;
12
+ import android.graphics.Rect;
13
+
14
+ import androidx.annotation.NonNull;
15
+
16
+ import com.bumptech.glide.Glide;
17
+ import com.bumptech.glide.RequestManager;
18
+ import com.facebook.react.bridge.ColorPropConverter;
19
+ import com.facebook.react.bridge.Dynamic;
20
+ import com.facebook.react.bridge.DynamicFromObject;
21
+ import com.facebook.react.bridge.ReadableArray;
22
+ import com.facebook.react.bridge.ReadableMap;
23
+ import com.facebook.react.bridge.ReadableType;
24
+ import com.facebook.react.bridge.WritableMap;
25
+ import com.facebook.react.bridge.WritableNativeMap;
26
+ import com.facebook.react.uimanager.BackgroundStyleApplicator;
27
+ import com.facebook.react.uimanager.LengthPercentage;
28
+ import com.facebook.react.uimanager.PointerEvents;
29
+ import com.facebook.react.uimanager.SimpleViewManager;
30
+ import com.facebook.react.bridge.ReactContext;
31
+ import com.facebook.react.bridge.UiThreadUtil;
32
+ import com.facebook.react.uimanager.Spacing;
33
+ import com.facebook.react.uimanager.ThemedReactContext;
34
+ import com.facebook.react.uimanager.PixelUtil;
35
+ import com.facebook.react.uimanager.ViewManagerDelegate;
36
+ import com.facebook.react.uimanager.ViewProps;
37
+ import com.facebook.react.uimanager.style.BackgroundImageLayer;
38
+ import com.facebook.react.uimanager.style.BorderRadiusProp;
39
+ import com.facebook.react.uimanager.style.BorderStyle;
40
+ import com.facebook.react.uimanager.style.LogicalEdge;
41
+ import com.facebook.react.viewmanagers.FastImageViewManagerDelegate;
42
+ import com.facebook.react.viewmanagers.FastImageViewManagerInterface;
43
+ import com.facebook.react.views.imagehelper.ResourceDrawableIdHelper;
44
+
45
+ import java.util.ArrayList;
46
+ import java.util.HashMap;
47
+ import java.util.LinkedHashSet;
48
+ import java.util.List;
49
+ import java.util.Map;
50
+ import java.util.Set;
51
+ import java.util.concurrent.atomic.AtomicLong;
52
+
53
+ import javax.annotation.Nullable;
54
+
55
+ // The FastImageView component (src/specs), with the props Codegen generates
56
+ // its interface from.
57
+ class FastImageViewManager extends SimpleViewManager<FastImageViewWithUrl>
58
+ implements FastImageViewManagerInterface<FastImageViewWithUrl> {
59
+
60
+ static final String REACT_CLASS = "FastImageView";
61
+ static final String REACT_ON_LOAD_START_EVENT = "onFastImageLoadStart";
62
+ static final String REACT_ON_PROGRESS_EVENT = "onFastImageProgress";
63
+ static final String LOG_TAG = "FastImage";
64
+ // Download progress, by the key downloads are shared by
65
+ // (FastImageSharedDownloads.key): the views that get it, those with an onProgress from
66
+ // their load's onLoadStart to its onLoad or onError, and the latest step a
67
+ // download reported, until the UI thread sends it. The UI thread adds and
68
+ // removes views, and downloads report steps on their own threads: only
69
+ // used under its lock.
70
+ private static final Map<String, Progress> PROGRESS = new HashMap<>();
71
+ // Numbers the steps downloads report, so a view can tell those reported
72
+ // before its load started (see FastImageViewWithUrl.takesProgress).
73
+ private static final AtomicLong REPORTED_STEPS = new AtomicLong();
74
+ // Whether any view gets progress (set under PROGRESS's lock), so downloads
75
+ // can skip reporting without taking any lock.
76
+ private static volatile boolean tracking = false;
77
+
78
+ private static final class Progress {
79
+ final Set<FastImageViewWithUrl> views = new LinkedHashSet<>();
80
+ // A step waits to be sent: later ones replace it meanwhile, so a url
81
+ // has at most one post to the UI thread at a time.
82
+ boolean posted;
83
+ long loaded;
84
+ long total;
85
+ long step;
86
+ }
87
+
88
+ // Codegen's delegate, plus the style props that React Native's View
89
+ // handles itself (ReactViewManager) and its base delegate drops for other
90
+ // views. FastImage is a single native view: they're the image's.
91
+ private final ViewManagerDelegate<FastImageViewWithUrl> delegate =
92
+ new FastImageViewManagerDelegate<FastImageViewWithUrl, FastImageViewManager>(this) {
93
+ @Override
94
+ public void setProperty(FastImageViewWithUrl view, String propName, @Nullable Object value) {
95
+ if (!setViewStyleProperty(view, propName, value)) {
96
+ super.setProperty(view, propName, value);
97
+ }
98
+ }
99
+ };
100
+
101
+ // In BorderRadiusProp's order.
102
+ private static final String[] BORDER_RADIUS_PROPS = {
103
+ ViewProps.BORDER_RADIUS,
104
+ ViewProps.BORDER_TOP_LEFT_RADIUS,
105
+ ViewProps.BORDER_TOP_RIGHT_RADIUS,
106
+ ViewProps.BORDER_BOTTOM_RIGHT_RADIUS,
107
+ ViewProps.BORDER_BOTTOM_LEFT_RADIUS,
108
+ ViewProps.BORDER_TOP_START_RADIUS,
109
+ ViewProps.BORDER_TOP_END_RADIUS,
110
+ ViewProps.BORDER_BOTTOM_START_RADIUS,
111
+ ViewProps.BORDER_BOTTOM_END_RADIUS,
112
+ ViewProps.BORDER_END_END_RADIUS,
113
+ ViewProps.BORDER_END_START_RADIUS,
114
+ ViewProps.BORDER_START_END_RADIUS,
115
+ ViewProps.BORDER_START_START_RADIUS,
116
+ };
117
+ // In LogicalEdge's order.
118
+ private static final String[] BORDER_WIDTH_PROPS = {
119
+ ViewProps.BORDER_WIDTH,
120
+ ViewProps.BORDER_LEFT_WIDTH,
121
+ ViewProps.BORDER_RIGHT_WIDTH,
122
+ ViewProps.BORDER_TOP_WIDTH,
123
+ ViewProps.BORDER_BOTTOM_WIDTH,
124
+ ViewProps.BORDER_START_WIDTH,
125
+ ViewProps.BORDER_END_WIDTH,
126
+ };
127
+ private static final String[] BORDER_COLOR_PROPS = {
128
+ ViewProps.BORDER_COLOR,
129
+ ViewProps.BORDER_LEFT_COLOR,
130
+ ViewProps.BORDER_RIGHT_COLOR,
131
+ ViewProps.BORDER_TOP_COLOR,
132
+ ViewProps.BORDER_BOTTOM_COLOR,
133
+ ViewProps.BORDER_START_COLOR,
134
+ ViewProps.BORDER_END_COLOR,
135
+ ViewProps.BORDER_BLOCK_COLOR,
136
+ ViewProps.BORDER_BLOCK_END_COLOR,
137
+ ViewProps.BORDER_BLOCK_START_COLOR,
138
+ };
139
+ private static final int[] BORDER_COLOR_SPACING_TYPES = {
140
+ Spacing.ALL,
141
+ Spacing.LEFT,
142
+ Spacing.RIGHT,
143
+ Spacing.TOP,
144
+ Spacing.BOTTOM,
145
+ Spacing.START,
146
+ Spacing.END,
147
+ Spacing.BLOCK,
148
+ Spacing.BLOCK_END,
149
+ Spacing.BLOCK_START,
150
+ };
151
+
152
+ // A View style prop, as ReactViewManager sets it (borders and background
153
+ // through BackgroundStyleApplicator, which also clips to the padding box:
154
+ // see FastImageViewWithUrl.onDraw). Returns whether it was one.
155
+ private static boolean setViewStyleProperty(FastImageViewWithUrl view, String propName, @Nullable Object value) {
156
+ for (int i = 0; i < BORDER_RADIUS_PROPS.length; i++) {
157
+ if (BORDER_RADIUS_PROPS[i].equals(propName)) {
158
+ BackgroundStyleApplicator.setBorderRadius(
159
+ view,
160
+ BorderRadiusProp.values()[i],
161
+ LengthPercentage.setFromDynamic(new DynamicFromObject(value), false));
162
+ return true;
163
+ }
164
+ }
165
+ for (int i = 0; i < BORDER_WIDTH_PROPS.length; i++) {
166
+ if (BORDER_WIDTH_PROPS[i].equals(propName)) {
167
+ // A removed width is null, not NaN: React Native's border
168
+ // insets (0.83 to 0.87) keep NaN, which doesn't fall back to
169
+ // the width for every edge (borderWidth) as an unset one does.
170
+ BackgroundStyleApplicator.setBorderWidth(
171
+ view,
172
+ LogicalEdge.values()[i],
173
+ value instanceof Number ? Float.valueOf(((Number) value).floatValue()) : null);
174
+ view.updateBorderPadding();
175
+ return true;
176
+ }
177
+ }
178
+ for (int i = 0; i < BORDER_COLOR_PROPS.length; i++) {
179
+ if (BORDER_COLOR_PROPS[i].equals(propName)) {
180
+ BackgroundStyleApplicator.setBorderColor(
181
+ view,
182
+ LogicalEdge.fromSpacingType(BORDER_COLOR_SPACING_TYPES[i]),
183
+ value == null ? null : ColorPropConverter.getColor(value, view.getContext()));
184
+ return true;
185
+ }
186
+ }
187
+ switch (propName) {
188
+ // A gradient (backgroundImage; experimental_backgroundImage until
189
+ // React Native 0.87), drawn under the image as a View draws it.
190
+ // Its size, position and repeat aren't: BackgroundStyleApplicator's
191
+ // setters for them are internal to React Native.
192
+ case "backgroundImage":
193
+ case "experimental_backgroundImage":
194
+ BackgroundStyleApplicator.setBackgroundImage(view, backgroundImageLayers(value, view));
195
+ return true;
196
+ case "borderStyle":
197
+ BackgroundStyleApplicator.setBorderStyle(
198
+ view, value instanceof String ? BorderStyle.fromString((String) value) : null);
199
+ return true;
200
+ case ViewProps.OVERFLOW:
201
+ view.setOverflow(value instanceof String ? (String) value : null);
202
+ return true;
203
+ case ViewProps.POINTER_EVENTS:
204
+ view.setPointerEvents(
205
+ PointerEvents.parsePointerEvents(value instanceof String ? (String) value : null));
206
+ return true;
207
+ case "hitSlop":
208
+ view.setHitSlopRect(hitSlopRect(value));
209
+ return true;
210
+ case "backfaceVisibility":
211
+ view.setBackfaceVisibility(value instanceof String ? (String) value : "visible");
212
+ return true;
213
+ default:
214
+ return false;
215
+ }
216
+ }
217
+
218
+ // backgroundImage's layers, as ReactViewManager parses them, or null for
219
+ // none.
220
+ @Nullable
221
+ private static List<BackgroundImageLayer> backgroundImageLayers(@Nullable Object value, FastImageViewWithUrl view) {
222
+ if (!(value instanceof ReadableArray)) return null;
223
+ ReadableArray layers = (ReadableArray) value;
224
+ if (layers.size() == 0) return null;
225
+ List<BackgroundImageLayer> parsed = new ArrayList<>(layers.size());
226
+ for (int i = 0; i < layers.size(); i++) {
227
+ BackgroundImageLayer layer = BackgroundImageLayer.Companion.parse(layers.getMap(i), view.getContext());
228
+ if (layer != null) parsed.add(layer);
229
+ }
230
+ return parsed;
231
+ }
232
+
233
+ // hitSlop: a number for every side, or { top, left, bottom, right }, in dp.
234
+ @Nullable
235
+ private static Rect hitSlopRect(@Nullable Object value) {
236
+ Dynamic hitSlop = new DynamicFromObject(value);
237
+ if (hitSlop.getType() == ReadableType.Number) {
238
+ int px = Math.round(PixelUtil.toPixelFromDIP(hitSlop.asDouble()));
239
+ return new Rect(px, px, px, px);
240
+ }
241
+ if (hitSlop.getType() == ReadableType.Map) {
242
+ ReadableMap map = hitSlop.asMap();
243
+ return new Rect(px(map, "left"), px(map, "top"), px(map, "right"), px(map, "bottom"));
244
+ }
245
+ return null;
246
+ }
247
+
248
+ private static int px(ReadableMap map, String key) {
249
+ return map.hasKey(key) && !map.isNull(key) ? Math.round(PixelUtil.toPixelFromDIP(map.getDouble(key))) : 0;
250
+ }
251
+
252
+ // React Native passes the layout's padding and borders together, but
253
+ // padding doesn't inset the image: the view's padding is its border
254
+ // widths instead (FastImageViewWithUrl.updateBorderPadding), as on iOS.
255
+ @Override
256
+ public void setPadding(FastImageViewWithUrl view, int left, int top, int right, int bottom) {}
257
+
258
+ // backfaceVisibility hides the view by its opacity, as React Native's View
259
+ // does: these keep the two in step.
260
+ @Override
261
+ public void setOpacity(@NonNull FastImageViewWithUrl view, float opacity) {
262
+ view.setOpacityIfPossible(opacity);
263
+ }
264
+
265
+ @Override
266
+ protected void setTransformProperty(
267
+ @NonNull FastImageViewWithUrl view,
268
+ @Nullable ReadableArray transforms,
269
+ @Nullable ReadableArray transformOrigin) {
270
+ super.setTransformProperty(view, transforms, transformOrigin);
271
+ view.setBackfaceVisibilityDependantOpacity();
272
+ }
273
+
274
+ @NonNull
275
+ @Override
276
+ public String getName() {
277
+ return REACT_CLASS;
278
+ }
279
+
280
+ @Override
281
+ protected ViewManagerDelegate<FastImageViewWithUrl> getDelegate() {
282
+ return delegate;
283
+ }
284
+
285
+ @NonNull
286
+ @Override
287
+ protected FastImageViewWithUrl createViewInstance(@NonNull ThemedReactContext reactContext) {
288
+ // Each view keeps the RequestManager for its own Activity. A single one
289
+ // shared by all views kept the last view's Activity alive after it was
290
+ // destroyed, and gave views another Activity's manager (#492).
291
+ RequestManager requestManager = null;
292
+ FastImageGlide.get(reactContext);
293
+ if (isValidContextForGlide(reactContext)) {
294
+ requestManager = Glide.with(reactContext);
295
+ } else if (getActivityFromContext(reactContext) == null) {
296
+ // Not in an Activity (e.g. a root view created with the application
297
+ // context): load with the application context, instead of leaving
298
+ // requestManager null and never loading (#520).
299
+ requestManager = Glide.with(reactContext.getApplicationContext());
300
+ }
301
+
302
+ return new FastImageViewWithUrl(reactContext, requestManager);
303
+ }
304
+
305
+ @Override
306
+ public void setSource(FastImageViewWithUrl view, Dynamic source) {
307
+ view.setSource(source.isNull() ? null : source.asMap());
308
+ }
309
+
310
+ @Override
311
+ public void setSources(FastImageViewWithUrl view, Dynamic sources) {
312
+ view.setSources(sources.isNull() ? null : sources.asArray());
313
+ }
314
+
315
+ // The resolved asset's uri.
316
+ @Override
317
+ public void setDefaultSource(FastImageViewWithUrl view, Dynamic source) {
318
+ view.setDefaultSource(
319
+ ResourceDrawableIdHelper.getResourceDrawable(
320
+ view.getContext(), source.isNull() ? null : source.asString()));
321
+ }
322
+
323
+ @Override
324
+ public void setTintColor(FastImageViewWithUrl view, @Nullable Integer color) {
325
+ if (color == null) {
326
+ view.clearColorFilter();
327
+ } else {
328
+ view.setColorFilter(color, PorterDuff.Mode.SRC_IN);
329
+ }
330
+ }
331
+
332
+ @Override
333
+ public void setRecyclingKey(FastImageViewWithUrl view, @Nullable String recyclingKey) {
334
+ view.setRecyclingKey(recyclingKey);
335
+ }
336
+
337
+ @Override
338
+ public void setLoopCount(FastImageViewWithUrl view, int loopCount) {
339
+ view.setLoopCount(loopCount);
340
+ }
341
+
342
+ @Override
343
+ public void setImageRendering(FastImageViewWithUrl view, @Nullable String imageRendering) {
344
+ view.setImageRendering(imageRendering);
345
+ }
346
+
347
+ @Override
348
+ public void setBlurRadius(FastImageViewWithUrl view, float blurRadius) {
349
+ view.setBlurRadius(PixelUtil.toPixelFromDIP(blurRadius));
350
+ }
351
+
352
+ @Override
353
+ public void setTransitionDuration(FastImageViewWithUrl view, double transitionDuration) {
354
+ view.setTransitionDuration((int) transitionDuration);
355
+ }
356
+
357
+ @Override
358
+ public void setTransitionBetweenImages(FastImageViewWithUrl view, boolean betweenImages) {
359
+ view.setTransitionBetweenImages(betweenImages);
360
+ }
361
+
362
+ @Override
363
+ public void setTransitionSkipOnCacheHit(FastImageViewWithUrl view, @Nullable String skipOnCacheHit) {
364
+ view.setTransitionSkipOnCacheHit(skipOnCacheHit);
365
+ }
366
+
367
+ @Override
368
+ public void setPaused(FastImageViewWithUrl view, boolean paused) {
369
+ view.setPaused(paused);
370
+ }
371
+
372
+ @Override
373
+ public void setDownsample(FastImageViewWithUrl view, boolean downsample) {
374
+ view.setDownsample(downsample);
375
+ }
376
+
377
+ // Which events JS has a handler for (FastImageEvents); with onProgress,
378
+ // the view gets its download's progress (see onDownloadProgress).
379
+ @Override
380
+ public void setHandledEvents(FastImageViewWithUrl view, int handledEvents) {
381
+ view.setHandledEvents(handledEvents);
382
+ }
383
+
384
+ // objectPosition: each axis's fraction of the free space, and offset (in
385
+ // dp, as pixels for the view).
386
+ @Override
387
+ public void setObjectPositionX(FastImageViewWithUrl view, float value) {
388
+ view.setObjectPositionX(value);
389
+ }
390
+
391
+ @Override
392
+ public void setObjectPositionXOffset(FastImageViewWithUrl view, float value) {
393
+ view.setObjectPositionXOffset(PixelUtil.toPixelFromDIP(value));
394
+ }
395
+
396
+ @Override
397
+ public void setObjectPositionY(FastImageViewWithUrl view, float value) {
398
+ view.setObjectPositionY(value);
399
+ }
400
+
401
+ @Override
402
+ public void setObjectPositionYOffset(FastImageViewWithUrl view, float value) {
403
+ view.setObjectPositionYOffset(PixelUtil.toPixelFromDIP(value));
404
+ }
405
+
406
+ @Override
407
+ public void setObjectFit(FastImageViewWithUrl view, @Nullable String objectFit) {
408
+ view.setObjectFit(FastImageViewConverter.getScaleType(objectFit));
409
+ }
410
+
411
+ @Override
412
+ public void onDropViewInstance(@NonNull FastImageViewWithUrl view) {
413
+ // Cancels its request, and nothing loads or tracks progress for it
414
+ // any more (the progress map doesn't keep it, or its Activity).
415
+ view.drop();
416
+
417
+ super.onDropViewInstance(view);
418
+ }
419
+
420
+ @Override
421
+ public Map<String, Object> getExportedCustomDirectEventTypeConstants() {
422
+ Map<String, Object> events = new HashMap<>();
423
+ for (String name : new String[] {
424
+ REACT_ON_LOAD_START_EVENT,
425
+ REACT_ON_PROGRESS_EVENT,
426
+ REACT_ON_LOAD_EVENT,
427
+ REACT_ON_ERROR_EVENT,
428
+ REACT_ON_LOAD_END_EVENT,
429
+ }) {
430
+ Map<String, Object> event = new HashMap<>();
431
+ event.put("registrationName", name);
432
+ events.put(name, event);
433
+ }
434
+ return events;
435
+ }
436
+
437
+ // On the UI thread (FastImageViewWithUrl's updateProgressTracking).
438
+ static void trackProgress(String key, FastImageViewWithUrl view) {
439
+ synchronized (PROGRESS) {
440
+ Progress progress = PROGRESS.get(key);
441
+ if (progress == null) {
442
+ progress = new Progress();
443
+ PROGRESS.put(key, progress);
444
+ }
445
+ progress.views.add(view);
446
+ tracking = true;
447
+ }
448
+ }
449
+
450
+ // On the UI thread. A key's entry goes once it has no views.
451
+ static void untrackProgress(String key, FastImageViewWithUrl view) {
452
+ synchronized (PROGRESS) {
453
+ Progress progress = PROGRESS.get(key);
454
+ if (progress == null) return;
455
+ progress.views.remove(view);
456
+ if (progress.views.isEmpty()) PROGRESS.remove(key);
457
+ tracking = !PROGRESS.isEmpty();
458
+ }
459
+ }
460
+
461
+ // Whether any view gets progress now (a hint: a view can start loading
462
+ // just after).
463
+ static boolean wantsProgress() {
464
+ return tracking;
465
+ }
466
+
467
+ // The latest step downloads reported (for a load that starts now).
468
+ static long progressStep() {
469
+ return REPORTED_STEPS.get();
470
+ }
471
+
472
+ // A download's progress (FastImageSharedDownloads), on its thread, which
473
+ // only notes it if a view wants it (so downloads of images without an
474
+ // onProgress post nothing). It's sent from the UI thread, where
475
+ // views start and end their loads: a view only gets it during its own load,
476
+ // not after its onLoad or once it loads again, and progress posted during
477
+ // a download comes before the onLoad Glide posts after it.
478
+ static void onDownloadProgress(final String key, long loaded, long total) {
479
+ synchronized (PROGRESS) {
480
+ Progress progress = PROGRESS.get(key);
481
+ if (progress == null) return;
482
+ // While a post is waiting, it keeps the furthest step: another
483
+ // download of the key may report one behind it meanwhile. A step
484
+ // kept keeps its number, so a load that started after it (e.g. a
485
+ // reload, whose download reports behind it) doesn't take it.
486
+ if (!progress.posted || (double) loaded / total >= (double) progress.loaded / progress.total) {
487
+ progress.loaded = loaded;
488
+ progress.total = total;
489
+ progress.step = REPORTED_STEPS.incrementAndGet();
490
+ }
491
+ if (progress.posted) return;
492
+ progress.posted = true;
493
+ }
494
+ UiThreadUtil.runOnUiThread(new Runnable() {
495
+ @Override
496
+ public void run() {
497
+ sendProgress(key);
498
+ }
499
+ });
500
+ }
501
+
502
+ private static void sendProgress(String key) {
503
+ List<FastImageViewWithUrl> views;
504
+ long loaded;
505
+ long total;
506
+ long step;
507
+ synchronized (PROGRESS) {
508
+ Progress progress = PROGRESS.get(key);
509
+ // Sent already, or no view wants it any more.
510
+ if (progress == null || !progress.posted) return;
511
+ progress.posted = false;
512
+ loaded = progress.loaded;
513
+ total = progress.total;
514
+ step = progress.step;
515
+ views = new ArrayList<>(progress.views);
516
+ }
517
+ for (FastImageViewWithUrl view : views) {
518
+ if (!view.takesProgress(step, loaded, total)) continue;
519
+ WritableMap event = new WritableNativeMap();
520
+ event.putInt("loaded", (int) loaded);
521
+ event.putInt("total", (int) total);
522
+ // Downloads only report a known length (see FastImageSharedDownloads).
523
+ event.putDouble("progress", Math.min(1.0, Math.max(0.0, (double) loaded / total)));
524
+ try {
525
+ FastImageEvents.send(view, REACT_ON_PROGRESS_EVENT, event);
526
+ } catch (RuntimeException e) {
527
+ // E.g. the view's React instance is gone: progress isn't worth
528
+ // crashing the app for.
529
+ // (Not the key: it has the request's headers.)
530
+ Log.w(LOG_TAG, "Couldn't send onProgress", e);
531
+ }
532
+ }
533
+ }
534
+
535
+ private static boolean isValidContextForGlide(final Context context) {
536
+ Activity activity = getActivityFromContext(context);
537
+
538
+ if (activity == null) {
539
+ return false;
540
+ }
541
+
542
+ return !isActivityDestroyed(activity);
543
+ }
544
+
545
+ // A view's context is the ThemedReactContext it was created with, but below
546
+ // Android 5 (API 21) AppCompatImageView wraps it in a TintContextWrapper, so
547
+ // it can't be cast directly (#840). Unwrap until the ReactContext.
548
+ @Nullable
549
+ static ReactContext getReactContext(Context context) {
550
+ while (context instanceof ContextWrapper) {
551
+ if (context instanceof ReactContext) {
552
+ return (ReactContext) context;
553
+ }
554
+ context = ((ContextWrapper) context).getBaseContext();
555
+ }
556
+ return null;
557
+ }
558
+
559
+ private static Activity getActivityFromContext(final Context context) {
560
+ if (context instanceof Activity) {
561
+ return (Activity) context;
562
+ }
563
+
564
+ if (context instanceof ThemedReactContext) {
565
+ final Context baseContext = ((ThemedReactContext) context).getBaseContext();
566
+ if (baseContext instanceof Activity) {
567
+ return (Activity) baseContext;
568
+ }
569
+
570
+ if (baseContext instanceof ContextWrapper) {
571
+ final ContextWrapper contextWrapper = (ContextWrapper) baseContext;
572
+ final Context wrapperBaseContext = contextWrapper.getBaseContext();
573
+ if (wrapperBaseContext instanceof Activity) {
574
+ return (Activity) wrapperBaseContext;
575
+ }
576
+ }
577
+ }
578
+
579
+ return null;
580
+ }
581
+
582
+ private static boolean isActivityDestroyed(Activity activity) {
583
+ return activity.isDestroyed() || activity.isFinishing();
584
+ }
585
+
586
+ @Override
587
+ protected void onAfterUpdateTransaction(@NonNull FastImageViewWithUrl view) {
588
+ super.onAfterUpdateTransaction(view);
589
+ view.onAfterUpdate(this, view.requestManager);
590
+ }
591
+ }