react-native-fast-image 8.30.1 → 8.31.1

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 (25) hide show
  1. package/README.md +460 -210
  2. package/android/build.gradle +13 -1
  3. package/android/src/glide-4.15/java/com/dylanvann/fastimage/FastImageAnimated.java +2 -2
  4. package/android/src/main/java/com/dylanvann/fastimage/FastImageApng.java +2 -2
  5. package/android/src/main/java/com/dylanvann/fastimage/FastImageGif.java +1 -1
  6. package/android/src/main/java/com/dylanvann/fastimage/FastImageGlide.java +76 -0
  7. package/android/src/main/java/com/dylanvann/fastimage/FastImageGlideModule.java +1 -0
  8. package/android/src/main/java/com/dylanvann/fastimage/FastImageOkHttpProgressGlideModule.java +25 -51
  9. package/android/src/main/java/com/dylanvann/fastimage/FastImageRequestListener.java +1 -2
  10. package/android/src/main/java/com/dylanvann/fastimage/FastImageSource.java +1 -1
  11. package/android/src/main/java/com/dylanvann/fastimage/FastImageSourceSize.java +1 -1
  12. package/android/src/main/java/com/dylanvann/fastimage/FastImageSvg.java +2 -0
  13. package/android/src/main/java/com/dylanvann/fastimage/FastImageUrl.java +36 -0
  14. package/android/src/main/java/com/dylanvann/fastimage/FastImageViewConverter.java +2 -1
  15. package/android/src/main/java/com/dylanvann/fastimage/FastImageViewManager.java +1 -0
  16. package/android/src/main/java/com/dylanvann/fastimage/FastImageViewModule.java +13 -10
  17. package/android/src/main/java/com/dylanvann/fastimage/FastImageWebGlideUrl.java +1 -0
  18. package/app.plugin.js +1 -1
  19. package/dist/index.d.ts +358 -63
  20. package/ios/FastImage/FFFAnimatedAVIFCoder.h +11 -0
  21. package/ios/FastImage/FFFAnimatedAVIFCoder.m +103 -0
  22. package/ios/FastImage/FFFastImageSource.h +4 -0
  23. package/ios/FastImage/FFFastImageSource.m +36 -2
  24. package/ios/FastImage/FFFastImageViewManager.m +1 -2
  25. package/package.json +4 -2
package/dist/index.d.ts CHANGED
@@ -1,40 +1,103 @@
1
1
  import React from 'react';
2
- import { LayoutChangeEvent, StyleProp, ViewStyle, ImageRequireSource, AccessibilityProps, ViewProps } from 'react-native';
2
+ import { StyleProp, ViewStyle, ImageRequireSource, AccessibilityProps, ViewProps } from 'react-native';
3
3
  import { cacheControl, priority, resizeMode } from './constants';
4
4
  type ColorValue = NonNullable<ViewStyle['backgroundColor']>;
5
+ /** How the image fits the view: see [`resizeMode`](#resizemode). */
5
6
  export type ResizeMode = 'contain' | 'cover' | 'stretch' | 'center' | 'repeat';
7
+ /** An image's load priority: see [`source.priority`](#sourcepriority). */
6
8
  export type Priority = 'low' | 'normal' | 'high';
9
+ /** How fresh an image must be: see [`source.cache`](#sourcecache). */
7
10
  export type Cache = 'immutable' | 'web' | 'cacheOnly';
11
+ /** A remote image to load, and how to load and cache it. */
8
12
  export type Source = {
13
+ /**
14
+ * Remote url to load the image from. e.g. `'https://example.com/image.jpg'`.
15
+ *
16
+ * Also loads local files (`file://`, and on Android `content://`), photo
17
+ * library images on iOS (`ph://`, see
18
+ * [Photo library images](#photo-library-images-ios)) and SVG images (see
19
+ * [SVG images](docs/formats.md#svg-images)).
20
+ */
9
21
  uri?: string;
22
+ /** Headers to load the image with. e.g. `{ Authorization: 'someAuthToken' }`. */
10
23
  headers?: {
11
24
  [key: string]: string;
12
25
  };
26
+ /**
27
+ * A hint for which images to start loading first when several are
28
+ * waiting: `'high'` ones before `'normal'` ones, and `'low'` ones after.
29
+ * It's best effort, not an order: several images load at once, and when
30
+ * each finishes depends on its size and the network.
31
+ *
32
+ * - `'low'`: e.g. images further down a list.
33
+ * - `'normal'`: the default.
34
+ * - `'high'`: e.g. the image the screen is about.
35
+ *
36
+ * @default 'normal'
37
+ */
13
38
  priority?: Priority;
39
+ /**
40
+ * How fresh the image must be. See
41
+ * [how caching is handled](docs/how-is-caching-handled.md) for how the
42
+ * options fit together.
43
+ *
44
+ * - `'immutable'`: loads the image once, then shows the cached copy until
45
+ * its url (or `cacheKey`) changes.
46
+ * - `'web'`: follows the server's HTTP cache headers, as a browser does,
47
+ * checking with the server when it loads. These responses are kept in
48
+ * their own HTTP cache (50 MB on each platform), which `clearDiskCache`
49
+ * also clears.
50
+ * - `'cacheOnly'`: only shows a cached image, without making a request.
51
+ *
52
+ * @default 'immutable'
53
+ */
14
54
  cache?: Cache;
15
55
  /**
16
- * The key the image is cached under, instead of its uri. For urls that
17
- * change while the image stays the same, e.g. signed urls with a token or
18
- * an expiry: use something stable, like the image's id.
56
+ * The key the image is cached under, instead of its uri. Use it for urls
57
+ * that change while the image stays the same, e.g. signed urls with a
58
+ * token or an expiry. Use something that identifies the image, and change
59
+ * it when the image changes (e.g. include a version or the time it was
60
+ * updated), or the old image keeps showing.
61
+ *
62
+ * Not used with `cache: 'web'`, which follows the HTTP cache (keyed by
63
+ * url).
64
+ *
65
+ * @example
66
+ * ```jsx
67
+ * <FastImage
68
+ * source={{
69
+ * uri: signedUrl,
70
+ * cacheKey: `avatar-${user.id}-${user.avatarUpdatedAt}`,
71
+ * }}
72
+ * />
73
+ * ```
19
74
  */
20
75
  cacheKey?: string;
21
76
  /**
22
- * Whether the decoded image is kept in the memory cache (default true).
23
- * With false it's only kept on disk: a view doesn't leave it in memory
24
- * once it stops showing it, and `FastImage.preload` downloads it without
25
- * decoding it. For large images shown once, like a full-screen photo; a
26
- * list scrolled back decodes them again.
77
+ * Whether the decoded image is kept in the memory cache. With `false`
78
+ * it's only kept on disk: a view doesn't leave it in memory once it stops
79
+ * showing it, and `FastImage.preload` downloads it without decoding it.
80
+ * Use it for large images that are shown once or rarely, like a
81
+ * full-screen photo: a decoded photo can take tens of MB of memory. Images
82
+ * shown again, like a list scrolled back, are decoded from disk again.
83
+ *
84
+ * @default true
27
85
  */
28
86
  memoryCache?: boolean;
29
87
  /**
30
- * With several sources (`source` as an array), the image's size at this
31
- * uri in pixels (width and height, times `scale` if given): the view
32
- * loads the source closest to its own size.
88
+ * With several sources (`source` as an array), the image's width at this
89
+ * uri, in pixels (times `scale`, if given).
33
90
  */
34
91
  width?: number;
92
+ /**
93
+ * With several sources (`source` as an array), the image's height at this
94
+ * uri, in pixels (times `scale`, if given).
95
+ */
35
96
  height?: number;
97
+ /** With several sources, the scale `width` and `height` are multiplied by. */
36
98
  scale?: number;
37
99
  };
100
+ /** How the image fades in: see [`transition`](#transition). */
38
101
  export type Transition = {
39
102
  /**
40
103
  * How long the fade takes, in milliseconds; 0 means no fade. Defaults to
@@ -64,6 +127,11 @@ export type Transition = {
64
127
  */
65
128
  skipOnCacheHit?: 'none' | 'memory' | 'all' | null;
66
129
  };
130
+ /**
131
+ * `onLoad`'s event: `nativeEvent` has the image's `width` and `height`, in
132
+ * pixels, and `target`, the view's React tag (missing on Android with the
133
+ * legacy architecture).
134
+ */
67
135
  export interface OnLoadEvent {
68
136
  nativeEvent: {
69
137
  width: number;
@@ -71,11 +139,19 @@ export interface OnLoadEvent {
71
139
  target?: number;
72
140
  };
73
141
  }
142
+ /**
143
+ * `onError`'s event: `nativeEvent.error` says what went wrong, e.g. an HTTP
144
+ * status code or an image that can't be decoded.
145
+ */
74
146
  export interface OnErrorEvent {
75
147
  nativeEvent: {
76
148
  error: string;
77
149
  };
78
150
  }
151
+ /**
152
+ * A load's result, as `onLoadEnd` gets it: `ok` with the image's size, or not
153
+ * `ok` with the error (as `onLoad` and `onError` get them).
154
+ */
79
155
  export type LoadResult = {
80
156
  ok: true;
81
157
  width: number;
@@ -84,17 +160,47 @@ export type LoadResult = {
84
160
  ok: false;
85
161
  error: string;
86
162
  };
163
+ /**
164
+ * `configureCache`'s changes: a number (0 for no limit), or `null` to go back
165
+ * to the app's native config (Info.plist, AndroidManifest.xml) or the
166
+ * platform's default. Leave one out to keep it. Changes are saved, and used on
167
+ * the next launches too. Android only has `maxDiskSize`.
168
+ */
87
169
  export interface CacheLimits {
170
+ /**
171
+ * The most bytes of images kept on disk. When it's over, the least
172
+ * recently used are removed (on iOS, until it's half this size). Default:
173
+ * no limit on iOS, 250 MB on Android.
174
+ */
88
175
  maxDiskSize?: number | null;
176
+ /**
177
+ * iOS: the seconds an image is kept on disk after it was last used, or 0
178
+ * to keep them until `maxDiskSize` removes them. Default: 1 week.
179
+ */
89
180
  maxDiskAge?: number | null;
181
+ /**
182
+ * iOS: the most bytes of decoded images kept in memory. Default: no limit
183
+ * (they're removed when the system is low on memory).
184
+ */
90
185
  maxMemorySize?: number | null;
91
186
  }
187
+ /**
188
+ * `configureCache`'s result: the limits in effect (0 for no limit), and the
189
+ * bytes the disk cache uses now. Android only has `maxDiskSize` and
190
+ * `diskSize`, and neither if the app has its own `AppGlideModule` (or
191
+ * expo-image, which has one).
192
+ */
92
193
  export interface CacheState {
93
194
  maxDiskSize?: number;
94
195
  maxDiskAge?: number;
95
196
  maxMemorySize?: number;
197
+ /** The bytes the disk cache uses now. */
96
198
  diskSize?: number;
97
199
  }
200
+ /**
201
+ * `getCachePath`'s and `writeToCache`'s result: `ok` with the file's path, or
202
+ * not `ok` with the error.
203
+ */
98
204
  export type CachePathResult = {
99
205
  ok: true;
100
206
  path: string;
@@ -102,6 +208,10 @@ export type CachePathResult = {
102
208
  ok: false;
103
209
  error: string;
104
210
  };
211
+ /**
212
+ * `onProgress`'s event: `nativeEvent` has the bytes `loaded` and the `total`,
213
+ * and `progress`, `loaded / total` from 0 to 1.
214
+ */
105
215
  export interface OnProgressEvent {
106
216
  nativeEvent: {
107
217
  loaded: number;
@@ -115,116 +225,301 @@ export interface ImageStyle extends ViewStyle {
115
225
  }
116
226
  export interface FastImageProps extends AccessibilityProps, ViewProps {
117
227
  /**
118
- * The image: a source, a `require()`d image, or several sources of the
119
- * same image at different sizes (each with its `width` and `height` in
120
- * pixels), of which the view loads the one closest to its size.
228
+ * Source for the remote image to load: a `Source`, a `require()`d image,
229
+ * or an array of the same image at different sizes.
230
+ *
231
+ * When `source` changes, the image that's showing stays until the new one
232
+ * has loaded. In views that get reused for other content, such as rows in
233
+ * FlashList or recyclerlistview, set `recyclingKey` so a reused row
234
+ * doesn't show the previous row's image.
235
+ *
236
+ * **Several sizes of an image.** `source` can also be an array of the
237
+ * same image at different sizes, each with its `width` and `height` in
238
+ * pixels (times `scale`, if it has one). The view loads the one whose size
239
+ * is closest to its own, in pixels, so a small view downloads a small
240
+ * image:
241
+ *
242
+ * ```jsx
243
+ * <FastImage
244
+ * style={{ width: 120, height: 120 }}
245
+ * source={[
246
+ * { uri: 'https://example.com/photo-200.jpg', width: 200, height: 200 },
247
+ * { uri: 'https://example.com/photo-800.jpg', width: 800, height: 800 },
248
+ * ]}
249
+ * />
250
+ * ```
251
+ *
252
+ * - The view picks once it has been laid out. One that has no size (e.g.
253
+ * sized from `onLoad`) loads the largest.
254
+ * - When the view's size changes so that another size fits better, it
255
+ * loads that one (with its load events), and keeps showing the current
256
+ * image until then, without a `transition`.
257
+ * - Each size is cached separately. A `cacheKey` applies to its own entry:
258
+ * give each size its own key (or none), or the sizes would replace each
259
+ * other in the cache.
260
+ * - `FastImage.preload`, `getCachePath` and `writeToCache` take one size:
261
+ * pass the one a view will show. An array fails with
262
+ * `{ ok: false, error }`.
263
+ * - An array of one is the same as that source.
121
264
  */
122
265
  source?: Source | Source[] | ImageRequireSource;
266
+ /**
267
+ * An asset loaded with `require(...)`, shown while the first image loads,
268
+ * and if an image fails to load. When `source` changes, the previous image
269
+ * shows while the new one loads instead (see `source` and
270
+ * `recyclingKey`).
271
+ *
272
+ * On Android, `defaultSource` doesn't show in debug builds: there the dev
273
+ * server serves `require()`d images, and `defaultSource` is only loaded
274
+ * from the app's resources.
275
+ */
123
276
  defaultSource?: ImageRequireSource;
277
+ /**
278
+ * How the image fills the view.
279
+ *
280
+ * - `'contain'`: scales it uniformly (keeping its aspect ratio) so all of
281
+ * it fits in the view (minus padding).
282
+ * - `'cover'`: scales it uniformly (keeping its aspect ratio) so it covers
283
+ * the view (minus padding), cropping what doesn't fit.
284
+ * - `'stretch'`: scales its width and height separately to fill the view,
285
+ * which can change its aspect ratio.
286
+ * - `'center'`: centers it at its own size, scaled down uniformly to fit
287
+ * if it's larger than the view.
288
+ * - `'repeat'`: repeats it to cover the view, from its top-left corner, at
289
+ * the image's own size in pixels (a bundled image at its size in
290
+ * points), scaled down to fit if it's larger than the view. An animated
291
+ * image repeats its first frame, and `defaultSource` repeats too.
292
+ *
293
+ * @default 'cover'
294
+ */
124
295
  resizeMode?: ResizeMode;
296
+ /**
297
+ * If true, the image is shown with React Native's `Image` instead, styled
298
+ * and laid out the same way. FastImage's own features, such as its
299
+ * caching options, `priority` and `transition`, don't apply.
300
+ */
125
301
  fallback?: boolean;
126
302
  /**
127
- * When `source` changes, the image showing stays until the new one has
128
- * loaded. For views that get reused for other content, such as rows in
129
- * FlashList or recyclerlistview, set this to something that identifies the
130
- * content (e.g. the item's id): when it changes, the image is cleared
131
- * right away instead, so a reused row doesn't show the previous row's
132
- * image. Unlike changing `key`, the view is kept.
303
+ * For views that get reused for other content, such as rows in FlashList
304
+ * or recyclerlistview. Set it to something that identifies the content,
305
+ * e.g. the item's id. When it changes, the image is cleared right away (to
306
+ * `defaultSource`, or blank) instead of staying until the new one has
307
+ * loaded. Unlike changing `key`, this keeps the view, which is what list
308
+ * recycling is for.
309
+ *
310
+ * @example
311
+ * ```jsx
312
+ * <FastImage recyclingKey={item.id} source={{ uri: item.imageUrl }} />
313
+ * ```
133
314
  */
134
315
  recyclingKey?: string | null;
135
316
  /**
136
- * How many times an animated image (GIF, animated WebP, APNG, and animated
137
- * AVIF on Android) plays: the file's own loop count by default, `true` to
138
- * loop forever, `false` to play once, or a number of times. Changing it
139
- * restarts the animation.
317
+ * How many times an animated image (GIF, animated WebP, APNG, or animated
318
+ * AVIF) plays:
319
+ *
320
+ * - Not set: as many times as the file says (like a browser).
321
+ * - `true`: forever.
322
+ * - `false`: once.
323
+ * - A number: that many times.
324
+ *
325
+ * Changing it restarts the animation.
140
326
  */
141
327
  loop?: boolean | number;
142
328
  /**
143
329
  * How the image is filtered when it's drawn smaller or larger than its
144
- * size: `'auto'` (default, the platform's usual filtering), `'smooth'`
145
- * (iOS only: keeps a large image drawn much smaller from looking jagged
146
- * or noisy; uses more memory, see the README) or `'pixelated'` (sharp
147
- * pixels, e.g. for pixel art).
330
+ * size (like CSS's `image-rendering`):
331
+ *
332
+ * - `'auto'`: the platform's usual filtering.
333
+ * - `'smooth'`: iOS only. Keeps a large image drawn much smaller than its
334
+ * size (e.g. a big photo as a thumbnail, or fine lines and text) from
335
+ * looking jagged or noisy. Such an image is already decoded at about the
336
+ * view's size by default (see `downsample`), so this is for images shown
337
+ * at less than half their size with `downsample={false}`, or a little
338
+ * smaller than their size. **It uses more memory:** the image is also
339
+ * kept at smaller sizes for drawing, about a third more than the decoded
340
+ * image. On Android it's the same as `'auto'` (images are always decoded
341
+ * at about the view's size there).
342
+ * - `'pixelated'`: sharp pixels, without smoothing, e.g. for pixel art
343
+ * drawn larger than its size. On Android, animated images are still
344
+ * smoothed.
345
+ *
346
+ * @default 'auto'
148
347
  */
149
348
  imageRendering?: 'auto' | 'smooth' | 'pixelated';
150
349
  /**
151
- * Pauses an animated image (GIF, animated WebP, APNG, and animated AVIF on
152
- * Android) on the frame it's showing; `false` plays it again from there
350
+ * Pauses an animated image (GIF, animated WebP, APNG, or animated AVIF) on
351
+ * the frame it's showing; `false` plays it again from there
153
352
  * (on Android, an animated WebP or AVIF plays again from its first frame:
154
353
  * Android can't resume one). Each image animates on its own, so pausing
155
354
  * one doesn't pause others showing the same file.
156
355
  */
157
356
  paused?: boolean;
158
357
  /**
159
- * Fades the image in when it loads: `true` for the platform's usual fade,
160
- * a duration in milliseconds, or a `Transition` (default: no fade). An
161
- * image fades in when it appears over nothing (see `betweenImages`),
162
- * unless it's from the memory cache (see `skipOnCacheHit`).
358
+ * Fades the image in when it loads. `true` uses the platform's usual
359
+ * fade, a number is the duration in milliseconds, or pass a `Transition`.
360
+ *
361
+ * Downloads, local files (`file://`, `content://`) and bundled images
362
+ * (`require()`) fade in. In lists that reuse views (e.g. FlashList), set
363
+ * `recyclingKey`, so a reused view starts empty and its image fades in,
364
+ * instead of showing the previous item's image until it loads.
365
+ *
366
+ * @default false
367
+ * @example
368
+ * ```jsx
369
+ * <FastImage source={{ uri }} transition />
370
+ * <FastImage source={{ uri }} transition={500} />
371
+ * <FastImage source={{ uri }} transition={{ betweenImages: true, skipOnCacheHit: 'none' }} />
372
+ * ```
163
373
  */
164
374
  transition?: boolean | number | Transition | null;
165
375
  /**
166
- * iOS only. Decodes a large image at about the size it's shown at, so it
167
- * takes much less memory. `true` (default). `false` decodes images at
168
- * full size, e.g. for an image that's zoomed in on with a transform. See
169
- * the README.
376
+ * Decodes a large image at about the size it's shown at, instead of at
377
+ * full size, so it takes much less memory.
378
+ *
379
+ * - `true`: an image at least twice the size its view needs is decoded
380
+ * at about the view's size. If the view grows, the image is decoded
381
+ * again for its new size (from the disk cache).
382
+ * - `false`: images are decoded at full size, e.g. for an image that's
383
+ * zoomed in on with a transform (a pinch-to-zoom viewer), which would
384
+ * otherwise show the smaller copy enlarged.
385
+ *
386
+ * It doesn't change `onLoad`'s width and height (the image's own size) or
387
+ * the cached file. Needs SDWebImage 5.19.7 or later: before 5.19 images
388
+ * are decoded at full size, and 5.19.0 to 5.19.6 show photos stored
389
+ * sideways with an EXIF orientation (most phone photos) sideways. Photo
390
+ * library images are always decoded this way, and on Android images are
391
+ * always decoded at about the view's size (but Android 16 and later
392
+ * decode animated WebP at full size, and scale it as they draw it).
393
+ *
394
+ * If you control the images, serve them at the size they're shown
395
+ * (resized on your server or by an image CDN), which also saves
396
+ * bandwidth.
397
+ *
398
+ * @platform ios
399
+ * @default true
170
400
  */
171
401
  downsample?: boolean;
172
402
  /**
173
- * Blurs the image by this radius, in points, like React Native's Image
174
- * (the same radius looks about the same on both platforms). `0` (default)
175
- * is no blur. For still images or occasional changes, not for animating.
176
- * Only the loaded image is blurred, not `defaultSource`. An animated image
177
- * shows its first frame, blurred. See the README.
403
+ * Blurs the image by this radius, in points (the same radius looks about
404
+ * the same on iOS and Android). `0` is no blur.
405
+ *
406
+ * It's for still images, or a radius that changes now and then (e.g.
407
+ * blurring a photo behind a sheet). Each change blurs the image again on
408
+ * the CPU, so don't animate it.
409
+ *
410
+ * - Only the loaded image is blurred, not `defaultSource`.
411
+ * - An animated image (GIF, animated WebP, APNG or AVIF) shows its first frame,
412
+ * blurred, and doesn't animate.
413
+ * - With `tintColor`, the blurred image is tinted.
414
+ * - The image is blurred at about the size it's shown at, off the main
415
+ * thread. The cached file stays the original image, so `getCachePath`
416
+ * and other views of it aren't affected.
417
+ * - Changing it blurs the image that's showing again, without sending the
418
+ * load events again.
419
+ *
420
+ * To animate a blur, or to blur an animated image, use React Native's
421
+ * `filter` style instead, which the GPU draws:
422
+ * `style={{ filter: [{ blur: 6 }] }}`. It needs the New Architecture.
423
+ * React Native's docs list `blur` for Android 12+ only; on iOS it's behind
424
+ * an experimental React Native feature flag (`enableSwiftUIBasedFilters`,
425
+ * SwiftUI-based filters).
426
+ *
427
+ * @default 0
178
428
  */
179
429
  blurRadius?: number;
430
+ /** Called when the image starts to load. */
180
431
  onLoadStart?(): void;
181
- onProgress?(event: OnProgressEvent): void;
182
- onLoad?(event: OnLoadEvent): void;
183
- onError?(event: OnErrorEvent): void;
184
432
  /**
185
- * Called once the image has loaded or failed to, with the result: `ok` and
186
- * the image's size, or not `ok` and the error.
433
+ * Called while the image downloads, with the bytes `loaded` so far, the
434
+ * `total`, and `progress` (`loaded / total`, from 0 to 1; the last event
435
+ * has 1). Not called while the total is unknown (a response without a
436
+ * `Content-Length`).
437
+ *
438
+ * @example
439
+ * ```jsx
440
+ * onProgress={e => console.log(e.nativeEvent.progress)}
441
+ * ```
187
442
  */
188
- onLoadEnd?(result: LoadResult): void;
443
+ onProgress?(event: OnProgressEvent): void;
189
444
  /**
190
- * onLayout function
445
+ * Called on a successful image fetch. Called with the width and height of
446
+ * the image itself, not of the view (on iOS, in points: an `@2x` asset
447
+ * reports half its pixel size).
191
448
  *
192
- * Invoked on mount and layout changes with
193
- *
194
- * {nativeEvent: { layout: {x, y, width, height}}}.
449
+ * @example
450
+ * ```jsx
451
+ * onLoad={e => console.log(e.nativeEvent.width, e.nativeEvent.height)}
452
+ * ```
195
453
  */
196
- onLayout?: (event: LayoutChangeEvent) => void;
454
+ onLoad?(event: OnLoadEvent): void;
197
455
  /**
456
+ * Called on an image fetching error, with a message describing it (e.g.
457
+ * the HTTP status code).
198
458
  *
199
- * Style
459
+ * @example
460
+ * ```jsx
461
+ * onError={e => console.log(e.nativeEvent.error)}
462
+ * ```
200
463
  */
201
- style?: StyleProp<ImageStyle>;
464
+ onError?(event: OnErrorEvent): void;
202
465
  /**
203
- * TintColor
466
+ * Called when the image finishes loading, whether it was successful or an
467
+ * error, with the result: `{ ok: true, width, height }` (the image's size,
468
+ * as `onLoad` gets it) or `{ ok: false, error }` (as `onError` gets it).
469
+ * TypeScript makes you check `ok` before reading the size or the error.
204
470
  *
205
- * If supplied, changes the color of all the non-transparent pixels to the given color.
471
+ * @example
472
+ * ```jsx
473
+ * <FastImage
474
+ * source={{ uri }}
475
+ * onLoadEnd={(result) => {
476
+ * if (result.ok) setAspectRatio(result.width / result.height)
477
+ * else setFailed(result.error)
478
+ * }}
479
+ * />
480
+ * ```
206
481
  */
207
- tintColor?: ColorValue;
482
+ onLoadEnd?(result: LoadResult): void;
483
+ /**
484
+ * The image's style: View's style props (`borderRadius` clips the image),
485
+ * and `tintColor`, as with React Native's `Image` (the `tintColor` prop
486
+ * wins).
487
+ */
488
+ style?: StyleProp<ImageStyle>;
208
489
  /**
209
- * A unique identifier for this element to be used in UI Automation testing scripts.
490
+ * If supplied, changes the color of all the non-transparent pixels to the
491
+ * given color.
210
492
  */
211
- testID?: string;
493
+ tintColor?: ColorValue;
212
494
  /**
213
- * Render children within the image.
495
+ * Render children within the image. In the next major version,
496
+ * `FastImage` won't render children: use `FastImageBackground`.
214
497
  */
215
498
  children?: React.ReactNode;
216
499
  }
500
+ /** A preloaded source that loaded (and is now cached). */
217
501
  export interface PreloadSuccess {
502
+ /** The source's uri. */
218
503
  uri: string;
219
504
  ok: true;
505
+ /** The image's width (as in `onLoad`). */
220
506
  width: number;
507
+ /** The image's height (as in `onLoad`). */
221
508
  height: number;
222
509
  }
510
+ /** A preloaded source that failed to load. */
223
511
  export interface PreloadFailure {
512
+ /** The source's uri (none for a source without one, e.g. `null`). */
224
513
  uri?: string;
225
514
  ok: false;
515
+ /** What went wrong. */
226
516
  error: string;
227
517
  }
518
+ /**
519
+ * `preload`'s result for a source. Check `ok` to tell which it is: e.g.
520
+ * `if (result.ok)` narrows it to a `PreloadSuccess`, with its size. Reading
521
+ * `width` or `error` without checking is a type error.
522
+ */
228
523
  export type PreloadResult = PreloadSuccess | PreloadFailure;
229
524
  export interface FastImageStaticProperties {
230
525
  resizeMode: typeof resizeMode;
@@ -0,0 +1,11 @@
1
+ #import <SDWebImage/SDImageIOAnimatedCoder.h>
2
+
3
+ // Animated AVIFs (AVIF image sequences), decoded with ImageIO, which reads
4
+ // them where it reads AVIF (iOS 16 and later): SDWebImage's own coders decode
5
+ // an AVIF's first frame only. Still AVIFs keep SDWebImage's ImageIO coder.
6
+ // Registered with SDWebImage's coders by FFFastImageSource.
7
+ @interface FFFAnimatedAVIFCoder : SDImageIOAnimatedCoder
8
+
9
+ @property (nonatomic, class, readonly, nonnull) FFFAnimatedAVIFCoder* sharedCoder;
10
+
11
+ @end
@@ -0,0 +1,103 @@
1
+ #import "FFFAnimatedAVIFCoder.h"
2
+ #import <ImageIO/ImageIO.h>
3
+
4
+ // SDWebImage has no format for AVIF: this is the value SDWebImageAVIFCoder
5
+ // uses, so an image reports the same format with either coder.
6
+ static const SDImageFormat FFFImageFormatAVIF = 15;
7
+
8
+ // Whether the data is an animated AVIF: a file whose first box is `ftyp`
9
+ // with the `avis` (image sequence) brand, as its major brand or a compatible
10
+ // one. The header comes first, so a partial download can be checked too.
11
+ static BOOL FFFIsAnimatedAVIF(NSData* data) {
12
+ if (data.length < 16) {
13
+ return NO;
14
+ }
15
+ const uint8_t* bytes = data.bytes;
16
+ if (memcmp(bytes + 4, "ftyp", 4) != 0) {
17
+ return NO;
18
+ }
19
+ if (memcmp(bytes + 8, "avis", 4) == 0) {
20
+ return YES;
21
+ }
22
+ // The compatible brands, after the major brand and its version.
23
+ uint32_t size = (uint32_t)bytes[0] << 24 | (uint32_t)bytes[1] << 16 | (uint32_t)bytes[2] << 8 | bytes[3];
24
+ NSUInteger end = MIN((NSUInteger)size, data.length);
25
+ for (NSUInteger offset = 16; offset + 4 <= end; offset += 4) {
26
+ if (memcmp(bytes + offset, "avis", 4) == 0) {
27
+ return YES;
28
+ }
29
+ }
30
+ return NO;
31
+ }
32
+
33
+ // Whether ImageIO reads AVIF image sequences on this version of iOS.
34
+ static BOOL FFFCanDecodeAVIFSequences(void) {
35
+ static BOOL supported;
36
+ static dispatch_once_t once;
37
+ dispatch_once(&once, ^{
38
+ NSArray* types = CFBridgingRelease(CGImageSourceCopyTypeIdentifiers());
39
+ supported = [types containsObject: @"public.avis"];
40
+ });
41
+ return supported;
42
+ }
43
+
44
+ @implementation FFFAnimatedAVIFCoder
45
+
46
+ + (FFFAnimatedAVIFCoder*) sharedCoder {
47
+ static FFFAnimatedAVIFCoder* coder;
48
+ static dispatch_once_t once;
49
+ dispatch_once(&once, ^{
50
+ coder = [FFFAnimatedAVIFCoder new];
51
+ });
52
+ return coder;
53
+ }
54
+
55
+ - (BOOL) canDecodeFromData: (NSData*)data {
56
+ return FFFIsAnimatedAVIF(data) && FFFCanDecodeAVIFSequences();
57
+ }
58
+
59
+ - (BOOL) canIncrementalDecodeFromData: (NSData*)data {
60
+ return [self canDecodeFromData: data];
61
+ }
62
+
63
+ // Only for decoding.
64
+ - (BOOL) canEncodeToFormat: (SDImageFormat)format {
65
+ return NO;
66
+ }
67
+
68
+ #pragma mark - SDImageIOAnimatedCoder
69
+
70
+ + (SDImageFormat) imageFormat {
71
+ return FFFImageFormatAVIF;
72
+ }
73
+
74
+ + (NSString*) imageUTType {
75
+ return @"public.avis";
76
+ }
77
+
78
+ // ImageIO's keys for AVIF sequences (kCGImagePropertyAVIS…), as strings so
79
+ // they build with any SDK. LoopCount is the number of plays, 0 for forever,
80
+ // as for GIF and APNG.
81
+ + (NSString*) dictionaryProperty {
82
+ return @"{AVIS}";
83
+ }
84
+
85
+ + (NSString*) unclampedDelayTimeProperty {
86
+ return @"UnclampedDelayTime";
87
+ }
88
+
89
+ + (NSString*) delayTimeProperty {
90
+ return @"DelayTime";
91
+ }
92
+
93
+ + (NSString*) loopCountProperty {
94
+ return @"LoopCount";
95
+ }
96
+
97
+ // A sequence without a repetition count plays forever (ImageIO reports
98
+ // LoopCount 0 for one).
99
+ + (NSUInteger) defaultLoopCount {
100
+ return 0;
101
+ }
102
+
103
+ @end