react-native-fast-image 8.31.0 → 8.31.2

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 CHANGED
@@ -5,6 +5,7 @@ Performant React Native image component.
5
5
  [![Version][version-badge]][package]
6
6
  [![Downloads][downloads-badge]][npmtrends]
7
7
  [![Build Status][build-badge]][build]
8
+ [![Docs][docs-badge]][docs]
8
9
 
9
10
  React Native's `Image` component handles image caching like browsers for the most part. If the server is returning proper cache control headers for images you'll generally get the sort of built in caching behavior you'd have in a browser. Even so, many people have noticed:
10
11
 
@@ -137,7 +138,7 @@ When `source` changes, the image that's showing stays until the new one has load
137
138
 
138
139
  Remote url to load the image from. e.g. `'https://example.com/image.jpg'`.
139
140
 
140
- Also loads local files (`file://`, and on Android `content://`), photo library images on iOS (`ph://`, see [Photo library images](#photo-library-images-ios)) and SVG images (see [SVG images](docs/formats.md#svg-images)).
141
+ Also loads local files (`file://`, and on Android `content://`), images in the app by name (e.g. `'my_image'`: in its asset catalog on iOS, a drawable on Android), photo library images on iOS (`ph://`, see [Photo library images](#photo-library-images-ios)) and SVG images (see [SVG images](docs/formats.md#svg-images)).
141
142
 
142
143
  ---
143
144
 
@@ -622,7 +623,7 @@ await FastImage.configureCache({ maxDiskSize: 500 * 1024 * 1024 })
622
623
  const { maxDiskSize, diskSize } = await FastImage.configureCache()
623
624
  ```
624
625
 
625
- On Android, if your app has its own `AppGlideModule` (see [using FastImage with an AppGlideModule](docs/android-build-settings.md#if-your-app-has-its-own-appglidemodule)), set the disk cache size there instead: `maxDiskSize` isn't applied or reported.
626
+ On Android, if your app has its own `AppGlideModule` (see [using FastImage with an AppGlideModule](docs/android-build-settings.md#if-your-app-has-its-own-appglidemodule)), set the disk cache size there instead: `maxDiskSize` isn't applied or reported. Likewise in an app with [expo-image](docs/android-build-settings.md#with-expo-image), whose Glide setup sets the disk cache.
626
627
 
627
628
  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.
628
629
 
@@ -658,7 +659,7 @@ How fresh an image must be: see [`source.cache`](#sourcecache).
658
659
 
659
660
  ### `CacheState`
660
661
 
661
- `configureCache`'s result: the limits in effect (0 for no limit), and the bytes the disk cache uses now. Android only has `maxDiskSize` and `diskSize`, and neither if the app has its own `AppGlideModule`.
662
+ `configureCache`'s result: the limits in effect (0 for no limit), and the bytes the disk cache uses now. Android only has `maxDiskSize` and `diskSize`, and neither if the app has its own `AppGlideModule` (or expo-image, which has one).
662
663
 
663
664
  - `maxDiskSize?` (`number`)
664
665
  - `maxDiskAge?` (`number`)
@@ -799,7 +800,7 @@ In `android/app/src/main/AndroidManifest.xml`, inside `<application>`:
799
800
  <meta-data android:name="fastimage.MAX_DISK_SIZE" android:value="209715200" />
800
801
  ```
801
802
 
802
- If your app has its own `AppGlideModule` (see [using FastImage with an AppGlideModule](docs/android-build-settings.md#if-your-app-has-its-own-appglidemodule)), set the disk cache size there instead.
803
+ If your app has its own `AppGlideModule` (see [using FastImage with an AppGlideModule](docs/android-build-settings.md#if-your-app-has-its-own-appglidemodule)), set the disk cache size there instead. In an app with [expo-image](docs/android-build-settings.md#with-expo-image), expo-image's Glide setup sets it.
803
804
 
804
805
  [^glide-memory]: Glide's default, which FastImage keeps: room for two screenfuls of decoded images (2 × the screen's width × height × 4 bytes, about 20 MB on a 1080 × 2400 screen), plus a pool of bitmaps to reuse (one screenful on Android 8 and later, four before). Together they're limited to 40% of the memory Android gives the app (33% on low-memory devices), and both shrink to fit.
805
806
 
@@ -846,6 +847,8 @@ Thanks to [@mobinni](https://github.com/mobinni) for helping with the conceptual
846
847
 
847
848
  [build-badge]: https://github.com/DylanVann/react-native-fast-image/actions/workflows/ci.yml/badge.svg
848
849
  [build]: https://github.com/DylanVann/react-native-fast-image/actions/workflows/ci.yml
850
+ [docs-badge]: https://img.shields.io/badge/docs-website-blue.svg
851
+ [docs]: https://react-native-fast-image.dylanvann.workers.dev
849
852
  [downloads-badge]: https://img.shields.io/npm/dm/react-native-fast-image.svg
850
853
  [npmtrends]: http://www.npmtrends.com/react-native-fast-image
851
854
  [package]: https://www.npmjs.com/package/react-native-fast-image
@@ -19,6 +19,18 @@ buildscript {
19
19
 
20
20
  apply plugin: 'com.android.library'
21
21
 
22
+ // Whether the app has expo-image (in a node_modules folder above the app's
23
+ // android folder, as Node finds it), which ships its own AppGlideModule.
24
+ def appHasExpoImage = {
25
+ for (def dir = rootProject.projectDir; dir != null; dir = dir.parentFile) {
26
+ if (new File(dir, "node_modules/expo-image/package.json").isFile()) return true
27
+ }
28
+ return false
29
+ }
30
+ // An app has one AppGlideModule: with expo-image, FastImage's is left out,
31
+ // and FastImage registers its Glide components itself (FastImageGlide).
32
+ def excludeAppGlideModule = safeExtGet('excludeAppGlideModule', appHasExpoImage())
33
+
22
34
  // When changing the default, revisit the Glide rules in consumer-rules.pro.
23
35
  // 4.15+ animates animated WebP (Android 9+) and AVIF (Android 12+).
24
36
  def glideVersion = safeExtGet('glideVersion', '4.16.0')
@@ -56,7 +68,7 @@ android {
56
68
  }
57
69
  java {
58
70
  srcDir glideHasAnimatedDecoder ? "src/glide-4.15/java" : "src/glide-older/java"
59
- if (safeExtGet('excludeAppGlideModule', false)) {
71
+ if (excludeAppGlideModule) {
60
72
  srcDir "src"
61
73
  exclude "**/FastImageGlideModule.java"
62
74
  // Only the one chosen above.
@@ -194,7 +194,7 @@ final class FastImageAnimated {
194
194
 
195
195
  @Override
196
196
  public boolean handles(@NonNull ByteBuffer source, @NonNull Options options) throws IOException {
197
- return glide.handles(source, options);
197
+ return FastImageGlide.isRequest(options) && glide.handles(source, options);
198
198
  }
199
199
 
200
200
  @Nullable
@@ -229,7 +229,7 @@ final class FastImageAnimated {
229
229
 
230
230
  @Override
231
231
  public boolean handles(@NonNull InputStream source, @NonNull Options options) throws IOException {
232
- return glide.handles(source, options);
232
+ return FastImageGlide.isRequest(options) && glide.handles(source, options);
233
233
  }
234
234
 
235
235
  // Read whole, as Glide's own does.
@@ -99,7 +99,7 @@ final class FastImageApng {
99
99
  private static final class BufferDecoder implements ResourceDecoder<ByteBuffer, Drawable> {
100
100
  @Override
101
101
  public boolean handles(@NonNull ByteBuffer source, @NonNull Options options) {
102
- return animates(options) && plays(source) >= 0;
102
+ return FastImageGlide.isRequest(options) && animates(options) && plays(source) >= 0;
103
103
  }
104
104
 
105
105
  @Nullable
@@ -113,7 +113,7 @@ final class FastImageApng {
113
113
  private static final class StreamDecoder implements ResourceDecoder<InputStream, Drawable> {
114
114
  @Override
115
115
  public boolean handles(@NonNull InputStream source, @NonNull Options options) throws IOException {
116
- if (!animates(options)) return false;
116
+ if (!FastImageGlide.isRequest(options) || !animates(options)) return false;
117
117
  byte[] head = new byte[HEAD_LENGTH];
118
118
  source.mark(HEAD_LENGTH);
119
119
  int read = 0;
@@ -40,7 +40,7 @@ final class FastImageGif {
40
40
  ByteBuffer data = gif.getBuffer();
41
41
  GifHeader header = new GifHeaderParser().setData(data).parseHeader();
42
42
  if (header.getNumFrames() <= 0 || header.getStatus() != GifDecoder.STATUS_OK) return null;
43
- Glide glide = Glide.get(context);
43
+ Glide glide = FastImageGlide.get(context);
44
44
  BitmapPool bitmapPool = glide.getBitmapPool();
45
45
  GifDecoder decoder = new StandardGifDecoder(
46
46
  new GifBitmapProvider(bitmapPool, glide.getArrayPool()),
@@ -0,0 +1,76 @@
1
+ package com.dylanvann.fastimage;
2
+
3
+ import android.content.Context;
4
+
5
+ import androidx.annotation.NonNull;
6
+
7
+ import com.bumptech.glide.Glide;
8
+ import com.bumptech.glide.Registry;
9
+ import com.bumptech.glide.load.Option;
10
+ import com.bumptech.glide.load.Options;
11
+
12
+ // FastImage's use of Glide, which the app may share with other libraries.
13
+ final class FastImageGlide {
14
+ private FastImageGlide() {}
15
+
16
+ // Set on FastImage's requests, so its decoders (animated WebP and AVIF,
17
+ // APNG, SVG) only take its own images: another library's images (e.g.
18
+ // expo-image's) keep that library's decoders.
19
+ static final Option<Boolean> REQUEST = Option.memory("com.dylanvann.fastimage.Request", false);
20
+
21
+ static boolean isRequest(@NonNull Options options) {
22
+ return Boolean.TRUE.equals(options.get(REQUEST));
23
+ }
24
+
25
+ // The Glide instance FastImage's components (FastImageOkHttpProgressGlideModule)
26
+ // were registered with.
27
+ private static volatile Glide registered;
28
+
29
+ // Whether Glide started with FastImage's AppGlideModule (as the app's).
30
+ private static volatile boolean appModule;
31
+
32
+ static void appModuleStarted() {
33
+ appModule = true;
34
+ }
35
+
36
+ static void registered(@NonNull Glide glide) {
37
+ registered = glide;
38
+ }
39
+
40
+ // Glide, with FastImage's components. Glide registers them if the app's
41
+ // generated Glide module lists FastImage's module, as FastImage's
42
+ // AppGlideModule's does. Another library's (e.g. expo-image's, which ships
43
+ // one) only lists the modules it was built with, so then they're
44
+ // registered here, before FastImage's first request.
45
+ @NonNull
46
+ static Glide get(@NonNull Context context) {
47
+ Glide glide = Glide.get(context);
48
+ // Glide registers them itself, when its registry is first used (in
49
+ // the background, by the first load: Glide 4.15+ builds it lazily).
50
+ if (appModule) return glide;
51
+ // Builds the registry, which registers them if the app's module lists
52
+ // FastImage's.
53
+ Registry registry = glide.getRegistry();
54
+ if (registered != glide) {
55
+ synchronized (FastImageGlide.class) {
56
+ if (registered != glide) {
57
+ new FastImageOkHttpProgressGlideModule()
58
+ .registerComponents(context.getApplicationContext(), glide, registry);
59
+ }
60
+ }
61
+ }
62
+ return glide;
63
+ }
64
+
65
+ // Glide, with FastImage's components registered now, for callers in the
66
+ // background that use them directly rather than through a load (the HTTP
67
+ // cache of `cache: 'web'` images, which registering them sets up): with
68
+ // FastImage's AppGlideModule, Glide registers them only when its registry
69
+ // is first used, e.g. by FastImage's first load.
70
+ @NonNull
71
+ static Glide getRegistered(@NonNull Context context) {
72
+ Glide glide = get(context);
73
+ glide.getRegistry();
74
+ return glide;
75
+ }
76
+ }
@@ -16,6 +16,7 @@ public final class FastImageGlideModule extends AppGlideModule {
16
16
  // FastImageCacheLimits), in Glide's default folder.
17
17
  @Override
18
18
  public void applyOptions(@NonNull Context context, @NonNull GlideBuilder builder) {
19
+ FastImageGlide.appModuleStarted();
19
20
  long maxDiskSize = FastImageCacheLimits.maxDiskSize(context);
20
21
  builder.setDiskCache(new InternalCacheDiskCacheFactory(context, maxDiskSize == 0 ? Long.MAX_VALUE : maxDiskSize));
21
22
  FastImageCacheLimits.startedMaxDiskSize = maxDiskSize;
@@ -119,11 +119,6 @@ public class FastImageOkHttpProgressGlideModule extends LibraryGlideModule {
119
119
  builder.cookieJar(new JavaNetCookieJar(new FastImageCookieHandler(context)));
120
120
  }
121
121
  OkHttpClient client = builder.build();
122
- registry.replace(GlideUrl.class, InputStream.class, new UrlLoaderFactory(client));
123
- FastImageSvg.register(registry, glide.getBitmapPool());
124
- FastImageAnimated.register(context, glide, registry);
125
- FastImageApng.register(registry);
126
-
127
122
  // `cache: 'web'` skips Glide's caches and relies on HTTP caching, so
128
123
  // those urls get a client with an HTTP cache (#280): one of their own,
129
124
  // not the app's (if it gave React Native's shared client one), so
@@ -132,70 +127,49 @@ public class FastImageOkHttpProgressGlideModule extends LibraryGlideModule {
132
127
  // store them twice).
133
128
  webCache = new Cache(new File(context.getCacheDir(), "fast-image-http-cache"), WEB_CACHE_SIZE);
134
129
  webClient = client.newBuilder().cache(webCache).build();
135
- registry.prepend(FastImageWebGlideUrl.class, InputStream.class, new WebUrlLoaderFactory(webClient));
130
+ registry.prepend(FastImageUrl.class, InputStream.class, new UrlLoaderFactory(client, webClient));
131
+ FastImageSvg.register(registry, glide.getBitmapPool());
132
+ FastImageAnimated.register(context, glide, registry);
133
+ FastImageApng.register(registry);
136
134
 
137
135
  // writeToCache: local files stored under a source's key.
138
136
  registry.prepend(FastImageCacheWrite.class, InputStream.class,
139
137
  new FastImageCacheWrite.LoaderFactory(context.getApplicationContext()));
138
+ FastImageGlide.registered(glide);
140
139
  }
141
140
 
142
- // Loads GlideUrls with the given client, except `web` ones
143
- // (FastImageWebGlideUrls): Glide gives a model to the loaders of its
144
- // superclasses too, and tries the next one when a load fails, so a `web`
145
- // image that failed (e.g. a 404) was requested again with this client,
146
- // without the HTTP cache, which also hid the failure.
147
- private static class UrlLoaderFactory implements ModelLoaderFactory<GlideUrl, InputStream> {
141
+ // Loads FastImage's remote images (FastImageUrls): `web` ones
142
+ // (FastImageWebGlideUrls) with the client with the HTTP cache, others with
143
+ // the other client, where a view loading an image that's being preloaded
144
+ // waits for the preload's download. FastImageUrl isn't a GlideUrl, so
145
+ // Glide doesn't also give these to other GlideUrl loaders, which it would
146
+ // try after a failed load (e.g. a 404, requested again).
147
+ private static class UrlLoaderFactory implements ModelLoaderFactory<FastImageUrl, InputStream> {
148
148
  private final OkHttpClient client;
149
+ private final OkHttpClient webClient;
149
150
 
150
- UrlLoaderFactory(OkHttpClient client) {
151
+ UrlLoaderFactory(OkHttpClient client, OkHttpClient webClient) {
151
152
  this.client = client;
153
+ this.webClient = webClient;
152
154
  }
153
155
 
154
156
  @NonNull
155
157
  @Override
156
- public ModelLoader<GlideUrl, InputStream> build(@NonNull MultiModelLoaderFactory multiFactory) {
158
+ public ModelLoader<FastImageUrl, InputStream> build(@NonNull MultiModelLoaderFactory multiFactory) {
157
159
  final OkHttpUrlLoader loader = new OkHttpUrlLoader(client);
158
- return new ModelLoader<GlideUrl, InputStream>() {
160
+ final OkHttpUrlLoader webLoader = new OkHttpUrlLoader(webClient);
161
+ return new ModelLoader<FastImageUrl, InputStream>() {
159
162
  @Override
160
- public LoadData<InputStream> buildLoadData(@NonNull GlideUrl model, int width, int height, @NonNull Options options) {
161
- LoadData<InputStream> data = loader.buildLoadData(model, width, height, options);
162
- // A view loading an image that's being preloaded waits
163
- // for the preload's download.
164
- return data == null ? null : FastImageSharedDownloads.share(data, model, options);
165
- }
166
-
167
- @Override
168
- public boolean handles(@NonNull GlideUrl model) {
169
- return !(model instanceof FastImageWebGlideUrl);
170
- }
171
- };
172
- }
173
-
174
- @Override
175
- public void teardown() {
176
- }
177
- }
178
-
179
- // Loads FastImageWebGlideUrls with the given client.
180
- private static class WebUrlLoaderFactory implements ModelLoaderFactory<FastImageWebGlideUrl, InputStream> {
181
- private final OkHttpClient client;
182
-
183
- WebUrlLoaderFactory(OkHttpClient client) {
184
- this.client = client;
185
- }
186
-
187
- @NonNull
188
- @Override
189
- public ModelLoader<FastImageWebGlideUrl, InputStream> build(@NonNull MultiModelLoaderFactory multiFactory) {
190
- final OkHttpUrlLoader loader = new OkHttpUrlLoader(client);
191
- return new ModelLoader<FastImageWebGlideUrl, InputStream>() {
192
- @Override
193
- public LoadData<InputStream> buildLoadData(@NonNull FastImageWebGlideUrl model, int width, int height, @NonNull Options options) {
194
- return loader.buildLoadData(model, width, height, options);
163
+ public LoadData<InputStream> buildLoadData(@NonNull FastImageUrl model, int width, int height, @NonNull Options options) {
164
+ if (model.url instanceof FastImageWebGlideUrl) {
165
+ return webLoader.buildLoadData(model.url, width, height, options);
166
+ }
167
+ LoadData<InputStream> data = loader.buildLoadData(model.url, width, height, options);
168
+ return data == null ? null : FastImageSharedDownloads.share(data, model.url, options);
195
169
  }
196
170
 
197
171
  @Override
198
- public boolean handles(@NonNull FastImageWebGlideUrl model) {
172
+ public boolean handles(@NonNull FastImageUrl model) {
199
173
  return true;
200
174
  }
201
175
  };
@@ -4,7 +4,6 @@ import android.graphics.drawable.Drawable;
4
4
 
5
5
  import com.bumptech.glide.load.DataSource;
6
6
  import com.bumptech.glide.load.engine.GlideException;
7
- import com.bumptech.glide.load.model.GlideUrl;
8
7
  import com.bumptech.glide.load.resource.gif.GifDrawable;
9
8
  import com.bumptech.glide.request.Request;
10
9
  import com.bumptech.glide.request.RequestListener;
@@ -83,7 +82,7 @@ public class FastImageRequestListener implements RequestListener<Drawable> {
83
82
  view.applyLoopCount((GifDrawable) resource);
84
83
  }
85
84
  if (!events) return false;
86
- boolean local = !(model instanceof GlideUrl);
85
+ boolean local = !(model instanceof FastImageUrl);
87
86
  int[] size = FastImageSourceSize.get(resource, model, local,
88
87
  dataSource == DataSource.RESOURCE_DISK_CACHE);
89
88
  if (size != null) {
@@ -122,7 +122,7 @@ public class FastImageSource extends ImageSource {
122
122
  if (isLocalFile()) {
123
123
  return getUri().toString();
124
124
  }
125
- return getGlideUrl();
125
+ return new FastImageUrl(getGlideUrl());
126
126
  }
127
127
 
128
128
  @Override
@@ -218,7 +218,7 @@ final class FastImageSourceSize {
218
218
  int orientation;
219
219
  stream = open(context, source);
220
220
  try {
221
- Glide glide = Glide.get(context);
221
+ Glide glide = FastImageGlide.get(context);
222
222
  orientation = ImageHeaderParserUtils.getOrientation(
223
223
  glide.getRegistry().getImageHeaderParsers(), stream, glide.getArrayPool());
224
224
  } finally {
@@ -118,6 +118,7 @@ final class FastImageSvg {
118
118
 
119
119
  @Override
120
120
  public boolean handles(@NonNull InputStream source, @NonNull Options options) throws IOException {
121
+ if (!FastImageGlide.isRequest(options)) return false;
121
122
  // Glide rewinds the stream before decoding.
122
123
  byte[] head = new byte[HEAD_LENGTH];
123
124
  int length = 0;
@@ -145,6 +146,7 @@ final class FastImageSvg {
145
146
 
146
147
  @Override
147
148
  public boolean handles(@NonNull ByteBuffer source, @NonNull Options options) {
149
+ if (!FastImageGlide.isRequest(options)) return false;
148
150
  ByteBuffer copy = source.duplicate();
149
151
  byte[] head = new byte[Math.min(HEAD_LENGTH, copy.remaining())];
150
152
  copy.get(head);
@@ -0,0 +1,36 @@
1
+ package com.dylanvann.fastimage;
2
+
3
+ import androidx.annotation.NonNull;
4
+
5
+ import com.bumptech.glide.load.model.GlideUrl;
6
+
7
+ // What FastImage loads a remote image with: its GlideUrl (url, headers, and
8
+ // cache key: the disk cache key stays the GlideUrl's), loaded by FastImage's
9
+ // own loader (FastImageOkHttpProgressGlideModule). Not a GlideUrl itself: the
10
+ // app's Glide may have other GlideUrl loaders (Glide's, another library's,
11
+ // e.g. expo-image's), which Glide would also try after a failed load, and
12
+ // FastImage leaves them as they are for the app's other loads.
13
+ final class FastImageUrl {
14
+ final GlideUrl url;
15
+
16
+ FastImageUrl(@NonNull GlideUrl url) {
17
+ this.url = url;
18
+ }
19
+
20
+ @Override
21
+ public boolean equals(Object other) {
22
+ return other instanceof FastImageUrl && url.equals(((FastImageUrl) other).url);
23
+ }
24
+
25
+ @Override
26
+ public int hashCode() {
27
+ return url.hashCode();
28
+ }
29
+
30
+ // The GlideUrl's (its cache key), which FastImageSourceSize keys sizes by.
31
+ @NonNull
32
+ @Override
33
+ public String toString() {
34
+ return url.toString();
35
+ }
36
+ }
@@ -122,7 +122,8 @@ class FastImageViewConverter {
122
122
  // memoryCache false: only kept on disk.
123
123
  .skipMemoryCache(skipMemoryCache || (imageSource != null && !imageSource.isMemoryCache()))
124
124
  .priority(priority)
125
- .placeholder(TRANSPARENT_DRAWABLE);
125
+ .placeholder(TRANSPARENT_DRAWABLE)
126
+ .set(FastImageGlide.REQUEST, true);
126
127
 
127
128
  // imageSource is null when only a defaultSource is shown.
128
129
  if (imageSource != null && imageSource.isResource()) {
@@ -53,6 +53,7 @@ class FastImageViewManager extends SimpleViewManager<FastImageViewWithUrl> imple
53
53
  // shared by all views kept the last view's Activity alive after it was
54
54
  // destroyed, and gave views another Activity's manager (#492).
55
55
  RequestManager requestManager = null;
56
+ FastImageGlide.get(reactContext);
56
57
  if (isValidContextForGlide(reactContext)) {
57
58
  requestManager = Glide.with(reactContext);
58
59
  } else if (getActivityFromContext(reactContext) == null) {
@@ -87,7 +87,7 @@ class FastImageViewModule extends ReactContextBaseJavaModule {
87
87
  BitmapFactory.decodeFile(file.getPath(), bounds);
88
88
  if (bounds.outWidth <= 0 || bounds.outHeight <= 0) return null;
89
89
  int orientation = ImageHeaderParser.UNKNOWN_ORIENTATION;
90
- Glide glide = Glide.get(context);
90
+ Glide glide = FastImageGlide.get(context);
91
91
  try (InputStream stream = new FileInputStream(file)) {
92
92
  orientation = ImageHeaderParserUtils.getOrientation(
93
93
  glide.getRegistry().getImageHeaderParsers(), stream, glide.getArrayPool());
@@ -119,6 +119,7 @@ class FastImageViewModule extends ReactContextBaseJavaModule {
119
119
  UiThreadUtil.runOnUiThread(new Runnable() {
120
120
  @Override
121
121
  public void run() {
122
+ FastImageGlide.get(context);
122
123
  final int count = sources.size();
123
124
  final WritableMap[] results = new WritableMap[count];
124
125
  // Sources of this call still to finish. It starts at the
@@ -270,7 +271,8 @@ class FastImageViewModule extends ReactContextBaseJavaModule {
270
271
  // thread. For a preload (`shared`), views that load the image meanwhile
271
272
  // wait for the download and read its file (FastImageSharedDownloads).
272
273
  private static void loadFile(Context context, Object model, RequestOptions options, boolean shared, final FileCallback callback) {
273
- final String key = shared && model instanceof GlideUrl ? ((GlideUrl) model).getCacheKey() : null;
274
+ final String key = shared && model instanceof FastImageUrl ? ((FastImageUrl) model).url.getCacheKey() : null;
275
+ FastImageGlide.get(context);
274
276
  Glide.with(context)
275
277
  .asFile()
276
278
  .load(model)
@@ -298,6 +300,7 @@ class FastImageViewModule extends ReactContextBaseJavaModule {
298
300
  @Nullable
299
301
  private static File cachedFile(Context context, Object model, RequestOptions options) {
300
302
  try {
303
+ FastImageGlide.get(context);
301
304
  return Glide.with(context)
302
305
  .asFile()
303
306
  .load(model)
@@ -348,7 +351,7 @@ class FastImageViewModule extends ReactContextBaseJavaModule {
348
351
  cachePathExecutor.execute(new Runnable() {
349
352
  @Override
350
353
  public void run() {
351
- File file = cachedFile(context, FastImageKeyedGlideUrl.forKey(cacheKey), new RequestOptions());
354
+ File file = cachedFile(context, new FastImageUrl(FastImageKeyedGlideUrl.forKey(cacheKey)), new RequestOptions());
352
355
  promise.resolve(file != null ? pathResult(file) : failure("Not in the disk cache"));
353
356
  }
354
357
  });
@@ -376,8 +379,8 @@ class FastImageViewModule extends ReactContextBaseJavaModule {
376
379
  public void run() {
377
380
  // On disk already: in Glide's cache (not for `web` images,
378
381
  // which it doesn't cache), or in the HTTP cache of `web`
379
- // images (set up by Glide, which is started first).
380
- Glide.get(context);
382
+ // images (set up when FastImage's components are registered).
383
+ FastImageGlide.getRegistered(context);
381
384
  File file = imageSource.isWebCache() ? null : cachedFile(context, imageSource.getSourceForLoad(), options);
382
385
  if (file == null) file = FastImageOkHttpProgressGlideModule.webCacheFile(imageSource.getUri().toString());
383
386
  if (file != null) {
@@ -486,7 +489,7 @@ class FastImageViewModule extends ReactContextBaseJavaModule {
486
489
  final GlideUrl key = imageSource != null && imageSource.isRemote()
487
490
  ? imageSource.getGlideUrl()
488
491
  : FastImageKeyedGlideUrl.forKey(cacheKey);
489
- final Object lookUp = imageSource != null && imageSource.isRemote() ? imageSource.getSourceForLoad() : key;
492
+ final Object lookUp = imageSource != null && imageSource.isRemote() ? imageSource.getSourceForLoad() : new FastImageUrl(key);
490
493
  // A file:// or content:// uri, or a path.
491
494
  Uri parsed = Uri.parse(file);
492
495
  final Uri fileUri = parsed.getScheme() == null ? Uri.fromFile(new File(file)) : parsed;
@@ -551,7 +554,7 @@ class FastImageViewModule extends ReactContextBaseJavaModule {
551
554
  cachePathExecutor.execute(new Runnable() {
552
555
  @Override
553
556
  public void run() {
554
- Glide.get(context);
557
+ FastImageGlide.get(context);
555
558
  WritableMap result = Arguments.createMap();
556
559
  long maxDiskSize = FastImageCacheLimits.startedMaxDiskSize;
557
560
  if (maxDiskSize >= 0) {
@@ -574,7 +577,7 @@ class FastImageViewModule extends ReactContextBaseJavaModule {
574
577
  activity.runOnUiThread(new Runnable() {
575
578
  @Override
576
579
  public void run() {
577
- Glide.get(activity.getApplicationContext()).clearMemory();
580
+ FastImageGlide.get(activity.getApplicationContext()).clearMemory();
578
581
  promise.resolve(null);
579
582
  }
580
583
  });
@@ -588,9 +591,9 @@ class FastImageViewModule extends ReactContextBaseJavaModule {
588
591
  return;
589
592
  }
590
593
 
591
- Glide.get(activity.getApplicationContext()).clearDiskCache();
594
+ FastImageGlide.getRegistered(activity.getApplicationContext()).clearDiskCache();
592
595
  // And the HTTP cache of `cache: 'web'` images, which Glide doesn't
593
- // cache.
596
+ // cache (set up when FastImage's components are registered).
594
597
  try {
595
598
  FastImageOkHttpProgressGlideModule.clearWebCache();
596
599
  } catch (IOException e) {
@@ -6,6 +6,7 @@ import com.bumptech.glide.load.model.Headers;
6
6
  // A url loaded with `cache: 'web'`. Glide doesn't cache these, so they load
7
7
  // through an OkHttp client with an HTTP cache (see
8
8
  // FastImageOkHttpProgressGlideModule) to follow the server's caching headers.
9
+ // Loaded as a FastImageUrl, like FastImage's other urls.
9
10
  class FastImageWebGlideUrl extends GlideUrl {
10
11
  FastImageWebGlideUrl(String url, Headers headers) {
11
12
  super(url, headers);
package/dist/index.d.ts CHANGED
@@ -13,8 +13,9 @@ export type Source = {
13
13
  /**
14
14
  * Remote url to load the image from. e.g. `'https://example.com/image.jpg'`.
15
15
  *
16
- * Also loads local files (`file://`, and on Android `content://`), photo
17
- * library images on iOS (`ph://`, see
16
+ * Also loads local files (`file://`, and on Android `content://`), images
17
+ * in the app by name (e.g. `'my_image'`: in its asset catalog on iOS, a
18
+ * drawable on Android), photo library images on iOS (`ph://`, see
18
19
  * [Photo library images](#photo-library-images-ios)) and SVG images (see
19
20
  * [SVG images](docs/formats.md#svg-images)).
20
21
  */
@@ -187,7 +188,8 @@ export interface CacheLimits {
187
188
  /**
188
189
  * `configureCache`'s result: the limits in effect (0 for no limit), and the
189
190
  * bytes the disk cache uses now. Android only has `maxDiskSize` and
190
- * `diskSize`, and neither if the app has its own `AppGlideModule`.
191
+ * `diskSize`, and neither if the app has its own `AppGlideModule` (or
192
+ * expo-image, which has one).
191
193
  */
192
194
  export interface CacheState {
193
195
  maxDiskSize?: number;
@@ -62,6 +62,75 @@ static CGSize FFFPhotoPixelSize(NSURL *url)
62
62
  return size;
63
63
  }
64
64
 
65
+ // An image in the app by its name (`uri: 'my_image'`), such as one in the
66
+ // app's asset catalog, as React Native's Image takes: RCTConvert makes a uri
67
+ // without a scheme a file in the app's resources, where such an image isn't a
68
+ // file. The name, or nil if the url is a file that exists or isn't in the
69
+ // app's resources.
70
+ static NSString *FFFBundleAssetName(NSURL *url)
71
+ {
72
+ if (!url.isFileURL) {
73
+ return nil;
74
+ }
75
+ NSString *resources = [NSBundle.mainBundle.resourcePath stringByAppendingString:@"/"];
76
+ NSString *path = url.path;
77
+ if (![path hasPrefix:resources] || path.length == resources.length ||
78
+ [NSFileManager.defaultManager fileExistsAtPath:path]) {
79
+ return nil;
80
+ }
81
+ return [path substringFromIndex:resources.length];
82
+ }
83
+
84
+ // Loads images in the app by name (FFFBundleAssetName) with UIImage
85
+ // imageNamed:, which finds them in the asset catalog (and image files in the
86
+ // app's resources, e.g. with @2x and @3x versions), as React Native's Image
87
+ // does.
88
+ @interface FFFBundleAssetLoader : NSObject <SDImageLoader>
89
+ @end
90
+
91
+ @implementation FFFBundleAssetLoader
92
+
93
+ + (FFFBundleAssetLoader *)sharedLoader
94
+ {
95
+ static FFFBundleAssetLoader *loader;
96
+ static dispatch_once_t once;
97
+ dispatch_once(&once, ^{
98
+ loader = [FFFBundleAssetLoader new];
99
+ });
100
+ return loader;
101
+ }
102
+
103
+ - (BOOL)canRequestImageForURL:(NSURL *)url
104
+ {
105
+ return YES;
106
+ }
107
+
108
+ - (id<SDWebImageOperation>)requestImageWithURL:(NSURL *)url
109
+ options:(SDWebImageOptions)options
110
+ context:(SDWebImageContext *)context
111
+ progress:(SDImageLoaderProgressBlock)progressBlock
112
+ completed:(SDImageLoaderCompletedBlock)completedBlock
113
+ {
114
+ NSString *name = FFFBundleAssetName(url);
115
+ UIImage *image = name ? [UIImage imageNamed:name] : nil;
116
+ if (completedBlock) {
117
+ if (image) {
118
+ completedBlock(image, nil, nil, YES);
119
+ } else {
120
+ NSString *message = [NSString stringWithFormat:@"No image named %@ in the app", name ?: url.path];
121
+ completedBlock(nil, nil, [NSError errorWithDomain:SDWebImageErrorDomain code:SDWebImageErrorInvalidURL userInfo:@{NSLocalizedDescriptionKey: message}], YES);
122
+ }
123
+ }
124
+ return nil;
125
+ }
126
+
127
+ - (BOOL)shouldBlockFailedURLWithURL:(NSURL *)url error:(NSError *)error
128
+ {
129
+ return NO;
130
+ }
131
+
132
+ @end
133
+
65
134
  // Loads photo library images (ph://<localIdentifier>) with
66
135
  // SDWebImagePhotosPlugin's loader: a dependency on iOS (the podspec), found
67
136
  // at runtime so that tvOS builds without it (an app can add it there). Its
@@ -368,6 +437,9 @@ static id<SDImageCoder> FFFSVGCoder;
368
437
  if ([_url.scheme isEqualToString:@"assets-library"]) {
369
438
  return [FFFPhotosLoader assetsLibraryLoader];
370
439
  }
440
+ if (FFFBundleAssetName(_url)) {
441
+ return [FFFBundleAssetLoader sharedLoader];
442
+ }
371
443
  if (_cacheControl != FFFCacheControlWeb) {
372
444
  return nil;
373
445
  }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "react-native-fast-image",
3
- "version": "8.31.0",
3
+ "version": "8.31.2",
4
4
  "description": "FastImage, performant React Native image component.",
5
5
  "keywords": [
6
6
  "cache",