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.
- package/README.md +87 -61
- package/RNFastImage.podspec +14 -9
- package/android/build.gradle +21 -47
- package/android/consumer-rules.pro +7 -7
- package/android/src/main/AndroidManifest.xml +1 -2
- package/android/src/main/java/com/{dylanvann/fastimage → fastimage}/FastImageAnimatable.java +1 -1
- package/android/src/main/java/com/{dylanvann/fastimage → fastimage}/FastImageAnimated.java +2 -2
- package/android/src/main/java/com/{dylanvann/fastimage → fastimage}/FastImageApng.java +2 -2
- package/android/src/main/java/com/{dylanvann/fastimage → fastimage}/FastImageBlur.java +4 -6
- package/android/src/main/java/com/{dylanvann/fastimage → fastimage}/FastImageCacheControl.java +1 -1
- package/android/src/main/java/com/{dylanvann/fastimage → fastimage}/FastImageCacheLimits.java +4 -1
- package/android/src/main/java/com/{dylanvann/fastimage → fastimage}/FastImageCacheWrite.java +1 -1
- package/android/src/main/java/com/fastimage/FastImageCrop.java +92 -0
- package/android/src/main/java/com/{dylanvann/fastimage → fastimage}/FastImageEvent.java +2 -3
- package/android/src/main/java/com/fastimage/FastImageEvents.java +74 -0
- package/android/src/main/java/com/{dylanvann/fastimage → fastimage}/FastImageGif.java +1 -1
- package/android/src/main/java/com/{dylanvann/fastimage → fastimage}/FastImageGlide.java +3 -3
- package/android/src/main/java/com/{dylanvann/fastimage → fastimage}/FastImageGlideModule.java +1 -1
- package/android/src/main/java/com/{dylanvann/fastimage → fastimage}/FastImageKeyedGlideUrl.java +1 -1
- package/android/src/main/java/com/{dylanvann/fastimage → fastimage}/FastImageOkHttpProgressGlideModule.java +36 -9
- package/android/src/main/java/com/{dylanvann/fastimage → fastimage}/FastImageRequestListener.java +6 -1
- package/android/src/main/java/com/{dylanvann/fastimage → fastimage}/FastImageSharedDownloads.java +1 -1
- package/android/src/main/java/com/{dylanvann/fastimage → fastimage}/FastImageSource.java +1 -1
- package/android/src/main/java/com/{dylanvann/fastimage → fastimage}/FastImageSourceSize.java +12 -2
- package/android/src/main/java/com/{dylanvann/fastimage → fastimage}/FastImageSvg.java +3 -3
- package/android/src/main/java/com/{dylanvann/fastimage → fastimage}/FastImageSvgRenderer.java +1 -1
- package/android/src/main/java/com/{dylanvann/fastimage → fastimage}/FastImageUrl.java +1 -1
- package/android/src/main/java/com/{dylanvann/fastimage → fastimage}/FastImageViewConverter.java +7 -8
- package/android/src/main/java/com/fastimage/FastImageViewManager.java +591 -0
- package/android/src/main/java/com/{dylanvann/fastimage → fastimage}/FastImageViewModule.java +31 -28
- package/android/src/main/java/com/fastimage/FastImageViewPackage.java +51 -0
- package/android/src/main/java/com/{dylanvann/fastimage → fastimage}/FastImageViewWithUrl.java +362 -129
- package/android/src/main/java/com/{dylanvann/fastimage → fastimage}/FastImageWebGlideUrl.java +1 -1
- package/app.plugin.js +2 -7
- package/dist/index.cjs.js +276 -157
- package/dist/index.cjs.js.flow +33 -17
- package/dist/index.cjs.web.js +211 -61
- package/dist/index.cjs.web.js.flow +33 -17
- package/dist/index.d.ts +83 -58
- package/dist/index.js +278 -159
- package/dist/index.js.flow +33 -17
- package/dist/index.web.js +212 -62
- package/dist/index.web.js.flow +33 -17
- package/dist/objectFit.d.ts +2 -2
- package/dist/objectPosition.d.ts +42 -0
- package/dist/specs/FastImageViewNativeComponent.d.ts +48 -0
- package/dist/specs/NativeFastImageModule.d.ts +11 -0
- package/dist/warnings.d.ts +3 -0
- package/ios/FastImage/FFFDownsampledImage.h +4 -8
- package/ios/FastImage/FFFDownsampledImage.m +0 -34
- package/ios/FastImage/FFFastImageModule.h +8 -0
- package/ios/FastImage/{FFFastImageViewManager.m → FFFastImageModule.mm} +70 -59
- package/ios/FastImage/FFFastImageView.h +36 -7
- package/ios/FastImage/FFFastImageView.m +201 -164
- package/ios/FastImage/FFFastImageViewComponentView.h +11 -0
- package/ios/FastImage/FFFastImageViewComponentView.mm +325 -0
- package/ios/FastImage/RCTConvert+FFFastImage.h +1 -1
- package/ios/FastImage/RCTConvert+FFFastImage.m +7 -10
- package/package.json +28 -9
- package/src/constants.ts +28 -0
- package/src/index.js.flow +179 -0
- package/src/index.tsx +1103 -0
- package/src/index.web.tsx +548 -0
- package/src/objectFit.ts +48 -0
- package/src/objectPosition.ts +243 -0
- package/src/specs/FastImageViewNativeComponent.ts +83 -0
- package/src/specs/NativeFastImageModule.ts +35 -0
- package/src/warnings.ts +37 -0
- package/android/src/main/AndroidManifestNew.xml +0 -3
- package/android/src/main/java/com/dylanvann/fastimage/FastImageCookieHandler.java +0 -63
- package/android/src/main/java/com/dylanvann/fastimage/FastImageEvents.java +0 -49
- package/android/src/main/java/com/dylanvann/fastimage/FastImageProgressListener.java +0 -18
- package/android/src/main/java/com/dylanvann/fastimage/FastImageShadowNode.java +0 -21
- package/android/src/main/java/com/dylanvann/fastimage/FastImageViewManager.java +0 -379
- package/android/src/main/java/com/dylanvann/fastimage/FastImageViewPackage.java +0 -27
- 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
|
+
}
|