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.
- package/README.md +24 -24
- package/RNFastImage.podspec +6 -4
- package/android/build.gradle +16 -24
- package/android/consumer-rules.pro +4 -15
- package/android/src/main/java/com/dylanvann/fastimage/FastImageAnimatable.java +1 -1
- package/android/src/{glide-4.15 → main}/java/com/dylanvann/fastimage/FastImageAnimated.java +0 -3
- package/android/src/main/java/com/dylanvann/fastimage/FastImageApng.java +139 -24
- package/android/src/main/java/com/dylanvann/fastimage/FastImageCookieHandler.java +1 -22
- package/android/src/main/java/com/dylanvann/fastimage/FastImageEvent.java +3 -14
- package/android/src/main/java/com/dylanvann/fastimage/FastImageEvents.java +6 -35
- package/android/src/main/java/com/dylanvann/fastimage/FastImageOkHttpProgressGlideModule.java +1 -1
- package/android/src/main/java/com/dylanvann/fastimage/FastImageSvg.java +6 -6
- package/android/src/main/java/com/dylanvann/fastimage/FastImageViewManager.java +1 -7
- package/android/src/main/java/com/dylanvann/fastimage/FastImageViewWithUrl.java +1 -1
- package/dist/index.d.ts +10 -9
- package/ios/FastImage/FFFastImageSource.h +1 -2
- package/ios/FastImage/FFFastImageSource.m +28 -74
- package/package.json +2 -6
- package/android/src/glide-older/java/com/dylanvann/fastimage/FastImageAnimated.java +0 -36
- package/android/src/main/java/com/dylanvann/fastimage/FastImageApngRenderer.java +0 -152
- package/ios/FastImage.xcodeproj/project.pbxproj +0 -479
- 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 |
|
|
36
|
-
| ------ | :---------------: |
|
|
37
|
-
| JPEG | ![Yes][yes] |
|
|
38
|
-
| PNG | ![Yes][yes] |
|
|
39
|
-
| APNG | ![Yes][yes] |
|
|
40
|
-
| GIF | ![Yes][yes] |
|
|
41
|
-
| WebP | ![Yes][yes] |
|
|
42
|
-
| AVIF | ![Yes][yes] |
|
|
43
|
-
| HEIC | ![Yes][yes] |
|
|
44
|
-
| SVG |
|
|
45
|
-
| ICO | ![Yes][yes] |
|
|
46
|
-
| BMP | ![Yes][yes] |
|
|
47
|
-
| TIFF | ![Yes][yes] |
|
|
48
|
-
| ICNS | ![Yes][yes] |
|
|
49
|
-
| PSD | ![Yes][yes][^psd] |
|
|
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
|
+
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
|
-
|
|
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 ([#
|
|
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
|
|
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
|
|
package/RNFastImage.podspec
CHANGED
|
@@ -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 => "
|
|
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
|
-
#
|
|
25
|
-
#
|
|
26
|
-
s.
|
|
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
|
package/android/build.gradle
CHANGED
|
@@ -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
|
-
|
|
42
|
-
glideVersionParts.group(1).toInteger()
|
|
43
|
-
|
|
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',
|
|
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
|
|
108
|
-
//
|
|
109
|
-
|
|
110
|
-
//
|
|
111
|
-
|
|
112
|
-
//
|
|
113
|
-
|
|
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
|
-
#
|
|
38
|
-
# (
|
|
39
|
-
#
|
|
40
|
-
#
|
|
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
|
-
//
|
|
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
|
|
23
|
-
//
|
|
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).
|
|
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
|
|
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
|
|
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(
|
|
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
|
-
|
|
15
|
-
|
|
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
|
-
|
|
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.
|
|
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
|
-
|
|
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(
|
|
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
|
}
|
package/android/src/main/java/com/dylanvann/fastimage/FastImageOkHttpProgressGlideModule.java
CHANGED
|
@@ -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(
|
|
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
|
|
23
|
-
//
|
|
24
|
-
//
|
|
25
|
-
//
|
|
26
|
-
//
|
|
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:
|
|
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;
|