react-native-fast-image 8.26.0 → 8.27.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.
- package/README.md +50 -30
- package/android/build.gradle +3 -0
- package/android/consumer-rules.pro +5 -0
- package/android/src/main/java/com/dylanvann/fastimage/FastImageOkHttpProgressGlideModule.java +6 -1
- package/android/src/main/java/com/dylanvann/fastimage/FastImageRequestListener.java +5 -2
- package/android/src/main/java/com/dylanvann/fastimage/FastImageSource.java +17 -0
- package/android/src/main/java/com/dylanvann/fastimage/FastImageSvg.java +161 -0
- package/android/src/main/java/com/dylanvann/fastimage/FastImageSvgRenderer.java +86 -0
- package/dist/index.d.ts +2 -12
- package/ios/FastImage/FFFastImageSource.h +4 -0
- package/ios/FastImage/FFFastImageSource.m +46 -0
- package/ios/FastImage/FFFastImageView.m +37 -1
- package/ios/FastImage/FFFastImageViewManager.m +2 -2
- package/package.json +2 -2
package/README.md
CHANGED
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
<h1 align="center">
|
|
2
|
-
|
|
2
|
+
React Native Fast Image
|
|
3
3
|
</h1>
|
|
4
4
|
|
|
5
5
|
<div align="center">
|
|
@@ -64,13 +64,21 @@ and
|
|
|
64
64
|
|
|
65
65
|
## Usage
|
|
66
66
|
|
|
67
|
-
|
|
67
|
+
Works with React Native 0.60 and later, with the New Architecture (through React Native's interop layer) and the legacy architecture, and with Expo. It's tested on React Native 0.87 with the New Architecture, 0.73 with the legacy architecture, and Expo SDK 57. A native New Architecture component is planned for the next major version.
|
|
68
68
|
|
|
69
69
|
```bash
|
|
70
|
-
|
|
70
|
+
npm install react-native-fast-image
|
|
71
71
|
cd ios && pod install
|
|
72
72
|
```
|
|
73
73
|
|
|
74
|
+
With Expo:
|
|
75
|
+
|
|
76
|
+
```bash
|
|
77
|
+
npx expo install react-native-fast-image
|
|
78
|
+
```
|
|
79
|
+
|
|
80
|
+
It has native code, so it needs a [development build](https://docs.expo.dev/develop/development-builds/introduction/) (e.g. `npx expo run:ios`), not Expo Go.
|
|
81
|
+
|
|
74
82
|
```jsx
|
|
75
83
|
import FastImage from 'react-native-fast-image'
|
|
76
84
|
|
|
@@ -78,7 +86,7 @@ const YourImage = () => (
|
|
|
78
86
|
<FastImage
|
|
79
87
|
style={{ width: 200, height: 200 }}
|
|
80
88
|
source={{
|
|
81
|
-
uri: 'https://
|
|
89
|
+
uri: 'https://example.com/image.jpg',
|
|
82
90
|
headers: { Authorization: 'someAuthToken' },
|
|
83
91
|
priority: FastImage.priority.normal,
|
|
84
92
|
}}
|
|
@@ -87,9 +95,9 @@ const YourImage = () => (
|
|
|
87
95
|
)
|
|
88
96
|
```
|
|
89
97
|
|
|
90
|
-
##
|
|
98
|
+
## If your app already has an AppGlideModule
|
|
91
99
|
|
|
92
|
-
|
|
100
|
+
If your Android app has its own Glide `AppGlideModule`, read [using FastImage with an AppGlideModule](docs/app-glide-module.md) first, or FastImage may not work.
|
|
93
101
|
|
|
94
102
|
## Properties
|
|
95
103
|
|
|
@@ -97,13 +105,13 @@ const YourImage = () => (
|
|
|
97
105
|
|
|
98
106
|
Source for the remote image to load.
|
|
99
107
|
|
|
100
|
-
When `source` changes, the image that's showing stays until the new one has loaded
|
|
108
|
+
When `source` changes, the image that's showing stays until the new one has loaded. In views that get reused for other content, such as rows in FlashList or recyclerlistview, set `recyclingKey` so a reused row doesn't show the previous row's image.
|
|
101
109
|
|
|
102
110
|
---
|
|
103
111
|
|
|
104
112
|
### `source.uri?: string`
|
|
105
113
|
|
|
106
|
-
Remote url to load the image from. e.g. `'https://
|
|
114
|
+
Remote url to load the image from. e.g. `'https://example.com/image.jpg'`.
|
|
107
115
|
|
|
108
116
|
#### Photo library images (iOS)
|
|
109
117
|
|
|
@@ -124,6 +132,26 @@ With Expo, add it with [expo-build-properties](https://docs.expo.dev/versions/la
|
|
|
124
132
|
|
|
125
133
|
The app needs access to the photo library, which it has if it got the url from there. A photo library image is decoded at about the view's size, since photos are large and usually shown small; `onLoad` still reports the photo's own size. `FastImage.preload` of a `ph://` source loads the full-size photo (there's no view to size it for), which doesn't make a view's smaller copy load faster, so preloading photo library images usually isn't worth it. Without the plugin, a `ph://` source fails with `onError`, saying so. `assets-library://` urls aren't supported. On Android, photo pickers give `content://` urls, which load as they are.
|
|
126
134
|
|
|
135
|
+
#### SVG images
|
|
136
|
+
|
|
137
|
+
SVG images (remote, bundled with `require()`, or local files) load when the app has an SVG library: SDWebImageSVGCoder on iOS and AndroidSVG on Android. Add them to the app:
|
|
138
|
+
|
|
139
|
+
```ruby
|
|
140
|
+
# ios/Podfile
|
|
141
|
+
pod 'SDWebImageSVGCoder'
|
|
142
|
+
```
|
|
143
|
+
|
|
144
|
+
```groovy
|
|
145
|
+
// android/app/build.gradle
|
|
146
|
+
dependencies {
|
|
147
|
+
implementation 'com.caverock:androidsvg-aar:1.4'
|
|
148
|
+
}
|
|
149
|
+
```
|
|
150
|
+
|
|
151
|
+
On Android either of AndroidSVG's packages works (`com.caverock:androidsvg-aar` or `com.caverock:androidsvg`), so an app that already has one needs nothing more. With Expo, add the pod with [expo-build-properties](https://docs.expo.dev/versions/latest/sdk/build-properties/) (`{ "ios": { "extraPods": [{ "name": "SDWebImageSVGCoder" }] } }`).
|
|
152
|
+
|
|
153
|
+
An SVG is drawn at the size it's shown at, so it's sharp at any size, and then works like any other image: `resizeMode`, `tintColor`, `blurRadius`, `transition` and caching. `onLoad` reports the SVG's own size: its `width` and `height`, or its `viewBox`'s (300x150 if it has neither). Animated SVGs (SMIL or CSS animations) show their first state. On iOS, SVG images need iOS 13 or later. Without the SVG library, an SVG image fails with `onError`, saying what to add (on iOS, for a url that ends in `.svg`).
|
|
154
|
+
|
|
127
155
|
---
|
|
128
156
|
|
|
129
157
|
### `source.headers?: object`
|
|
@@ -206,7 +234,7 @@ Whether the decoded image is kept in the memory cache. **Default: true.** With `
|
|
|
206
234
|
|
|
207
235
|
- An asset loaded with `require(...)`.
|
|
208
236
|
- Shown while the first image loads, and if an image fails to load. When `source` changes, the previous image shows while the new one loads instead (see `source` and `recyclingKey`).
|
|
209
|
-
-
|
|
237
|
+
- On Android, `defaultSource` doesn't show in debug builds: there the dev server serves `require()`d images, and `defaultSource` is only loaded from the app's resources.
|
|
210
238
|
|
|
211
239
|
---
|
|
212
240
|
|
|
@@ -225,7 +253,7 @@ For views that get reused for other content, such as rows in FlashList or recycl
|
|
|
225
253
|
- `FastImage.resizeMode.contain` - Scale the image uniformly (maintain the image's aspect ratio) so that both dimensions (width and height) of the image will be equal to or less than the corresponding dimension of the view (minus padding).
|
|
226
254
|
- `FastImage.resizeMode.cover` **(Default)** - Scale the image uniformly (maintain the image's aspect ratio) so that both dimensions (width and height) of the image will be equal to or larger than the corresponding dimension of the view (minus padding).
|
|
227
255
|
- `FastImage.resizeMode.stretch` - Scale width and height independently, This may change the aspect ratio of the src.
|
|
228
|
-
- `FastImage.resizeMode.center` -
|
|
256
|
+
- `FastImage.resizeMode.center` - Center the image at its own size, scaled down uniformly to fit if it's larger than the view.
|
|
229
257
|
- `FastImage.resizeMode.repeat` - Repeat the image to cover the view, from its top-left corner, at the image's own size in pixels (a bundled image at its size in points), scaled down to fit if it's larger than the view. An animated image repeats its first frame, and `defaultSource` repeats too.
|
|
230
258
|
|
|
231
259
|
---
|
|
@@ -292,7 +320,7 @@ Decoding a smaller copy can take a little longer, so use it where the memory mat
|
|
|
292
320
|
|
|
293
321
|
### `blurRadius?: number`
|
|
294
322
|
|
|
295
|
-
Blurs the image by this radius, in points
|
|
323
|
+
Blurs the image by this radius, in points (the same radius looks about the same on iOS and Android). `0` **(Default)** is no blur.
|
|
296
324
|
|
|
297
325
|
It's for still images, or a radius that changes now and then (e.g. blurring a photo behind a sheet). Each change blurs the image again on the CPU, so don't animate it.
|
|
298
326
|
|
|
@@ -358,10 +386,9 @@ A React Native style. Supports using `borderRadius`.
|
|
|
358
386
|
|
|
359
387
|
---
|
|
360
388
|
|
|
361
|
-
### `fallback
|
|
389
|
+
### `fallback?: boolean`
|
|
362
390
|
|
|
363
|
-
If true
|
|
364
|
-
In this case the image will still be styled and laid out the same way as `FastImage`.
|
|
391
|
+
If true, the image is shown with React Native's `Image` instead, styled and laid out the same way. FastImage's own features, such as its caching options, `priority` and `transition`, don't apply.
|
|
365
392
|
|
|
366
393
|
---
|
|
367
394
|
|
|
@@ -371,7 +398,7 @@ If supplied, changes the color of all the non-transparent pixels to the given co
|
|
|
371
398
|
|
|
372
399
|
## `FastImageBackground`
|
|
373
400
|
|
|
374
|
-
An image with content on top of it
|
|
401
|
+
An image with content on top of it: a view that the image fills, with the children on top.
|
|
375
402
|
|
|
376
403
|
Use it rather than giving `FastImage` children: in the next major version, `FastImage` won't render children, since the image will be a single native view ([#1137](https://github.com/DylanVann/react-native-fast-image/pull/1137)), which can't hold them. `FastImageBackground` works the same in both.
|
|
377
404
|
|
|
@@ -380,7 +407,7 @@ import { FastImageBackground } from 'react-native-fast-image'
|
|
|
380
407
|
|
|
381
408
|
const Banner = () => (
|
|
382
409
|
<FastImageBackground
|
|
383
|
-
source={{ uri: 'https://
|
|
410
|
+
source={{ uri: 'https://example.com/banner.jpg' }}
|
|
384
411
|
style={{ width: 200, height: 100 }}
|
|
385
412
|
imageStyle={{ borderRadius: 8 }}
|
|
386
413
|
>
|
|
@@ -403,11 +430,11 @@ Preload images to display later. e.g.
|
|
|
403
430
|
```js
|
|
404
431
|
FastImage.preload([
|
|
405
432
|
{
|
|
406
|
-
uri: 'https://
|
|
433
|
+
uri: 'https://example.com/image-1.jpg',
|
|
407
434
|
headers: { Authorization: 'someAuthToken' },
|
|
408
435
|
},
|
|
409
436
|
{
|
|
410
|
-
uri: 'https://
|
|
437
|
+
uri: 'https://example.com/image-2.jpg',
|
|
411
438
|
headers: { Authorization: 'someAuthToken' },
|
|
412
439
|
},
|
|
413
440
|
])
|
|
@@ -529,13 +556,13 @@ await FastImage.configureCache({ maxDiskSize: 500 * 1024 * 1024 })
|
|
|
529
556
|
const { maxDiskSize, diskSize } = await FastImage.configureCache()
|
|
530
557
|
```
|
|
531
558
|
|
|
532
|
-
On Android, if your app has its own `AppGlideModule` (see [
|
|
559
|
+
On Android, if your app has its own `AppGlideModule` (see [using FastImage with an AppGlideModule](docs/app-glide-module.md)), set the disk cache size there instead: `maxDiskSize` isn't applied or reported.
|
|
533
560
|
|
|
534
561
|
Images with `cache: 'web'` are kept in their own HTTP cache instead, up to 50 MB on each platform, which these limits don't change.
|
|
535
562
|
|
|
536
563
|
## Web
|
|
537
564
|
|
|
538
|
-
|
|
565
|
+
FastImage works on the web with [react-native-web](https://necolas.github.io/react-native-web/). Bundlers pick the web version through the package's `browser` field, or the `.web.js` files next to the native ones.
|
|
539
566
|
|
|
540
567
|
Support is minimal:
|
|
541
568
|
|
|
@@ -552,13 +579,6 @@ If you have any problems using this library try the steps in [troubleshooting](d
|
|
|
552
579
|
|
|
553
580
|
[Follow these instructions to get the example app running.](docs/development.md)
|
|
554
581
|
|
|
555
|
-
## Supported React Native Versions
|
|
556
|
-
|
|
557
|
-
This project only aims to support the latest version of React Native.\
|
|
558
|
-
This simplifies the development and the testing of the project.
|
|
559
|
-
|
|
560
|
-
If you require new features or bug fixes for older versions you can fork this project.
|
|
561
|
-
|
|
562
582
|
## Credits
|
|
563
583
|
|
|
564
584
|
The idea for this modules came from
|
|
@@ -571,9 +591,9 @@ Thanks to [@mobinni](https://github.com/mobinni) for helping with the conceptual
|
|
|
571
591
|
|
|
572
592
|
## Licenses
|
|
573
593
|
|
|
574
|
-
-
|
|
575
|
-
- SDWebImage -
|
|
576
|
-
- Glide - BSD, part MIT and Apache 2.0. See
|
|
594
|
+
- React Native Fast Image - MIT © [Dylan Vann](https://github.com/DylanVann). See the [LICENSE](LICENSE) file.
|
|
595
|
+
- [SDWebImage](https://github.com/SDWebImage/SDWebImage) (iOS) - MIT. See its [LICENSE](https://github.com/SDWebImage/SDWebImage/blob/master/LICENSE) file.
|
|
596
|
+
- [Glide](https://github.com/bumptech/glide) (Android) - BSD, part MIT and Apache 2.0. See its [LICENSE](https://github.com/bumptech/glide/blob/master/LICENSE) file.
|
|
577
597
|
|
|
578
598
|
[build-badge]: https://github.com/DylanVann/react-native-fast-image/actions/workflows/ci.yml/badge.svg
|
|
579
599
|
[build]: https://github.com/DylanVann/react-native-fast-image/actions/workflows/ci.yml
|
package/android/build.gradle
CHANGED
|
@@ -81,4 +81,7 @@ dependencies {
|
|
|
81
81
|
implementation "com.github.bumptech.glide:annotations:${glideVersion}"
|
|
82
82
|
implementation "com.github.bumptech.glide:okhttp3-integration:${glideVersion}"
|
|
83
83
|
annotationProcessor "com.github.bumptech.glide:compiler:${glideVersion}"
|
|
84
|
+
// SVG images, when the app has AndroidSVG (either package): not shipped,
|
|
85
|
+
// so there's never a second copy of it (see FastImageSvg).
|
|
86
|
+
compileOnly 'com.caverock:androidsvg-aar:1.4'
|
|
84
87
|
}
|
|
@@ -41,3 +41,8 @@
|
|
|
41
41
|
-keep class com.facebook.react.uimanager.UIManagerHelper {
|
|
42
42
|
public static *** getEventDispatcherForReactTag(com.facebook.react.bridge.ReactContext, int);
|
|
43
43
|
}
|
|
44
|
+
|
|
45
|
+
# AndroidSVG, which FastImage uses for SVG images when the app has it
|
|
46
|
+
# (FastImageSvgRenderer), and doesn't ship. Without it, R8 would stop at the
|
|
47
|
+
# missing classes.
|
|
48
|
+
-dontwarn com.caverock.androidsvg.**
|
package/android/src/main/java/com/dylanvann/fastimage/FastImageOkHttpProgressGlideModule.java
CHANGED
|
@@ -120,6 +120,7 @@ public class FastImageOkHttpProgressGlideModule extends LibraryGlideModule {
|
|
|
120
120
|
}
|
|
121
121
|
OkHttpClient client = builder.build();
|
|
122
122
|
registry.replace(GlideUrl.class, InputStream.class, new UrlLoaderFactory(client));
|
|
123
|
+
FastImageSvg.register(registry, glide.getBitmapPool());
|
|
123
124
|
|
|
124
125
|
// `cache: 'web'` skips Glide's caches and relies on HTTP caching, so
|
|
125
126
|
// those urls get a client with an HTTP cache (#280): one of their own,
|
|
@@ -202,7 +203,7 @@ public class FastImageOkHttpProgressGlideModule extends LibraryGlideModule {
|
|
|
202
203
|
|
|
203
204
|
// Fails a successful response that's clearly not an image: its type isn't
|
|
204
205
|
// an image's (text, JSON, XML other than SVG) and its first bytes aren't a
|
|
205
|
-
// format Glide recognizes, e.g. a captive portal's or a proxy's HTML page
|
|
206
|
+
// format Glide recognizes or an SVG's, e.g. a captive portal's or a proxy's HTML page
|
|
206
207
|
// sent with status 200. Glide keeps the bytes it downloads in its disk
|
|
207
208
|
// cache before decoding them (and doesn't remove them when that fails), as
|
|
208
209
|
// does the HTTP cache of `web` images, so every later load of the url
|
|
@@ -227,6 +228,10 @@ public class FastImageOkHttpProgressGlideModule extends LibraryGlideModule {
|
|
|
227
228
|
if (imageType != ImageHeaderParser.ImageType.UNKNOWN) {
|
|
228
229
|
return response;
|
|
229
230
|
}
|
|
231
|
+
byte[] head = response.peekBody(1024).bytes();
|
|
232
|
+
if (FastImageSvg.looksLikeSvg(head, head.length)) {
|
|
233
|
+
return response;
|
|
234
|
+
}
|
|
230
235
|
response.close();
|
|
231
236
|
throw new IOException("Not an image (Content-Type: " + type + ")");
|
|
232
237
|
}
|
|
@@ -54,11 +54,14 @@ public class FastImageRequestListener implements RequestListener<Drawable> {
|
|
|
54
54
|
return false;
|
|
55
55
|
}
|
|
56
56
|
|
|
57
|
-
// The first root cause's message, e.g. "Not Found, status code: 404".
|
|
58
57
|
// The first root cause's message, e.g. "Not Found, status code: 404" (also
|
|
59
|
-
// for preload results).
|
|
58
|
+
// for preload results). For an SVG without AndroidSVG, what to add: other
|
|
59
|
+
// decoders (e.g. Glide's video one) also tried it, and failed first.
|
|
60
60
|
static String errorMessage(@androidx.annotation.Nullable GlideException e) {
|
|
61
61
|
if (e != null) {
|
|
62
|
+
for (Throwable cause : e.getRootCauses()) {
|
|
63
|
+
if (FastImageSvg.MISSING.equals(cause.getMessage())) return FastImageSvg.MISSING;
|
|
64
|
+
}
|
|
62
65
|
for (Throwable cause : e.getRootCauses()) {
|
|
63
66
|
if (cause.getMessage() != null) return cause.getMessage();
|
|
64
67
|
}
|
|
@@ -57,6 +57,13 @@ public class FastImageSource extends ImageSource {
|
|
|
57
57
|
super(context, source, width, height);
|
|
58
58
|
mHeaders = headers == null ? Headers.DEFAULT : headers;
|
|
59
59
|
mUri = super.getUri();
|
|
60
|
+
if (mUri == null || TextUtils.isEmpty(mUri.toString())) {
|
|
61
|
+
// A bundled image that release builds package as a raw resource
|
|
62
|
+
// (e.g. an SVG, which isn't a drawable): React Native's
|
|
63
|
+
// ImageSource only looks for drawables.
|
|
64
|
+
Uri raw = rawResourceUri(context, source);
|
|
65
|
+
if (raw != null) mUri = raw;
|
|
66
|
+
}
|
|
60
67
|
|
|
61
68
|
if (isResource() && TextUtils.isEmpty(mUri.toString())) {
|
|
62
69
|
throw new Resources.NotFoundException("Local Resource Not Found. Resource: '" + getSource() + "'.");
|
|
@@ -76,6 +83,16 @@ public class FastImageSource extends ImageSource {
|
|
|
76
83
|
}
|
|
77
84
|
|
|
78
85
|
|
|
86
|
+
// The raw resource with the source's name (a bundled image's source is
|
|
87
|
+
// its resource name, e.g. src_images_logo), or null.
|
|
88
|
+
@Nullable
|
|
89
|
+
private static Uri rawResourceUri(Context context, @Nullable String source) {
|
|
90
|
+
if (source == null || source.isEmpty() || source.contains(":")) return null;
|
|
91
|
+
int id = context.getResources().getIdentifier(source, "raw", context.getPackageName());
|
|
92
|
+
if (id == 0) return null;
|
|
93
|
+
return Uri.parse(ANDROID_RESOURCE_SCHEME + "://" + context.getPackageName() + "/" + id);
|
|
94
|
+
}
|
|
95
|
+
|
|
79
96
|
public boolean isBase64Resource() {
|
|
80
97
|
return mUri != null && FastImageSource.isBase64Uri(mUri);
|
|
81
98
|
}
|
|
@@ -0,0 +1,161 @@
|
|
|
1
|
+
package com.dylanvann.fastimage;
|
|
2
|
+
|
|
3
|
+
import android.graphics.Bitmap;
|
|
4
|
+
|
|
5
|
+
import androidx.annotation.NonNull;
|
|
6
|
+
import androidx.annotation.Nullable;
|
|
7
|
+
|
|
8
|
+
import com.bumptech.glide.Registry;
|
|
9
|
+
import com.bumptech.glide.load.Options;
|
|
10
|
+
import com.bumptech.glide.load.ResourceDecoder;
|
|
11
|
+
import com.bumptech.glide.load.engine.Resource;
|
|
12
|
+
import com.bumptech.glide.load.engine.bitmap_recycle.BitmapPool;
|
|
13
|
+
import com.bumptech.glide.util.ByteBufferUtil;
|
|
14
|
+
|
|
15
|
+
import java.io.ByteArrayOutputStream;
|
|
16
|
+
import java.io.IOException;
|
|
17
|
+
import java.io.InputStream;
|
|
18
|
+
import java.nio.ByteBuffer;
|
|
19
|
+
import java.nio.charset.StandardCharsets;
|
|
20
|
+
import java.util.Locale;
|
|
21
|
+
|
|
22
|
+
// SVG images (static), drawn with AndroidSVG when the app has it: either of
|
|
23
|
+
// its packages (com.caverock:androidsvg or com.caverock:androidsvg-aar).
|
|
24
|
+
// FastImage compiles against it but doesn't ship it, so an app never gets a
|
|
25
|
+
// second copy of it (from another library, or its own). Without it, an SVG
|
|
26
|
+
// image fails with a message saying what to add.
|
|
27
|
+
//
|
|
28
|
+
// The decoders come after Glide's own, so they're only asked about data
|
|
29
|
+
// Glide couldn't decode, and only take data that starts like an SVG. This
|
|
30
|
+
// class doesn't use AndroidSVG itself (FastImageSvgRenderer does), so it
|
|
31
|
+
// loads without it.
|
|
32
|
+
final class FastImageSvg {
|
|
33
|
+
static final String MISSING =
|
|
34
|
+
"SVG images need AndroidSVG: add implementation 'com.caverock:androidsvg-aar:1.4' to the app's dependencies";
|
|
35
|
+
private static final int HEAD_LENGTH = 1024;
|
|
36
|
+
@Nullable
|
|
37
|
+
private static Boolean available;
|
|
38
|
+
|
|
39
|
+
private FastImageSvg() {
|
|
40
|
+
}
|
|
41
|
+
|
|
42
|
+
static synchronized boolean available() {
|
|
43
|
+
if (available == null) {
|
|
44
|
+
try {
|
|
45
|
+
Class.forName("com.caverock.androidsvg.SVG");
|
|
46
|
+
available = true;
|
|
47
|
+
} catch (Throwable e) {
|
|
48
|
+
available = false;
|
|
49
|
+
}
|
|
50
|
+
}
|
|
51
|
+
return available;
|
|
52
|
+
}
|
|
53
|
+
|
|
54
|
+
static void register(@NonNull Registry registry, @NonNull BitmapPool pool) {
|
|
55
|
+
registry.append(InputStream.class, Bitmap.class, new StreamDecoder(pool))
|
|
56
|
+
// Data Glide holds in memory, e.g. from its disk cache.
|
|
57
|
+
.append(ByteBuffer.class, Bitmap.class, new BufferDecoder(pool));
|
|
58
|
+
}
|
|
59
|
+
|
|
60
|
+
// Whether the data starts like an SVG document: its first element is
|
|
61
|
+
// <svg> (after a byte order mark, whitespace, an XML declaration, comments
|
|
62
|
+
// or a doctype), so e.g. an HTML page with an inline SVG isn't one.
|
|
63
|
+
static boolean looksLikeSvg(@NonNull byte[] head, int length) {
|
|
64
|
+
String text = new String(head, 0, length, StandardCharsets.UTF_8).toLowerCase(Locale.ROOT);
|
|
65
|
+
int i = text.startsWith("\uFEFF") ? 1 : 0;
|
|
66
|
+
while (i < text.length()) {
|
|
67
|
+
char c = text.charAt(i);
|
|
68
|
+
if (Character.isWhitespace(c)) {
|
|
69
|
+
i++;
|
|
70
|
+
} else if (text.startsWith("<?", i)) {
|
|
71
|
+
i = skipPast(text, i, "?>");
|
|
72
|
+
} else if (text.startsWith("<!--", i)) {
|
|
73
|
+
i = skipPast(text, i, "-->");
|
|
74
|
+
} else if (text.startsWith("<!", i)) {
|
|
75
|
+
// A doctype, whose internal subset ([...]) can have '>'s.
|
|
76
|
+
int subset = text.indexOf('[', i);
|
|
77
|
+
int close = text.indexOf('>', i);
|
|
78
|
+
if (subset >= 0 && subset < close) i = skipPast(text, subset, "]");
|
|
79
|
+
if (i >= 0) i = skipPast(text, i, ">");
|
|
80
|
+
} else {
|
|
81
|
+
return text.startsWith("<svg", i);
|
|
82
|
+
}
|
|
83
|
+
if (i < 0) return false;
|
|
84
|
+
}
|
|
85
|
+
return false;
|
|
86
|
+
}
|
|
87
|
+
|
|
88
|
+
// The index after the end marker from i, or -1 if it isn't there.
|
|
89
|
+
private static int skipPast(String text, int i, String end) {
|
|
90
|
+
int found = text.indexOf(end, i);
|
|
91
|
+
return found < 0 ? -1 : found + end.length();
|
|
92
|
+
}
|
|
93
|
+
|
|
94
|
+
private static byte[] readAll(InputStream stream) throws IOException {
|
|
95
|
+
ByteArrayOutputStream out = new ByteArrayOutputStream();
|
|
96
|
+
byte[] chunk = new byte[16 * 1024];
|
|
97
|
+
int read;
|
|
98
|
+
while ((read = stream.read(chunk)) != -1) {
|
|
99
|
+
out.write(chunk, 0, read);
|
|
100
|
+
}
|
|
101
|
+
return out.toByteArray();
|
|
102
|
+
}
|
|
103
|
+
|
|
104
|
+
private static Resource<Bitmap> decode(byte[] data, int width, int height, Options options, BitmapPool pool)
|
|
105
|
+
throws IOException {
|
|
106
|
+
if (!available()) {
|
|
107
|
+
throw new IOException(MISSING);
|
|
108
|
+
}
|
|
109
|
+
return FastImageSvgRenderer.render(data, width, height, options, pool);
|
|
110
|
+
}
|
|
111
|
+
|
|
112
|
+
private static final class StreamDecoder implements ResourceDecoder<InputStream, Bitmap> {
|
|
113
|
+
private final BitmapPool pool;
|
|
114
|
+
|
|
115
|
+
StreamDecoder(BitmapPool pool) {
|
|
116
|
+
this.pool = pool;
|
|
117
|
+
}
|
|
118
|
+
|
|
119
|
+
@Override
|
|
120
|
+
public boolean handles(@NonNull InputStream source, @NonNull Options options) throws IOException {
|
|
121
|
+
// Glide rewinds the stream before decoding.
|
|
122
|
+
byte[] head = new byte[HEAD_LENGTH];
|
|
123
|
+
int length = 0;
|
|
124
|
+
int read;
|
|
125
|
+
while (length < head.length && (read = source.read(head, length, head.length - length)) != -1) {
|
|
126
|
+
length += read;
|
|
127
|
+
}
|
|
128
|
+
return looksLikeSvg(head, length);
|
|
129
|
+
}
|
|
130
|
+
|
|
131
|
+
@Nullable
|
|
132
|
+
@Override
|
|
133
|
+
public Resource<Bitmap> decode(@NonNull InputStream source, int width, int height, @NonNull Options options)
|
|
134
|
+
throws IOException {
|
|
135
|
+
return FastImageSvg.decode(readAll(source), width, height, options, pool);
|
|
136
|
+
}
|
|
137
|
+
}
|
|
138
|
+
|
|
139
|
+
private static final class BufferDecoder implements ResourceDecoder<ByteBuffer, Bitmap> {
|
|
140
|
+
private final BitmapPool pool;
|
|
141
|
+
|
|
142
|
+
BufferDecoder(BitmapPool pool) {
|
|
143
|
+
this.pool = pool;
|
|
144
|
+
}
|
|
145
|
+
|
|
146
|
+
@Override
|
|
147
|
+
public boolean handles(@NonNull ByteBuffer source, @NonNull Options options) {
|
|
148
|
+
ByteBuffer copy = source.duplicate();
|
|
149
|
+
byte[] head = new byte[Math.min(HEAD_LENGTH, copy.remaining())];
|
|
150
|
+
copy.get(head);
|
|
151
|
+
return looksLikeSvg(head, head.length);
|
|
152
|
+
}
|
|
153
|
+
|
|
154
|
+
@Nullable
|
|
155
|
+
@Override
|
|
156
|
+
public Resource<Bitmap> decode(@NonNull ByteBuffer source, int width, int height, @NonNull Options options)
|
|
157
|
+
throws IOException {
|
|
158
|
+
return FastImageSvg.decode(readAll(ByteBufferUtil.toStream(source)), width, height, options, pool);
|
|
159
|
+
}
|
|
160
|
+
}
|
|
161
|
+
}
|
|
@@ -0,0 +1,86 @@
|
|
|
1
|
+
package com.dylanvann.fastimage;
|
|
2
|
+
|
|
3
|
+
import android.graphics.Bitmap;
|
|
4
|
+
import android.graphics.Canvas;
|
|
5
|
+
import android.graphics.RectF;
|
|
6
|
+
|
|
7
|
+
import com.bumptech.glide.load.Options;
|
|
8
|
+
import com.bumptech.glide.load.engine.Resource;
|
|
9
|
+
import com.bumptech.glide.load.engine.bitmap_recycle.BitmapPool;
|
|
10
|
+
import com.bumptech.glide.load.resource.bitmap.BitmapResource;
|
|
11
|
+
import com.bumptech.glide.load.resource.bitmap.DownsampleStrategy;
|
|
12
|
+
import com.caverock.androidsvg.SVG;
|
|
13
|
+
import com.caverock.androidsvg.SVGParseException;
|
|
14
|
+
|
|
15
|
+
import java.io.ByteArrayInputStream;
|
|
16
|
+
import java.io.IOException;
|
|
17
|
+
|
|
18
|
+
// Draws an SVG into a bitmap with AndroidSVG, at the size the request's
|
|
19
|
+
// downsample strategy gives for the view (as Glide decodes other images, but
|
|
20
|
+
// also larger than the SVG's own size, as it's drawn, not scaled up). Glide
|
|
21
|
+
// then applies the view's transformations (scale type, blur) as for any
|
|
22
|
+
// bitmap. Only used when the app has AndroidSVG (see FastImageSvg).
|
|
23
|
+
final class FastImageSvgRenderer {
|
|
24
|
+
// An SVG with neither a size nor a viewBox, as browsers size one.
|
|
25
|
+
private static final float DEFAULT_WIDTH = 300;
|
|
26
|
+
private static final float DEFAULT_HEIGHT = 150;
|
|
27
|
+
// At most this many pixels (a 4096 x 4096 bitmap), however large the view.
|
|
28
|
+
private static final float MAX_PIXELS = 4096f * 4096f;
|
|
29
|
+
|
|
30
|
+
private FastImageSvgRenderer() {
|
|
31
|
+
}
|
|
32
|
+
|
|
33
|
+
static Resource<Bitmap> render(byte[] data, int width, int height, Options options, BitmapPool pool)
|
|
34
|
+
throws IOException {
|
|
35
|
+
SVG svg;
|
|
36
|
+
try {
|
|
37
|
+
svg = SVG.getFromInputStream(new ByteArrayInputStream(data));
|
|
38
|
+
} catch (SVGParseException e) {
|
|
39
|
+
throw new IOException("Cannot decode the SVG: " + e.getMessage(), e);
|
|
40
|
+
}
|
|
41
|
+
// The SVG's own size: its width and height, or its viewBox's.
|
|
42
|
+
RectF viewBox = svg.getDocumentViewBox();
|
|
43
|
+
float ownWidth = svg.getDocumentWidth();
|
|
44
|
+
float ownHeight = svg.getDocumentHeight();
|
|
45
|
+
if (viewBox != null && viewBox.width() > 0 && viewBox.height() > 0) {
|
|
46
|
+
if (ownWidth <= 0 && ownHeight <= 0) {
|
|
47
|
+
ownWidth = viewBox.width();
|
|
48
|
+
ownHeight = viewBox.height();
|
|
49
|
+
} else if (ownWidth <= 0) {
|
|
50
|
+
ownWidth = ownHeight * viewBox.width() / viewBox.height();
|
|
51
|
+
} else if (ownHeight <= 0) {
|
|
52
|
+
ownHeight = ownWidth * viewBox.height() / viewBox.width();
|
|
53
|
+
}
|
|
54
|
+
} else {
|
|
55
|
+
if (ownWidth <= 0 || ownHeight <= 0) {
|
|
56
|
+
ownWidth = DEFAULT_WIDTH;
|
|
57
|
+
ownHeight = DEFAULT_HEIGHT;
|
|
58
|
+
}
|
|
59
|
+
// Scaled to the bitmap through a viewBox of its own size.
|
|
60
|
+
svg.setDocumentViewBox(0, 0, ownWidth, ownHeight);
|
|
61
|
+
}
|
|
62
|
+
int sourceWidth = Math.max(1, Math.round(ownWidth));
|
|
63
|
+
int sourceHeight = Math.max(1, Math.round(ownHeight));
|
|
64
|
+
|
|
65
|
+
// The strategy also records the SVG's own size for onLoad
|
|
66
|
+
// (FastImageSourceSize.Capture).
|
|
67
|
+
DownsampleStrategy strategy = options.get(DownsampleStrategy.OPTION);
|
|
68
|
+
int targetWidth = width > 0 ? width : sourceWidth;
|
|
69
|
+
int targetHeight = height > 0 ? height : sourceHeight;
|
|
70
|
+
float scale = strategy != null
|
|
71
|
+
? strategy.getScaleFactor(sourceWidth, sourceHeight, targetWidth, targetHeight)
|
|
72
|
+
: 1f;
|
|
73
|
+
if (Float.isNaN(scale) || Float.isInfinite(scale) || scale <= 0) scale = 1f;
|
|
74
|
+
float pixels = sourceWidth * scale * sourceHeight * scale;
|
|
75
|
+
if (pixels > MAX_PIXELS) scale *= (float) Math.sqrt(MAX_PIXELS / pixels);
|
|
76
|
+
int bitmapWidth = Math.max(1, Math.round(sourceWidth * scale));
|
|
77
|
+
int bitmapHeight = Math.max(1, Math.round(sourceHeight * scale));
|
|
78
|
+
|
|
79
|
+
svg.setDocumentWidth(bitmapWidth);
|
|
80
|
+
svg.setDocumentHeight(bitmapHeight);
|
|
81
|
+
// Cleared (transparent), from Glide's pool.
|
|
82
|
+
Bitmap bitmap = pool.get(bitmapWidth, bitmapHeight, Bitmap.Config.ARGB_8888);
|
|
83
|
+
svg.renderToCanvas(new Canvas(bitmap));
|
|
84
|
+
return BitmapResource.obtain(bitmap, pool);
|
|
85
|
+
}
|
|
86
|
+
}
|
package/dist/index.d.ts
CHANGED
|
@@ -110,18 +110,8 @@ export interface OnProgressEvent {
|
|
|
110
110
|
};
|
|
111
111
|
}
|
|
112
112
|
export interface ImageStyle extends ViewStyle {
|
|
113
|
-
|
|
114
|
-
|
|
115
|
-
borderBottomRightRadius?: number;
|
|
116
|
-
backgroundColor?: ColorValue;
|
|
117
|
-
borderColor?: ColorValue;
|
|
118
|
-
borderWidth?: number;
|
|
119
|
-
borderRadius?: number;
|
|
120
|
-
borderTopLeftRadius?: number;
|
|
121
|
-
borderTopRightRadius?: number;
|
|
122
|
-
overlayColor?: ColorValue;
|
|
123
|
-
tintColor?: ColorValue;
|
|
124
|
-
opacity?: number;
|
|
113
|
+
overlayColor?: ViewStyle['backgroundColor'];
|
|
114
|
+
tintColor?: ViewStyle['backgroundColor'];
|
|
125
115
|
}
|
|
126
116
|
export interface FastImageProps extends AccessibilityProps, ViewProps {
|
|
127
117
|
/**
|
|
@@ -64,6 +64,10 @@ typedef NS_ENUM(NSInteger, FFFCacheControl) {
|
|
|
64
64
|
// which is decoded at about the view's size.
|
|
65
65
|
- (BOOL)isPhotoLibrary;
|
|
66
66
|
|
|
67
|
+
// The error's message, or for an SVG image that couldn't be decoded because
|
|
68
|
+
// the app doesn't have SDWebImageSVGCoder, what to add.
|
|
69
|
+
- (NSString *)errorMessage:(NSError *)error;
|
|
70
|
+
|
|
67
71
|
// A photo library image's own size in pixels (for onLoad when it comes from
|
|
68
72
|
// a cache, where the size it was decoded from isn't recorded), or zero.
|
|
69
73
|
- (CGSize)photoPixelSize;
|
|
@@ -5,8 +5,12 @@
|
|
|
5
5
|
#import <SDWebImage/SDWebImageError.h>
|
|
6
6
|
#import <SDWebImage/SDWebImageDownloaderOperation.h>
|
|
7
7
|
#import <SDWebImage/NSData+ImageContentType.h>
|
|
8
|
+
#import <SDWebImage/SDImageCodersManager.h>
|
|
8
9
|
#import <objc/message.h>
|
|
9
10
|
|
|
11
|
+
// In FFFastImageView.m.
|
|
12
|
+
FOUNDATION_EXTERN NSString *FFFErrorMessage(NSError *error);
|
|
13
|
+
|
|
10
14
|
static NSUInteger const FFFWebCacheSize = 50 * 1024 * 1024;
|
|
11
15
|
|
|
12
16
|
// The photo's own size in pixels, looked up by its identifier (the url after
|
|
@@ -173,8 +177,41 @@ static CGSize FFFPhotoPixelSize(NSURL *url)
|
|
|
173
177
|
|
|
174
178
|
@end
|
|
175
179
|
|
|
180
|
+
// SDWebImageSVGCoder's coder, when the app has the pod (SVG images): found at
|
|
181
|
+
// runtime, so FastImage doesn't depend on it. Nil otherwise.
|
|
182
|
+
static id<SDImageCoder> FFFSVGCoder;
|
|
183
|
+
|
|
176
184
|
@implementation FFFastImageSource
|
|
177
185
|
|
|
186
|
+
+ (void)initialize
|
|
187
|
+
{
|
|
188
|
+
if (self != [FFFastImageSource class]) {
|
|
189
|
+
return;
|
|
190
|
+
}
|
|
191
|
+
// Registered with SDWebImage's coders, as the pod's setup does (unless
|
|
192
|
+
// the app has already): SDWebImage then picks it for SVG data, including
|
|
193
|
+
// for FastImage's animated and downsampled image classes. Giving the
|
|
194
|
+
// coder per load instead (SDWebImageContextImageCoder) would make it the
|
|
195
|
+
// only coder for everything in that load. So the app's own SDWebImage
|
|
196
|
+
// use can decode SVG too, which is what the pod is for.
|
|
197
|
+
Class coderClass = NSClassFromString(@"SDImageSVGCoder");
|
|
198
|
+
SEL shared = NSSelectorFromString(@"sharedCoder");
|
|
199
|
+
if (!coderClass || ![coderClass respondsToSelector:shared]) {
|
|
200
|
+
return;
|
|
201
|
+
}
|
|
202
|
+
id coder = ((id (*)(id, SEL))objc_msgSend)(coderClass, shared);
|
|
203
|
+
if (![coder conformsToProtocol:@protocol(SDImageCoder)]) {
|
|
204
|
+
return;
|
|
205
|
+
}
|
|
206
|
+
FFFSVGCoder = coder;
|
|
207
|
+
for (id<SDImageCoder> registered in SDImageCodersManager.sharedManager.coders) {
|
|
208
|
+
if ([registered isKindOfClass:coderClass]) {
|
|
209
|
+
return;
|
|
210
|
+
}
|
|
211
|
+
}
|
|
212
|
+
[SDImageCodersManager.sharedManager addCoder:coder];
|
|
213
|
+
}
|
|
214
|
+
|
|
178
215
|
- (instancetype)initWithURL:(NSURL *)url
|
|
179
216
|
priority:(FFFPriority)priority
|
|
180
217
|
headers:(NSDictionary *)headers
|
|
@@ -216,6 +253,15 @@ static CGSize FFFPhotoPixelSize(NSURL *url)
|
|
|
216
253
|
return 0;
|
|
217
254
|
}
|
|
218
255
|
|
|
256
|
+
- (NSString *)errorMessage:(NSError *)error
|
|
257
|
+
{
|
|
258
|
+
if (!FFFSVGCoder && [error.domain isEqualToString:SDWebImageErrorDomain] && error.code == SDWebImageErrorBadImageData &&
|
|
259
|
+
[_url.path.lowercaseString hasSuffix:@".svg"]) {
|
|
260
|
+
return @"SVG images need SDWebImageSVGCoder: add pod 'SDWebImageSVGCoder' to the app's Podfile";
|
|
261
|
+
}
|
|
262
|
+
return FFFErrorMessage(error);
|
|
263
|
+
}
|
|
264
|
+
|
|
219
265
|
- (BOOL)isPhotoLibrary
|
|
220
266
|
{
|
|
221
267
|
return [_url.scheme isEqualToString:@"ph"];
|
|
@@ -355,6 +355,25 @@ static UIImage* FFFBlurredImage(UIImage* image, CGFloat scale, CGFloat radius, B
|
|
|
355
355
|
}
|
|
356
356
|
}
|
|
357
357
|
|
|
358
|
+
// A vector image drawn into a bitmap, with its size in points, at a scale
|
|
359
|
+
// that's sharp at the size the view shows it (or its own size before the view
|
|
360
|
+
// has one).
|
|
361
|
+
- (UIImage*) rasterizedImage: (UIImage*)image {
|
|
362
|
+
CGSize size = image.size;
|
|
363
|
+
if (size.width <= 0 || size.height <= 0) {
|
|
364
|
+
return image;
|
|
365
|
+
}
|
|
366
|
+
CGSize view = self.bounds.size;
|
|
367
|
+
CGFloat shown = MAX(1, MAX(view.width / size.width, view.height / size.height));
|
|
368
|
+
UIGraphicsImageRendererFormat* format = [UIGraphicsImageRendererFormat preferredFormat];
|
|
369
|
+
format.scale = (self.window.screen.scale ?: [UIScreen mainScreen].scale) * shown;
|
|
370
|
+
format.opaque = NO;
|
|
371
|
+
UIGraphicsImageRenderer* renderer = [[UIGraphicsImageRenderer alloc] initWithSize: size format: format];
|
|
372
|
+
return [renderer imageWithActions: ^(UIGraphicsImageRendererContext* context) {
|
|
373
|
+
[image drawInRect: CGRectMake(0, 0, size.width, size.height)];
|
|
374
|
+
}];
|
|
375
|
+
}
|
|
376
|
+
|
|
358
377
|
// Shows the image, tinted as React Native's Image does it: UIKit draws a
|
|
359
378
|
// template image in the view's tintColor, so no tinted copy of the image is
|
|
360
379
|
// made. SDAnimatedImageView draws the frames of an animated image itself,
|
|
@@ -382,6 +401,11 @@ static UIImage* FFFBlurredImage(UIImage* image, CGFloat scale, CGFloat radius, B
|
|
|
382
401
|
if (tint) {
|
|
383
402
|
if (animated) {
|
|
384
403
|
image = [UIImage imageWithCGImage: image.CGImage scale: image.scale orientation: image.imageOrientation];
|
|
404
|
+
} else if (!image.CGImage && !repeats) {
|
|
405
|
+
// A vector image (an SVG), which UIKit doesn't draw as a
|
|
406
|
+
// template: drawn into a bitmap at the size it's shown at
|
|
407
|
+
// (tiledImage: draws one for repeat).
|
|
408
|
+
image = [self rasterizedImage: image];
|
|
385
409
|
}
|
|
386
410
|
image = [image imageWithRenderingMode: UIImageRenderingModeAlwaysTemplate];
|
|
387
411
|
}
|
|
@@ -399,6 +423,18 @@ static UIImage* FFFBlurredImage(UIImage* image, CGFloat scale, CGFloat radius, B
|
|
|
399
423
|
// animated image's first frame, scaled down to fit the view if it's larger,
|
|
400
424
|
// as React Native's Image does (and Android here).
|
|
401
425
|
- (UIImage*) tiledImage: (UIImage*)image {
|
|
426
|
+
if (!image.CGImage && image.size.width > 0 && image.size.height > 0) {
|
|
427
|
+
// A vector image (an SVG): drawn at its own size in pixels (its width
|
|
428
|
+
// and height), as Android tiles it and as any other image's size is.
|
|
429
|
+
UIImage* vector = image;
|
|
430
|
+
UIGraphicsImageRendererFormat* format = [UIGraphicsImageRendererFormat preferredFormat];
|
|
431
|
+
format.scale = 1;
|
|
432
|
+
format.opaque = NO;
|
|
433
|
+
UIGraphicsImageRenderer* renderer = [[UIGraphicsImageRenderer alloc] initWithSize: vector.size format: format];
|
|
434
|
+
image = [[renderer imageWithActions: ^(UIGraphicsImageRendererContext* context) {
|
|
435
|
+
[vector drawInRect: CGRectMake(0, 0, vector.size.width, vector.size.height)];
|
|
436
|
+
}] imageWithRenderingMode: vector.renderingMode];
|
|
437
|
+
}
|
|
402
438
|
CGImageRef cgImage = image.CGImage;
|
|
403
439
|
if (!cgImage) {
|
|
404
440
|
return image;
|
|
@@ -1065,7 +1101,7 @@ NSString *FFFErrorMessage(NSError *error)
|
|
|
1065
1101
|
if (error) {
|
|
1066
1102
|
// SDWebImage shows the placeholder (defaultSource or nothing).
|
|
1067
1103
|
weakSelf.showsLoadedImage = NO;
|
|
1068
|
-
[weakSelf sendOnError:
|
|
1104
|
+
[weakSelf sendOnError: [source errorMessage: error]];
|
|
1069
1105
|
if (weakSelf.onFastImageLoadEnd) {
|
|
1070
1106
|
weakSelf.onFastImageLoadEnd([weakSelf loadEndEvent: NO]);
|
|
1071
1107
|
}
|
|
@@ -293,7 +293,7 @@ RCT_EXPORT_METHOD(preload:(nonnull NSArray<FFFastImageSource *> *)sources
|
|
|
293
293
|
FFFQueueLoad(source, options, [context copy], ^(UIImage *image, NSError *error) {
|
|
294
294
|
results[idx] = image
|
|
295
295
|
? @{@"ok": @YES, @"width": @(image.size.width), @"height": @(image.size.height)}
|
|
296
|
-
: FFFFailure(
|
|
296
|
+
: FFFFailure([source errorMessage:error]);
|
|
297
297
|
finishOne();
|
|
298
298
|
});
|
|
299
299
|
}];
|
|
@@ -351,7 +351,7 @@ RCT_EXPORT_METHOD(getCachePath:(FFFastImageSource *)source
|
|
|
351
351
|
context[SDWebImageContextStoreCacheType] = @(SDImageCacheTypeDisk);
|
|
352
352
|
FFFQueueLoad(source, options, [context copy], ^(UIImage *image, NSError *error) {
|
|
353
353
|
if (!image) {
|
|
354
|
-
resolve(FFFFailure(
|
|
354
|
+
resolve(FFFFailure([source errorMessage:error]));
|
|
355
355
|
return;
|
|
356
356
|
}
|
|
357
357
|
FFFFindCachedFile(key, resolveFile, ^{
|
package/package.json
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "react-native-fast-image",
|
|
3
|
-
"version": "8.
|
|
4
|
-
"description": "
|
|
3
|
+
"version": "8.27.1",
|
|
4
|
+
"description": "FastImage, performant React Native image component.",
|
|
5
5
|
"keywords": [
|
|
6
6
|
"cache",
|
|
7
7
|
"cached",
|