react-native-fast-image 8.31.2 → 9.0.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (22) hide show
  1. package/README.md +24 -24
  2. package/RNFastImage.podspec +6 -4
  3. package/android/build.gradle +16 -24
  4. package/android/consumer-rules.pro +4 -15
  5. package/android/src/main/java/com/dylanvann/fastimage/FastImageAnimatable.java +1 -1
  6. package/android/src/{glide-4.15 → main}/java/com/dylanvann/fastimage/FastImageAnimated.java +0 -3
  7. package/android/src/main/java/com/dylanvann/fastimage/FastImageApng.java +139 -24
  8. package/android/src/main/java/com/dylanvann/fastimage/FastImageCookieHandler.java +1 -22
  9. package/android/src/main/java/com/dylanvann/fastimage/FastImageEvent.java +3 -14
  10. package/android/src/main/java/com/dylanvann/fastimage/FastImageEvents.java +6 -35
  11. package/android/src/main/java/com/dylanvann/fastimage/FastImageOkHttpProgressGlideModule.java +1 -1
  12. package/android/src/main/java/com/dylanvann/fastimage/FastImageSvg.java +6 -6
  13. package/android/src/main/java/com/dylanvann/fastimage/FastImageViewManager.java +1 -7
  14. package/android/src/main/java/com/dylanvann/fastimage/FastImageViewWithUrl.java +1 -1
  15. package/dist/index.d.ts +10 -9
  16. package/ios/FastImage/FFFastImageSource.h +1 -2
  17. package/ios/FastImage/FFFastImageSource.m +28 -74
  18. package/package.json +2 -6
  19. package/android/src/glide-older/java/com/dylanvann/fastimage/FastImageAnimated.java +0 -36
  20. package/android/src/main/java/com/dylanvann/fastimage/FastImageApngRenderer.java +0 -152
  21. package/ios/FastImage.xcodeproj/project.pbxproj +0 -479
  22. package/ios/FastImage.xcodeproj/xcshareddata/xcschemes/FastImage-tvOS.xcscheme +0 -80
package/README.md CHANGED
@@ -26,40 +26,38 @@ React Native's `Image` component handles image caching like browsers for the mos
26
26
  - **Responsive images**: give several sizes, and the one that fits the view loads ([`source`](#source)).
27
27
  - **Animated images** (GIF, WebP, APNG and AVIF) that can [loop](#loop) and [pause](#paused).
28
28
  - **Fades** in and between images ([`transition`](#transition)).
29
- - [**SVG**](docs/formats.md#svg-images) and [**photo library**](#photo-library-images-ios) images.
29
+ - [**SVG**](docs/formats.md#svg-images) and [**photo library**](#photo-library-images-ios-and-tvos) images.
30
30
  - [**`FastImageBackground`**](#fastimagebackground) for content over an image.
31
31
  - **Expo and web support**, with a [config plugin](#expo-config-plugin) for Expo.
32
32
 
33
33
  ## Image formats
34
34
 
35
- | Format | iOS | Android |
36
- | ------ | :---------------: | :---------------: |
37
- | JPEG | ![Yes][yes] | ![Yes][yes] |
38
- | PNG | ![Yes][yes] | ![Yes][yes] |
39
- | APNG | ![Yes][yes] | ![Yes][yes] |
40
- | GIF | ![Yes][yes] | ![Yes][yes] |
41
- | WebP | ![Yes][yes] | ![Yes][yes] |
42
- | AVIF | ![Yes][yes] | ![Yes][yes] |
43
- | HEIC | ![Yes][yes] | ![Yes][yes] |
44
- | SVG | ![Yes][yes][^svg] | ![Yes][yes][^svg] |
45
- | ICO | ![Yes][yes] | ![Yes][yes] |
46
- | BMP | ![Yes][yes] | ![Yes][yes] |
47
- | TIFF | ![Yes][yes] | ![No][no] |
48
- | ICNS | ![Yes][yes] | ![No][no] |
49
- | PSD | ![Yes][yes][^psd] | ![No][no] |
35
+ | Format | iOS | Android |
36
+ | ------ | :---------------: | :---------: |
37
+ | JPEG | ![Yes][yes] | ![Yes][yes] |
38
+ | PNG | ![Yes][yes] | ![Yes][yes] |
39
+ | APNG | ![Yes][yes] | ![Yes][yes] |
40
+ | GIF | ![Yes][yes] | ![Yes][yes] |
41
+ | WebP | ![Yes][yes] | ![Yes][yes] |
42
+ | AVIF | ![Yes][yes] | ![Yes][yes] |
43
+ | HEIC | ![Yes][yes] | ![Yes][yes] |
44
+ | SVG | ![Yes][yes] | ![Yes][yes] |
45
+ | ICO | ![Yes][yes] | ![Yes][yes] |
46
+ | BMP | ![Yes][yes] | ![Yes][yes] |
47
+ | TIFF | ![Yes][yes] | ![No][no] |
48
+ | ICNS | ![Yes][yes] | ![No][no] |
49
+ | PSD | ![Yes][yes][^psd] | ![No][no] |
50
50
 
51
51
  See [Formats](docs/formats.md) for the versions of iOS and Android each format needs, and what decodes it.
52
52
 
53
53
  [yes]: docs/assets/yes.svg
54
54
  [no]: docs/assets/no.svg
55
55
 
56
- [^svg]: With an SVG library in the app: see [SVG images](docs/formats.md#svg-images).
57
-
58
56
  [^psd]: Its composite image (the layers as Photoshop saved them flattened).
59
57
 
60
58
  ## Installation
61
59
 
62
- 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 a future major version.
60
+ Works with React Native 0.65 and later (iOS 13 and later, Android `minSdkVersion` 21 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 a future major version.
63
61
 
64
62
  ```bash
65
63
  npm install react-native-fast-image
@@ -138,7 +136,7 @@ When `source` changes, the image that's showing stays until the new one has load
138
136
 
139
137
  Remote url to load the image from. e.g. `'https://example.com/image.jpg'`.
140
138
 
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)).
139
+ 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 and tvOS (`ph://`, see [Photo library images](#photo-library-images-ios-and-tvos)) and SVG images (see [SVG images](docs/formats.md#svg-images)).
142
140
 
143
141
  ---
144
142
 
@@ -435,7 +433,9 @@ If supplied, changes the color of all the non-transparent pixels to the given co
435
433
 
436
434
  **Type:** `ReactNode`
437
435
 
438
- Render children within the image. In the next major version, `FastImage` won't render children: use `FastImageBackground`.
436
+ **Deprecated:** In the next major version, `FastImage` won't render children: use `FastImageBackground`.
437
+
438
+ Render children within the image.
439
439
 
440
440
  <!-- api:props end -->
441
441
 
@@ -443,7 +443,7 @@ Render children within the image. In the next major version, `FastImage` won't r
443
443
 
444
444
  An image with content on top of it: a view that the image fills, with the children on top.
445
445
 
446
- 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.
446
+ 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 ([#1220](https://github.com/DylanVann/react-native-fast-image/pull/1220)), which can't hold them. `FastImageBackground` works the same in both.
447
447
 
448
448
  ```jsx
449
449
  import { FastImageBackground } from 'react-native-fast-image'
@@ -804,9 +804,9 @@ If your app has its own `AppGlideModule` (see [using FastImage with an AppGlideM
804
804
 
805
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.
806
806
 
807
- ## Photo library images (iOS)
807
+ ## Photo library images (iOS and tvOS)
808
808
 
809
- A photo library url (`ph://<localIdentifier>`, as camera roll libraries give) loads with SDWebImagePhotosPlugin, which FastImage includes on iOS.
809
+ A photo library url (`ph://<localIdentifier>`, as camera roll libraries give) loads with SDWebImagePhotosPlugin, which FastImage includes.
810
810
 
811
811
  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. `assets-library://` urls aren't supported. On Android, photo pickers give `content://` urls, which load as they are.
812
812
 
@@ -9,7 +9,7 @@ Pod::Spec.new do |s|
9
9
  s.authors = { "Dylan Vann" => "dylan@dylanvann.com" }
10
10
  s.homepage = "https://github.com/DylanVann/react-native-fast-image#readme"
11
11
  s.license = "MIT"
12
- s.platforms = { :ios => "8.0", :tvos => "9.0" }
12
+ s.platforms = { :ios => "13.0", :tvos => "13.0" }
13
13
  s.framework = 'UIKit'
14
14
  s.requires_arc = true
15
15
  s.source = { :git => "https://github.com/DylanVann/react-native-fast-image.git", :tag => "v#{s.version}" }
@@ -21,7 +21,9 @@ Pod::Spec.new do |s|
21
21
  # manifest Apple requires.
22
22
  s.dependency 'SDWebImage', '>= 5.11.1', '< 6.0'
23
23
  s.dependency 'SDWebImageWebPCoder', '>= 0.8.4', '< 1.0'
24
- # Photo library images (ph://). iOS only: it needs tvOS 10, and FastImage
25
- # finds it at runtime, so a tvOS app can still add it.
26
- s.ios.dependency 'SDWebImagePhotosPlugin', '>= 1.2.0', '< 2.0'
24
+ # SVG images. Every 1.x needs iOS 13 (Apple's SVG renderer). From 1.7, as
25
+ # expo-image's, so apps with it share one copy.
26
+ s.dependency 'SDWebImageSVGCoder', '>= 1.7.0', '< 2.0'
27
+ # Photo library images (ph://).
28
+ s.dependency 'SDWebImagePhotosPlugin', '>= 1.2.0', '< 2.0'
27
29
  end
@@ -32,15 +32,16 @@ def appHasExpoImage = {
32
32
  def excludeAppGlideModule = safeExtGet('excludeAppGlideModule', appHasExpoImage())
33
33
 
34
34
  // When changing the default, revisit the Glide rules in consumer-rules.pro.
35
- // 4.15+ animates animated WebP (Android 9+) and AVIF (Android 12+).
35
+ // 4.15+ animates animated WebP (Android 9+) and AVIF (Android 12+), which
36
+ // FastImageAnimated builds on. A version that isn't numbered (e.g. "4.+") is
37
+ // taken to be recent.
36
38
  def glideVersion = safeExtGet('glideVersion', '4.16.0')
37
- // Whether it's 4.15 or later, which FastImageAnimated (src/glide-4.15) builds
38
- // on; an older one gets a stand-in (src/glide-older). A version that isn't
39
- // numbered (e.g. "4.+") is taken to be recent.
40
39
  def glideVersionParts = glideVersion.toString() =~ /^(\d+)\.(\d+)/
41
- def glideHasAnimatedDecoder = !glideVersionParts.find() ||
42
- glideVersionParts.group(1).toInteger() > 4 ||
43
- (glideVersionParts.group(1).toInteger() == 4 && glideVersionParts.group(2).toInteger() >= 15)
40
+ if (glideVersionParts.find() &&
41
+ (glideVersionParts.group(1).toInteger() < 4 ||
42
+ (glideVersionParts.group(1).toInteger() == 4 && glideVersionParts.group(2).toInteger() < 15))) {
43
+ throw new GradleException("react-native-fast-image needs Glide 4.15 or later, but glideVersion is ${glideVersion}.")
44
+ }
44
45
 
45
46
  // Newer versions of the Android Gradle plugin (7.3+) take the namespace from
46
47
  // build.gradle and warn about the package attribute in AndroidManifest.xml;
@@ -54,7 +55,7 @@ android {
54
55
  compileSdkVersion safeExtGet('compileSdkVersion', 28)
55
56
  buildToolsVersion safeExtGet('buildToolsVersion', '28.0.3')
56
57
  defaultConfig {
57
- minSdkVersion safeExtGet('minSdkVersion', 16)
58
+ minSdkVersion safeExtGet('minSdkVersion', 21)
58
59
  targetSdkVersion safeExtGet('targetSdkVersion', 28)
59
60
  versionCode 1
60
61
  versionName "1.0"
@@ -67,12 +68,8 @@ android {
67
68
  manifest.srcFile "src/main/AndroidManifestNew.xml"
68
69
  }
69
70
  java {
70
- srcDir glideHasAnimatedDecoder ? "src/glide-4.15/java" : "src/glide-older/java"
71
71
  if (excludeAppGlideModule) {
72
- srcDir "src"
73
72
  exclude "**/FastImageGlideModule.java"
74
- // Only the one chosen above.
75
- exclude "glide-*/**"
76
73
  }
77
74
  }
78
75
  }
@@ -104,16 +101,11 @@ dependencies {
104
101
  implementation "com.github.bumptech.glide:annotations:${glideVersion}"
105
102
  implementation "com.github.bumptech.glide:okhttp3-integration:${glideVersion}"
106
103
  annotationProcessor "com.github.bumptech.glide:compiler:${glideVersion}"
107
- // SVG images, when the app has AndroidSVG (either package): not shipped,
108
- // so there's never a second copy of it (see FastImageSvg).
109
- compileOnly 'com.caverock:androidsvg-aar:1.4'
110
- // Animated PNGs, with APNG4Android (see FastImageApng), which needs
111
- // minSdkVersion 21: shipped when the app's is 21 or later. With an older
112
- // one, FastImage only compiles against it, and APNGs show their first
113
- // frame.
114
- if (safeExtGet('minSdkVersion', 16).toString().toInteger() >= 21) {
115
- implementation 'com.github.penfeizhou.android.animation:apng:3.0.5'
116
- } else {
117
- compileOnly 'com.github.penfeizhou.android.animation:apng:3.0.5'
118
- }
104
+ // SVG images (see FastImageSvg). The package expo-image uses too, so
105
+ // apps with it get one copy. An app with the other package
106
+ // (com.caverock:androidsvg, the same classes) excludes this one (see
107
+ // docs/troubleshooting.md); FastImageSvg works with either.
108
+ implementation 'com.caverock:androidsvg-aar:1.4'
109
+ // Animated PNGs (see FastImageApng).
110
+ implementation 'com.github.penfeizhou.android.animation:apng:3.0.5'
119
111
  }
@@ -34,19 +34,8 @@
34
34
  public *;
35
35
  }
36
36
 
37
- # React Native's event dispatcher, which FastImage looks up by name
38
- # (FastImageEvents), so it still compiles against React Native 0.60-0.62,
39
- # which don't have it. R8 would rename it, and on the New Architecture every
40
- # event (onLoad, onError, ...) would then be dropped.
41
- -keep class com.facebook.react.uimanager.UIManagerHelper {
42
- public static *** getEventDispatcherForReactTag(com.facebook.react.bridge.ReactContext, int);
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.
37
+ # AndroidSVG, which FastImage includes, but an app with its other package
38
+ # (com.caverock:androidsvg) leaves out (docs/troubleshooting.md), and may not
39
+ # have at all: then SVG images fail with a message (FastImageSvg) instead of
40
+ # the app's R8 build failing on the missing classes.
48
41
  -dontwarn com.caverock.androidsvg.**
49
-
50
- # APNG4Android, which FastImage uses for animated PNGs (FastImageApngRenderer),
51
- # and doesn't ship to apps with a minSdkVersion below 21 (build.gradle).
52
- -dontwarn com.github.penfeizhou.animation.**
@@ -1,7 +1,7 @@
1
1
  package com.dylanvann.fastimage;
2
2
 
3
3
  // An animated image that applies FastImage's loop and paused itself (an APNG,
4
- // FastImageApngRenderer). The view's target starts it when it's shown.
4
+ // FastImageApng). The view's target starts it when it's shown.
5
5
  interface FastImageAnimatable {
6
6
  // loopCount as FastImageViewWithUrl has it: -1, as many times as the file
7
7
  // says; 0, forever; n, n times. restart: play it again from its first
@@ -49,9 +49,6 @@ import java.util.concurrent.Executors;
49
49
  // Not animated (dontAnimate: blurRadius, resizeMode repeat), they're decoded
50
50
  // as their first frame, a bitmap, as GIFs are, and as Glide did before 4.15.
51
51
  // Glide's own decoder for them doesn't check that option.
52
- //
53
- // Built only with Glide 4.15 or later, which has the decoder this wraps; with
54
- // an older Glide, src/glide-older's stand-in does nothing (build.gradle).
55
52
  final class FastImageAnimated {
56
53
  private FastImageAnimated() {}
57
54
 
@@ -1,7 +1,7 @@
1
1
  package com.dylanvann.fastimage;
2
2
 
3
+ import android.graphics.Rect;
3
4
  import android.graphics.drawable.Drawable;
4
- import android.os.Build;
5
5
 
6
6
  import androidx.annotation.NonNull;
7
7
  import androidx.annotation.Nullable;
@@ -12,6 +12,10 @@ import com.bumptech.glide.load.ResourceDecoder;
12
12
  import com.bumptech.glide.load.engine.Resource;
13
13
  import com.bumptech.glide.load.resource.gif.GifOptions;
14
14
  import com.bumptech.glide.util.ByteBufferUtil;
15
+ import com.github.penfeizhou.animation.apng.APNGDrawable;
16
+ import com.github.penfeizhou.animation.apng.decode.APNGDecoder;
17
+ import com.github.penfeizhou.animation.loader.ByteBufferLoader;
18
+ import com.github.penfeizhou.animation.loader.Loader;
15
19
 
16
20
  import java.io.IOException;
17
21
  import java.io.InputStream;
@@ -19,41 +23,23 @@ import java.nio.ByteBuffer;
19
23
  import java.nio.charset.StandardCharsets;
20
24
 
21
25
  // Animated PNGs (APNG), animated with APNG4Android
22
- // (com.github.penfeizhou.android.animation:apng), which FastImage ships when
23
- // the app's minSdkVersion is 21 or later, as APNG4Android needs (build.gradle).
24
- // Without it, an APNG shows its first frame, as Android shows it. Android and
25
- // Glide don't animate APNG themselves.
26
+ // (com.github.penfeizhou.android.animation:apng), which FastImage includes.
27
+ // Android and Glide don't animate APNG themselves.
26
28
  //
27
29
  // The decoders come before Glide's own, and only take PNGs with an animation
28
30
  // control chunk (acTL), when the request animates (not with dontAnimate:
29
31
  // blurRadius, resizeMode repeat, which get the first frame from Glide's
30
- // decoder). This class doesn't use APNG4Android itself
31
- // (FastImageApngRenderer does), so it loads without it.
32
+ // decoder).
32
33
  final class FastImageApng {
33
34
  // As much of the start of the file as is read to find acTL, which comes
34
35
  // before the image data.
35
36
  private static final int HEAD_LENGTH = 4096;
36
37
  private static final byte[] SIGNATURE = {(byte) 0x89, 'P', 'N', 'G', '\r', '\n', 0x1a, '\n'};
37
- @Nullable
38
- private static Boolean available;
39
38
 
40
39
  private FastImageApng() {
41
40
  }
42
41
 
43
- static synchronized boolean available() {
44
- if (available == null) {
45
- try {
46
- // Android 5+, as APNG4Android needs.
47
- available = Build.VERSION.SDK_INT >= Build.VERSION_CODES.LOLLIPOP && FastImageApngRenderer.available();
48
- } catch (Throwable e) {
49
- available = false;
50
- }
51
- }
52
- return available;
53
- }
54
-
55
42
  static void register(@NonNull Registry registry) {
56
- if (!available()) return;
57
43
  registry.prepend(InputStream.class, Drawable.class, new StreamDecoder())
58
44
  .prepend(ByteBuffer.class, Drawable.class, new BufferDecoder());
59
45
  }
@@ -106,7 +92,7 @@ final class FastImageApng {
106
92
  @Override
107
93
  public Resource<Drawable> decode(@NonNull ByteBuffer source, int width, int height, @NonNull Options options)
108
94
  throws IOException {
109
- return FastImageApngRenderer.decode(source.duplicate(), plays(source));
95
+ return decodeApng(source.duplicate(), plays(source));
110
96
  }
111
97
  }
112
98
 
@@ -135,7 +121,136 @@ final class FastImageApng {
135
121
  public Resource<Drawable> decode(@NonNull InputStream source, int width, int height, @NonNull Options options)
136
122
  throws IOException {
137
123
  ByteBuffer data = ByteBufferUtil.fromStream(source);
138
- return FastImageApngRenderer.decode(data, plays(data));
124
+ return decodeApng(data, plays(data));
125
+ }
126
+ }
127
+
128
+ // APNG4Android: Glide keeps the image's data in its memory cache, and each
129
+ // view gets a drawable of its own over it, so views don't share playback:
130
+ // making one only reads the image's header, and it decodes its frames (at a
131
+ // sample size from the size it's drawn at) on APNG4Android's threads as it
132
+ // plays.
133
+ private static Loader loader(ByteBuffer data) {
134
+ return new ByteBufferLoader() {
135
+ @Override
136
+ public ByteBuffer getByteBuffer() {
137
+ ByteBuffer buffer = data.duplicate();
138
+ buffer.position(0);
139
+ return buffer;
140
+ }
141
+ };
142
+ }
143
+
144
+ // Null if APNG4Android can't read it (Glide then decodes it as a PNG).
145
+ @Nullable
146
+ private static Resource<Drawable> decodeApng(@NonNull ByteBuffer data, int plays) {
147
+ // Reads the header, waiting for APNG4Android's thread.
148
+ Rect bounds = new APNGDecoder(loader(data), null).getBounds();
149
+ if (bounds.isEmpty()) return null;
150
+ return new ApngResource(data, plays, bounds);
151
+ }
152
+
153
+ private static final class ApngResource implements Resource<Drawable> {
154
+ private final ByteBuffer data;
155
+ private final int plays;
156
+ private final Rect bounds;
157
+
158
+ ApngResource(ByteBuffer data, int plays, Rect bounds) {
159
+ this.data = data;
160
+ this.plays = plays;
161
+ this.bounds = bounds;
162
+ }
163
+
164
+ @NonNull
165
+ @Override
166
+ public Class<Drawable> getResourceClass() {
167
+ return Drawable.class;
168
+ }
169
+
170
+ @NonNull
171
+ @Override
172
+ public Drawable get() {
173
+ return new ApngDrawable(loader(data), plays);
174
+ }
175
+
176
+ // The data, and a frame at full size (each view's drawable has its
177
+ // own, at the size it's drawn at).
178
+ @Override
179
+ public int getSize() {
180
+ // As a long: a very large image's frame overflows an int.
181
+ long size = data.limit() + (long) bounds.width() * bounds.height() * 4;
182
+ return (int) Math.min(size, Integer.MAX_VALUE);
183
+ }
184
+
185
+ @Override
186
+ public void recycle() {
187
+ }
188
+ }
189
+
190
+ private static final class ApngDrawable extends APNGDrawable implements FastImageAnimatable {
191
+ // How many times the file says it plays (0: forever).
192
+ private final int plays;
193
+ private volatile boolean paused = false;
194
+ // Once a frame has been rendered (on the decoder's thread).
195
+ private volatile boolean rendered = false;
196
+
197
+ ApngDrawable(Loader loader, int plays) {
198
+ super(loader);
199
+ this.plays = plays;
200
+ }
201
+
202
+ @Override
203
+ public void setLoopCount(int loopCount, boolean restart) {
204
+ // Plays (0: forever), as APNG4Android counts them.
205
+ setLoopLimit(loopCount == -1 ? plays : loopCount);
206
+ if (restart) {
207
+ reset();
208
+ start();
209
+ }
210
+ }
211
+
212
+ @Override
213
+ public void setPaused(boolean paused) {
214
+ this.paused = paused;
215
+ // It starts and stops itself as it's shown and hidden, but not
216
+ // while paused.
217
+ setAutoPlay(!paused);
218
+ if (paused) {
219
+ // Before the first frame, onStart pauses once it's rendered:
220
+ // the decoder's start clears a pause, then skips rendering if
221
+ // one came in meanwhile, which left the view blank.
222
+ if (rendered) pause();
223
+ } else {
224
+ resume();
225
+ }
226
+ }
227
+
228
+ // From its first frame. The target starts it right after
229
+ // setVisible(true) has (auto play): APNG4Android's start() stops and
230
+ // starts a running decoder, and a stop made while the decoder is
231
+ // still starting can win over that start, leaving it stopped on its
232
+ // first frame. A running one is reset to its first frame instead.
233
+ @Override
234
+ public void start() {
235
+ if (isRunning()) {
236
+ getFrameSeqDecoder().reset();
237
+ return;
238
+ }
239
+ super.start();
240
+ }
241
+
242
+ @Override
243
+ public void onRender(ByteBuffer byteBuffer) {
244
+ super.onRender(byteBuffer);
245
+ rendered = true;
246
+ }
247
+
248
+ // On its decoder thread, once starting has drawn the first frame:
249
+ // starting clears a pause made before it got there, so pause again.
250
+ @Override
251
+ public void onStart() {
252
+ super.onStart();
253
+ if (paused) getFrameSeqDecoder().pause();
139
254
  }
140
255
  }
141
256
  }
@@ -1,10 +1,7 @@
1
1
  package com.dylanvann.fastimage;
2
2
 
3
- import android.content.Context;
4
- import android.os.Build;
5
3
  import android.text.TextUtils;
6
4
  import android.webkit.CookieManager;
7
- import android.webkit.CookieSyncManager;
8
5
 
9
6
  import androidx.annotation.Nullable;
10
7
 
@@ -21,14 +18,9 @@ import java.util.Map;
21
18
  // without the ReactContext that one needs (up to at least 0.77). Used through
22
19
  // OkHttp's JavaNetCookieJar, which parses the cookies.
23
20
  final class FastImageCookieHandler extends CookieHandler {
24
- private final Context context;
25
21
  @Nullable
26
22
  private CookieManager cookieManager;
27
23
 
28
- FastImageCookieHandler(Context context) {
29
- this.context = context.getApplicationContext();
30
- }
31
-
32
24
  @Override
33
25
  public Map<String, List<String>> get(URI uri, Map<String, List<String>> requestHeaders) {
34
26
  CookieManager cookieManager = cookieManager();
@@ -52,28 +44,15 @@ final class FastImageCookieHandler extends CookieHandler {
52
44
  added = true;
53
45
  }
54
46
  }
55
- if (added) flush(cookieManager);
56
- }
57
-
58
- @SuppressWarnings("deprecation")
59
- private static void flush(CookieManager cookieManager) {
60
- if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.LOLLIPOP) {
61
- cookieManager.flush();
62
- } else {
63
- CookieSyncManager.getInstance().sync();
64
- }
47
+ if (added) cookieManager.flush();
65
48
  }
66
49
 
67
50
  // null while the WebView isn't available (not installed, or being
68
51
  // updated): requests go without cookies then, instead of failing.
69
52
  @Nullable
70
- @SuppressWarnings("deprecation")
71
53
  private synchronized CookieManager cookieManager() {
72
54
  if (cookieManager == null) {
73
55
  try {
74
- if (Build.VERSION.SDK_INT < Build.VERSION_CODES.LOLLIPOP) {
75
- CookieSyncManager.createInstance(context);
76
- }
77
56
  cookieManager = CookieManager.getInstance();
78
57
  } catch (Exception e) {
79
58
  return null;
@@ -2,7 +2,6 @@ package com.dylanvann.fastimage;
2
2
 
3
3
  import com.facebook.react.bridge.WritableMap;
4
4
  import com.facebook.react.uimanager.events.Event;
5
- import com.facebook.react.uimanager.events.RCTEventEmitter;
6
5
 
7
6
  // An event for a FastImage view, sent with the EventDispatcher (see
8
7
  // FastImageEvents) instead of the RCTEventEmitter JS module, which the New
@@ -11,11 +10,8 @@ class FastImageEvent extends Event<FastImageEvent> {
11
10
  private final String name;
12
11
  private final WritableMap data;
13
12
 
14
- // Event(viewTag) works back to React Native 0.60. Without a surface id,
15
- // Fabric finds the view by its tag.
16
- @SuppressWarnings("deprecation")
17
- FastImageEvent(int viewTag, String name, WritableMap data) {
18
- super(viewTag);
13
+ FastImageEvent(int surfaceId, int viewTag, String name, WritableMap data) {
14
+ super(surfaceId, viewTag);
19
15
  this.name = name;
20
16
  this.data = data;
21
17
  }
@@ -31,15 +27,8 @@ class FastImageEvent extends Event<FastImageEvent> {
31
27
  return false;
32
28
  }
33
29
 
34
- // Overrides Event.getEventData on React Native 0.65+ (not on older ones,
35
- // hence no @Override).
30
+ @Override
36
31
  protected WritableMap getEventData() {
37
32
  return data;
38
33
  }
39
-
40
- @Override
41
- @SuppressWarnings("deprecation")
42
- public void dispatch(RCTEventEmitter rctEventEmitter) {
43
- rctEventEmitter.receiveEvent(getViewTag(), name, data);
44
- }
45
34
  }
@@ -2,23 +2,14 @@ package com.dylanvann.fastimage;
2
2
 
3
3
  import android.view.View;
4
4
 
5
- import androidx.annotation.Nullable;
6
-
7
5
  import com.facebook.react.bridge.Arguments;
8
6
  import com.facebook.react.bridge.ReactContext;
9
7
  import com.facebook.react.bridge.WritableMap;
10
- import com.facebook.react.uimanager.UIManagerModule;
8
+ import com.facebook.react.uimanager.UIManagerHelper;
11
9
  import com.facebook.react.uimanager.events.EventDispatcher;
12
10
 
13
- import java.lang.reflect.Method;
14
-
15
11
  // Sends FastImage view events through React Native's EventDispatcher.
16
12
  final class FastImageEvents {
17
- // UIManagerHelper.getEventDispatcherForReactTag (React Native 0.63+, also
18
- // bridgeless). Looked up by reflection so older versions still compile.
19
- @Nullable
20
- private static final Method GET_EVENT_DISPATCHER = findGetEventDispatcher();
21
-
22
13
  private FastImageEvents() {
23
14
  }
24
15
 
@@ -47,32 +38,12 @@ final class FastImageEvents {
47
38
  static void send(View view, String name, WritableMap data) {
48
39
  ReactContext context = FastImageViewManager.getReactContext(view.getContext());
49
40
  if (context == null) return;
50
- EventDispatcher dispatcher = eventDispatcher(context, view.getId());
41
+ // The view's event dispatcher, on either architecture (also
42
+ // bridgeless).
43
+ EventDispatcher dispatcher = UIManagerHelper.getEventDispatcherForReactTag(context, view.getId());
51
44
  if (dispatcher != null) {
52
- dispatcher.dispatchEvent(new FastImageEvent(view.getId(), name, data));
53
- }
54
- }
55
-
56
- @Nullable
57
- private static EventDispatcher eventDispatcher(ReactContext context, int viewTag) {
58
- if (GET_EVENT_DISPATCHER != null) {
59
- try {
60
- return (EventDispatcher) GET_EVENT_DISPATCHER.invoke(null, context, viewTag);
61
- } catch (Exception ignored) {
62
- // Fall back to the UIManagerModule's.
63
- }
64
- }
65
- UIManagerModule uiManager = context.getNativeModule(UIManagerModule.class);
66
- return uiManager == null ? null : uiManager.getEventDispatcher();
67
- }
68
-
69
- @Nullable
70
- private static Method findGetEventDispatcher() {
71
- try {
72
- return Class.forName("com.facebook.react.uimanager.UIManagerHelper")
73
- .getMethod("getEventDispatcherForReactTag", ReactContext.class, int.class);
74
- } catch (Exception e) {
75
- return null;
45
+ dispatcher.dispatchEvent(new FastImageEvent(
46
+ UIManagerHelper.getSurfaceId(view), view.getId(), name, data));
76
47
  }
77
48
  }
78
49
  }
@@ -116,7 +116,7 @@ public class FastImageOkHttpProgressGlideModule extends LibraryGlideModule {
116
116
  // images were loaded without the app's cookies, unlike on iOS. Use the
117
117
  // same cookie store. A cookie jar the app set up itself is kept.
118
118
  if (sharedClient.cookieJar() instanceof CookieJarContainer) {
119
- builder.cookieJar(new JavaNetCookieJar(new FastImageCookieHandler(context)));
119
+ builder.cookieJar(new JavaNetCookieJar(new FastImageCookieHandler()));
120
120
  }
121
121
  OkHttpClient client = builder.build();
122
122
  // `cache: 'web'` skips Glide's caches and relies on HTTP caching, so
@@ -19,11 +19,11 @@ import java.nio.ByteBuffer;
19
19
  import java.nio.charset.StandardCharsets;
20
20
  import java.util.Locale;
21
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.
22
+ // SVG images (static), drawn with AndroidSVG, which FastImage includes
23
+ // (com.caverock:androidsvg-aar). An app with its other package
24
+ // (com.caverock:androidsvg, the same classes) leaves FastImage's out
25
+ // (docs/troubleshooting.md), so it may be missing: then an SVG image fails
26
+ // with a message saying what to do.
27
27
  //
28
28
  // The decoders come after Glide's own, so they're only asked about data
29
29
  // Glide couldn't decode, and only take data that starts like an SVG. This
@@ -31,7 +31,7 @@ import java.util.Locale;
31
31
  // loads without it.
32
32
  final class FastImageSvg {
33
33
  static final String MISSING =
34
- "SVG images need AndroidSVG: add implementation 'com.caverock:androidsvg-aar:1.4' to the app's dependencies";
34
+ "SVG images need AndroidSVG: the app leaves FastImage's out (an exclude of com.caverock:androidsvg-aar), so add its other package (implementation 'com.caverock:androidsvg:1.4') or remove the exclude";
35
35
  private static final int HEAD_LENGTH = 1024;
36
36
  @Nullable
37
37
  private static Boolean available;