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.
Files changed (78) hide show
  1. package/README.md +97 -61
  2. package/RNFastImage.podspec +14 -9
  3. package/android/build.gradle +21 -47
  4. package/android/consumer-rules.pro +7 -7
  5. package/android/src/main/AndroidManifest.xml +1 -2
  6. package/android/src/main/java/com/{dylanvann/fastimage → fastimage}/FastImageAnimatable.java +1 -1
  7. package/android/src/main/java/com/{dylanvann/fastimage → fastimage}/FastImageAnimated.java +2 -2
  8. package/android/src/main/java/com/{dylanvann/fastimage → fastimage}/FastImageApng.java +2 -2
  9. package/android/src/main/java/com/{dylanvann/fastimage → fastimage}/FastImageBlur.java +4 -6
  10. package/android/src/main/java/com/{dylanvann/fastimage → fastimage}/FastImageCacheControl.java +1 -1
  11. package/android/src/main/java/com/{dylanvann/fastimage → fastimage}/FastImageCacheLimits.java +4 -1
  12. package/android/src/main/java/com/{dylanvann/fastimage → fastimage}/FastImageCacheWrite.java +1 -1
  13. package/android/src/main/java/com/fastimage/FastImageCrop.java +92 -0
  14. package/android/src/main/java/com/{dylanvann/fastimage → fastimage}/FastImageEvent.java +2 -3
  15. package/android/src/main/java/com/fastimage/FastImageEvents.java +74 -0
  16. package/android/src/main/java/com/{dylanvann/fastimage → fastimage}/FastImageGif.java +1 -1
  17. package/android/src/main/java/com/{dylanvann/fastimage → fastimage}/FastImageGlide.java +13 -3
  18. package/android/src/main/java/com/{dylanvann/fastimage → fastimage}/FastImageGlideModule.java +1 -1
  19. package/android/src/main/java/com/{dylanvann/fastimage → fastimage}/FastImageKeyedGlideUrl.java +1 -1
  20. package/android/src/main/java/com/{dylanvann/fastimage → fastimage}/FastImageOkHttpProgressGlideModule.java +49 -14
  21. package/android/src/main/java/com/{dylanvann/fastimage → fastimage}/FastImageRequestListener.java +6 -1
  22. package/android/src/main/java/com/{dylanvann/fastimage → fastimage}/FastImageSharedDownloads.java +1 -1
  23. package/android/src/main/java/com/{dylanvann/fastimage → fastimage}/FastImageSource.java +12 -1
  24. package/android/src/main/java/com/{dylanvann/fastimage → fastimage}/FastImageSourceSize.java +12 -2
  25. package/android/src/main/java/com/{dylanvann/fastimage → fastimage}/FastImageSvg.java +3 -3
  26. package/android/src/main/java/com/{dylanvann/fastimage → fastimage}/FastImageSvgRenderer.java +1 -1
  27. package/android/src/main/java/com/{dylanvann/fastimage → fastimage}/FastImageUrl.java +1 -1
  28. package/android/src/main/java/com/{dylanvann/fastimage → fastimage}/FastImageViewConverter.java +24 -9
  29. package/android/src/main/java/com/fastimage/FastImageViewManager.java +591 -0
  30. package/android/src/main/java/com/{dylanvann/fastimage → fastimage}/FastImageViewModule.java +50 -28
  31. package/android/src/main/java/com/fastimage/FastImageViewPackage.java +51 -0
  32. package/android/src/main/java/com/{dylanvann/fastimage → fastimage}/FastImageViewWithUrl.java +352 -126
  33. package/android/src/main/java/com/{dylanvann/fastimage → fastimage}/FastImageWebGlideUrl.java +1 -1
  34. package/app.plugin.js +2 -7
  35. package/dist/index.cjs.js +276 -157
  36. package/dist/index.cjs.js.flow +34 -17
  37. package/dist/index.cjs.web.js +211 -61
  38. package/dist/index.cjs.web.js.flow +34 -17
  39. package/dist/index.d.ts +108 -58
  40. package/dist/index.js +278 -159
  41. package/dist/index.js.flow +34 -17
  42. package/dist/index.web.js +212 -62
  43. package/dist/index.web.js.flow +34 -17
  44. package/dist/objectFit.d.ts +2 -2
  45. package/dist/objectPosition.d.ts +42 -0
  46. package/dist/specs/FastImageViewNativeComponent.d.ts +48 -0
  47. package/dist/specs/NativeFastImageModule.d.ts +11 -0
  48. package/dist/warnings.d.ts +3 -0
  49. package/ios/FastImage/FFFDownsampledImage.h +4 -8
  50. package/ios/FastImage/FFFDownsampledImage.m +0 -34
  51. package/ios/FastImage/FFFastImageModule.h +8 -0
  52. package/ios/FastImage/{FFFastImageViewManager.m → FFFastImageModule.mm} +89 -59
  53. package/ios/FastImage/FFFastImageSource.h +4 -0
  54. package/ios/FastImage/FFFastImageSource.m +5 -1
  55. package/ios/FastImage/FFFastImageView.h +36 -7
  56. package/ios/FastImage/FFFastImageView.m +229 -172
  57. package/ios/FastImage/FFFastImageViewComponentView.h +11 -0
  58. package/ios/FastImage/FFFastImageViewComponentView.mm +325 -0
  59. package/ios/FastImage/RCTConvert+FFFastImage.h +1 -1
  60. package/ios/FastImage/RCTConvert+FFFastImage.m +9 -10
  61. package/package.json +28 -9
  62. package/src/constants.ts +28 -0
  63. package/src/index.js.flow +180 -0
  64. package/src/index.tsx +1128 -0
  65. package/src/index.web.tsx +548 -0
  66. package/src/objectFit.ts +48 -0
  67. package/src/objectPosition.ts +243 -0
  68. package/src/specs/FastImageViewNativeComponent.ts +83 -0
  69. package/src/specs/NativeFastImageModule.ts +35 -0
  70. package/src/warnings.ts +37 -0
  71. package/android/src/main/AndroidManifestNew.xml +0 -3
  72. package/android/src/main/java/com/dylanvann/fastimage/FastImageCookieHandler.java +0 -63
  73. package/android/src/main/java/com/dylanvann/fastimage/FastImageEvents.java +0 -49
  74. package/android/src/main/java/com/dylanvann/fastimage/FastImageProgressListener.java +0 -18
  75. package/android/src/main/java/com/dylanvann/fastimage/FastImageShadowNode.java +0 -21
  76. package/android/src/main/java/com/dylanvann/fastimage/FastImageViewManager.java +0 -379
  77. package/android/src/main/java/com/dylanvann/fastimage/FastImageViewPackage.java +0 -27
  78. 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