react-native-fast-image 8.14.0 → 8.16.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 +29 -2
- package/android/src/main/java/com/dylanvann/fastimage/FastImageEvents.java +18 -0
- package/android/src/main/java/com/dylanvann/fastimage/FastImageKeyedGlideUrl.java +37 -0
- package/android/src/main/java/com/dylanvann/fastimage/FastImageRequestListener.java +4 -3
- package/android/src/main/java/com/dylanvann/fastimage/FastImageSource.java +17 -3
- package/android/src/main/java/com/dylanvann/fastimage/FastImageViewConverter.java +4 -0
- package/android/src/main/java/com/dylanvann/fastimage/FastImageViewWithUrl.java +3 -3
- package/dist/index.cjs.js +36 -4
- package/dist/index.cjs.js.flow +6 -1
- package/dist/index.d.ts +19 -1
- package/dist/index.js +37 -5
- package/dist/index.js.flow +6 -1
- package/ios/FastImage/FFFDownsampledImage.h +2 -2
- package/ios/FastImage/FFFDownsampledImage.m +4 -4
- package/ios/FastImage/FFFastImageSource.h +6 -0
- package/ios/FastImage/FFFastImageSource.m +12 -0
- package/ios/FastImage/FFFastImageView.m +16 -6
- package/ios/FastImage/FFFastImageViewManager.m +10 -1
- package/ios/FastImage/RCTConvert+FFFastImage.m +2 -0
- package/package.json +1 -1
package/README.md
CHANGED
|
@@ -146,6 +146,23 @@ Indicates the load order priority of an image. Images with `FastImage.priority.h
|
|
|
146
146
|
|
|
147
147
|
---
|
|
148
148
|
|
|
149
|
+
### `source.cacheKey?: string`
|
|
150
|
+
|
|
151
|
+
The key the image is cached under, instead of its uri. Use it for urls that change while the image stays the same, e.g. signed urls with a token or an expiry. Use something that identifies the image, and change it when the image changes (e.g. include a version or the time it was updated), or the old image keeps showing:
|
|
152
|
+
|
|
153
|
+
```jsx
|
|
154
|
+
<FastImage
|
|
155
|
+
source={{
|
|
156
|
+
uri: signedUrl,
|
|
157
|
+
cacheKey: `avatar-${user.id}-${user.avatarUpdatedAt}`,
|
|
158
|
+
}}
|
|
159
|
+
/>
|
|
160
|
+
```
|
|
161
|
+
|
|
162
|
+
Not used with `cache: 'web'`, which follows the HTTP cache (keyed by url).
|
|
163
|
+
|
|
164
|
+
---
|
|
165
|
+
|
|
149
166
|
### `defaultSource?: number`
|
|
150
167
|
|
|
151
168
|
- An asset loaded with `require(...)`.
|
|
@@ -242,9 +259,19 @@ e.g. `onError={e => console.log(e.nativeEvent.error)}`
|
|
|
242
259
|
|
|
243
260
|
---
|
|
244
261
|
|
|
245
|
-
### `onLoadEnd?: () => void`
|
|
262
|
+
### `onLoadEnd?: (result) => void`
|
|
263
|
+
|
|
264
|
+
Called when the image finishes loading, whether it was successful or an error, with the result: `{ ok: true, width, height }` (the image's size, as `onLoad` gets it) or `{ ok: false, error }` (as `onError` gets it). TypeScript makes you check `ok` before reading the size or the error.
|
|
246
265
|
|
|
247
|
-
|
|
266
|
+
```jsx
|
|
267
|
+
<FastImage
|
|
268
|
+
source={{ uri }}
|
|
269
|
+
onLoadEnd={(result) => {
|
|
270
|
+
if (result.ok) setAspectRatio(result.width / result.height)
|
|
271
|
+
else setFailed(result.error)
|
|
272
|
+
}}
|
|
273
|
+
/>
|
|
274
|
+
```
|
|
248
275
|
|
|
249
276
|
---
|
|
250
277
|
|
|
@@ -26,6 +26,24 @@ final class FastImageEvents {
|
|
|
26
26
|
send(view, name, Arguments.createMap());
|
|
27
27
|
}
|
|
28
28
|
|
|
29
|
+
// onLoadEnd with the load's result: ok with the image's size, as onLoad
|
|
30
|
+
// sent it.
|
|
31
|
+
static void sendLoadEnd(View view, int width, int height) {
|
|
32
|
+
WritableMap event = Arguments.createMap();
|
|
33
|
+
event.putBoolean("ok", true);
|
|
34
|
+
event.putInt("width", width);
|
|
35
|
+
event.putInt("height", height);
|
|
36
|
+
send(view, FastImageRequestListener.REACT_ON_LOAD_END_EVENT, event);
|
|
37
|
+
}
|
|
38
|
+
|
|
39
|
+
// onLoadEnd with the load's result: not ok, with the error onError sent.
|
|
40
|
+
static void sendLoadEnd(View view, String error) {
|
|
41
|
+
WritableMap event = Arguments.createMap();
|
|
42
|
+
event.putBoolean("ok", false);
|
|
43
|
+
event.putString("error", error);
|
|
44
|
+
send(view, FastImageRequestListener.REACT_ON_LOAD_END_EVENT, event);
|
|
45
|
+
}
|
|
46
|
+
|
|
29
47
|
static void send(View view, String name, WritableMap data) {
|
|
30
48
|
ReactContext context = FastImageViewManager.getReactContext(view.getContext());
|
|
31
49
|
if (context == null) return;
|
|
@@ -0,0 +1,37 @@
|
|
|
1
|
+
package com.dylanvann.fastimage;
|
|
2
|
+
|
|
3
|
+
import androidx.annotation.NonNull;
|
|
4
|
+
import androidx.annotation.Nullable;
|
|
5
|
+
|
|
6
|
+
import com.bumptech.glide.load.model.GlideUrl;
|
|
7
|
+
import com.bumptech.glide.load.model.Headers;
|
|
8
|
+
|
|
9
|
+
// A url cached under the source's `cacheKey` instead of the url. Glide keys
|
|
10
|
+
// its disk cache by getCacheKey(), and its memory cache by equals() and
|
|
11
|
+
// hashCode(), which also compare the headers: here they only use the key, so
|
|
12
|
+
// a url whose token or headers change still hits both caches. The request
|
|
13
|
+
// still uses the url and headers.
|
|
14
|
+
class FastImageKeyedGlideUrl extends GlideUrl {
|
|
15
|
+
private final String mCacheKey;
|
|
16
|
+
|
|
17
|
+
FastImageKeyedGlideUrl(String url, Headers headers, @NonNull String cacheKey) {
|
|
18
|
+
super(url, headers);
|
|
19
|
+
mCacheKey = cacheKey;
|
|
20
|
+
}
|
|
21
|
+
|
|
22
|
+
@Override
|
|
23
|
+
public String getCacheKey() {
|
|
24
|
+
return mCacheKey;
|
|
25
|
+
}
|
|
26
|
+
|
|
27
|
+
@Override
|
|
28
|
+
public boolean equals(@Nullable Object other) {
|
|
29
|
+
return other instanceof FastImageKeyedGlideUrl
|
|
30
|
+
&& mCacheKey.equals(((FastImageKeyedGlideUrl) other).mCacheKey);
|
|
31
|
+
}
|
|
32
|
+
|
|
33
|
+
@Override
|
|
34
|
+
public int hashCode() {
|
|
35
|
+
return mCacheKey.hashCode();
|
|
36
|
+
}
|
|
37
|
+
}
|
|
@@ -41,10 +41,11 @@ public class FastImageRequestListener implements RequestListener<Drawable> {
|
|
|
41
41
|
}
|
|
42
42
|
FastImageViewWithUrl view = (FastImageViewWithUrl) ((ImageViewTarget) target).getView();
|
|
43
43
|
view.onImageFailed(thumbnail);
|
|
44
|
+
String error = errorMessage(e);
|
|
44
45
|
WritableMap event = new WritableNativeMap();
|
|
45
|
-
event.putString("error",
|
|
46
|
+
event.putString("error", error);
|
|
46
47
|
FastImageEvents.send(view, REACT_ON_ERROR_EVENT, event);
|
|
47
|
-
FastImageEvents.
|
|
48
|
+
FastImageEvents.sendLoadEnd(view, error);
|
|
48
49
|
return false;
|
|
49
50
|
}
|
|
50
51
|
|
|
@@ -98,6 +99,6 @@ public class FastImageRequestListener implements RequestListener<Drawable> {
|
|
|
98
99
|
event.putInt("width", size[0]);
|
|
99
100
|
event.putInt("height", size[1]);
|
|
100
101
|
FastImageEvents.send(view, REACT_ON_LOAD_EVENT, event);
|
|
101
|
-
FastImageEvents.
|
|
102
|
+
FastImageEvents.sendLoadEnd(view, size[0], size[1]);
|
|
102
103
|
}
|
|
103
104
|
}
|
|
@@ -124,9 +124,23 @@ public class FastImageSource extends ImageSource {
|
|
|
124
124
|
mWebCache = webCache;
|
|
125
125
|
}
|
|
126
126
|
|
|
127
|
+
// `cacheKey`: the key to cache the image under instead of its url.
|
|
128
|
+
@Nullable
|
|
129
|
+
private String mCacheKey = null;
|
|
130
|
+
|
|
131
|
+
void setCacheKey(@Nullable String cacheKey) {
|
|
132
|
+
mCacheKey = cacheKey == null || cacheKey.isEmpty() ? null : cacheKey;
|
|
133
|
+
}
|
|
134
|
+
|
|
127
135
|
public GlideUrl getGlideUrl() {
|
|
128
|
-
|
|
129
|
-
|
|
130
|
-
|
|
136
|
+
String url = getUri().toString();
|
|
137
|
+
if (mWebCache) {
|
|
138
|
+
// Follows the HTTP cache, which is keyed by url.
|
|
139
|
+
return new FastImageWebGlideUrl(url, getHeaders());
|
|
140
|
+
}
|
|
141
|
+
if (mCacheKey != null) {
|
|
142
|
+
return new FastImageKeyedGlideUrl(url, getHeaders(), mCacheKey);
|
|
143
|
+
}
|
|
144
|
+
return new GlideUrl(url, getHeaders());
|
|
131
145
|
}
|
|
132
146
|
}
|
|
@@ -19,6 +19,7 @@ import com.facebook.react.bridge.JSApplicationIllegalArgumentException;
|
|
|
19
19
|
import com.facebook.react.bridge.NoSuchKeyException;
|
|
20
20
|
import com.facebook.react.bridge.ReadableMap;
|
|
21
21
|
import com.facebook.react.bridge.ReadableMapKeySetIterator;
|
|
22
|
+
import com.facebook.react.bridge.ReadableType;
|
|
22
23
|
|
|
23
24
|
import java.util.HashMap;
|
|
24
25
|
import java.util.Map;
|
|
@@ -63,6 +64,9 @@ class FastImageViewConverter {
|
|
|
63
64
|
if (source == null) return null;
|
|
64
65
|
FastImageSource imageSource = new FastImageSource(context, source.getString("uri"), getHeaders(source));
|
|
65
66
|
imageSource.setWebCache(getCacheControl(source) == FastImageCacheControl.WEB);
|
|
67
|
+
if (source.hasKey("cacheKey") && source.getType("cacheKey") == ReadableType.String) {
|
|
68
|
+
imageSource.setCacheKey(source.getString("cacheKey"));
|
|
69
|
+
}
|
|
66
70
|
return imageSource;
|
|
67
71
|
}
|
|
68
72
|
|
|
@@ -1,7 +1,6 @@
|
|
|
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;
|
|
5
4
|
|
|
6
5
|
import android.annotation.SuppressLint;
|
|
7
6
|
import android.content.Context;
|
|
@@ -376,10 +375,11 @@ class FastImageViewWithUrl extends AppCompatImageView {
|
|
|
376
375
|
// resolved: fire onError and onLoadEnd and show defaultSource, as on iOS
|
|
377
376
|
// (#1028, #945).
|
|
378
377
|
if (mSource != null && (imageSource == null || imageSource.getUri().toString().length() == 0)) {
|
|
378
|
+
String error = "Invalid source: " + mSource;
|
|
379
379
|
WritableMap event = new WritableNativeMap();
|
|
380
|
-
event.putString("error",
|
|
380
|
+
event.putString("error", error);
|
|
381
381
|
FastImageEvents.send(this, REACT_ON_ERROR_EVENT, event);
|
|
382
|
-
FastImageEvents.
|
|
382
|
+
FastImageEvents.sendLoadEnd(this, error);
|
|
383
383
|
|
|
384
384
|
// Cancel existing requests.
|
|
385
385
|
clearView(requestManager);
|
package/dist/index.cjs.js
CHANGED
|
@@ -73,12 +73,24 @@ function loopCount(loop) {
|
|
|
73
73
|
if (loop === false) return 1;
|
|
74
74
|
return Number.isFinite(loop) ? Math.max(1, Math.floor(loop)) : 0;
|
|
75
75
|
}
|
|
76
|
+
function loadResult(event) {
|
|
77
|
+
var _event$width, _event$height, _event$error;
|
|
78
|
+
return event.ok ? {
|
|
79
|
+
ok: true,
|
|
80
|
+
width: (_event$width = event.width) !== null && _event$width !== void 0 ? _event$width : 0,
|
|
81
|
+
height: (_event$height = event.height) !== null && _event$height !== void 0 ? _event$height : 0
|
|
82
|
+
} : {
|
|
83
|
+
ok: false,
|
|
84
|
+
error: (_event$error = event.error) !== null && _event$error !== void 0 ? _event$error : "Failed to load the image"
|
|
85
|
+
};
|
|
86
|
+
}
|
|
76
87
|
function withoutCache(source) {
|
|
77
88
|
const { cache: _cache, ...rest } = source || {};
|
|
78
89
|
return rest;
|
|
79
90
|
}
|
|
80
91
|
function FastImageBase({ source, defaultSource, tintColor, onLoadStart, onProgress, onLoad, onError, onLoadEnd, style, fallback, children, resizeMode = "cover", loop, forwardedRef, onLayout, pointerEvents, ...viewProps }) {
|
|
81
92
|
const { onClick, ...props } = viewProps;
|
|
93
|
+
const fallbackResult = (0, react.useRef)(void 0);
|
|
82
94
|
const wrapperProps = {
|
|
83
95
|
onLayout,
|
|
84
96
|
onClick,
|
|
@@ -103,9 +115,29 @@ function FastImageBase({ source, defaultSource, tintColor, onLoadStart, onProgre
|
|
|
103
115
|
defaultSource,
|
|
104
116
|
onLoadStart,
|
|
105
117
|
onProgress,
|
|
106
|
-
onLoad
|
|
107
|
-
|
|
108
|
-
|
|
118
|
+
onLoad: onLoadEnd ? (event) => {
|
|
119
|
+
const { width, height } = event.nativeEvent.source;
|
|
120
|
+
fallbackResult.current = {
|
|
121
|
+
ok: true,
|
|
122
|
+
width,
|
|
123
|
+
height
|
|
124
|
+
};
|
|
125
|
+
onLoad === null || onLoad === void 0 || onLoad(event);
|
|
126
|
+
} : onLoad,
|
|
127
|
+
onError: onLoadEnd ? (event) => {
|
|
128
|
+
fallbackResult.current = {
|
|
129
|
+
ok: false,
|
|
130
|
+
error: String(event.nativeEvent.error)
|
|
131
|
+
};
|
|
132
|
+
onError === null || onError === void 0 || onError(event);
|
|
133
|
+
} : onError,
|
|
134
|
+
onLoadEnd: onLoadEnd && (() => {
|
|
135
|
+
var _fallbackResult$curre;
|
|
136
|
+
return onLoadEnd((_fallbackResult$curre = fallbackResult.current) !== null && _fallbackResult$curre !== void 0 ? _fallbackResult$curre : {
|
|
137
|
+
ok: false,
|
|
138
|
+
error: "Failed to load the image"
|
|
139
|
+
});
|
|
140
|
+
}),
|
|
109
141
|
resizeMode
|
|
110
142
|
}), children);
|
|
111
143
|
}
|
|
@@ -126,7 +158,7 @@ function FastImageBase({ source, defaultSource, tintColor, onLoadStart, onProgre
|
|
|
126
158
|
onFastImageProgress: onProgress,
|
|
127
159
|
onFastImageLoad: onLoad,
|
|
128
160
|
onFastImageError: onError,
|
|
129
|
-
onFastImageLoadEnd: onLoadEnd,
|
|
161
|
+
onFastImageLoadEnd: onLoadEnd && ((event) => onLoadEnd(loadResult(event.nativeEvent))),
|
|
130
162
|
resizeMode
|
|
131
163
|
}), children);
|
|
132
164
|
}
|
package/dist/index.cjs.js.flow
CHANGED
|
@@ -49,6 +49,10 @@ export type ResizeModes = $Values<ResizeMode>
|
|
|
49
49
|
export type Priorities = $Values<Priority>
|
|
50
50
|
export type CacheControls = $Values<CacheControl>
|
|
51
51
|
|
|
52
|
+
export type LoadResult =
|
|
53
|
+
| {| ok: true, width: number, height: number |}
|
|
54
|
+
| {| ok: false, error: string |}
|
|
55
|
+
|
|
52
56
|
export type PreloadSuccess = {|
|
|
53
57
|
uri: string,
|
|
54
58
|
ok: true,
|
|
@@ -72,13 +76,14 @@ export type FastImageSource = {
|
|
|
72
76
|
headers?: Object,
|
|
73
77
|
priority?: Priorities,
|
|
74
78
|
cache?: CacheControls,
|
|
79
|
+
cacheKey?: string,
|
|
75
80
|
}
|
|
76
81
|
|
|
77
82
|
export type FastImageProps = $ReadOnly<{|
|
|
78
83
|
...ViewProps,
|
|
79
84
|
onError?: ?(event: OnErrorEvent) => void,
|
|
80
85
|
onLoad?: ?(event: OnLoadEvent) => void,
|
|
81
|
-
onLoadEnd?: ?() => void,
|
|
86
|
+
onLoadEnd?: ?(result: LoadResult) => void,
|
|
82
87
|
onLoadStart?: ?() => void,
|
|
83
88
|
onProgress?: ?(event: OnProgressEvent) => void,
|
|
84
89
|
|
package/dist/index.d.ts
CHANGED
|
@@ -27,6 +27,12 @@ export type Source = {
|
|
|
27
27
|
};
|
|
28
28
|
priority?: Priority;
|
|
29
29
|
cache?: Cache;
|
|
30
|
+
/**
|
|
31
|
+
* The key the image is cached under, instead of its uri. For urls that
|
|
32
|
+
* change while the image stays the same, e.g. signed urls with a token or
|
|
33
|
+
* an expiry: use something stable, like the image's id.
|
|
34
|
+
*/
|
|
35
|
+
cacheKey?: string;
|
|
30
36
|
};
|
|
31
37
|
export interface OnLoadEvent {
|
|
32
38
|
nativeEvent: {
|
|
@@ -40,6 +46,14 @@ export interface OnErrorEvent {
|
|
|
40
46
|
error: string;
|
|
41
47
|
};
|
|
42
48
|
}
|
|
49
|
+
export type LoadResult = {
|
|
50
|
+
ok: true;
|
|
51
|
+
width: number;
|
|
52
|
+
height: number;
|
|
53
|
+
} | {
|
|
54
|
+
ok: false;
|
|
55
|
+
error: string;
|
|
56
|
+
};
|
|
43
57
|
export interface OnProgressEvent {
|
|
44
58
|
nativeEvent: {
|
|
45
59
|
loaded: number;
|
|
@@ -105,7 +119,11 @@ export interface FastImageProps extends AccessibilityProps, ViewProps {
|
|
|
105
119
|
onProgress?(event: OnProgressEvent): void;
|
|
106
120
|
onLoad?(event: OnLoadEvent): void;
|
|
107
121
|
onError?(event: OnErrorEvent): void;
|
|
108
|
-
|
|
122
|
+
/**
|
|
123
|
+
* Called once the image has loaded or failed to, with the result: `ok` and
|
|
124
|
+
* the image's size, or not `ok` and the error.
|
|
125
|
+
*/
|
|
126
|
+
onLoadEnd?(result: LoadResult): void;
|
|
109
127
|
/**
|
|
110
128
|
* onLayout function
|
|
111
129
|
*
|
package/dist/index.js
CHANGED
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import React, { forwardRef, memo } from "react";
|
|
1
|
+
import React, { forwardRef, memo, useRef } from "react";
|
|
2
2
|
import { Image, NativeModules, Platform, StyleSheet, View, requireNativeComponent } from "react-native";
|
|
3
3
|
//#region src/index.tsx
|
|
4
4
|
const resizeMode = {
|
|
@@ -46,12 +46,24 @@ function loopCount(loop) {
|
|
|
46
46
|
if (loop === false) return 1;
|
|
47
47
|
return Number.isFinite(loop) ? Math.max(1, Math.floor(loop)) : 0;
|
|
48
48
|
}
|
|
49
|
+
function loadResult(event) {
|
|
50
|
+
var _event$width, _event$height, _event$error;
|
|
51
|
+
return event.ok ? {
|
|
52
|
+
ok: true,
|
|
53
|
+
width: (_event$width = event.width) !== null && _event$width !== void 0 ? _event$width : 0,
|
|
54
|
+
height: (_event$height = event.height) !== null && _event$height !== void 0 ? _event$height : 0
|
|
55
|
+
} : {
|
|
56
|
+
ok: false,
|
|
57
|
+
error: (_event$error = event.error) !== null && _event$error !== void 0 ? _event$error : "Failed to load the image"
|
|
58
|
+
};
|
|
59
|
+
}
|
|
49
60
|
function withoutCache(source) {
|
|
50
61
|
const { cache: _cache, ...rest } = source || {};
|
|
51
62
|
return rest;
|
|
52
63
|
}
|
|
53
64
|
function FastImageBase({ source, defaultSource, tintColor, onLoadStart, onProgress, onLoad, onError, onLoadEnd, style, fallback, children, resizeMode = "cover", loop, forwardedRef, onLayout, pointerEvents, ...viewProps }) {
|
|
54
65
|
const { onClick, ...props } = viewProps;
|
|
66
|
+
const fallbackResult = useRef(void 0);
|
|
55
67
|
const wrapperProps = {
|
|
56
68
|
onLayout,
|
|
57
69
|
onClick,
|
|
@@ -76,9 +88,29 @@ function FastImageBase({ source, defaultSource, tintColor, onLoadStart, onProgre
|
|
|
76
88
|
defaultSource,
|
|
77
89
|
onLoadStart,
|
|
78
90
|
onProgress,
|
|
79
|
-
onLoad
|
|
80
|
-
|
|
81
|
-
|
|
91
|
+
onLoad: onLoadEnd ? (event) => {
|
|
92
|
+
const { width, height } = event.nativeEvent.source;
|
|
93
|
+
fallbackResult.current = {
|
|
94
|
+
ok: true,
|
|
95
|
+
width,
|
|
96
|
+
height
|
|
97
|
+
};
|
|
98
|
+
onLoad === null || onLoad === void 0 || onLoad(event);
|
|
99
|
+
} : onLoad,
|
|
100
|
+
onError: onLoadEnd ? (event) => {
|
|
101
|
+
fallbackResult.current = {
|
|
102
|
+
ok: false,
|
|
103
|
+
error: String(event.nativeEvent.error)
|
|
104
|
+
};
|
|
105
|
+
onError === null || onError === void 0 || onError(event);
|
|
106
|
+
} : onError,
|
|
107
|
+
onLoadEnd: onLoadEnd && (() => {
|
|
108
|
+
var _fallbackResult$curre;
|
|
109
|
+
return onLoadEnd((_fallbackResult$curre = fallbackResult.current) !== null && _fallbackResult$curre !== void 0 ? _fallbackResult$curre : {
|
|
110
|
+
ok: false,
|
|
111
|
+
error: "Failed to load the image"
|
|
112
|
+
});
|
|
113
|
+
}),
|
|
82
114
|
resizeMode
|
|
83
115
|
}), children);
|
|
84
116
|
}
|
|
@@ -99,7 +131,7 @@ function FastImageBase({ source, defaultSource, tintColor, onLoadStart, onProgre
|
|
|
99
131
|
onFastImageProgress: onProgress,
|
|
100
132
|
onFastImageLoad: onLoad,
|
|
101
133
|
onFastImageError: onError,
|
|
102
|
-
onFastImageLoadEnd: onLoadEnd,
|
|
134
|
+
onFastImageLoadEnd: onLoadEnd && ((event) => onLoadEnd(loadResult(event.nativeEvent))),
|
|
103
135
|
resizeMode
|
|
104
136
|
}), children);
|
|
105
137
|
}
|
package/dist/index.js.flow
CHANGED
|
@@ -49,6 +49,10 @@ export type ResizeModes = $Values<ResizeMode>
|
|
|
49
49
|
export type Priorities = $Values<Priority>
|
|
50
50
|
export type CacheControls = $Values<CacheControl>
|
|
51
51
|
|
|
52
|
+
export type LoadResult =
|
|
53
|
+
| {| ok: true, width: number, height: number |}
|
|
54
|
+
| {| ok: false, error: string |}
|
|
55
|
+
|
|
52
56
|
export type PreloadSuccess = {|
|
|
53
57
|
uri: string,
|
|
54
58
|
ok: true,
|
|
@@ -72,13 +76,14 @@ export type FastImageSource = {
|
|
|
72
76
|
headers?: Object,
|
|
73
77
|
priority?: Priorities,
|
|
74
78
|
cache?: CacheControls,
|
|
79
|
+
cacheKey?: string,
|
|
75
80
|
}
|
|
76
81
|
|
|
77
82
|
export type FastImageProps = $ReadOnly<{|
|
|
78
83
|
...ViewProps,
|
|
79
84
|
onError?: ?(event: OnErrorEvent) => void,
|
|
80
85
|
onLoad?: ?(event: OnLoadEvent) => void,
|
|
81
|
-
onLoadEnd?: ?() => void,
|
|
86
|
+
onLoadEnd?: ?(result: LoadResult) => void,
|
|
82
87
|
onLoadStart?: ?() => void,
|
|
83
88
|
onProgress?: ?(event: OnProgressEvent) => void,
|
|
84
89
|
|
|
@@ -14,8 +14,8 @@
|
|
|
14
14
|
+ (NSURL*) loadURLForURL: (NSURL*)url;
|
|
15
15
|
|
|
16
16
|
// Sets a load's context to decode the image for a box (in pixels) to cover
|
|
17
|
-
// or fit in.
|
|
18
|
-
+ (void) addToContext: (SDWebImageMutableContext*)context
|
|
17
|
+
// or fit in. `key` is the source's cache key (its url, or its cacheKey).
|
|
18
|
+
+ (void) addToContext: (SDWebImageMutableContext*)context forKey: (NSString*)key box: (CGSize)box cover: (BOOL)cover;
|
|
19
19
|
|
|
20
20
|
// The size of the full image (in points, like UIImage's size) that an image
|
|
21
21
|
// loaded this way was decoded from, for onLoad, or zero for other images.
|
|
@@ -100,11 +100,11 @@ static CGSize FFFPixelSize(NSData* data) {
|
|
|
100
100
|
return components.URL ?: url;
|
|
101
101
|
}
|
|
102
102
|
|
|
103
|
-
+ (void) addToContext: (SDWebImageMutableContext*)context
|
|
103
|
+
+ (void) addToContext: (SDWebImageMutableContext*)context forKey: (NSString*)key box: (CGSize)box cover: (BOOL)cover {
|
|
104
104
|
context[SDWebImageContextAnimatedImageClass] = [FFFDownsampledImage class];
|
|
105
|
-
// Cached under the
|
|
106
|
-
// keeps one download for every size and for loads
|
|
107
|
-
|
|
105
|
+
// Cached under the source's own key (not the url from loadURLForURL:),
|
|
106
|
+
// so the disk cache keeps one download for every size and for loads
|
|
107
|
+
// that aren't downsampled.
|
|
108
108
|
context[SDWebImageContextCacheKeyFilter] = [SDWebImageCacheKeyFilter cacheKeyFilterWithBlock: ^NSString* (NSURL* _Nonnull loadURL) {
|
|
109
109
|
return key;
|
|
110
110
|
}];
|
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
#import <Foundation/Foundation.h>
|
|
2
2
|
#import <UIKit/UIKit.h>
|
|
3
3
|
#import <SDWebImage/SDWebImageDownloaderRequestModifier.h>
|
|
4
|
+
#import <SDWebImage/SDWebImageCacheKeyFilter.h>
|
|
4
5
|
|
|
5
6
|
typedef NS_ENUM(NSInteger, FFFPriority) {
|
|
6
7
|
FFFPriorityLow,
|
|
@@ -27,6 +28,8 @@ typedef NS_ENUM(NSInteger, FFFCacheControl) {
|
|
|
27
28
|
@property (nonatomic) NSDictionary *headers;
|
|
28
29
|
// cache control mode
|
|
29
30
|
@property (nonatomic) FFFCacheControl cacheControl;
|
|
31
|
+
// The key to cache the image under instead of its url, or nil.
|
|
32
|
+
@property (nonatomic, copy) NSString *cacheKey;
|
|
30
33
|
|
|
31
34
|
- (instancetype)initWithURL:(NSURL *)url
|
|
32
35
|
priority:(FFFPriority)priority
|
|
@@ -36,4 +39,7 @@ typedef NS_ENUM(NSInteger, FFFCacheControl) {
|
|
|
36
39
|
// Adds this source's headers to its image requests.
|
|
37
40
|
- (SDWebImageDownloaderRequestModifier *)requestModifier;
|
|
38
41
|
|
|
42
|
+
// Caches the image under cacheKey, or nil without one.
|
|
43
|
+
- (SDWebImageCacheKeyFilter *)cacheKeyFilter;
|
|
44
|
+
|
|
39
45
|
@end
|
|
@@ -17,6 +17,18 @@
|
|
|
17
17
|
return self;
|
|
18
18
|
}
|
|
19
19
|
|
|
20
|
+
- (SDWebImageCacheKeyFilter *)cacheKeyFilter
|
|
21
|
+
{
|
|
22
|
+
NSString* cacheKey = _cacheKey;
|
|
23
|
+
// `cache: 'web'` follows the HTTP cache, which is keyed by url.
|
|
24
|
+
if (cacheKey.length == 0 || _cacheControl == FFFCacheControlWeb) {
|
|
25
|
+
return nil;
|
|
26
|
+
}
|
|
27
|
+
return [SDWebImageCacheKeyFilter cacheKeyFilterWithBlock: ^NSString* _Nullable (NSURL* _Nonnull url) {
|
|
28
|
+
return cacheKey;
|
|
29
|
+
}];
|
|
30
|
+
}
|
|
31
|
+
|
|
20
32
|
- (SDWebImageDownloaderRequestModifier *)requestModifier
|
|
21
33
|
{
|
|
22
34
|
NSDictionary* headers = _headers;
|
|
@@ -161,7 +161,7 @@ static CFTimeInterval FFFEnteredBackgroundAt = 0;
|
|
|
161
161
|
- (void) setOnFastImageLoadEnd: (RCTDirectEventBlock)onFastImageLoadEnd {
|
|
162
162
|
_onFastImageLoadEnd = onFastImageLoadEnd;
|
|
163
163
|
if (self.hasCompleted && _onFastImageLoadEnd) {
|
|
164
|
-
_onFastImageLoadEnd(
|
|
164
|
+
_onFastImageLoadEnd([self loadEndEvent: YES]);
|
|
165
165
|
}
|
|
166
166
|
}
|
|
167
167
|
|
|
@@ -284,6 +284,14 @@ NSString *FFFErrorMessage(NSError *error)
|
|
|
284
284
|
}
|
|
285
285
|
}
|
|
286
286
|
|
|
287
|
+
// onLoadEnd's event: the load's result, as onLoad or onError sent it (ok: its
|
|
288
|
+
// size, or not ok: the error).
|
|
289
|
+
- (NSDictionary*) loadEndEvent: (BOOL)ok {
|
|
290
|
+
NSMutableDictionary* event = [NSMutableDictionary dictionaryWithDictionary: (ok ? self.onLoadEvent : self.onErrorEvent) ?: @{}];
|
|
291
|
+
event[@"ok"] = @(ok);
|
|
292
|
+
return event;
|
|
293
|
+
}
|
|
294
|
+
|
|
287
295
|
- (void) setSource: (FFFastImageSource*)source {
|
|
288
296
|
if (_source != source) {
|
|
289
297
|
_source = source;
|
|
@@ -429,7 +437,7 @@ NSString *FFFErrorMessage(NSError *error)
|
|
|
429
437
|
self.showsLoadedImage = NO;
|
|
430
438
|
[self sendOnError: @"Failed to decode the image"];
|
|
431
439
|
if (self.onFastImageLoadEnd) {
|
|
432
|
-
self.onFastImageLoadEnd(
|
|
440
|
+
self.onFastImageLoadEnd([self loadEndEvent: NO]);
|
|
433
441
|
}
|
|
434
442
|
return;
|
|
435
443
|
}
|
|
@@ -445,7 +453,7 @@ NSString *FFFErrorMessage(NSError *error)
|
|
|
445
453
|
[self sendOnLoad: image];
|
|
446
454
|
|
|
447
455
|
if (self.onFastImageLoadEnd) {
|
|
448
|
-
self.onFastImageLoadEnd(
|
|
456
|
+
self.onFastImageLoadEnd([self loadEndEvent: YES]);
|
|
449
457
|
}
|
|
450
458
|
return;
|
|
451
459
|
}
|
|
@@ -510,6 +518,7 @@ NSString *FFFErrorMessage(NSError *error)
|
|
|
510
518
|
- (SDWebImageContext*) loadContext {
|
|
511
519
|
SDWebImageMutableContext* context = [NSMutableDictionary dictionary];
|
|
512
520
|
context[SDWebImageContextDownloadRequestModifier] = _source.requestModifier;
|
|
521
|
+
context[SDWebImageContextCacheKeyFilter] = _source.cacheKeyFilter;
|
|
513
522
|
CGSize box = [self decodeBox];
|
|
514
523
|
self.decodedBox = box;
|
|
515
524
|
self.decodedCover = [self decodeCovers];
|
|
@@ -517,7 +526,8 @@ NSString *FFFErrorMessage(NSError *error)
|
|
|
517
526
|
context[SDWebImageContextAnimatedImageClass] = [SDAnimatedImage class];
|
|
518
527
|
return context;
|
|
519
528
|
}
|
|
520
|
-
|
|
529
|
+
NSString* key = _source.cacheKeyFilter ? _source.cacheKey : _source.url.absoluteString;
|
|
530
|
+
[FFFDownsampledImage addToContext: context forKey: key box: box cover: self.decodedCover];
|
|
521
531
|
return context;
|
|
522
532
|
}
|
|
523
533
|
|
|
@@ -621,14 +631,14 @@ NSString *FFFErrorMessage(NSError *error)
|
|
|
621
631
|
weakSelf.showsLoadedImage = NO;
|
|
622
632
|
[weakSelf sendOnError: FFFErrorMessage(error)];
|
|
623
633
|
if (weakSelf.onFastImageLoadEnd) {
|
|
624
|
-
weakSelf.onFastImageLoadEnd(
|
|
634
|
+
weakSelf.onFastImageLoadEnd([weakSelf loadEndEvent: NO]);
|
|
625
635
|
}
|
|
626
636
|
} else {
|
|
627
637
|
weakSelf.hasCompleted = YES;
|
|
628
638
|
weakSelf.showsLoadedImage = YES;
|
|
629
639
|
[weakSelf sendOnLoad: image];
|
|
630
640
|
if (weakSelf.onFastImageLoadEnd) {
|
|
631
|
-
weakSelf.onFastImageLoadEnd(
|
|
641
|
+
weakSelf.onFastImageLoadEnd([weakSelf loadEndEvent: YES]);
|
|
632
642
|
}
|
|
633
643
|
}
|
|
634
644
|
}];
|
|
@@ -37,6 +37,15 @@ RCT_REMAP_VIEW_PROPERTY(tintColor, imageColor, UIColor)
|
|
|
37
37
|
static NSMutableArray<dispatch_block_t> *FFFPendingPreloads;
|
|
38
38
|
static NSUInteger FFFPreloadsInFlight;
|
|
39
39
|
|
|
40
|
+
// A preload's context: the source's headers and cache key.
|
|
41
|
+
static SDWebImageMutableContext *FFFPreloadContext(FFFastImageSource *source)
|
|
42
|
+
{
|
|
43
|
+
SDWebImageMutableContext *context = [NSMutableDictionary dictionary];
|
|
44
|
+
context[SDWebImageContextDownloadRequestModifier] = source.requestModifier;
|
|
45
|
+
context[SDWebImageContextCacheKeyFilter] = source.cacheKeyFilter;
|
|
46
|
+
return context;
|
|
47
|
+
}
|
|
48
|
+
|
|
40
49
|
static NSUInteger FFFPreloadLimit(void)
|
|
41
50
|
{
|
|
42
51
|
NSUInteger limit = [SDWebImagePrefetcher sharedImagePrefetcher].maxConcurrentPrefetchCount;
|
|
@@ -109,7 +118,7 @@ RCT_EXPORT_METHOD(preload:(nonnull NSArray<FFFastImageSource *> *)sources
|
|
|
109
118
|
__block BOOL done = NO;
|
|
110
119
|
[[SDWebImageManager sharedManager] loadImageWithURL:source.url
|
|
111
120
|
options:options
|
|
112
|
-
context
|
|
121
|
+
context:[FFFPreloadContext(source) copy]
|
|
113
122
|
progress:nil
|
|
114
123
|
completed:^(UIImage *image, NSData *data, NSError *error, SDImageCacheType cacheType, BOOL finished, NSURL *imageURL) {
|
|
115
124
|
if (!finished || done) {
|
|
@@ -47,6 +47,8 @@ RCT_ENUM_CONVERTER(FFFCacheControl, (@{
|
|
|
47
47
|
FFFastImageSource *imageSource = [[FFFastImageSource alloc] initWithURL:uri priority:priority headers:headers cacheControl:cacheControl];
|
|
48
48
|
id priorityJSON = json[@"priority"];
|
|
49
49
|
imageSource.hasPriority = priorityJSON && priorityJSON != (id)kCFNull;
|
|
50
|
+
NSString *cacheKey = [self NSString:json[@"cacheKey"]];
|
|
51
|
+
imageSource.cacheKey = cacheKey.length > 0 ? cacheKey : nil;
|
|
50
52
|
|
|
51
53
|
return imageSource;
|
|
52
54
|
}
|