react-native-fast-image 9.3.7 → 10.1.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 +97 -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 +13 -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 +49 -14
- 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 +12 -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 +24 -9
- package/android/src/main/java/com/fastimage/FastImageViewManager.java +591 -0
- package/android/src/main/java/com/{dylanvann/fastimage → fastimage}/FastImageViewModule.java +50 -28
- package/android/src/main/java/com/fastimage/FastImageViewPackage.java +51 -0
- package/android/src/main/java/com/{dylanvann/fastimage → fastimage}/FastImageViewWithUrl.java +352 -126
- 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 +34 -17
- package/dist/index.cjs.web.js +211 -61
- package/dist/index.cjs.web.js.flow +34 -17
- package/dist/index.d.ts +108 -58
- package/dist/index.js +278 -159
- package/dist/index.js.flow +34 -17
- package/dist/index.web.js +212 -62
- package/dist/index.web.js.flow +34 -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} +89 -59
- package/ios/FastImage/FFFastImageSource.h +4 -0
- package/ios/FastImage/FFFastImageSource.m +5 -1
- package/ios/FastImage/FFFastImageView.h +36 -7
- package/ios/FastImage/FFFastImageView.m +229 -172
- 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 +9 -10
- package/package.json +28 -9
- package/src/constants.ts +28 -0
- package/src/index.js.flow +180 -0
- package/src/index.tsx +1128 -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
package/src/index.tsx
ADDED
|
@@ -0,0 +1,1128 @@
|
|
|
1
|
+
import React, { forwardRef, memo } from 'react'
|
|
2
|
+
import {
|
|
3
|
+
ColorValue,
|
|
4
|
+
View,
|
|
5
|
+
Image,
|
|
6
|
+
StyleSheet,
|
|
7
|
+
StyleProp,
|
|
8
|
+
ViewStyle,
|
|
9
|
+
ImageRequireSource,
|
|
10
|
+
Platform,
|
|
11
|
+
AccessibilityProps,
|
|
12
|
+
ViewProps,
|
|
13
|
+
} from 'react-native'
|
|
14
|
+
import { cacheControl, priority, resizeMode } from './constants'
|
|
15
|
+
import { fromStyle, resolveObjectFit } from './objectFit'
|
|
16
|
+
import { objectPositionOrCentered, type ObjectPosition } from './objectPosition'
|
|
17
|
+
import FastImageView from './specs/FastImageViewNativeComponent'
|
|
18
|
+
import NativeFastImageModule from './specs/NativeFastImageModule'
|
|
19
|
+
import { warnIfChildren, warnOnce } from './warnings'
|
|
20
|
+
|
|
21
|
+
/** How the image fits the view: see [`objectFit`](#objectfit). */
|
|
22
|
+
export type ObjectFit = 'fill' | 'contain' | 'cover' | 'none' | 'scale-down'
|
|
23
|
+
|
|
24
|
+
export type { ObjectPosition, PositionLength } from './objectPosition'
|
|
25
|
+
|
|
26
|
+
/** An image's load priority: see [`source.priority`](#sourcepriority). */
|
|
27
|
+
export type Priority = 'low' | 'normal' | 'high'
|
|
28
|
+
|
|
29
|
+
/** How fresh an image must be: see [`source.cache`](#sourcecache). */
|
|
30
|
+
export type Cache = 'immutable' | 'web' | 'cacheOnly'
|
|
31
|
+
|
|
32
|
+
/** A remote image to load, and how to load and cache it. */
|
|
33
|
+
export type Source = {
|
|
34
|
+
/**
|
|
35
|
+
* Remote url to load the image from. e.g. `'https://example.com/image.jpg'`.
|
|
36
|
+
*
|
|
37
|
+
* Also loads local files (`file://`, and on Android `content://`), images
|
|
38
|
+
* in the app by name (e.g. `'my_image'`: in its asset catalog on iOS, a
|
|
39
|
+
* drawable on Android), photo library images on iOS and tvOS (`ph://`,
|
|
40
|
+
* see [Photo library images](#photo-library-images-ios-and-tvos)) and SVG
|
|
41
|
+
* images (see [SVG images](docs/formats.md#svg-images)).
|
|
42
|
+
*/
|
|
43
|
+
uri?: string
|
|
44
|
+
/** Headers to load the image with. e.g. `{ Authorization: 'someAuthToken' }`. */
|
|
45
|
+
headers?: { [key: string]: string }
|
|
46
|
+
/**
|
|
47
|
+
* A hint for which images to start loading first when several are
|
|
48
|
+
* waiting: `'high'` ones before `'normal'` ones, and `'low'` ones after.
|
|
49
|
+
* It's best effort, not an order: several images load at once, and when
|
|
50
|
+
* each finishes depends on its size and the network.
|
|
51
|
+
*
|
|
52
|
+
* - `'low'`: e.g. images further down a list.
|
|
53
|
+
* - `'normal'`: the default.
|
|
54
|
+
* - `'high'`: e.g. the image the screen is about.
|
|
55
|
+
*
|
|
56
|
+
* @default 'normal'
|
|
57
|
+
*/
|
|
58
|
+
priority?: Priority
|
|
59
|
+
/**
|
|
60
|
+
* How fresh the image must be. See
|
|
61
|
+
* [how caching is handled](docs/how-is-caching-handled.md) for how the
|
|
62
|
+
* options fit together.
|
|
63
|
+
*
|
|
64
|
+
* - `'immutable'`: loads the image once, then shows the cached copy until
|
|
65
|
+
* its url (or `cacheKey`) changes.
|
|
66
|
+
* - `'web'`: follows the server's HTTP cache headers, as a browser does,
|
|
67
|
+
* checking with the server when it loads. These responses are kept in
|
|
68
|
+
* their own HTTP cache (50 MB on each platform), which `clearDiskCache`
|
|
69
|
+
* also clears.
|
|
70
|
+
* - `'cacheOnly'`: only shows a cached image, without making a request.
|
|
71
|
+
*
|
|
72
|
+
* @default 'immutable'
|
|
73
|
+
*/
|
|
74
|
+
cache?: Cache
|
|
75
|
+
/**
|
|
76
|
+
* The key the image is cached under, instead of its uri. Use it for urls
|
|
77
|
+
* that change while the image stays the same, e.g. signed urls with a
|
|
78
|
+
* token or an expiry. Use something that identifies the image, and change
|
|
79
|
+
* it when the image changes (e.g. include a version or the time it was
|
|
80
|
+
* updated), or the old image keeps showing.
|
|
81
|
+
*
|
|
82
|
+
* Not used with `cache: 'web'`, which follows the HTTP cache (keyed by
|
|
83
|
+
* url).
|
|
84
|
+
*
|
|
85
|
+
* @example
|
|
86
|
+
* ```jsx
|
|
87
|
+
* <FastImage
|
|
88
|
+
* source={{
|
|
89
|
+
* uri: signedUrl,
|
|
90
|
+
* cacheKey: `avatar-${user.id}-${user.avatarUpdatedAt}`,
|
|
91
|
+
* }}
|
|
92
|
+
* />
|
|
93
|
+
* ```
|
|
94
|
+
*/
|
|
95
|
+
cacheKey?: string
|
|
96
|
+
/**
|
|
97
|
+
* Whether the decoded image is kept in the memory cache. With `false`
|
|
98
|
+
* it's only kept on disk: a view doesn't leave it in memory once it stops
|
|
99
|
+
* showing it, and `FastImage.preload` downloads it without decoding it.
|
|
100
|
+
* Use it for large images that are shown once or rarely, like a
|
|
101
|
+
* full-screen photo: a decoded photo can take tens of MB of memory. Images
|
|
102
|
+
* shown again, like a list scrolled back, are decoded from disk again.
|
|
103
|
+
*
|
|
104
|
+
* @default true
|
|
105
|
+
*/
|
|
106
|
+
memoryCache?: boolean
|
|
107
|
+
/**
|
|
108
|
+
* Whether the image is kept on disk. With `false` it isn't kept in the
|
|
109
|
+
* disk cache or an HTTP cache, or read from them. Use it for images the
|
|
110
|
+
* app shouldn't
|
|
111
|
+
* keep once they've been shown (e.g. disappearing messages, or where a
|
|
112
|
+
* security policy doesn't allow caching sensitive images), images that
|
|
113
|
+
* change every time they load, or large images shown once, which would
|
|
114
|
+
* push other images out of the disk cache.
|
|
115
|
+
* The image is downloaded again whenever it isn't in memory, e.g. for a
|
|
116
|
+
* view of another size (or on Android, a change of `blurRadius` or
|
|
117
|
+
* `objectFit`), so with `memoryCache: false` too it's downloaded each
|
|
118
|
+
* time it's shown. With `cache: 'web'` it isn't kept in memory either,
|
|
119
|
+
* so it's downloaded every time it loads. With `cache: 'cacheOnly'` it's
|
|
120
|
+
* only looked for in memory. On Android, a large download is held in a
|
|
121
|
+
* temporary file while it loads, as every download is, deleted as soon
|
|
122
|
+
* as it's opened.
|
|
123
|
+
*
|
|
124
|
+
* `FastImage.preload`, `getCachePath` and `writeToCache` resolve not `ok`
|
|
125
|
+
* for it: there's no file, and a view decodes the image for its own
|
|
126
|
+
* size, so it may download a preloaded one again. A source for the same
|
|
127
|
+
* url (or `cacheKey`) without `diskCache: false` still stores the image.
|
|
128
|
+
*
|
|
129
|
+
* @default true
|
|
130
|
+
*/
|
|
131
|
+
diskCache?: boolean
|
|
132
|
+
/**
|
|
133
|
+
* With several sources (`source` as an array), the image's width at this
|
|
134
|
+
* uri, in pixels (times `scale`, if given).
|
|
135
|
+
*/
|
|
136
|
+
width?: number
|
|
137
|
+
/**
|
|
138
|
+
* With several sources (`source` as an array), the image's height at this
|
|
139
|
+
* uri, in pixels (times `scale`, if given).
|
|
140
|
+
*/
|
|
141
|
+
height?: number
|
|
142
|
+
/** With several sources, the scale `width` and `height` are multiplied by. */
|
|
143
|
+
scale?: number
|
|
144
|
+
}
|
|
145
|
+
|
|
146
|
+
/** How the image fades in: see [`transition`](#transition). */
|
|
147
|
+
export type Transition = {
|
|
148
|
+
/**
|
|
149
|
+
* How long the fade takes, in milliseconds; 0 means no fade. Defaults to
|
|
150
|
+
* the default transition's (see `FastImage.setDefaults`) if it fades,
|
|
151
|
+
* otherwise the platform's usual length: 300 ms on Android, 250 ms on iOS.
|
|
152
|
+
*/
|
|
153
|
+
duration?: number
|
|
154
|
+
/**
|
|
155
|
+
* Also fades between images: a new `source` replacing an image that's
|
|
156
|
+
* showing cross-dissolves from it. Otherwise only an image that appears
|
|
157
|
+
* over nothing (or over `defaultSource`) fades in, and a new source
|
|
158
|
+
* replaces the image showing at once, once it has loaded.
|
|
159
|
+
* @default false
|
|
160
|
+
*/
|
|
161
|
+
betweenImages?: boolean
|
|
162
|
+
/**
|
|
163
|
+
* Skips the fade for an image from a cache, so images already loaded
|
|
164
|
+
* show at once, e.g. in a list scrolled back up or a reused row.
|
|
165
|
+
*
|
|
166
|
+
* - `'memory'`: skips it for images from the memory cache.
|
|
167
|
+
* - `'all'`: skips it for images from the memory or disk cache too, so
|
|
168
|
+
* images that download fade in, and local files and bundled images
|
|
169
|
+
* usually only the first time (the disk cache usually keeps them too).
|
|
170
|
+
* - `'none'`: always fades.
|
|
171
|
+
*
|
|
172
|
+
* On Android, a `cache: 'web'` image from its HTTP cache, without a request,
|
|
173
|
+
* counts as one from the memory cache (these images aren't kept there).
|
|
174
|
+
* @default 'memory'
|
|
175
|
+
*/
|
|
176
|
+
skipOnCacheHit?: 'none' | 'memory' | 'all' | null
|
|
177
|
+
}
|
|
178
|
+
|
|
179
|
+
/**
|
|
180
|
+
* `onLoad`'s event: `nativeEvent` has the image's own `width` and `height`
|
|
181
|
+
* (see `onLoad`), and `target`, the view's React tag (on iOS and Android; not
|
|
182
|
+
* on the web).
|
|
183
|
+
*/
|
|
184
|
+
export interface OnLoadEvent {
|
|
185
|
+
nativeEvent: {
|
|
186
|
+
width: number
|
|
187
|
+
height: number
|
|
188
|
+
target?: number
|
|
189
|
+
}
|
|
190
|
+
}
|
|
191
|
+
|
|
192
|
+
/**
|
|
193
|
+
* `onError`'s event: `nativeEvent.error` says what went wrong, e.g. an HTTP
|
|
194
|
+
* status code or an image that can't be decoded.
|
|
195
|
+
*/
|
|
196
|
+
export interface OnErrorEvent {
|
|
197
|
+
nativeEvent: {
|
|
198
|
+
error: string
|
|
199
|
+
}
|
|
200
|
+
}
|
|
201
|
+
|
|
202
|
+
/**
|
|
203
|
+
* A load's result, as `onLoadEnd` gets it: `ok` with the image's size, or not
|
|
204
|
+
* `ok` with the error (as `onLoad` and `onError` get them).
|
|
205
|
+
*/
|
|
206
|
+
export type LoadResult =
|
|
207
|
+
| { ok: true; width: number; height: number }
|
|
208
|
+
| { ok: false; error: string }
|
|
209
|
+
|
|
210
|
+
/**
|
|
211
|
+
* `configureCache`'s changes: a number (0 for no limit), or `null` to go back
|
|
212
|
+
* to the app's native config (Info.plist, AndroidManifest.xml) or the
|
|
213
|
+
* platform's default. Leave one out to keep it. Changes are saved, and used on
|
|
214
|
+
* the next launches too. Android only has `maxDiskSize`.
|
|
215
|
+
*/
|
|
216
|
+
export interface CacheLimits {
|
|
217
|
+
/**
|
|
218
|
+
* The most bytes of images kept on disk. When it's over, the least
|
|
219
|
+
* recently used are removed (on iOS, until it's half this size). Default:
|
|
220
|
+
* no limit on iOS, 250 MB on Android.
|
|
221
|
+
*/
|
|
222
|
+
maxDiskSize?: number | null
|
|
223
|
+
/**
|
|
224
|
+
* iOS: the seconds an image is kept on disk after it was last used, or 0
|
|
225
|
+
* to keep them until `maxDiskSize` removes them. Default: 1 week.
|
|
226
|
+
*/
|
|
227
|
+
maxDiskAge?: number | null
|
|
228
|
+
/**
|
|
229
|
+
* iOS: the most bytes of decoded images kept in memory. Default: no limit
|
|
230
|
+
* (they're removed when the system is low on memory).
|
|
231
|
+
*/
|
|
232
|
+
maxMemorySize?: number | null
|
|
233
|
+
}
|
|
234
|
+
|
|
235
|
+
/**
|
|
236
|
+
* `configureCache`'s result: the limits in effect (0 for no limit), and the
|
|
237
|
+
* bytes the disk cache uses now. Android only has `maxDiskSize` and
|
|
238
|
+
* `diskSize`, and neither if the app has its own `AppGlideModule` (or
|
|
239
|
+
* expo-image, which has one).
|
|
240
|
+
*/
|
|
241
|
+
export interface CacheState {
|
|
242
|
+
maxDiskSize?: number
|
|
243
|
+
maxDiskAge?: number
|
|
244
|
+
maxMemorySize?: number
|
|
245
|
+
/** The bytes the disk cache uses now. */
|
|
246
|
+
diskSize?: number
|
|
247
|
+
}
|
|
248
|
+
|
|
249
|
+
/**
|
|
250
|
+
* `getCachePath`'s and `writeToCache`'s result: `ok` with the file's path, or
|
|
251
|
+
* not `ok` with the error.
|
|
252
|
+
*/
|
|
253
|
+
export type CachePathResult =
|
|
254
|
+
| { ok: true; path: string }
|
|
255
|
+
| { ok: false; error: string }
|
|
256
|
+
|
|
257
|
+
/**
|
|
258
|
+
* `clearMemoryCache`'s and `clearDiskCache`'s result: `ok` once the cache is
|
|
259
|
+
* cleared, or not `ok` with the error.
|
|
260
|
+
*/
|
|
261
|
+
export type ClearCacheResult = { ok: true } | { ok: false; error: string }
|
|
262
|
+
|
|
263
|
+
/**
|
|
264
|
+
* `onProgress`'s event: `nativeEvent` has the bytes `loaded` and the `total`,
|
|
265
|
+
* and `progress`, `loaded / total` from 0 to 1.
|
|
266
|
+
*/
|
|
267
|
+
export interface OnProgressEvent {
|
|
268
|
+
nativeEvent: {
|
|
269
|
+
loaded: number
|
|
270
|
+
total: number
|
|
271
|
+
progress: number
|
|
272
|
+
}
|
|
273
|
+
}
|
|
274
|
+
|
|
275
|
+
// Extends ViewStyle rather than FlexStyle/TransformsStyle/ShadowStyleIOS, which
|
|
276
|
+
// React Native's Strict TypeScript API (types_generated: opt-in from 0.80, the
|
|
277
|
+
// default from 0.87) doesn't export. Only the image's own keys are added; the
|
|
278
|
+
// rest (radii, opacity, colors) come from ViewStyle, so they're the app's
|
|
279
|
+
// React Native types (e.g. string radii, Animated values).
|
|
280
|
+
export interface ImageStyle extends ViewStyle {
|
|
281
|
+
tintColor?: ColorValue
|
|
282
|
+
objectFit?: ObjectFit
|
|
283
|
+
}
|
|
284
|
+
|
|
285
|
+
export interface FastImageProps extends AccessibilityProps, ViewProps {
|
|
286
|
+
/**
|
|
287
|
+
* Source for the remote image to load: a `Source`, a `require()`d image,
|
|
288
|
+
* or an array of the same image at different sizes.
|
|
289
|
+
*
|
|
290
|
+
* When `source` changes, the image that's showing stays until the new one
|
|
291
|
+
* has loaded. In views that get reused for other content, such as rows in
|
|
292
|
+
* FlashList or recyclerlistview, set `recyclingKey` so a reused row
|
|
293
|
+
* doesn't show the previous row's image.
|
|
294
|
+
*
|
|
295
|
+
* **Several sizes of an image.** `source` can also be an array of the
|
|
296
|
+
* same image at different sizes, each with its `width` and `height` in
|
|
297
|
+
* pixels (times `scale`, if it has one). The view loads the one whose size
|
|
298
|
+
* is closest to its own, in pixels, so a small view downloads a small
|
|
299
|
+
* image:
|
|
300
|
+
*
|
|
301
|
+
* ```jsx
|
|
302
|
+
* <FastImage
|
|
303
|
+
* style={{ width: 120, height: 120 }}
|
|
304
|
+
* source={[
|
|
305
|
+
* { uri: 'https://example.com/photo-200.jpg', width: 200, height: 200 },
|
|
306
|
+
* { uri: 'https://example.com/photo-800.jpg', width: 800, height: 800 },
|
|
307
|
+
* ]}
|
|
308
|
+
* />
|
|
309
|
+
* ```
|
|
310
|
+
*
|
|
311
|
+
* - The view picks once it has been laid out. One that has no size (e.g.
|
|
312
|
+
* sized from `onLoad`) loads the largest.
|
|
313
|
+
* - When the view's size changes so that another size fits better, it
|
|
314
|
+
* loads that one (with its load events), and keeps showing the current
|
|
315
|
+
* image until then, without a `transition`.
|
|
316
|
+
* - Each size is cached separately. A `cacheKey` applies to its own entry:
|
|
317
|
+
* give each size its own key (or none), or the sizes would replace each
|
|
318
|
+
* other in the cache.
|
|
319
|
+
* - `FastImage.preload`, `getCachePath` and `writeToCache` take one size:
|
|
320
|
+
* pass the one a view will show. An array fails with
|
|
321
|
+
* `{ ok: false, error }`.
|
|
322
|
+
* - An array of one is the same as that source.
|
|
323
|
+
*/
|
|
324
|
+
source?: Source | Source[] | ImageRequireSource
|
|
325
|
+
/**
|
|
326
|
+
* An asset loaded with `require(...)`, shown while the first image loads,
|
|
327
|
+
* and if an image fails to load. When `source` changes, the previous image
|
|
328
|
+
* shows while the new one loads instead (see `source` and
|
|
329
|
+
* `recyclingKey`).
|
|
330
|
+
*
|
|
331
|
+
* On Android, `defaultSource` doesn't show in debug builds: there the dev
|
|
332
|
+
* server serves `require()`d images, and `defaultSource` is only loaded
|
|
333
|
+
* from the app's resources.
|
|
334
|
+
*/
|
|
335
|
+
defaultSource?: ImageRequireSource
|
|
336
|
+
/**
|
|
337
|
+
* How the image fits the view, as CSS's `object-fit` does.
|
|
338
|
+
*
|
|
339
|
+
* - `'cover'`: scales it uniformly (keeping its aspect ratio) so it covers
|
|
340
|
+
* the view (inside its borders), cropping what doesn't fit.
|
|
341
|
+
* - `'contain'`: scales it uniformly (keeping its aspect ratio) so all of
|
|
342
|
+
* it fits in the view (inside its borders).
|
|
343
|
+
* - `'fill'`: scales its width and height separately to fill the view,
|
|
344
|
+
* which can change its aspect ratio.
|
|
345
|
+
* - `'none'`: shows it at its own size, centered, cropped if it's larger
|
|
346
|
+
* than the view.
|
|
347
|
+
* - `'scale-down'`: shows it at its own size, centered, or scaled down
|
|
348
|
+
* uniformly to fit if it's larger than the view (the smaller of `'none'`
|
|
349
|
+
* and `'contain'`).
|
|
350
|
+
*
|
|
351
|
+
* An image's own size is its size in pixels, as points (dp on Android),
|
|
352
|
+
* as CSS counts an image's pixels: a 300 × 200 image is 300 × 200 points.
|
|
353
|
+
* A bundled image (`require()`) is its size in points.
|
|
354
|
+
*
|
|
355
|
+
* It can also be set in `style`; the prop wins.
|
|
356
|
+
*
|
|
357
|
+
* @default 'cover'
|
|
358
|
+
*/
|
|
359
|
+
objectFit?: ObjectFit
|
|
360
|
+
/**
|
|
361
|
+
* Where the image is in the view, as CSS's `object-position` places it:
|
|
362
|
+
* where its edges are when it doesn't fill the view exactly (`contain`,
|
|
363
|
+
* `cover`, `none` and `scale-down`; with `fill` it always fills it).
|
|
364
|
+
*
|
|
365
|
+
* - A string in CSS's syntax: one or two keywords (`'top'`,
|
|
366
|
+
* `'left bottom'`), lengths in points (`'10px 20px'`, with `px`) or
|
|
367
|
+
* percentages (`'25% 75%'`), or offsets from an edge
|
|
368
|
+
* (`'right 10px bottom 20%'`). A percentage puts that point of the image
|
|
369
|
+
* at that point of the view: `'0% 0%'` is the top-left corner,
|
|
370
|
+
* `'100% 100%'` the bottom-right.
|
|
371
|
+
* - An object with one of `top` and `bottom` and one of `left` and
|
|
372
|
+
* `right`, each in points (a number) or a percentage (`'25%'`), e.g.
|
|
373
|
+
* `{ right: 10, bottom: '20%' }`. A missing axis is centered.
|
|
374
|
+
*
|
|
375
|
+
* Points are dp on Android. An invalid value is ignored (the image is
|
|
376
|
+
* centered), with a warning in development. `defaultSource` is placed
|
|
377
|
+
* the same way.
|
|
378
|
+
*
|
|
379
|
+
* @default '50% 50%'
|
|
380
|
+
* @example
|
|
381
|
+
* ```jsx
|
|
382
|
+
* <FastImage source={{ uri }} objectFit="cover" objectPosition="top" />
|
|
383
|
+
* ```
|
|
384
|
+
*/
|
|
385
|
+
objectPosition?: ObjectPosition
|
|
386
|
+
/**
|
|
387
|
+
* For views that get reused for other content, such as rows in FlashList
|
|
388
|
+
* or recyclerlistview. Set it to something that identifies the content,
|
|
389
|
+
* e.g. the item's id. When it changes, the image is cleared right away (to
|
|
390
|
+
* `defaultSource`, or blank) instead of staying until the new one has
|
|
391
|
+
* loaded. Unlike changing `key`, this keeps the view, which is what list
|
|
392
|
+
* recycling is for.
|
|
393
|
+
*
|
|
394
|
+
* @example
|
|
395
|
+
* ```jsx
|
|
396
|
+
* <FastImage recyclingKey={item.id} source={{ uri: item.imageUrl }} />
|
|
397
|
+
* ```
|
|
398
|
+
*/
|
|
399
|
+
recyclingKey?: string | null
|
|
400
|
+
/**
|
|
401
|
+
* How many times an animated image (GIF, animated WebP, APNG, or animated
|
|
402
|
+
* AVIF) plays:
|
|
403
|
+
*
|
|
404
|
+
* - Not set: as many times as the file says (like a browser).
|
|
405
|
+
* - `true`: forever.
|
|
406
|
+
* - `false`: once.
|
|
407
|
+
* - A number: that many times.
|
|
408
|
+
*
|
|
409
|
+
* Changing it restarts the animation.
|
|
410
|
+
*/
|
|
411
|
+
loop?: boolean | number
|
|
412
|
+
/**
|
|
413
|
+
* How the image is filtered when it's drawn smaller or larger than its
|
|
414
|
+
* size (like CSS's `image-rendering`):
|
|
415
|
+
*
|
|
416
|
+
* - `'auto'`: the platform's usual filtering.
|
|
417
|
+
* - `'smooth'`: iOS only. Keeps a large image drawn much smaller than its
|
|
418
|
+
* size (e.g. a big photo as a thumbnail, or fine lines and text) from
|
|
419
|
+
* looking jagged or noisy. Such an image is already decoded at about the
|
|
420
|
+
* view's size by default (see `downsample`), so this is for images shown
|
|
421
|
+
* at less than half their size with `downsample={false}`, or a little
|
|
422
|
+
* smaller than their size. **It uses more memory:** the image is also
|
|
423
|
+
* kept at smaller sizes for drawing, about a third more than the decoded
|
|
424
|
+
* image. On Android it's the same as `'auto'`.
|
|
425
|
+
* - `'pixelated'`: sharp pixels, without smoothing, e.g. for pixel art
|
|
426
|
+
* drawn larger than its size. On Android, animated images are still
|
|
427
|
+
* smoothed.
|
|
428
|
+
*
|
|
429
|
+
* @default 'auto'
|
|
430
|
+
*/
|
|
431
|
+
imageRendering?: 'auto' | 'smooth' | 'pixelated'
|
|
432
|
+
/**
|
|
433
|
+
* Pauses an animated image (GIF, animated WebP, APNG, or animated AVIF) on
|
|
434
|
+
* the frame it's showing; `false` plays it again from there
|
|
435
|
+
* (on Android, an animated WebP or AVIF plays again from its first frame:
|
|
436
|
+
* Android can't resume one). Each image animates on its own, so pausing
|
|
437
|
+
* one doesn't pause others showing the same file.
|
|
438
|
+
*/
|
|
439
|
+
paused?: boolean
|
|
440
|
+
/**
|
|
441
|
+
* Fades the image in when it loads. `true` fades for the default's
|
|
442
|
+
* duration (see `FastImage.setDefaults`), or the platform's usual length
|
|
443
|
+
* if the default doesn't fade; a number is the duration in milliseconds,
|
|
444
|
+
* `false` (or 0) is no fade, or pass a `Transition`.
|
|
445
|
+
*
|
|
446
|
+
* By default, images fade in on Android (300 ms), and not on iOS.
|
|
447
|
+
* `FastImage.setDefaults` changes that for every image; an image's own
|
|
448
|
+
* `transition` wins, and the settings it leaves out come from the
|
|
449
|
+
* default. `null` is the default.
|
|
450
|
+
*
|
|
451
|
+
* Downloads, local files (`file://`, `content://`) and bundled images
|
|
452
|
+
* (`require()`) fade in. In lists that reuse views (e.g. FlashList), set
|
|
453
|
+
* `recyclingKey`, so a reused view starts empty and its image fades in,
|
|
454
|
+
* instead of showing the previous item's image until it loads.
|
|
455
|
+
*
|
|
456
|
+
* @example
|
|
457
|
+
* ```jsx
|
|
458
|
+
* <FastImage source={{ uri }} transition />
|
|
459
|
+
* <FastImage source={{ uri }} transition={500} />
|
|
460
|
+
* <FastImage source={{ uri }} transition={{ betweenImages: true, skipOnCacheHit: 'none' }} />
|
|
461
|
+
* ```
|
|
462
|
+
*/
|
|
463
|
+
transition?: boolean | number | Transition | null
|
|
464
|
+
/**
|
|
465
|
+
* Decodes a large image at about the size it's shown at, instead of at
|
|
466
|
+
* full size, so it takes much less memory.
|
|
467
|
+
*
|
|
468
|
+
* - `true`: an image at least twice the size its view needs is decoded
|
|
469
|
+
* at about the view's size (on Android, any image larger than its
|
|
470
|
+
* view). If the view grows, the image is decoded again for its new size
|
|
471
|
+
* (from the disk cache).
|
|
472
|
+
* - `false`: images are decoded at full size, e.g. for an image that's
|
|
473
|
+
* zoomed in on with a transform (a pinch-to-zoom viewer), which would
|
|
474
|
+
* otherwise show the smaller copy enlarged. **It uses more memory:** a
|
|
475
|
+
* 12-megapixel photo takes about 48 MB decoded, however small it's
|
|
476
|
+
* shown. On Android, an image over 16 megapixels (4096 × 4096) is
|
|
477
|
+
* decoded at the largest size under that, which Android can still
|
|
478
|
+
* draw.
|
|
479
|
+
*
|
|
480
|
+
* It doesn't change `onLoad`'s width and height (the image's own size) or
|
|
481
|
+
* the cached file. Photo library images are always decoded at about the
|
|
482
|
+
* view's size. Android 16 and later decode animated WebP at full size
|
|
483
|
+
* either way, and scale it as they draw it.
|
|
484
|
+
*
|
|
485
|
+
* If you control the images, serve them at the size they're shown
|
|
486
|
+
* (resized on your server or by an image CDN), which also saves
|
|
487
|
+
* bandwidth.
|
|
488
|
+
*
|
|
489
|
+
* @default true
|
|
490
|
+
*/
|
|
491
|
+
downsample?: boolean
|
|
492
|
+
// The radius is converted from points to the bitmap's pixels on both
|
|
493
|
+
// platforms, so it matches React Native's Image.
|
|
494
|
+
/**
|
|
495
|
+
* Blurs the image by this radius, in points (the same radius looks about
|
|
496
|
+
* the same on iOS and Android). `0` is no blur.
|
|
497
|
+
*
|
|
498
|
+
* It's for still images, or a radius that changes now and then (e.g.
|
|
499
|
+
* blurring a photo behind a sheet). Each change blurs the image again on
|
|
500
|
+
* the CPU, so don't animate it.
|
|
501
|
+
*
|
|
502
|
+
* - Only the loaded image is blurred, not `defaultSource`.
|
|
503
|
+
* - An animated image (GIF, animated WebP, APNG or AVIF) shows its first frame,
|
|
504
|
+
* blurred, and doesn't animate.
|
|
505
|
+
* - With `tintColor`, the blurred image is tinted.
|
|
506
|
+
* - The image is blurred at about the size it's shown at, off the main
|
|
507
|
+
* thread. The cached file stays the original image, so `getCachePath`
|
|
508
|
+
* and other views of it aren't affected.
|
|
509
|
+
* - Changing it blurs the image that's showing again, without sending the
|
|
510
|
+
* load events again.
|
|
511
|
+
*
|
|
512
|
+
* To animate a blur, or to blur an animated image, use React Native's
|
|
513
|
+
* `filter` style instead, which the GPU draws:
|
|
514
|
+
* `style={{ filter: [{ blur: 6 }] }}`. React Native's docs list `blur` for Android 12+ only; on iOS it's behind
|
|
515
|
+
* an experimental React Native feature flag (`enableSwiftUIBasedFilters`,
|
|
516
|
+
* SwiftUI-based filters).
|
|
517
|
+
*
|
|
518
|
+
* @default 0
|
|
519
|
+
*/
|
|
520
|
+
blurRadius?: number
|
|
521
|
+
|
|
522
|
+
/** Called when the image starts to load. */
|
|
523
|
+
onLoadStart?(): void
|
|
524
|
+
|
|
525
|
+
/**
|
|
526
|
+
* Called while the image downloads, with the bytes `loaded` so far, the
|
|
527
|
+
* `total`, and `progress` (`loaded / total`, from 0 to 1; the last event
|
|
528
|
+
* has 1). Not called while the total is unknown (a response without a
|
|
529
|
+
* `Content-Length`).
|
|
530
|
+
*
|
|
531
|
+
* @example
|
|
532
|
+
* ```jsx
|
|
533
|
+
* onProgress={e => console.log(e.nativeEvent.progress)}
|
|
534
|
+
* ```
|
|
535
|
+
*/
|
|
536
|
+
onProgress?(event: OnProgressEvent): void
|
|
537
|
+
|
|
538
|
+
/**
|
|
539
|
+
* Called on a successful image fetch. Called with the width and height of
|
|
540
|
+
* the image itself, not of the view (on iOS, in points: an `@2x` asset
|
|
541
|
+
* reports half its pixel size).
|
|
542
|
+
*
|
|
543
|
+
* @example
|
|
544
|
+
* ```jsx
|
|
545
|
+
* onLoad={e => console.log(e.nativeEvent.width, e.nativeEvent.height)}
|
|
546
|
+
* ```
|
|
547
|
+
*/
|
|
548
|
+
onLoad?(event: OnLoadEvent): void
|
|
549
|
+
|
|
550
|
+
/**
|
|
551
|
+
* Called on an image fetching error, with a message describing it (e.g.
|
|
552
|
+
* the HTTP status code). On iOS and Android, a download that gets nothing
|
|
553
|
+
* from the server for 15 seconds fails too (on Android, unless the app's
|
|
554
|
+
* OkHttp client has timeouts of its own).
|
|
555
|
+
*
|
|
556
|
+
* @example
|
|
557
|
+
* ```jsx
|
|
558
|
+
* onError={e => console.log(e.nativeEvent.error)}
|
|
559
|
+
* ```
|
|
560
|
+
*/
|
|
561
|
+
onError?(event: OnErrorEvent): void
|
|
562
|
+
|
|
563
|
+
/**
|
|
564
|
+
* Called when the image finishes loading, whether it was successful or an
|
|
565
|
+
* error, with the result: `{ ok: true, width, height }` (the image's size,
|
|
566
|
+
* as `onLoad` gets it) or `{ ok: false, error }` (as `onError` gets it).
|
|
567
|
+
* TypeScript makes you check `ok` before reading the size or the error.
|
|
568
|
+
*
|
|
569
|
+
* @example
|
|
570
|
+
* ```jsx
|
|
571
|
+
* <FastImage
|
|
572
|
+
* source={{ uri }}
|
|
573
|
+
* onLoadEnd={(result) => {
|
|
574
|
+
* if (result.ok) setAspectRatio(result.width / result.height)
|
|
575
|
+
* else setFailed(result.error)
|
|
576
|
+
* }}
|
|
577
|
+
* />
|
|
578
|
+
* ```
|
|
579
|
+
*/
|
|
580
|
+
onLoadEnd?(result: LoadResult): void
|
|
581
|
+
|
|
582
|
+
/**
|
|
583
|
+
* The image's style: View's style props (`borderRadius` clips the image,
|
|
584
|
+
* and borders inset it, but padding doesn't), and `tintColor`, as with
|
|
585
|
+
* React Native's `Image` (the `tintColor` prop wins).
|
|
586
|
+
*/
|
|
587
|
+
style?: StyleProp<ImageStyle>
|
|
588
|
+
|
|
589
|
+
/**
|
|
590
|
+
* If supplied, changes the color of all the non-transparent pixels to the
|
|
591
|
+
* given color.
|
|
592
|
+
*/
|
|
593
|
+
tintColor?: ColorValue
|
|
594
|
+
|
|
595
|
+
/**
|
|
596
|
+
* FastImage is a single native view, which doesn't take children (since
|
|
597
|
+
* 10.0): use `FastImageBackground` for content over an image.
|
|
598
|
+
*
|
|
599
|
+
* @hidden
|
|
600
|
+
*/
|
|
601
|
+
children?: "FastImage doesn't take children: use FastImageBackground for content over an image"
|
|
602
|
+
}
|
|
603
|
+
|
|
604
|
+
const resolveDefaultSource = (
|
|
605
|
+
defaultSource?: ImageRequireSource,
|
|
606
|
+
): string | object | null => {
|
|
607
|
+
if (!defaultSource) {
|
|
608
|
+
return null
|
|
609
|
+
}
|
|
610
|
+
const resolved = Image.resolveAssetSource(defaultSource)
|
|
611
|
+
if (!resolved) {
|
|
612
|
+
return null
|
|
613
|
+
}
|
|
614
|
+
// Android takes the uri, and loads it as a Drawable with React Native's
|
|
615
|
+
// helpers; iOS the image source ({ uri, width, height, scale }).
|
|
616
|
+
return Platform.OS === 'android' ? resolved.uri : resolved
|
|
617
|
+
}
|
|
618
|
+
|
|
619
|
+
// The native loopCount: -1 for the file's own, 0 for forever, or a number of
|
|
620
|
+
// plays. Always sent, since native would reset a removed prop to 0 (forever).
|
|
621
|
+
function loopCount(loop: boolean | number | undefined) {
|
|
622
|
+
if (loop === undefined) return -1
|
|
623
|
+
if (loop === true) return 0
|
|
624
|
+
if (loop === false) return 1
|
|
625
|
+
return Number.isFinite(loop) ? Math.max(1, Math.floor(loop)) : 0
|
|
626
|
+
}
|
|
627
|
+
|
|
628
|
+
// onLoadEnd's result from the native event (ok with the image's size, or not
|
|
629
|
+
// ok with the error).
|
|
630
|
+
function loadResult(event: {
|
|
631
|
+
ok?: boolean
|
|
632
|
+
width?: number
|
|
633
|
+
height?: number
|
|
634
|
+
error?: string
|
|
635
|
+
}): LoadResult {
|
|
636
|
+
return event.ok
|
|
637
|
+
? { ok: true, width: event.width ?? 0, height: event.height ?? 0 }
|
|
638
|
+
: { ok: false, error: event.error ?? 'Failed to load the image' }
|
|
639
|
+
}
|
|
640
|
+
|
|
641
|
+
// The native view's handledEvents (see specs/FastImageViewNativeComponent.ts):
|
|
642
|
+
// the events with a handler, which are the only ones it sends.
|
|
643
|
+
function handledEvents({
|
|
644
|
+
onLoadStart,
|
|
645
|
+
onProgress,
|
|
646
|
+
onLoad,
|
|
647
|
+
onError,
|
|
648
|
+
onLoadEnd,
|
|
649
|
+
}: FastImageProps) {
|
|
650
|
+
return (
|
|
651
|
+
(onLoadStart ? 1 : 0) |
|
|
652
|
+
(onProgress ? 2 : 0) |
|
|
653
|
+
(onLoad ? 4 : 0) |
|
|
654
|
+
(onError ? 8 : 0) |
|
|
655
|
+
(onLoadEnd ? 16 : 0)
|
|
656
|
+
)
|
|
657
|
+
}
|
|
658
|
+
|
|
659
|
+
// The platform's usual fade length: Glide's and React Native's Image's on
|
|
660
|
+
// Android, Core Animation's default on iOS.
|
|
661
|
+
function usualFadeMs() {
|
|
662
|
+
return Platform.OS === 'ios' ? 250 : 300
|
|
663
|
+
}
|
|
664
|
+
|
|
665
|
+
// The native transition props. Always sent, so removing `transition` goes
|
|
666
|
+
// back to the default.
|
|
667
|
+
type TransitionProps = {
|
|
668
|
+
transitionDuration: number
|
|
669
|
+
transitionBetweenImages: boolean
|
|
670
|
+
transitionSkipOnCacheHit: string
|
|
671
|
+
}
|
|
672
|
+
|
|
673
|
+
const NO_TRANSITION: TransitionProps = {
|
|
674
|
+
transitionDuration: 0,
|
|
675
|
+
transitionBetweenImages: false,
|
|
676
|
+
transitionSkipOnCacheHit: 'memory',
|
|
677
|
+
}
|
|
678
|
+
|
|
679
|
+
// A transition (an image's, or setDefaults') over `base`: the settings it
|
|
680
|
+
// leaves out come from base, and null (or undefined) is base. true, or a
|
|
681
|
+
// missing duration, is base's duration if it fades, otherwise the platform's
|
|
682
|
+
// usual one.
|
|
683
|
+
function resolveTransition(
|
|
684
|
+
transition: FastImageProps['transition'],
|
|
685
|
+
base: TransitionProps,
|
|
686
|
+
): TransitionProps {
|
|
687
|
+
if (transition == null) return base
|
|
688
|
+
if (transition === false) return { ...base, transitionDuration: 0 }
|
|
689
|
+
const usual =
|
|
690
|
+
base.transitionDuration > 0 ? base.transitionDuration : usualFadeMs()
|
|
691
|
+
if (transition === true) return { ...base, transitionDuration: usual }
|
|
692
|
+
const { duration, betweenImages, skipOnCacheHit }: Transition =
|
|
693
|
+
typeof transition === 'number' ? { duration: transition } : transition
|
|
694
|
+
const resolvedDuration = duration ?? usual
|
|
695
|
+
return {
|
|
696
|
+
transitionDuration:
|
|
697
|
+
typeof resolvedDuration === 'number' && resolvedDuration > 0
|
|
698
|
+
? resolvedDuration
|
|
699
|
+
: 0,
|
|
700
|
+
transitionBetweenImages: betweenImages ?? base.transitionBetweenImages,
|
|
701
|
+
transitionSkipOnCacheHit:
|
|
702
|
+
skipOnCacheHit ?? base.transitionSkipOnCacheHit,
|
|
703
|
+
}
|
|
704
|
+
}
|
|
705
|
+
|
|
706
|
+
// The built-in default: Android fades images in, as React Native's Image does;
|
|
707
|
+
// iOS doesn't, as UIImageView and SDWebImage don't.
|
|
708
|
+
function builtInTransition() {
|
|
709
|
+
return resolveTransition(Platform.OS === 'android', NO_TRANSITION)
|
|
710
|
+
}
|
|
711
|
+
|
|
712
|
+
// The default transition set with FastImage.setDefaults (undefined: the
|
|
713
|
+
// built-in one).
|
|
714
|
+
let defaultTransition: TransitionProps | undefined
|
|
715
|
+
|
|
716
|
+
// React Native's global: true in development (Metro and React Native's Jest
|
|
717
|
+
// preset set it), false in release builds.
|
|
718
|
+
declare const __DEV__: boolean
|
|
719
|
+
|
|
720
|
+
function FastImageBase({
|
|
721
|
+
source,
|
|
722
|
+
defaultSource,
|
|
723
|
+
tintColor,
|
|
724
|
+
onLoadStart,
|
|
725
|
+
onProgress,
|
|
726
|
+
onLoad,
|
|
727
|
+
onError,
|
|
728
|
+
onLoadEnd,
|
|
729
|
+
style,
|
|
730
|
+
children,
|
|
731
|
+
objectFit,
|
|
732
|
+
objectPosition,
|
|
733
|
+
loop,
|
|
734
|
+
transition,
|
|
735
|
+
forwardedRef,
|
|
736
|
+
...viewProps
|
|
737
|
+
}: FastImageProps & { forwardedRef: React.Ref<any> }) {
|
|
738
|
+
const {
|
|
739
|
+
fallback,
|
|
740
|
+
resizeMode: _resizeMode,
|
|
741
|
+
...props
|
|
742
|
+
} = viewProps as typeof viewProps & {
|
|
743
|
+
// Removed in 10.0 (React Native's Image instead of FastImage's view).
|
|
744
|
+
fallback?: unknown
|
|
745
|
+
// Removed in 10.0 (objectFit instead): ignored, so it reaches no
|
|
746
|
+
// native prop.
|
|
747
|
+
resizeMode?: unknown
|
|
748
|
+
}
|
|
749
|
+
if (__DEV__ && fallback !== undefined) {
|
|
750
|
+
warnOnce(
|
|
751
|
+
'fallback',
|
|
752
|
+
"react-native-fast-image: `fallback` was removed in 10.0, and does nothing: images always load natively. Remove it, or use React Native's Image for those images.",
|
|
753
|
+
'warn',
|
|
754
|
+
)
|
|
755
|
+
}
|
|
756
|
+
// FastImage is a single native view: children aren't rendered (as with
|
|
757
|
+
// React Native's Image).
|
|
758
|
+
if (__DEV__) {
|
|
759
|
+
warnIfChildren(children)
|
|
760
|
+
}
|
|
761
|
+
// tintColor can also be set in style, as with React Native's Image. The
|
|
762
|
+
// prop wins.
|
|
763
|
+
const resolvedTintColor =
|
|
764
|
+
tintColor != null ? tintColor : fromStyle(style, 'tintColor')
|
|
765
|
+
const fit = resolveObjectFit(objectFit, style)
|
|
766
|
+
const position = objectPositionOrCentered(objectPosition)
|
|
767
|
+
|
|
768
|
+
// Several sources are picked from natively, for the view's size; one in
|
|
769
|
+
// an array is a plain source.
|
|
770
|
+
const sources = Array.isArray(source)
|
|
771
|
+
? source.length === 1
|
|
772
|
+
? undefined
|
|
773
|
+
: source
|
|
774
|
+
: undefined
|
|
775
|
+
const single = Array.isArray(source) ? source[0] : source
|
|
776
|
+
const resolvedSource = sources
|
|
777
|
+
? undefined
|
|
778
|
+
: Image.resolveAssetSource(single as any)
|
|
779
|
+
const resolvedDefaultSource = resolveDefaultSource(defaultSource)
|
|
780
|
+
|
|
781
|
+
return (
|
|
782
|
+
<FastImageView
|
|
783
|
+
{...props}
|
|
784
|
+
ref={forwardedRef}
|
|
785
|
+
// Clips the image to borderRadius by default, as React Native's
|
|
786
|
+
// Image does.
|
|
787
|
+
style={[styles.image, style]}
|
|
788
|
+
// null (no key) as undefined, which the native prop takes.
|
|
789
|
+
recyclingKey={props.recyclingKey ?? undefined}
|
|
790
|
+
loopCount={loopCount(loop)}
|
|
791
|
+
{...resolveTransition(
|
|
792
|
+
transition,
|
|
793
|
+
defaultTransition ?? builtInTransition(),
|
|
794
|
+
)}
|
|
795
|
+
source={resolvedSource}
|
|
796
|
+
sources={sources}
|
|
797
|
+
defaultSource={resolvedDefaultSource}
|
|
798
|
+
onFastImageLoadStart={onLoadStart}
|
|
799
|
+
onFastImageProgress={onProgress}
|
|
800
|
+
// The native views only send the events with a handler (and only
|
|
801
|
+
// track progress with onProgress).
|
|
802
|
+
handledEvents={handledEvents({
|
|
803
|
+
onLoadStart,
|
|
804
|
+
onProgress,
|
|
805
|
+
onLoad,
|
|
806
|
+
onError,
|
|
807
|
+
onLoadEnd,
|
|
808
|
+
})}
|
|
809
|
+
onFastImageLoad={onLoad}
|
|
810
|
+
onFastImageError={onError}
|
|
811
|
+
onFastImageLoadEnd={
|
|
812
|
+
onLoadEnd &&
|
|
813
|
+
((event: { nativeEvent: any }) =>
|
|
814
|
+
onLoadEnd(loadResult(event.nativeEvent)))
|
|
815
|
+
}
|
|
816
|
+
// After style, whose tintColor and objectFit reach the native
|
|
817
|
+
// view's props too: these win.
|
|
818
|
+
tintColor={resolvedTintColor}
|
|
819
|
+
objectFit={fit}
|
|
820
|
+
objectPositionX={position.x.fraction}
|
|
821
|
+
objectPositionXOffset={position.x.offset}
|
|
822
|
+
objectPositionY={position.y.fraction}
|
|
823
|
+
objectPositionYOffset={position.y.offset}
|
|
824
|
+
/>
|
|
825
|
+
)
|
|
826
|
+
}
|
|
827
|
+
|
|
828
|
+
const FastImageMemo = memo(FastImageBase)
|
|
829
|
+
|
|
830
|
+
// What a ref to FastImage or FastImageBackground gets: FastImage's native
|
|
831
|
+
// view, or FastImageBackground's view. Typed as a View's (they have the same
|
|
832
|
+
// methods, e.g. measure). ComponentRef, as
|
|
833
|
+
// React Native's types declare View as a class, and its Strict TypeScript API
|
|
834
|
+
// (opt-in from 0.80, the default from 0.87) as a function component that takes
|
|
835
|
+
// a ref.
|
|
836
|
+
type ViewRef = React.ComponentRef<typeof View>
|
|
837
|
+
|
|
838
|
+
const FastImageComponent: React.ForwardRefExoticComponent<
|
|
839
|
+
FastImageProps & React.RefAttributes<ViewRef>
|
|
840
|
+
> = forwardRef((props: FastImageProps, ref: React.Ref<ViewRef>) => (
|
|
841
|
+
<FastImageMemo forwardedRef={ref} {...props} />
|
|
842
|
+
))
|
|
843
|
+
|
|
844
|
+
FastImageComponent.displayName = 'FastImage'
|
|
845
|
+
|
|
846
|
+
/** A preloaded source that loaded (and is now cached). */
|
|
847
|
+
export interface PreloadSuccess {
|
|
848
|
+
/** The source's uri. */
|
|
849
|
+
uri: string
|
|
850
|
+
ok: true
|
|
851
|
+
/** The image's width (as in `onLoad`). */
|
|
852
|
+
width: number
|
|
853
|
+
/** The image's height (as in `onLoad`). */
|
|
854
|
+
height: number
|
|
855
|
+
}
|
|
856
|
+
|
|
857
|
+
/** A preloaded source that failed to load. */
|
|
858
|
+
export interface PreloadFailure {
|
|
859
|
+
/** The source's uri (none for a source without one, e.g. `null`). */
|
|
860
|
+
uri?: string
|
|
861
|
+
ok: false
|
|
862
|
+
/** What went wrong. */
|
|
863
|
+
error: string
|
|
864
|
+
}
|
|
865
|
+
|
|
866
|
+
/**
|
|
867
|
+
* `preload`'s result for a source. Check `ok` to tell which it is: e.g.
|
|
868
|
+
* `if (result.ok)` narrows it to a `PreloadSuccess`, with its size. Reading
|
|
869
|
+
* `width` or `error` without checking is a type error.
|
|
870
|
+
*/
|
|
871
|
+
export type PreloadResult = PreloadSuccess | PreloadFailure
|
|
872
|
+
|
|
873
|
+
// A result as native sends it (without the uri).
|
|
874
|
+
type NativePreloadResult =
|
|
875
|
+
| Omit<PreloadSuccess, 'uri'>
|
|
876
|
+
| Omit<PreloadFailure, 'uri'>
|
|
877
|
+
|
|
878
|
+
const noResult: NativePreloadResult = { ok: false, error: 'No result' }
|
|
879
|
+
|
|
880
|
+
// The native module, or an error saying why it's missing, thrown when a
|
|
881
|
+
// function is called (as React Native's own optional modules do), so
|
|
882
|
+
// importing FastImage works without it (Jest, Expo Go).
|
|
883
|
+
function nativeModule(): NonNullable<typeof NativeFastImageModule> {
|
|
884
|
+
if (!NativeFastImageModule) {
|
|
885
|
+
throw new Error(
|
|
886
|
+
"react-native-fast-image: the native module FastImageModule isn't " +
|
|
887
|
+
'in this app. Rebuild the app after installing the package ' +
|
|
888
|
+
'(run pod install on iOS). It needs the New Architecture, and ' +
|
|
889
|
+
"isn't in Expo Go: use a development build. In Jest, mock the " +
|
|
890
|
+
'function (e.g. jest.spyOn(FastImage, "preload")).',
|
|
891
|
+
)
|
|
892
|
+
}
|
|
893
|
+
return NativeFastImageModule
|
|
894
|
+
}
|
|
895
|
+
|
|
896
|
+
export interface FastImageStaticProperties {
|
|
897
|
+
/**
|
|
898
|
+
* @deprecated `resizeMode` was removed in 10.0, and a `resizeMode` prop
|
|
899
|
+
* is ignored: use `objectFit` instead (`stretch` is `fill`, `center` is
|
|
900
|
+
* `scale-down`). Kept so code that reads it doesn't throw.
|
|
901
|
+
*/
|
|
902
|
+
resizeMode: typeof resizeMode
|
|
903
|
+
/**
|
|
904
|
+
* @deprecated Use the strings instead, e.g. `priority: 'high'`.
|
|
905
|
+
*/
|
|
906
|
+
priority: typeof priority
|
|
907
|
+
/**
|
|
908
|
+
* @deprecated Use the strings instead, e.g. `cache: 'immutable'`.
|
|
909
|
+
*/
|
|
910
|
+
cacheControl: typeof cacheControl
|
|
911
|
+
preload: (sources: Source[]) => Promise<PreloadResult[]>
|
|
912
|
+
/**
|
|
913
|
+
* Removes every image from the memory cache. Resolves once it's done, or
|
|
914
|
+
* not `ok` with the error if it couldn't (Android: Glide failed to
|
|
915
|
+
* start). Never rejects.
|
|
916
|
+
*/
|
|
917
|
+
clearMemoryCache: () => Promise<ClearCacheResult>
|
|
918
|
+
/**
|
|
919
|
+
* Removes every image from the disk cache, and the HTTP cache of
|
|
920
|
+
* `cache: 'web'` images. Resolves once it's done, or not `ok` with the
|
|
921
|
+
* error if it couldn't (Android: Glide failed to start). Never rejects.
|
|
922
|
+
*/
|
|
923
|
+
clearDiskCache: () => Promise<ClearCacheResult>
|
|
924
|
+
/**
|
|
925
|
+
* The path of the source's downloaded file in the disk cache, downloading
|
|
926
|
+
* it first if it isn't there (without decoding it). With
|
|
927
|
+
* `cache: 'cacheOnly'`, or a `cacheKey` without a `uri`, it doesn't
|
|
928
|
+
* download. Never rejects.
|
|
929
|
+
*/
|
|
930
|
+
getCachePath: (source: Source) => Promise<CachePathResult>
|
|
931
|
+
/**
|
|
932
|
+
* Stores a local image file (a `file://` uri or a path, or on Android a
|
|
933
|
+
* `content://` uri) as the source's image in the disk cache, so views and
|
|
934
|
+
* preloads of the source show it without downloading it. A source with a
|
|
935
|
+
* `cacheKey` doesn't need a `uri`. Resolves with its cached file. Doesn't
|
|
936
|
+
* replace an image that's already cached, and isn't for `cache: 'web'`
|
|
937
|
+
* sources. Never rejects.
|
|
938
|
+
*/
|
|
939
|
+
writeToCache: (source: Source, file: string) => Promise<CachePathResult>
|
|
940
|
+
/**
|
|
941
|
+
* Changes the cache's limits at runtime (only those given; 0 for no
|
|
942
|
+
* limit, null to go back to the app's native config) and saves them, then
|
|
943
|
+
* resolves with the limits in effect and the disk cache's size; without
|
|
944
|
+
* limits, it only resolves with them. iOS applies changes at once.
|
|
945
|
+
* Android only has `maxDiskSize`, applied when Glide starts: at once if
|
|
946
|
+
* it hasn't started yet in this launch, otherwise from the next launch.
|
|
947
|
+
* FastImage starts it when its first view is created (before the view has
|
|
948
|
+
* a source), and for `preload`, `getCachePath`, `writeToCache`,
|
|
949
|
+
* `clearDiskCache` and `configureCache` itself, even without limits (e.g.
|
|
950
|
+
* to read the disk cache's size).
|
|
951
|
+
*/
|
|
952
|
+
configureCache: (limits?: CacheLimits) => Promise<CacheState>
|
|
953
|
+
/**
|
|
954
|
+
* Changes the defaults of every image (only those given; null goes back
|
|
955
|
+
* to the built-in one). An image already showing uses a change once one
|
|
956
|
+
* of its props changes, for its next load. Not saved: call it before
|
|
957
|
+
* images render, e.g. in index.js.
|
|
958
|
+
*/
|
|
959
|
+
setDefaults: (defaults: FastImageDefaults) => void
|
|
960
|
+
}
|
|
961
|
+
|
|
962
|
+
/** `FastImage.setDefaults`'s settings. */
|
|
963
|
+
export interface FastImageDefaults {
|
|
964
|
+
/**
|
|
965
|
+
* The `transition` of images that don't set one (an image's own settings
|
|
966
|
+
* win, and those it leaves out come from this). `null` goes back to the
|
|
967
|
+
* built-in default (see `transition`).
|
|
968
|
+
*/
|
|
969
|
+
transition?: boolean | number | Transition | null
|
|
970
|
+
}
|
|
971
|
+
|
|
972
|
+
const FastImage: React.ForwardRefExoticComponent<
|
|
973
|
+
FastImageProps & React.RefAttributes<ViewRef>
|
|
974
|
+
> &
|
|
975
|
+
FastImageStaticProperties = FastImageComponent as any
|
|
976
|
+
|
|
977
|
+
FastImage.cacheControl = cacheControl
|
|
978
|
+
|
|
979
|
+
FastImage.priority = priority
|
|
980
|
+
|
|
981
|
+
FastImage.resizeMode = resizeMode
|
|
982
|
+
|
|
983
|
+
FastImage.setDefaults = ({ transition }: FastImageDefaults = {}) => {
|
|
984
|
+
if (transition === null) {
|
|
985
|
+
defaultTransition = undefined
|
|
986
|
+
} else if (transition !== undefined) {
|
|
987
|
+
defaultTransition = resolveTransition(transition, builtInTransition())
|
|
988
|
+
}
|
|
989
|
+
}
|
|
990
|
+
|
|
991
|
+
// preload, getCachePath and writeToCache take one source per image, not
|
|
992
|
+
// several sizes of one: pick the size (e.g. the one a view will show).
|
|
993
|
+
const ONE_SOURCE =
|
|
994
|
+
'Takes one source, not an array of sizes: pass the size to use'
|
|
995
|
+
|
|
996
|
+
// Whether a source is an object, which native can read. Other values (null, a
|
|
997
|
+
// url string, a number, an array) are sent as {}, which fails as a source
|
|
998
|
+
// without a uri: native would crash reading them as a source.
|
|
999
|
+
const isSourceObject = (source: unknown): source is Source =>
|
|
1000
|
+
typeof source === 'object' && source !== null && !Array.isArray(source)
|
|
1001
|
+
|
|
1002
|
+
FastImage.preload = (sources: Source[]) =>
|
|
1003
|
+
// Entries that aren't source objects are sent as {}, so native results
|
|
1004
|
+
// line up with the sources (iOS drops null entries). Arrays' results are
|
|
1005
|
+
// replaced.
|
|
1006
|
+
Promise.resolve(
|
|
1007
|
+
nativeModule().preload(
|
|
1008
|
+
sources.map((s) => (isSourceObject(s) ? s : {})),
|
|
1009
|
+
) as Promise<NativePreloadResult[]>,
|
|
1010
|
+
).then((results?: NativePreloadResult[]) =>
|
|
1011
|
+
sources.map((source, i): PreloadResult => {
|
|
1012
|
+
if (Array.isArray(source)) {
|
|
1013
|
+
return { ok: false, error: ONE_SOURCE, uri: undefined }
|
|
1014
|
+
}
|
|
1015
|
+
const uri = isSourceObject(source) ? source.uri : undefined
|
|
1016
|
+
const result = results?.[i] ?? noResult
|
|
1017
|
+
if (!result.ok) return { ...result, uri }
|
|
1018
|
+
// Native already fails a source without a uri.
|
|
1019
|
+
return typeof uri === 'string'
|
|
1020
|
+
? { ...result, uri }
|
|
1021
|
+
: { ok: false, error: 'Invalid source: no uri', uri }
|
|
1022
|
+
}),
|
|
1023
|
+
)
|
|
1024
|
+
|
|
1025
|
+
FastImage.clearMemoryCache = (): Promise<ClearCacheResult> =>
|
|
1026
|
+
nativeModule().clearMemoryCache() as Promise<ClearCacheResult>
|
|
1027
|
+
|
|
1028
|
+
FastImage.clearDiskCache = (): Promise<ClearCacheResult> =>
|
|
1029
|
+
nativeModule().clearDiskCache() as Promise<ClearCacheResult>
|
|
1030
|
+
|
|
1031
|
+
FastImage.configureCache = (limits?: CacheLimits): Promise<CacheState> => {
|
|
1032
|
+
// Sent as the limits to set (numbers) and the names of those to reset
|
|
1033
|
+
// (null), never as null values: iOS's TurboModule leaves those out of the
|
|
1034
|
+
// object it passes, so both platforms read resets from `reset`.
|
|
1035
|
+
const set: { [name: string]: number } = {}
|
|
1036
|
+
const reset: string[] = []
|
|
1037
|
+
for (const [name, value] of Object.entries(limits ?? {})) {
|
|
1038
|
+
if (value === null) reset.push(name)
|
|
1039
|
+
else if (typeof value === 'number') set[name] = value
|
|
1040
|
+
}
|
|
1041
|
+
return nativeModule().configureCache({
|
|
1042
|
+
...set,
|
|
1043
|
+
reset,
|
|
1044
|
+
}) as Promise<CacheState>
|
|
1045
|
+
}
|
|
1046
|
+
|
|
1047
|
+
FastImage.writeToCache = (
|
|
1048
|
+
source: Source,
|
|
1049
|
+
file: string,
|
|
1050
|
+
): Promise<CachePathResult> =>
|
|
1051
|
+
Array.isArray(source)
|
|
1052
|
+
? Promise.resolve({ ok: false, error: ONE_SOURCE })
|
|
1053
|
+
: Promise.resolve(
|
|
1054
|
+
nativeModule().writeToCache(
|
|
1055
|
+
isSourceObject(source) ? source : {},
|
|
1056
|
+
file,
|
|
1057
|
+
) as Promise<CachePathResult>,
|
|
1058
|
+
)
|
|
1059
|
+
|
|
1060
|
+
FastImage.getCachePath = (source: Source): Promise<CachePathResult> =>
|
|
1061
|
+
Array.isArray(source)
|
|
1062
|
+
? Promise.resolve({ ok: false, error: ONE_SOURCE })
|
|
1063
|
+
: Promise.resolve(
|
|
1064
|
+
nativeModule().getCachePath(
|
|
1065
|
+
isSourceObject(source) ? source : {},
|
|
1066
|
+
) as Promise<CachePathResult>,
|
|
1067
|
+
)
|
|
1068
|
+
|
|
1069
|
+
const styles = StyleSheet.create({
|
|
1070
|
+
image: {
|
|
1071
|
+
overflow: 'hidden',
|
|
1072
|
+
},
|
|
1073
|
+
})
|
|
1074
|
+
|
|
1075
|
+
export interface FastImageBackgroundProps extends Omit<
|
|
1076
|
+
FastImageProps,
|
|
1077
|
+
'style' | 'children'
|
|
1078
|
+
> {
|
|
1079
|
+
/** The container's style; the image fills it. */
|
|
1080
|
+
style?: StyleProp<ViewStyle>
|
|
1081
|
+
/** The image's style. */
|
|
1082
|
+
imageStyle?: StyleProp<ImageStyle>
|
|
1083
|
+
/** A ref to the image (the FastImage inside). */
|
|
1084
|
+
imageRef?: React.Ref<any>
|
|
1085
|
+
/** Content shown on top of the image. */
|
|
1086
|
+
children?: React.ReactNode
|
|
1087
|
+
}
|
|
1088
|
+
|
|
1089
|
+
/**
|
|
1090
|
+
* An image with content on top of it, like React Native's `ImageBackground`: a
|
|
1091
|
+
* view that the image fills, with the children on top (`FastImage` is a single
|
|
1092
|
+
* native view, which doesn't take children). The other props go to the image;
|
|
1093
|
+
* the ref is the view's.
|
|
1094
|
+
*/
|
|
1095
|
+
export const FastImageBackground: React.ForwardRefExoticComponent<
|
|
1096
|
+
FastImageBackgroundProps & React.RefAttributes<ViewRef>
|
|
1097
|
+
> = forwardRef(
|
|
1098
|
+
(
|
|
1099
|
+
{
|
|
1100
|
+
style,
|
|
1101
|
+
imageStyle,
|
|
1102
|
+
imageRef,
|
|
1103
|
+
children,
|
|
1104
|
+
importantForAccessibility,
|
|
1105
|
+
...props
|
|
1106
|
+
}: FastImageBackgroundProps,
|
|
1107
|
+
ref: React.Ref<ViewRef>,
|
|
1108
|
+
) => (
|
|
1109
|
+
<View
|
|
1110
|
+
accessibilityIgnoresInvertColors
|
|
1111
|
+
importantForAccessibility={importantForAccessibility}
|
|
1112
|
+
style={style}
|
|
1113
|
+
ref={ref}
|
|
1114
|
+
>
|
|
1115
|
+
<FastImageComponent
|
|
1116
|
+
{...props}
|
|
1117
|
+
importantForAccessibility={importantForAccessibility}
|
|
1118
|
+
style={[StyleSheet.absoluteFill, imageStyle]}
|
|
1119
|
+
ref={imageRef}
|
|
1120
|
+
/>
|
|
1121
|
+
{children}
|
|
1122
|
+
</View>
|
|
1123
|
+
),
|
|
1124
|
+
)
|
|
1125
|
+
|
|
1126
|
+
FastImageBackground.displayName = 'FastImageBackground'
|
|
1127
|
+
|
|
1128
|
+
export default FastImage
|