`
237 |
238 | Clear all images from disk cache.
239 |
240 | ## Troubleshooting
241 |
242 | If you have any problems using this library try the steps in [troubleshooting](docs/troubleshooting.md) and see if they fix it.
243 |
244 | ## Development
245 |
246 | [Follow these instructions to get the example app running.](docs/development.md)
247 |
248 | ## Supported React Native Versions
249 |
250 | This project only aims to support the latest version of React Native.\
251 | This simplifies the development and the testing of the project.
252 |
253 | If you require new features or bug fixes for older versions you can fork this project.
254 |
255 | ## Credits
256 |
257 | The idea for this modules came from
258 | [vovkasm's](https://github.com/vovkasm)
259 | [react-native-web-image](https://github.com/vovkasm/react-native-web-image)
260 | package.
261 | It also uses Glide and SDWebImage, but didn't have some features I needed (priority, headers).
262 |
263 | Thanks to [@mobinni](https://github.com/mobinni) for helping with the conceptualization
264 |
265 | ## Licenses
266 |
267 | - FastImage - MIT © [DylanVann](https://github.com/DylanVann)
268 | - SDWebImage - `MIT`
269 | - Glide - BSD, part MIT and Apache 2.0. See the [LICENSE](https://github.com/bumptech/glide/blob/master/license) file for details.
270 |
271 | [build-badge]: https://github.com/dylanvann/react-native-fast-image/workflows/CI/badge.svg
272 | [build]: https://github.com/DylanVann/react-native-fast-image/actions?query=workflow%3ACI
273 | [coverage-badge]: https://img.shields.io/codecov/c/github/dylanvann/react-native-fast-image.svg
274 | [coverage]: https://codecov.io/github/dylanvann/react-native-fast-image
275 | [downloads-badge]: https://img.shields.io/npm/dm/react-native-fast-image.svg
276 | [npmtrends]: http://www.npmtrends.com/react-native-fast-image
277 | [package]: https://www.npmjs.com/package/react-native-fast-image
278 | [version-badge]: https://img.shields.io/npm/v/react-native-fast-image.svg
279 | [twitter]: https://twitter.com/home?status=Check%20out%20react-native-fast-image%20by%20%40atomarranger%20https%3A//github.com/DylanVann/react-native-fast-image
280 | [twitter-badge]: https://img.shields.io/twitter/url/https/github.com/DylanVann/react-native-fast-image.svg?style=social
281 | [github-watch-badge]: https://img.shields.io/github/watchers/dylanvann/react-native-fast-image.svg?style=social
282 | [github-watch]: https://github.com/dylanvann/react-native-fast-image/watchers
283 | [github-star-badge]: https://img.shields.io/github/stars/dylanvann/react-native-fast-image.svg?style=social
284 | [github-star]: https://github.com/dylanvann/react-native-fast-image/stargazers
285 |
--------------------------------------------------------------------------------
/RNFastImage.podspec:
--------------------------------------------------------------------------------
1 | require 'json'
2 |
3 | Pod::Spec.new do |s|
4 | package = JSON.parse(File.read(File.join(__dir__, 'package.json')))
5 |
6 | s.name = "RNFastImage"
7 | s.version = package['version']
8 | s.summary = package['description']
9 | s.authors = { "Dylan Vann" => "dylan@dylanvann.com" }
10 | s.homepage = "https://github.com/DylanVann/react-native-fast-image#readme"
11 | s.license = "MIT"
12 | s.platforms = { :ios => "8.0", :tvos => "9.0" }
13 | s.framework = 'UIKit'
14 | s.requires_arc = true
15 | s.source = { :git => "https://github.com/DylanVann/react-native-fast-image.git", :tag => "v#{s.version}" }
16 | s.source_files = "ios/**/*.{h,m}"
17 |
18 | s.dependency 'React-Core'
19 | s.dependency 'SDWebImage', '~> 5.11.1'
20 | s.dependency 'SDWebImageWebPCoder', '~> 0.8.4'
21 | end
22 |
--------------------------------------------------------------------------------
/ReactNativeFastImageExample/.buckconfig:
--------------------------------------------------------------------------------
1 |
2 | [android]
3 | target = Google Inc.:Google APIs:23
4 |
5 | [maven_repositories]
6 | central = https://repo1.maven.org/maven2
7 |
--------------------------------------------------------------------------------
/ReactNativeFastImageExample/.editorconfig:
--------------------------------------------------------------------------------
1 | # Windows files
2 | [*.bat]
3 | end_of_line = crlf
4 |
--------------------------------------------------------------------------------
/ReactNativeFastImageExample/.eslintrc.js:
--------------------------------------------------------------------------------
1 | module.exports = {
2 | root: true,
3 | extends: '@react-native-community',
4 | rules: {
5 | semi: ['error', 'never'],
6 | 'react-native/no-inline-styles': 'off',
7 | },
8 | }
9 |
--------------------------------------------------------------------------------
/ReactNativeFastImageExample/.gitattributes:
--------------------------------------------------------------------------------
1 | # Windows files should use crlf line endings
2 | # https://help.github.com/articles/dealing-with-line-endings/
3 | *.bat text eol=crlf
4 |
--------------------------------------------------------------------------------
/ReactNativeFastImageExample/.gitignore:
--------------------------------------------------------------------------------
1 | # OSX
2 | #
3 | .DS_Store
4 |
5 | # Xcode
6 | #
7 | build/
8 | *.pbxuser
9 | !default.pbxuser
10 | *.mode1v3
11 | !default.mode1v3
12 | *.mode2v3
13 | !default.mode2v3
14 | *.perspectivev3
15 | !default.perspectivev3
16 | xcuserdata
17 | *.xccheckout
18 | *.moved-aside
19 | DerivedData
20 | *.hmap
21 | *.ipa
22 | *.xcuserstate
23 |
24 | # Android/IntelliJ
25 | #
26 | build/
27 | .idea
28 | .gradle
29 | local.properties
30 | *.iml
31 | *.hprof
32 |
33 | # node.js
34 | #
35 | node_modules/
36 | npm-debug.log
37 | yarn-error.log
38 |
39 | # BUCK
40 | buck-out/
41 | \.buckd/
42 | *.keystore
43 | !debug.keystore
44 |
45 | # fastlane
46 | #
47 | # It is recommended to not store the screenshots in the git repo. Instead, use fastlane to re-generate the
48 | # screenshots whenever they are needed.
49 | # For more information about the recommended setup visit:
50 | # https://docs.fastlane.tools/best-practices/source-control/
51 |
52 | */fastlane/report.xml
53 | */fastlane/Preview.html
54 | */fastlane/screenshots
55 |
56 | # Bundle artifact
57 | *.jsbundle
58 |
59 | # CocoaPods
60 | /ios/Pods/
61 |
--------------------------------------------------------------------------------
/ReactNativeFastImageExample/.prettierrc.js:
--------------------------------------------------------------------------------
1 | module.exports = {
2 | semi: false,
3 | singleQuote: true,
4 | trailingComma: 'all',
5 | tabWidth: 4,
6 | }
7 |
--------------------------------------------------------------------------------
/ReactNativeFastImageExample/.watchmanconfig:
--------------------------------------------------------------------------------
1 | {}
2 |
--------------------------------------------------------------------------------
/ReactNativeFastImageExample/__tests__/App-test.tsx:
--------------------------------------------------------------------------------
1 | /**
2 | * @format
3 | */
4 |
5 | import 'react-native'
6 | import React from 'react'
7 | import App from '../src'
8 |
9 | // Note: test renderer must be required after react-native.
10 | import renderer from 'react-test-renderer'
11 |
12 | it('renders correctly', () => {
13 | renderer.create()
14 | })
15 |
--------------------------------------------------------------------------------
/ReactNativeFastImageExample/android/app/_BUCK:
--------------------------------------------------------------------------------
1 | # To learn about Buck see [Docs](https://buckbuild.com/).
2 | # To run your application with Buck:
3 | # - install Buck
4 | # - `npm start` - to start the packager
5 | # - `cd android`
6 | # - `keytool -genkey -v -keystore keystores/debug.keystore -storepass android -alias androiddebugkey -keypass android -dname "CN=Android Debug,O=Android,C=US"`
7 | # - `./gradlew :app:copyDownloadableDepsToLibs` - make all Gradle compile dependencies available to Buck
8 | # - `buck install -r android/app` - compile, install and run application
9 | #
10 |
11 | load(":build_defs.bzl", "create_aar_targets", "create_jar_targets")
12 |
13 | lib_deps = []
14 |
15 | create_aar_targets(glob(["libs/*.aar"]))
16 |
17 | create_jar_targets(glob(["libs/*.jar"]))
18 |
19 | android_library(
20 | name = "all-libs",
21 | exported_deps = lib_deps,
22 | )
23 |
24 | android_library(
25 | name = "app-code",
26 | srcs = glob([
27 | "src/main/java/**/*.java",
28 | ]),
29 | deps = [
30 | ":all-libs",
31 | ":build_config",
32 | ":res",
33 | ],
34 | )
35 |
36 | android_build_config(
37 | name = "build_config",
38 | package = "com.reactnativefastimageexample",
39 | )
40 |
41 | android_resource(
42 | name = "res",
43 | package = "com.reactnativefastimageexample",
44 | res = "src/main/res",
45 | )
46 |
47 | android_binary(
48 | name = "app",
49 | keystore = "//android/keystores:debug",
50 | manifest = "src/main/AndroidManifest.xml",
51 | package_type = "debug",
52 | deps = [
53 | ":app-code",
54 | ],
55 | )
56 |
--------------------------------------------------------------------------------
/ReactNativeFastImageExample/android/app/build.gradle:
--------------------------------------------------------------------------------
1 | apply plugin: "com.android.application"
2 |
3 | import com.android.build.OutputFile
4 |
5 | /**
6 | * The react.gradle file registers a task for each build variant (e.g. bundleDebugJsAndAssets
7 | * and bundleReleaseJsAndAssets).
8 | * These basically call `react-native bundle` with the correct arguments during the Android build
9 | * cycle. By default, bundleDebugJsAndAssets is skipped, as in debug/dev mode we prefer to load the
10 | * bundle directly from the development server. Below you can see all the possible configurations
11 | * and their defaults. If you decide to add a configuration block, make sure to add it before the
12 | * `apply from: "../../node_modules/react-native/react.gradle"` line.
13 | *
14 | * project.ext.react = [
15 | * // the name of the generated asset file containing your JS bundle
16 | * bundleAssetName: "index.android.bundle",
17 | *
18 | * // the entry file for bundle generation. If none specified and
19 | * // "index.android.js" exists, it will be used. Otherwise "index.js" is
20 | * // default. Can be overridden with ENTRY_FILE environment variable.
21 | * entryFile: "index.android.js",
22 | *
23 | * // https://reactnative.dev/docs/performance#enable-the-ram-format
24 | * bundleCommand: "ram-bundle",
25 | *
26 | * // whether to bundle JS and assets in debug mode
27 | * bundleInDebug: false,
28 | *
29 | * // whether to bundle JS and assets in release mode
30 | * bundleInRelease: true,
31 | *
32 | * // whether to bundle JS and assets in another build variant (if configured).
33 | * // See http://tools.android.com/tech-docs/new-build-system/user-guide#TOC-Build-Variants
34 | * // The configuration property can be in the following formats
35 | * // 'bundleIn${productFlavor}${buildType}'
36 | * // 'bundleIn${buildType}'
37 | * // bundleInFreeDebug: true,
38 | * // bundleInPaidRelease: true,
39 | * // bundleInBeta: true,
40 | *
41 | * // whether to disable dev mode in custom build variants (by default only disabled in release)
42 | * // for example: to disable dev mode in the staging build type (if configured)
43 | * devDisabledInStaging: true,
44 | * // The configuration property can be in the following formats
45 | * // 'devDisabledIn${productFlavor}${buildType}'
46 | * // 'devDisabledIn${buildType}'
47 | *
48 | * // the root of your project, i.e. where "package.json" lives
49 | * root: "../../",
50 | *
51 | * // where to put the JS bundle asset in debug mode
52 | * jsBundleDirDebug: "$buildDir/intermediates/assets/debug",
53 | *
54 | * // where to put the JS bundle asset in release mode
55 | * jsBundleDirRelease: "$buildDir/intermediates/assets/release",
56 | *
57 | * // where to put drawable resources / React Native assets, e.g. the ones you use via
58 | * // require('./image.png')), in debug mode
59 | * resourcesDirDebug: "$buildDir/intermediates/res/merged/debug",
60 | *
61 | * // where to put drawable resources / React Native assets, e.g. the ones you use via
62 | * // require('./image.png')), in release mode
63 | * resourcesDirRelease: "$buildDir/intermediates/res/merged/release",
64 | *
65 | * // by default the gradle tasks are skipped if none of the JS files or assets change; this means
66 | * // that we don't look at files in android/ or ios/ to determine whether the tasks are up to
67 | * // date; if you have any other folders that you want to ignore for performance reasons (gradle
68 | * // indexes the entire tree), add them here. Alternatively, if you have JS files in android/
69 | * // for example, you might want to remove it from here.
70 | * inputExcludes: ["android/**", "ios/**"],
71 | *
72 | * // override which node gets called and with what additional arguments
73 | * nodeExecutableAndArgs: ["node"],
74 | *
75 | * // supply additional arguments to the packager
76 | * extraPackagerArgs: []
77 | * ]
78 | */
79 |
80 | project.ext.react = [
81 | enableHermes: false, // clean and rebuild if changing
82 | ]
83 |
84 | apply from: "../../node_modules/react-native/react.gradle"
85 |
86 | /**
87 | * Set this to true to create two separate APKs instead of one:
88 | * - An APK that only works on ARM devices
89 | * - An APK that only works on x86 devices
90 | * The advantage is the size of the APK is reduced by about 4MB.
91 | * Upload all the APKs to the Play Store and people will download
92 | * the correct one based on the CPU architecture of their device.
93 | */
94 | def enableSeparateBuildPerCPUArchitecture = false
95 |
96 | /**
97 | * Run Proguard to shrink the Java bytecode in release builds.
98 | */
99 | def enableProguardInReleaseBuilds = false
100 |
101 | /**
102 | * The preferred build flavor of JavaScriptCore.
103 | *
104 | * For example, to use the international variant, you can use:
105 | * `def jscFlavor = 'org.webkit:android-jsc-intl:+'`
106 | *
107 | * The international variant includes ICU i18n library and necessary data
108 | * allowing to use e.g. `Date.toLocaleString` and `String.localeCompare` that
109 | * give correct results when using with locales other than en-US. Note that
110 | * this variant is about 6MiB larger per architecture than default.
111 | */
112 | def jscFlavor = 'org.webkit:android-jsc:+'
113 |
114 | /**
115 | * Whether to enable the Hermes VM.
116 | *
117 | * This should be set on project.ext.react and mirrored here. If it is not set
118 | * on project.ext.react, JavaScript will not be compiled to Hermes Bytecode
119 | * and the benefits of using Hermes will therefore be sharply reduced.
120 | */
121 | def enableHermes = project.ext.react.get("enableHermes", false);
122 |
123 | android {
124 | ndkVersion rootProject.ext.ndkVersion
125 |
126 | compileSdkVersion rootProject.ext.compileSdkVersion
127 |
128 | defaultConfig {
129 | applicationId "com.reactnativefastimageexample"
130 | minSdkVersion rootProject.ext.minSdkVersion
131 | targetSdkVersion rootProject.ext.targetSdkVersion
132 | versionCode 1
133 | versionName "1.0"
134 | }
135 | splits {
136 | abi {
137 | reset()
138 | enable enableSeparateBuildPerCPUArchitecture
139 | universalApk false // If true, also generate a universal APK
140 | include "armeabi-v7a", "x86", "arm64-v8a", "x86_64"
141 | }
142 | }
143 | signingConfigs {
144 | debug {
145 | storeFile file('debug.keystore')
146 | storePassword 'android'
147 | keyAlias 'androiddebugkey'
148 | keyPassword 'android'
149 | }
150 | }
151 | buildTypes {
152 | debug {
153 | signingConfig signingConfigs.debug
154 | }
155 | release {
156 | // Caution! In production, you need to generate your own keystore file.
157 | // see https://reactnative.dev/docs/signed-apk-android.
158 | signingConfig signingConfigs.debug
159 | minifyEnabled enableProguardInReleaseBuilds
160 | proguardFiles getDefaultProguardFile("proguard-android.txt"), "proguard-rules.pro"
161 | }
162 | }
163 |
164 | // applicationVariants are e.g. debug, release
165 | applicationVariants.all { variant ->
166 | variant.outputs.each { output ->
167 | // For each separate APK per architecture, set a unique version code as described here:
168 | // https://developer.android.com/studio/build/configure-apk-splits.html
169 | // Example: versionCode 1 will generate 1001 for armeabi-v7a, 1002 for x86, etc.
170 | def versionCodes = ["armeabi-v7a": 1, "x86": 2, "arm64-v8a": 3, "x86_64": 4]
171 | def abi = output.getFilter(OutputFile.ABI)
172 | if (abi != null) { // null for the universal-debug, universal-release variants
173 | output.versionCodeOverride =
174 | defaultConfig.versionCode * 1000 + versionCodes.get(abi)
175 | }
176 |
177 | }
178 | }
179 | }
180 |
181 | dependencies {
182 | implementation fileTree(dir: "libs", include: ["*.jar"])
183 | //noinspection GradleDynamicVersion
184 | implementation "com.facebook.react:react-native:+" // From node_modules
185 |
186 | implementation "androidx.swiperefreshlayout:swiperefreshlayout:1.0.0"
187 |
188 | debugImplementation("com.facebook.flipper:flipper:${FLIPPER_VERSION}") {
189 | exclude group:'com.facebook.fbjni'
190 | }
191 |
192 | debugImplementation("com.facebook.flipper:flipper-network-plugin:${FLIPPER_VERSION}") {
193 | exclude group:'com.facebook.flipper'
194 | exclude group:'com.squareup.okhttp3', module:'okhttp'
195 | }
196 |
197 | debugImplementation("com.facebook.flipper:flipper-fresco-plugin:${FLIPPER_VERSION}") {
198 | exclude group:'com.facebook.flipper'
199 | }
200 |
201 | if (enableHermes) {
202 | def hermesPath = "../../node_modules/hermes-engine/android/";
203 | debugImplementation files(hermesPath + "hermes-debug.aar")
204 | releaseImplementation files(hermesPath + "hermes-release.aar")
205 | } else {
206 | implementation jscFlavor
207 | }
208 | }
209 |
210 | // Run this once to be able to run the application with BUCK
211 | // puts all compile dependencies into folder libs for BUCK to use
212 | task copyDownloadableDepsToLibs(type: Copy) {
213 | from configurations.implementation
214 | into 'libs'
215 | }
216 |
217 | apply from: file("../../node_modules/@react-native-community/cli-platform-android/native_modules.gradle"); applyNativeModulesAppBuildGradle(project)
218 | project.ext.vectoricons = [
219 | iconFontNames: [ 'Ionicons.ttf'] // Name of the font files you want to copy
220 | ]
221 |
222 | apply from: "../../node_modules/react-native-vector-icons/fonts.gradle"
223 |
--------------------------------------------------------------------------------
/ReactNativeFastImageExample/android/app/build_defs.bzl:
--------------------------------------------------------------------------------
1 | """Helper definitions to glob .aar and .jar targets"""
2 |
3 | def create_aar_targets(aarfiles):
4 | for aarfile in aarfiles:
5 | name = "aars__" + aarfile[aarfile.rindex("/") + 1:aarfile.rindex(".aar")]
6 | lib_deps.append(":" + name)
7 | android_prebuilt_aar(
8 | name = name,
9 | aar = aarfile,
10 | )
11 |
12 | def create_jar_targets(jarfiles):
13 | for jarfile in jarfiles:
14 | name = "jars__" + jarfile[jarfile.rindex("/") + 1:jarfile.rindex(".jar")]
15 | lib_deps.append(":" + name)
16 | prebuilt_jar(
17 | name = name,
18 | binary_jar = jarfile,
19 | )
20 |
--------------------------------------------------------------------------------
/ReactNativeFastImageExample/android/app/debug.keystore:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/DylanVann/react-native-fast-image/9ab80fcd570b7f56da66ab20e52c9a35934067c9/ReactNativeFastImageExample/android/app/debug.keystore
--------------------------------------------------------------------------------
/ReactNativeFastImageExample/android/app/proguard-rules.pro:
--------------------------------------------------------------------------------
1 | # Add project specific ProGuard rules here.
2 | # By default, the flags in this file are appended to flags specified
3 | # in /usr/local/Cellar/android-sdk/24.3.3/tools/proguard/proguard-android.txt
4 | # You can edit the include path and order by changing the proguardFiles
5 | # directive in build.gradle.
6 | #
7 | # For more details, see
8 | # http://developer.android.com/guide/developing/tools/proguard.html
9 |
10 | # Add any project specific keep options here:
11 |
--------------------------------------------------------------------------------
/ReactNativeFastImageExample/android/app/src/debug/AndroidManifest.xml:
--------------------------------------------------------------------------------
1 |
2 |
4 |
5 |
6 |
7 |
11 |
12 |
13 |
14 |
--------------------------------------------------------------------------------
/ReactNativeFastImageExample/android/app/src/debug/java/com/reactnativefastimageexample/ReactNativeFlipper.java:
--------------------------------------------------------------------------------
1 | /**
2 | * Copyright (c) Facebook, Inc. and its affiliates.
3 | *
4 | * This source code is licensed under the MIT license found in the LICENSE file in the root
5 | * directory of this source tree.
6 | */
7 | package com.reactnativefastimageexample;
8 |
9 | import android.content.Context;
10 | import com.facebook.flipper.android.AndroidFlipperClient;
11 | import com.facebook.flipper.android.utils.FlipperUtils;
12 | import com.facebook.flipper.core.FlipperClient;
13 | import com.facebook.flipper.plugins.crashreporter.CrashReporterPlugin;
14 | import com.facebook.flipper.plugins.databases.DatabasesFlipperPlugin;
15 | import com.facebook.flipper.plugins.fresco.FrescoFlipperPlugin;
16 | import com.facebook.flipper.plugins.inspector.DescriptorMapping;
17 | import com.facebook.flipper.plugins.inspector.InspectorFlipperPlugin;
18 | import com.facebook.flipper.plugins.network.FlipperOkhttpInterceptor;
19 | import com.facebook.flipper.plugins.network.NetworkFlipperPlugin;
20 | import com.facebook.flipper.plugins.react.ReactFlipperPlugin;
21 | import com.facebook.flipper.plugins.sharedpreferences.SharedPreferencesFlipperPlugin;
22 | import com.facebook.react.ReactInstanceManager;
23 | import com.facebook.react.bridge.ReactContext;
24 | import com.facebook.react.modules.network.NetworkingModule;
25 | import okhttp3.OkHttpClient;
26 |
27 | public class ReactNativeFlipper {
28 | public static void initializeFlipper(Context context, ReactInstanceManager reactInstanceManager) {
29 | if (FlipperUtils.shouldEnableFlipper(context)) {
30 | final FlipperClient client = AndroidFlipperClient.getInstance(context);
31 |
32 | client.addPlugin(new InspectorFlipperPlugin(context, DescriptorMapping.withDefaults()));
33 | client.addPlugin(new ReactFlipperPlugin());
34 | client.addPlugin(new DatabasesFlipperPlugin(context));
35 | client.addPlugin(new SharedPreferencesFlipperPlugin(context));
36 | client.addPlugin(CrashReporterPlugin.getInstance());
37 |
38 | NetworkFlipperPlugin networkFlipperPlugin = new NetworkFlipperPlugin();
39 | NetworkingModule.setCustomClientBuilder(
40 | new NetworkingModule.CustomClientBuilder() {
41 | @Override
42 | public void apply(OkHttpClient.Builder builder) {
43 | builder.addNetworkInterceptor(new FlipperOkhttpInterceptor(networkFlipperPlugin));
44 | }
45 | });
46 | client.addPlugin(networkFlipperPlugin);
47 | client.start();
48 |
49 | // Fresco Plugin needs to ensure that ImagePipelineFactory is initialized
50 | // Hence we run if after all native modules have been initialized
51 | ReactContext reactContext = reactInstanceManager.getCurrentReactContext();
52 | if (reactContext == null) {
53 | reactInstanceManager.addReactInstanceEventListener(
54 | new ReactInstanceManager.ReactInstanceEventListener() {
55 | @Override
56 | public void onReactContextInitialized(ReactContext reactContext) {
57 | reactInstanceManager.removeReactInstanceEventListener(this);
58 | reactContext.runOnNativeModulesQueueThread(
59 | new Runnable() {
60 | @Override
61 | public void run() {
62 | client.addPlugin(new FrescoFlipperPlugin());
63 | }
64 | });
65 | }
66 | });
67 | } else {
68 | client.addPlugin(new FrescoFlipperPlugin());
69 | }
70 | }
71 | }
72 | }
73 |
--------------------------------------------------------------------------------
/ReactNativeFastImageExample/android/app/src/main/AndroidManifest.xml:
--------------------------------------------------------------------------------
1 |
3 |
4 |
5 |
6 |
13 |
19 |
20 |
21 |
22 |
23 |
24 |
25 |
26 |
--------------------------------------------------------------------------------
/ReactNativeFastImageExample/android/app/src/main/java/com/reactnativefastimageexample/MainActivity.java:
--------------------------------------------------------------------------------
1 | package com.reactnativefastimageexample;
2 |
3 | import com.facebook.react.ReactActivity;
4 |
5 | public class MainActivity extends ReactActivity {
6 |
7 | /**
8 | * Returns the name of the main component registered from JavaScript. This is used to schedule
9 | * rendering of the component.
10 | */
11 | @Override
12 | protected String getMainComponentName() {
13 | return "ReactNativeFastImageExample";
14 | }
15 | }
16 |
--------------------------------------------------------------------------------
/ReactNativeFastImageExample/android/app/src/main/java/com/reactnativefastimageexample/MainApplication.java:
--------------------------------------------------------------------------------
1 | package com.reactnativefastimageexample;
2 |
3 | import android.app.Application;
4 | import android.content.Context;
5 | import com.facebook.react.PackageList;
6 | import com.facebook.react.ReactApplication;
7 | import com.facebook.react.ReactInstanceManager;
8 | import com.facebook.react.ReactNativeHost;
9 | import com.facebook.react.ReactPackage;
10 | import com.facebook.soloader.SoLoader;
11 | import java.lang.reflect.InvocationTargetException;
12 | import java.util.List;
13 |
14 | public class MainApplication extends Application implements ReactApplication {
15 |
16 | private final ReactNativeHost mReactNativeHost =
17 | new ReactNativeHost(this) {
18 | @Override
19 | public boolean getUseDeveloperSupport() {
20 | return BuildConfig.DEBUG;
21 | }
22 |
23 | @Override
24 | protected List getPackages() {
25 | @SuppressWarnings("UnnecessaryLocalVariable")
26 | List packages = new PackageList(this).getPackages();
27 | // Packages that cannot be autolinked yet can be added manually here, for example:
28 | // packages.add(new MyReactNativePackage());
29 | return packages;
30 | }
31 |
32 | @Override
33 | protected String getJSMainModuleName() {
34 | return "index";
35 | }
36 | };
37 |
38 | @Override
39 | public ReactNativeHost getReactNativeHost() {
40 | return mReactNativeHost;
41 | }
42 |
43 | @Override
44 | public void onCreate() {
45 | super.onCreate();
46 | SoLoader.init(this, /* native exopackage */ false);
47 | initializeFlipper(this, getReactNativeHost().getReactInstanceManager());
48 | }
49 |
50 | /**
51 | * Loads Flipper in React Native templates. Call this in the onCreate method with something like
52 | * initializeFlipper(this, getReactNativeHost().getReactInstanceManager());
53 | *
54 | * @param context
55 | * @param reactInstanceManager
56 | */
57 | private static void initializeFlipper(
58 | Context context, ReactInstanceManager reactInstanceManager) {
59 | if (BuildConfig.DEBUG) {
60 | try {
61 | /*
62 | We use reflection here to pick up the class that initializes Flipper,
63 | since Flipper library is not available in release mode
64 | */
65 | Class> aClass = Class.forName("com.reactnativefastimageexample.ReactNativeFlipper");
66 | aClass
67 | .getMethod("initializeFlipper", Context.class, ReactInstanceManager.class)
68 | .invoke(null, context, reactInstanceManager);
69 | } catch (ClassNotFoundException e) {
70 | e.printStackTrace();
71 | } catch (NoSuchMethodException e) {
72 | e.printStackTrace();
73 | } catch (IllegalAccessException e) {
74 | e.printStackTrace();
75 | } catch (InvocationTargetException e) {
76 | e.printStackTrace();
77 | }
78 | }
79 | }
80 | }
81 |
--------------------------------------------------------------------------------
/ReactNativeFastImageExample/android/app/src/main/res/mipmap-hdpi/ic_launcher.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/DylanVann/react-native-fast-image/9ab80fcd570b7f56da66ab20e52c9a35934067c9/ReactNativeFastImageExample/android/app/src/main/res/mipmap-hdpi/ic_launcher.png
--------------------------------------------------------------------------------
/ReactNativeFastImageExample/android/app/src/main/res/mipmap-hdpi/ic_launcher_round.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/DylanVann/react-native-fast-image/9ab80fcd570b7f56da66ab20e52c9a35934067c9/ReactNativeFastImageExample/android/app/src/main/res/mipmap-hdpi/ic_launcher_round.png
--------------------------------------------------------------------------------
/ReactNativeFastImageExample/android/app/src/main/res/mipmap-mdpi/ic_launcher.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/DylanVann/react-native-fast-image/9ab80fcd570b7f56da66ab20e52c9a35934067c9/ReactNativeFastImageExample/android/app/src/main/res/mipmap-mdpi/ic_launcher.png
--------------------------------------------------------------------------------
/ReactNativeFastImageExample/android/app/src/main/res/mipmap-mdpi/ic_launcher_round.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/DylanVann/react-native-fast-image/9ab80fcd570b7f56da66ab20e52c9a35934067c9/ReactNativeFastImageExample/android/app/src/main/res/mipmap-mdpi/ic_launcher_round.png
--------------------------------------------------------------------------------
/ReactNativeFastImageExample/android/app/src/main/res/mipmap-xhdpi/ic_launcher.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/DylanVann/react-native-fast-image/9ab80fcd570b7f56da66ab20e52c9a35934067c9/ReactNativeFastImageExample/android/app/src/main/res/mipmap-xhdpi/ic_launcher.png
--------------------------------------------------------------------------------
/ReactNativeFastImageExample/android/app/src/main/res/mipmap-xhdpi/ic_launcher_round.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/DylanVann/react-native-fast-image/9ab80fcd570b7f56da66ab20e52c9a35934067c9/ReactNativeFastImageExample/android/app/src/main/res/mipmap-xhdpi/ic_launcher_round.png
--------------------------------------------------------------------------------
/ReactNativeFastImageExample/android/app/src/main/res/mipmap-xxhdpi/ic_launcher.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/DylanVann/react-native-fast-image/9ab80fcd570b7f56da66ab20e52c9a35934067c9/ReactNativeFastImageExample/android/app/src/main/res/mipmap-xxhdpi/ic_launcher.png
--------------------------------------------------------------------------------
/ReactNativeFastImageExample/android/app/src/main/res/mipmap-xxhdpi/ic_launcher_round.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/DylanVann/react-native-fast-image/9ab80fcd570b7f56da66ab20e52c9a35934067c9/ReactNativeFastImageExample/android/app/src/main/res/mipmap-xxhdpi/ic_launcher_round.png
--------------------------------------------------------------------------------
/ReactNativeFastImageExample/android/app/src/main/res/mipmap-xxxhdpi/ic_launcher.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/DylanVann/react-native-fast-image/9ab80fcd570b7f56da66ab20e52c9a35934067c9/ReactNativeFastImageExample/android/app/src/main/res/mipmap-xxxhdpi/ic_launcher.png
--------------------------------------------------------------------------------
/ReactNativeFastImageExample/android/app/src/main/res/mipmap-xxxhdpi/ic_launcher_round.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/DylanVann/react-native-fast-image/9ab80fcd570b7f56da66ab20e52c9a35934067c9/ReactNativeFastImageExample/android/app/src/main/res/mipmap-xxxhdpi/ic_launcher_round.png
--------------------------------------------------------------------------------
/ReactNativeFastImageExample/android/app/src/main/res/values/strings.xml:
--------------------------------------------------------------------------------
1 |
2 | ReactNativeFastImageExample
3 |
4 |
--------------------------------------------------------------------------------
/ReactNativeFastImageExample/android/app/src/main/res/values/styles.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
8 |
9 |
10 |
--------------------------------------------------------------------------------
/ReactNativeFastImageExample/android/build.gradle:
--------------------------------------------------------------------------------
1 | // Top-level build file where you can add configuration options common to all sub-projects/modules.
2 |
3 | buildscript {
4 | ext {
5 | buildToolsVersion = "30.0.2"
6 | minSdkVersion = 21
7 | compileSdkVersion = 30
8 | targetSdkVersion = 30
9 | ndkVersion = "20.1.5948944"
10 | }
11 | repositories {
12 | google()
13 | mavenCentral()
14 | }
15 | dependencies {
16 | classpath("com.android.tools.build:gradle:4.2.1")
17 | // NOTE: Do not place your application dependencies here; they belong
18 | // in the individual module build.gradle files
19 | }
20 | }
21 |
22 | allprojects {
23 | repositories {
24 | mavenCentral()
25 | mavenLocal()
26 | maven {
27 | // All of React Native (JS, Obj-C sources, Android binaries) is installed from npm
28 | url("$rootDir/../node_modules/react-native/android")
29 | }
30 | maven {
31 | // Android JSC is installed from npm
32 | url("$rootDir/../node_modules/jsc-android/dist")
33 | }
34 |
35 | google()
36 | maven { url 'https://www.jitpack.io' }
37 | }
38 | }
39 |
--------------------------------------------------------------------------------
/ReactNativeFastImageExample/android/gradle.properties:
--------------------------------------------------------------------------------
1 | # Project-wide Gradle settings.
2 |
3 | # IDE (e.g. Android Studio) users:
4 | # Gradle settings configured through the IDE *will override*
5 | # any settings specified in this file.
6 |
7 | # For more details on how to configure your build environment visit
8 | # http://www.gradle.org/docs/current/userguide/build_environment.html
9 |
10 | # Specifies the JVM arguments used for the daemon process.
11 | # The setting is particularly useful for tweaking memory settings.
12 | # Default value: -Xmx10248m -XX:MaxPermSize=256m
13 | # org.gradle.jvmargs=-Xmx2048m -XX:MaxPermSize=512m -XX:+HeapDumpOnOutOfMemoryError -Dfile.encoding=UTF-8
14 |
15 | # When configured, Gradle will run in incubating parallel mode.
16 | # This option should only be used with decoupled projects. More details, visit
17 | # http://www.gradle.org/docs/current/userguide/multi_project_builds.html#sec:decoupled_projects
18 | # org.gradle.parallel=true
19 |
20 | # AndroidX package structure to make it clearer which packages are bundled with the
21 | # Android operating system, and which are packaged with your app's APK
22 | # https://developer.android.com/topic/libraries/support-library/androidx-rn
23 | android.useAndroidX=true
24 | # Automatically convert third-party libraries to use AndroidX
25 | android.enableJetifier=true
26 |
27 | # Version of flipper SDK to use with React Native
28 | FLIPPER_VERSION=0.93.0
29 |
--------------------------------------------------------------------------------
/ReactNativeFastImageExample/android/gradle/wrapper/gradle-wrapper.jar:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/DylanVann/react-native-fast-image/9ab80fcd570b7f56da66ab20e52c9a35934067c9/ReactNativeFastImageExample/android/gradle/wrapper/gradle-wrapper.jar
--------------------------------------------------------------------------------
/ReactNativeFastImageExample/android/gradle/wrapper/gradle-wrapper.properties:
--------------------------------------------------------------------------------
1 | distributionBase=GRADLE_USER_HOME
2 | distributionPath=wrapper/dists
3 | distributionUrl=https\://services.gradle.org/distributions/gradle-6.9-all.zip
4 | zipStoreBase=GRADLE_USER_HOME
5 | zipStorePath=wrapper/dists
6 |
--------------------------------------------------------------------------------
/ReactNativeFastImageExample/android/gradlew:
--------------------------------------------------------------------------------
1 | #!/usr/bin/env sh
2 |
3 | #
4 | # Copyright 2015 the original author or authors.
5 | #
6 | # Licensed under the Apache License, Version 2.0 (the "License");
7 | # you may not use this file except in compliance with the License.
8 | # You may obtain a copy of the License at
9 | #
10 | # https://www.apache.org/licenses/LICENSE-2.0
11 | #
12 | # Unless required by applicable law or agreed to in writing, software
13 | # distributed under the License is distributed on an "AS IS" BASIS,
14 | # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
15 | # See the License for the specific language governing permissions and
16 | # limitations under the License.
17 | #
18 |
19 | ##############################################################################
20 | ##
21 | ## Gradle start up script for UN*X
22 | ##
23 | ##############################################################################
24 |
25 | # Attempt to set APP_HOME
26 | # Resolve links: $0 may be a link
27 | PRG="$0"
28 | # Need this for relative symlinks.
29 | while [ -h "$PRG" ] ; do
30 | ls=`ls -ld "$PRG"`
31 | link=`expr "$ls" : '.*-> \(.*\)$'`
32 | if expr "$link" : '/.*' > /dev/null; then
33 | PRG="$link"
34 | else
35 | PRG=`dirname "$PRG"`"/$link"
36 | fi
37 | done
38 | SAVED="`pwd`"
39 | cd "`dirname \"$PRG\"`/" >/dev/null
40 | APP_HOME="`pwd -P`"
41 | cd "$SAVED" >/dev/null
42 |
43 | APP_NAME="Gradle"
44 | APP_BASE_NAME=`basename "$0"`
45 |
46 | # Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script.
47 | DEFAULT_JVM_OPTS='"-Xmx64m" "-Xms64m"'
48 |
49 | # Use the maximum available, or set MAX_FD != -1 to use that value.
50 | MAX_FD="maximum"
51 |
52 | warn () {
53 | echo "$*"
54 | }
55 |
56 | die () {
57 | echo
58 | echo "$*"
59 | echo
60 | exit 1
61 | }
62 |
63 | # OS specific support (must be 'true' or 'false').
64 | cygwin=false
65 | msys=false
66 | darwin=false
67 | nonstop=false
68 | case "`uname`" in
69 | CYGWIN* )
70 | cygwin=true
71 | ;;
72 | Darwin* )
73 | darwin=true
74 | ;;
75 | MINGW* )
76 | msys=true
77 | ;;
78 | NONSTOP* )
79 | nonstop=true
80 | ;;
81 | esac
82 |
83 | CLASSPATH=$APP_HOME/gradle/wrapper/gradle-wrapper.jar
84 |
85 |
86 | # Determine the Java command to use to start the JVM.
87 | if [ -n "$JAVA_HOME" ] ; then
88 | if [ -x "$JAVA_HOME/jre/sh/java" ] ; then
89 | # IBM's JDK on AIX uses strange locations for the executables
90 | JAVACMD="$JAVA_HOME/jre/sh/java"
91 | else
92 | JAVACMD="$JAVA_HOME/bin/java"
93 | fi
94 | if [ ! -x "$JAVACMD" ] ; then
95 | die "ERROR: JAVA_HOME is set to an invalid directory: $JAVA_HOME
96 |
97 | Please set the JAVA_HOME variable in your environment to match the
98 | location of your Java installation."
99 | fi
100 | else
101 | JAVACMD="java"
102 | which java >/dev/null 2>&1 || die "ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH.
103 |
104 | Please set the JAVA_HOME variable in your environment to match the
105 | location of your Java installation."
106 | fi
107 |
108 | # Increase the maximum file descriptors if we can.
109 | if [ "$cygwin" = "false" -a "$darwin" = "false" -a "$nonstop" = "false" ] ; then
110 | MAX_FD_LIMIT=`ulimit -H -n`
111 | if [ $? -eq 0 ] ; then
112 | if [ "$MAX_FD" = "maximum" -o "$MAX_FD" = "max" ] ; then
113 | MAX_FD="$MAX_FD_LIMIT"
114 | fi
115 | ulimit -n $MAX_FD
116 | if [ $? -ne 0 ] ; then
117 | warn "Could not set maximum file descriptor limit: $MAX_FD"
118 | fi
119 | else
120 | warn "Could not query maximum file descriptor limit: $MAX_FD_LIMIT"
121 | fi
122 | fi
123 |
124 | # For Darwin, add options to specify how the application appears in the dock
125 | if $darwin; then
126 | GRADLE_OPTS="$GRADLE_OPTS \"-Xdock:name=$APP_NAME\" \"-Xdock:icon=$APP_HOME/media/gradle.icns\""
127 | fi
128 |
129 | # For Cygwin or MSYS, switch paths to Windows format before running java
130 | if [ "$cygwin" = "true" -o "$msys" = "true" ] ; then
131 | APP_HOME=`cygpath --path --mixed "$APP_HOME"`
132 | CLASSPATH=`cygpath --path --mixed "$CLASSPATH"`
133 |
134 | JAVACMD=`cygpath --unix "$JAVACMD"`
135 |
136 | # We build the pattern for arguments to be converted via cygpath
137 | ROOTDIRSRAW=`find -L / -maxdepth 1 -mindepth 1 -type d 2>/dev/null`
138 | SEP=""
139 | for dir in $ROOTDIRSRAW ; do
140 | ROOTDIRS="$ROOTDIRS$SEP$dir"
141 | SEP="|"
142 | done
143 | OURCYGPATTERN="(^($ROOTDIRS))"
144 | # Add a user-defined pattern to the cygpath arguments
145 | if [ "$GRADLE_CYGPATTERN" != "" ] ; then
146 | OURCYGPATTERN="$OURCYGPATTERN|($GRADLE_CYGPATTERN)"
147 | fi
148 | # Now convert the arguments - kludge to limit ourselves to /bin/sh
149 | i=0
150 | for arg in "$@" ; do
151 | CHECK=`echo "$arg"|egrep -c "$OURCYGPATTERN" -`
152 | CHECK2=`echo "$arg"|egrep -c "^-"` ### Determine if an option
153 |
154 | if [ $CHECK -ne 0 ] && [ $CHECK2 -eq 0 ] ; then ### Added a condition
155 | eval `echo args$i`=`cygpath --path --ignore --mixed "$arg"`
156 | else
157 | eval `echo args$i`="\"$arg\""
158 | fi
159 | i=`expr $i + 1`
160 | done
161 | case $i in
162 | 0) set -- ;;
163 | 1) set -- "$args0" ;;
164 | 2) set -- "$args0" "$args1" ;;
165 | 3) set -- "$args0" "$args1" "$args2" ;;
166 | 4) set -- "$args0" "$args1" "$args2" "$args3" ;;
167 | 5) set -- "$args0" "$args1" "$args2" "$args3" "$args4" ;;
168 | 6) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" ;;
169 | 7) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" "$args6" ;;
170 | 8) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" "$args6" "$args7" ;;
171 | 9) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" "$args6" "$args7" "$args8" ;;
172 | esac
173 | fi
174 |
175 | # Escape application args
176 | save () {
177 | for i do printf %s\\n "$i" | sed "s/'/'\\\\''/g;1s/^/'/;\$s/\$/' \\\\/" ; done
178 | echo " "
179 | }
180 | APP_ARGS=`save "$@"`
181 |
182 | # Collect all arguments for the java command, following the shell quoting and substitution rules
183 | eval set -- $DEFAULT_JVM_OPTS $JAVA_OPTS $GRADLE_OPTS "\"-Dorg.gradle.appname=$APP_BASE_NAME\"" -classpath "\"$CLASSPATH\"" org.gradle.wrapper.GradleWrapperMain "$APP_ARGS"
184 |
185 | exec "$JAVACMD" "$@"
186 |
--------------------------------------------------------------------------------
/ReactNativeFastImageExample/android/gradlew.bat:
--------------------------------------------------------------------------------
1 | @rem
2 | @rem Copyright 2015 the original author or authors.
3 | @rem
4 | @rem Licensed under the Apache License, Version 2.0 (the "License");
5 | @rem you may not use this file except in compliance with the License.
6 | @rem You may obtain a copy of the License at
7 | @rem
8 | @rem https://www.apache.org/licenses/LICENSE-2.0
9 | @rem
10 | @rem Unless required by applicable law or agreed to in writing, software
11 | @rem distributed under the License is distributed on an "AS IS" BASIS,
12 | @rem WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13 | @rem See the License for the specific language governing permissions and
14 | @rem limitations under the License.
15 | @rem
16 |
17 | @if "%DEBUG%" == "" @echo off
18 | @rem ##########################################################################
19 | @rem
20 | @rem Gradle startup script for Windows
21 | @rem
22 | @rem ##########################################################################
23 |
24 | @rem Set local scope for the variables with windows NT shell
25 | if "%OS%"=="Windows_NT" setlocal
26 |
27 | set DIRNAME=%~dp0
28 | if "%DIRNAME%" == "" set DIRNAME=.
29 | set APP_BASE_NAME=%~n0
30 | set APP_HOME=%DIRNAME%
31 |
32 | @rem Resolve any "." and ".." in APP_HOME to make it shorter.
33 | for %%i in ("%APP_HOME%") do set APP_HOME=%%~fi
34 |
35 | @rem Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script.
36 | set DEFAULT_JVM_OPTS="-Xmx64m" "-Xms64m"
37 |
38 | @rem Find java.exe
39 | if defined JAVA_HOME goto findJavaFromJavaHome
40 |
41 | set JAVA_EXE=java.exe
42 | %JAVA_EXE% -version >NUL 2>&1
43 | if "%ERRORLEVEL%" == "0" goto execute
44 |
45 | echo.
46 | echo ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH.
47 | echo.
48 | echo Please set the JAVA_HOME variable in your environment to match the
49 | echo location of your Java installation.
50 |
51 | goto fail
52 |
53 | :findJavaFromJavaHome
54 | set JAVA_HOME=%JAVA_HOME:"=%
55 | set JAVA_EXE=%JAVA_HOME%/bin/java.exe
56 |
57 | if exist "%JAVA_EXE%" goto execute
58 |
59 | echo.
60 | echo ERROR: JAVA_HOME is set to an invalid directory: %JAVA_HOME%
61 | echo.
62 | echo Please set the JAVA_HOME variable in your environment to match the
63 | echo location of your Java installation.
64 |
65 | goto fail
66 |
67 | :execute
68 | @rem Setup the command line
69 |
70 | set CLASSPATH=%APP_HOME%\gradle\wrapper\gradle-wrapper.jar
71 |
72 |
73 | @rem Execute Gradle
74 | "%JAVA_EXE%" %DEFAULT_JVM_OPTS% %JAVA_OPTS% %GRADLE_OPTS% "-Dorg.gradle.appname=%APP_BASE_NAME%" -classpath "%CLASSPATH%" org.gradle.wrapper.GradleWrapperMain %*
75 |
76 | :end
77 | @rem End local scope for the variables with windows NT shell
78 | if "%ERRORLEVEL%"=="0" goto mainEnd
79 |
80 | :fail
81 | rem Set variable GRADLE_EXIT_CONSOLE if you need the _script_ return code instead of
82 | rem the _cmd.exe /c_ return code!
83 | if not "" == "%GRADLE_EXIT_CONSOLE%" exit 1
84 | exit /b 1
85 |
86 | :mainEnd
87 | if "%OS%"=="Windows_NT" endlocal
88 |
89 | :omega
90 |
--------------------------------------------------------------------------------
/ReactNativeFastImageExample/android/settings.gradle:
--------------------------------------------------------------------------------
1 | rootProject.name = 'ReactNativeFastImageExample'
2 | apply from: file("../node_modules/@react-native-community/cli-platform-android/native_modules.gradle"); applyNativeModulesSettingsGradle(settings)
3 | include ':app'
4 |
--------------------------------------------------------------------------------
/ReactNativeFastImageExample/app.json:
--------------------------------------------------------------------------------
1 | {
2 | "name": "ReactNativeFastImageExample",
3 | "displayName": "ReactNativeFastImageExample"
4 | }
5 |
--------------------------------------------------------------------------------
/ReactNativeFastImageExample/babel.config.js:
--------------------------------------------------------------------------------
1 | module.exports = {
2 | presets: ['module:metro-react-native-babel-preset'],
3 | }
4 |
--------------------------------------------------------------------------------
/ReactNativeFastImageExample/index.js:
--------------------------------------------------------------------------------
1 | /**
2 | * @format
3 | */
4 |
5 | import { AppRegistry } from 'react-native'
6 | import App from './src'
7 | import { name as appName } from './app.json'
8 |
9 | AppRegistry.registerComponent(appName, () => App)
10 |
--------------------------------------------------------------------------------
/ReactNativeFastImageExample/ios/Podfile:
--------------------------------------------------------------------------------
1 | require_relative '../node_modules/react-native/scripts/react_native_pods'
2 | require_relative '../node_modules/@react-native-community/cli-platform-ios/native_modules'
3 |
4 | platform :ios, '11.0'
5 |
6 | target 'ReactNativeFastImageExample' do
7 | config = use_native_modules!
8 |
9 | use_react_native!(
10 | :path => config[:reactNativePath],
11 | # to enable hermes on iOS, change `false` to `true` and then install pods
12 | :hermes_enabled => false
13 | )
14 |
15 | target 'ReactNativeFastImageExampleTests' do
16 | inherit! :complete
17 | # Pods for testing
18 | end
19 |
20 | # Enables Flipper.
21 | #
22 | # Note that if you have use_frameworks! enabled, Flipper will not work and
23 | # you should disable the next line.
24 | use_flipper!()
25 |
26 | post_install do |installer|
27 | react_native_post_install(installer)
28 | end
29 | end
--------------------------------------------------------------------------------
/ReactNativeFastImageExample/ios/ReactNativeFastImageExample.xcodeproj/xcshareddata/xcschemes/ReactNativeFastImageExample.xcscheme:
--------------------------------------------------------------------------------
1 |
2 |
5 |
8 |
9 |
15 |
21 |
22 |
23 |
24 |
25 |
30 |
31 |
33 |
39 |
40 |
41 |
42 |
43 |
53 |
55 |
61 |
62 |
63 |
64 |
70 |
72 |
78 |
79 |
80 |
81 |
83 |
84 |
87 |
88 |
89 |
--------------------------------------------------------------------------------
/ReactNativeFastImageExample/ios/ReactNativeFastImageExample.xcworkspace/contents.xcworkspacedata:
--------------------------------------------------------------------------------
1 |
2 |
4 |
6 |
7 |
9 |
10 |
11 |
--------------------------------------------------------------------------------
/ReactNativeFastImageExample/ios/ReactNativeFastImageExample.xcworkspace/xcshareddata/IDEWorkspaceChecks.plist:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 | IDEDidComputeMac32BitWarning
6 |
7 |
8 |
9 |
--------------------------------------------------------------------------------
/ReactNativeFastImageExample/ios/ReactNativeFastImageExample/AppDelegate.h:
--------------------------------------------------------------------------------
1 | #import
2 | #import
3 |
4 | @interface AppDelegate : UIResponder
5 |
6 | @property (nonatomic, strong) UIWindow *window;
7 |
8 | @end
9 |
--------------------------------------------------------------------------------
/ReactNativeFastImageExample/ios/ReactNativeFastImageExample/AppDelegate.m:
--------------------------------------------------------------------------------
1 | #import "AppDelegate.h"
2 |
3 | #import
4 | #import
5 | #import
6 |
7 | #ifdef FB_SONARKIT_ENABLED
8 | #import
9 | #import
10 | #import
11 | #import
12 | #import
13 | #import
14 |
15 | static void InitializeFlipper(UIApplication *application) {
16 | FlipperClient *client = [FlipperClient sharedClient];
17 | SKDescriptorMapper *layoutDescriptorMapper = [[SKDescriptorMapper alloc] initWithDefaults];
18 | [client addPlugin:[[FlipperKitLayoutPlugin alloc] initWithRootNode:application withDescriptorMapper:layoutDescriptorMapper]];
19 | [client addPlugin:[[FKUserDefaultsPlugin alloc] initWithSuiteName:nil]];
20 | [client addPlugin:[FlipperKitReactPlugin new]];
21 | [client addPlugin:[[FlipperKitNetworkPlugin alloc] initWithNetworkAdapter:[SKIOSNetworkAdapter new]]];
22 | [client start];
23 | }
24 | #endif
25 |
26 | @implementation AppDelegate
27 |
28 | - (BOOL)application:(UIApplication *)application didFinishLaunchingWithOptions:(NSDictionary *)launchOptions
29 | {
30 | #ifdef FB_SONARKIT_ENABLED
31 | InitializeFlipper(application);
32 | #endif
33 |
34 | RCTBridge *bridge = [[RCTBridge alloc] initWithDelegate:self launchOptions:launchOptions];
35 | RCTRootView *rootView = [[RCTRootView alloc] initWithBridge:bridge
36 | moduleName:@"ReactNativeFastImageExample"
37 | initialProperties:nil];
38 |
39 | if (@available(iOS 13.0, *)) {
40 | rootView.backgroundColor = [UIColor systemBackgroundColor];
41 | } else {
42 | rootView.backgroundColor = [UIColor whiteColor];
43 | }
44 |
45 | self.window = [[UIWindow alloc] initWithFrame:[UIScreen mainScreen].bounds];
46 | UIViewController *rootViewController = [UIViewController new];
47 | rootViewController.view = rootView;
48 | self.window.rootViewController = rootViewController;
49 | [self.window makeKeyAndVisible];
50 | return YES;
51 | }
52 |
53 | - (NSURL *)sourceURLForBridge:(RCTBridge *)bridge
54 | {
55 | #if DEBUG
56 | return [[RCTBundleURLProvider sharedSettings] jsBundleURLForBundleRoot:@"index" fallbackResource:nil];
57 | #else
58 | return [[NSBundle mainBundle] URLForResource:@"main" withExtension:@"jsbundle"];
59 | #endif
60 | }
61 |
62 | @end
63 |
--------------------------------------------------------------------------------
/ReactNativeFastImageExample/ios/ReactNativeFastImageExample/Images.xcassets/AppIcon.appiconset/Contents.json:
--------------------------------------------------------------------------------
1 | {
2 | "images" : [
3 | {
4 | "idiom" : "iphone",
5 | "size" : "29x29",
6 | "scale" : "2x"
7 | },
8 | {
9 | "idiom" : "iphone",
10 | "size" : "29x29",
11 | "scale" : "3x"
12 | },
13 | {
14 | "idiom" : "iphone",
15 | "size" : "40x40",
16 | "scale" : "2x"
17 | },
18 | {
19 | "idiom" : "iphone",
20 | "size" : "40x40",
21 | "scale" : "3x"
22 | },
23 | {
24 | "idiom" : "iphone",
25 | "size" : "60x60",
26 | "scale" : "2x"
27 | },
28 | {
29 | "idiom" : "iphone",
30 | "size" : "60x60",
31 | "scale" : "3x"
32 | }
33 | ],
34 | "info" : {
35 | "version" : 1,
36 | "author" : "xcode"
37 | }
38 | }
--------------------------------------------------------------------------------
/ReactNativeFastImageExample/ios/ReactNativeFastImageExample/Images.xcassets/Contents.json:
--------------------------------------------------------------------------------
1 | {
2 | "info" : {
3 | "version" : 1,
4 | "author" : "xcode"
5 | }
6 | }
7 |
--------------------------------------------------------------------------------
/ReactNativeFastImageExample/ios/ReactNativeFastImageExample/Info.plist:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 | CFBundleDevelopmentRegion
6 | en
7 | CFBundleDisplayName
8 | ReactNativeFastImageExample
9 | CFBundleExecutable
10 | $(EXECUTABLE_NAME)
11 | CFBundleIdentifier
12 | $(PRODUCT_BUNDLE_IDENTIFIER)
13 | CFBundleInfoDictionaryVersion
14 | 6.0
15 | CFBundleName
16 | $(PRODUCT_NAME)
17 | CFBundlePackageType
18 | APPL
19 | CFBundleShortVersionString
20 | 1.0
21 | CFBundleSignature
22 | ????
23 | CFBundleVersion
24 | 1
25 | LSRequiresIPhoneOS
26 |
27 | NSAppTransportSecurity
28 |
29 | NSExceptionDomains
30 |
31 | localhost
32 |
33 | NSExceptionAllowsInsecureHTTPLoads
34 |
35 |
36 |
37 |
38 | NSLocationWhenInUseUsageDescription
39 |
40 | NSPhotoLibraryUsageDescription
41 | Need your photos.
42 | UIAppFonts
43 |
44 | Ionicons.ttf
45 |
46 | UILaunchStoryboardName
47 | LaunchScreen
48 | UIRequiredDeviceCapabilities
49 |
50 | armv7
51 |
52 | UISupportedInterfaceOrientations
53 |
54 | UIInterfaceOrientationPortrait
55 | UIInterfaceOrientationLandscapeLeft
56 | UIInterfaceOrientationLandscapeRight
57 |
58 | UIViewControllerBasedStatusBarAppearance
59 |
60 |
61 |
62 |
--------------------------------------------------------------------------------
/ReactNativeFastImageExample/ios/ReactNativeFastImageExample/LaunchScreen.storyboard:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
7 |
8 |
9 |
10 |
11 |
12 |
13 |
14 |
15 |
16 |
17 |
18 |
19 |
24 |
29 |
30 |
31 |
32 |
33 |
34 |
35 |
36 |
37 |
38 |
39 |
40 |
41 |
42 |
43 |
44 |
45 |
46 |
47 |
48 |
--------------------------------------------------------------------------------
/ReactNativeFastImageExample/ios/ReactNativeFastImageExample/main.m:
--------------------------------------------------------------------------------
1 | #import
2 |
3 | #import "AppDelegate.h"
4 |
5 | int main(int argc, char * argv[]) {
6 | @autoreleasepool {
7 | return UIApplicationMain(argc, argv, nil, NSStringFromClass([AppDelegate class]));
8 | }
9 | }
10 |
--------------------------------------------------------------------------------
/ReactNativeFastImageExample/ios/ReactNativeFastImageExampleTests/Info.plist:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 | CFBundleDevelopmentRegion
6 | en
7 | CFBundleExecutable
8 | $(EXECUTABLE_NAME)
9 | CFBundleIdentifier
10 | $(PRODUCT_BUNDLE_IDENTIFIER)
11 | CFBundleInfoDictionaryVersion
12 | 6.0
13 | CFBundleName
14 | $(PRODUCT_NAME)
15 | CFBundlePackageType
16 | BNDL
17 | CFBundleShortVersionString
18 | 1.0
19 | CFBundleSignature
20 | ????
21 | CFBundleVersion
22 | 1
23 |
24 |
25 |
--------------------------------------------------------------------------------
/ReactNativeFastImageExample/ios/ReactNativeFastImageExampleTests/ReactNativeFastImageExampleTests.m:
--------------------------------------------------------------------------------
1 | #import
2 | #import
3 |
4 | #import
5 | #import
6 |
7 | #define TIMEOUT_SECONDS 600
8 | #define TEXT_TO_LOOK_FOR @"Welcome to React"
9 |
10 | @interface ReactNativeFastImageExampleTests : XCTestCase
11 |
12 | @end
13 |
14 | @implementation ReactNativeFastImageExampleTests
15 |
16 | - (BOOL)findSubviewInView:(UIView *)view matching:(BOOL(^)(UIView *view))test
17 | {
18 | if (test(view)) {
19 | return YES;
20 | }
21 | for (UIView *subview in [view subviews]) {
22 | if ([self findSubviewInView:subview matching:test]) {
23 | return YES;
24 | }
25 | }
26 | return NO;
27 | }
28 |
29 | - (void)testRendersWelcomeScreen
30 | {
31 | UIViewController *vc = [[[RCTSharedApplication() delegate] window] rootViewController];
32 | NSDate *date = [NSDate dateWithTimeIntervalSinceNow:TIMEOUT_SECONDS];
33 | BOOL foundElement = NO;
34 |
35 | __block NSString *redboxError = nil;
36 | #ifdef DEBUG
37 | RCTSetLogFunction(^(RCTLogLevel level, RCTLogSource source, NSString *fileName, NSNumber *lineNumber, NSString *message) {
38 | if (level >= RCTLogLevelError) {
39 | redboxError = message;
40 | }
41 | });
42 | #endif
43 |
44 | while ([date timeIntervalSinceNow] > 0 && !foundElement && !redboxError) {
45 | [[NSRunLoop mainRunLoop] runMode:NSDefaultRunLoopMode beforeDate:[NSDate dateWithTimeIntervalSinceNow:0.1]];
46 | [[NSRunLoop mainRunLoop] runMode:NSRunLoopCommonModes beforeDate:[NSDate dateWithTimeIntervalSinceNow:0.1]];
47 |
48 | foundElement = [self findSubviewInView:vc.view matching:^BOOL(UIView *view) {
49 | if ([view.accessibilityLabel isEqualToString:TEXT_TO_LOOK_FOR]) {
50 | return YES;
51 | }
52 | return NO;
53 | }];
54 | }
55 |
56 | #ifdef DEBUG
57 | RCTSetLogFunction(RCTDefaultLogFunction);
58 | #endif
59 |
60 | XCTAssertNil(redboxError, @"RedBox error: %@", redboxError);
61 | XCTAssertTrue(foundElement, @"Couldn't find element with text '%@' in %d seconds", TEXT_TO_LOOK_FOR, TIMEOUT_SECONDS);
62 | }
63 |
64 |
65 | @end
66 |
--------------------------------------------------------------------------------
/ReactNativeFastImageExample/metro.config.js:
--------------------------------------------------------------------------------
1 | /**
2 | * Metro configuration for React Native
3 | * https://github.com/facebook/react-native
4 | *
5 | * @format
6 | */
7 |
8 | module.exports = {
9 | transformer: {
10 | getTransformOptions: async () => ({
11 | transform: {
12 | experimentalImportSupport: false,
13 | inlineRequires: true,
14 | },
15 | }),
16 | },
17 | }
18 |
--------------------------------------------------------------------------------
/ReactNativeFastImageExample/package.json:
--------------------------------------------------------------------------------
1 | {
2 | "name": "reactnativefastimageexample",
3 | "version": "0.0.1",
4 | "private": true,
5 | "scripts": {
6 | "android": "react-native run-android",
7 | "ios": "react-native run-ios",
8 | "start": "react-native start",
9 | "test": "jest",
10 | "lint": "eslint . --ext .js,.jsx,.ts,.tsx"
11 | },
12 | "jest": {
13 | "moduleFileExtensions": [
14 | "ts",
15 | "tsx",
16 | "js",
17 | "jsx",
18 | "json",
19 | "node"
20 | ],
21 | "preset": "react-native"
22 | },
23 | "resolutions": {
24 | "@types/react": "^17"
25 | },
26 | "dependencies": {
27 | "@react-native-community/masked-view": "^0.1.7",
28 | "@react-navigation/bottom-tabs": "^6.0.5",
29 | "@react-navigation/native": "^6.0.2",
30 | "@react-navigation/stack": "^6.0.7",
31 | "react": "17.0.2",
32 | "react-native": "0.65.1",
33 | "react-native-fast-image": "^8.1.3",
34 | "react-native-gesture-handler": "^1.6.0",
35 | "react-native-image-picker": "^4.0.6",
36 | "react-native-image-progress": "^1.1.1",
37 | "react-native-reanimated": "^2.2.2",
38 | "react-native-safe-area-context": "^3.3.2",
39 | "react-native-screens": "^3.7.2",
40 | "react-native-status-bar-height": "^2.1.0",
41 | "react-native-vector-icons": "^8.1.0"
42 | },
43 | "devDependencies": {
44 | "@babel/core": "^7.12.9",
45 | "@babel/runtime": "^7.12.5",
46 | "@react-native-community/eslint-config": "^2.0.0",
47 | "@types/jest": "^26.0.23",
48 | "@types/react-native": "^0.65.0",
49 | "@types/react-test-renderer": "^17.0.1",
50 | "babel-jest": "^26.6.3",
51 | "eslint": "^7.14.0",
52 | "jest": "^26.6.3",
53 | "metro-react-native-babel-preset": "^0.66.0",
54 | "react-native-codegen": "^0.0.7",
55 | "react-test-renderer": "17.0.2",
56 | "typescript": "^3.8.3"
57 | }
58 | }
59 |
--------------------------------------------------------------------------------
/ReactNativeFastImageExample/src/AutoSizeExample.tsx:
--------------------------------------------------------------------------------
1 | import React, { useCallback, useMemo, useState } from 'react'
2 | import { StyleSheet, View } from 'react-native'
3 | import SectionFlex from './SectionFlex'
4 | import FastImage, { FastImageProps } from 'react-native-fast-image'
5 | import Section from './Section'
6 | import FeatureText from './FeatureText'
7 | import { useCacheBust } from './useCacheBust'
8 |
9 | const GIF_URL =
10 | 'https://cdn-images-1.medium.com/max/1600/1*-CY5bU4OqiJRox7G00sftw.gif'
11 |
12 | interface AutoSizingImageProps extends FastImageProps {
13 | onLoad?: (event: any) => void
14 | defaultHeight?: number
15 | width: number
16 | style?: any
17 | }
18 |
19 | const AutoSizingImage = (props: AutoSizingImageProps) => {
20 | const [dimensions, setDimensions] = useState({
21 | height: 0,
22 | width: 0,
23 | })
24 |
25 | const propsOnLoad = props.onLoad
26 | const onLoad = useCallback(
27 | (e: any) => {
28 | const {
29 | nativeEvent: { width, height },
30 | } = e
31 | setDimensions({ width, height })
32 | if (propsOnLoad) {
33 | propsOnLoad(e)
34 | }
35 | },
36 | [propsOnLoad],
37 | )
38 |
39 | const height = useMemo(() => {
40 | if (!dimensions.height) {
41 | return props.defaultHeight === undefined ? 300 : props.defaultHeight
42 | }
43 | const ratio = dimensions.height / dimensions.width
44 | return props.width * ratio
45 | }, [dimensions.height, dimensions.width, props.defaultHeight, props.width])
46 | return (
47 |
52 | )
53 | }
54 |
55 | export const AutoSizeExample = () => {
56 | const { bust, url } = useCacheBust(GIF_URL)
57 | return (
58 |
59 |
62 |
63 |
68 |
69 |
70 | )
71 | }
72 |
73 | const styles = StyleSheet.create({
74 | image: {
75 | backgroundColor: '#ddd',
76 | margin: 20,
77 | flex: 0,
78 | },
79 | })
80 |
--------------------------------------------------------------------------------
/ReactNativeFastImageExample/src/BorderRadiusExample.tsx:
--------------------------------------------------------------------------------
1 | import React from 'react'
2 | import { StyleSheet, View } from 'react-native'
3 | import SectionFlex from './SectionFlex'
4 | import FastImage from 'react-native-fast-image'
5 | import Section from './Section'
6 | import FeatureText from './FeatureText'
7 | import { useCacheBust } from './useCacheBust'
8 |
9 | const IMAGE_URL = 'https://media.giphy.com/media/GEsoqZDGVoisw/giphy.gif'
10 |
11 | export const BorderRadiusExample = () => {
12 | const { query, bust } = useCacheBust('')
13 | return (
14 |
15 |
18 |
19 |
25 |
31 |
32 |
33 | )
34 | }
35 |
36 | const styles = StyleSheet.create({
37 | imageSquare: {
38 | borderRadius: 50,
39 | height: 100,
40 | backgroundColor: '#ddd',
41 | margin: 20,
42 | width: 100,
43 | flex: 0,
44 | },
45 | imageRectangular: {
46 | borderRadius: 50,
47 | borderTopLeftRadius: 10,
48 | borderBottomRightRadius: 10,
49 | height: 100,
50 | backgroundColor: '#ddd',
51 | margin: 20,
52 | flex: 1,
53 | },
54 | plus: {
55 | width: 30,
56 | height: 30,
57 | position: 'absolute',
58 | bottom: 0,
59 | right: 0,
60 | },
61 | })
62 |
--------------------------------------------------------------------------------
/ReactNativeFastImageExample/src/BulletText.tsx:
--------------------------------------------------------------------------------
1 | import React from 'react'
2 | import FeatureText from './FeatureText'
3 |
4 | interface BulletTextProps {
5 | text?: string
6 | children?: any
7 | }
8 |
9 | const BulletText = ({ text, children }: BulletTextProps) => (
10 |
11 | )
12 |
13 | export default BulletText
14 |
--------------------------------------------------------------------------------
/ReactNativeFastImageExample/src/Button.tsx:
--------------------------------------------------------------------------------
1 | import React from 'react'
2 | import { StyleSheet, Text, TouchableOpacity, View } from 'react-native'
3 |
4 | interface ButtonProps {
5 | text: string
6 | onPress: () => void
7 | }
8 |
9 | const Button = ({ text, onPress }: ButtonProps) => (
10 |
11 |
12 | {text}
13 |
14 |
15 | )
16 |
17 | const styles = StyleSheet.create({
18 | button: {
19 | backgroundColor: 'black',
20 | margin: 10,
21 | height: 44,
22 | paddingLeft: 10,
23 | paddingRight: 10,
24 | borderRadius: 10,
25 | alignItems: 'center',
26 | justifyContent: 'center',
27 | },
28 | text: {
29 | color: 'white',
30 | },
31 | })
32 |
33 | export default Button
34 |
--------------------------------------------------------------------------------
/ReactNativeFastImageExample/src/DefaultImageGrid.tsx:
--------------------------------------------------------------------------------
1 | import React from 'react'
2 | import { Image } from 'react-native'
3 | import { ImageGrid } from './ImageGrid'
4 |
5 | const DefaultImageGrid = () =>
6 |
7 | export default DefaultImageGrid
8 |
--------------------------------------------------------------------------------
/ReactNativeFastImageExample/src/FastImageExamples.tsx:
--------------------------------------------------------------------------------
1 | import React from 'react'
2 | import { ScrollView, StatusBar, StyleSheet, Text, View } from 'react-native'
3 | import Section from './Section'
4 | import FeatureText from './FeatureText'
5 | import StatusBarUnderlay, { STATUS_BAR_HEIGHT } from './StatusBarUnderlay'
6 | import { PriorityExample } from './PriorityExample'
7 | import { GifExample } from './GifExample'
8 | import { BorderRadiusExample } from './BorderRadiusExample'
9 | import { ProgressExample } from './ProgressExample'
10 | import { PreloadExample } from './PreloadExample'
11 | import { ResizeModeExample } from './ResizeModeExample'
12 | import { TintColorExample } from './TintColorExample'
13 | import { LocalImagesExample } from './LocalImagesExample'
14 | import { AutoSizeExample } from './AutoSizeExample'
15 |
16 | const FastImageExample = () => (
17 |
18 |
23 |
27 |
28 |
29 | 🚩 FastImage
30 |
31 |
32 |
33 |
34 |
35 |
36 |
37 |
38 |
39 |
40 |
41 |
42 |
43 |
44 |
45 | )
46 |
47 | const styles = StyleSheet.create({
48 | titleText: {
49 | fontWeight: '900',
50 | marginBottom: 20,
51 | color: '#222',
52 | },
53 | contentContainer: {
54 | marginTop: 20,
55 | },
56 | image: {
57 | flex: 1,
58 | height: 100,
59 | backgroundColor: '#ddd',
60 | margin: 10,
61 | },
62 | container: {
63 | flex: 1,
64 | alignItems: 'stretch',
65 | backgroundColor: '#fff',
66 | },
67 | scrollContainer: {
68 | marginTop: STATUS_BAR_HEIGHT,
69 | },
70 | scrollContentContainer: {
71 | alignItems: 'stretch',
72 | flex: 0,
73 | },
74 | })
75 |
76 | export default FastImageExample
77 |
--------------------------------------------------------------------------------
/ReactNativeFastImageExample/src/FastImageGrid.tsx:
--------------------------------------------------------------------------------
1 | import React from 'react'
2 | import FastImage from 'react-native-fast-image'
3 | import { ImageGrid } from './ImageGrid'
4 |
5 | const FastImageGrid = () =>
6 |
7 | export default FastImageGrid
8 |
--------------------------------------------------------------------------------
/ReactNativeFastImageExample/src/FeatureText.tsx:
--------------------------------------------------------------------------------
1 | import React from 'react'
2 | import { StyleSheet, Text } from 'react-native'
3 |
4 | interface FeatureTextProps {
5 | text?: string
6 | style?: any
7 | children?: any
8 | }
9 |
10 | export default function FeatureText({
11 | text,
12 | style,
13 | children,
14 | }: FeatureTextProps) {
15 | return {text || children}
16 | }
17 |
18 | const styles = StyleSheet.create({
19 | style: {
20 | color: '#222',
21 | },
22 | })
23 |
--------------------------------------------------------------------------------
/ReactNativeFastImageExample/src/GifExample.tsx:
--------------------------------------------------------------------------------
1 | import React from 'react'
2 | import { StyleSheet, View } from 'react-native'
3 | import SectionFlex from './SectionFlex'
4 | import FastImage from 'react-native-fast-image'
5 | import Section from './Section'
6 | import FeatureText from './FeatureText'
7 | import { useCacheBust } from './useCacheBust'
8 |
9 | const GIF_URL =
10 | 'https://cdn-images-1.medium.com/max/1600/1*-CY5bU4OqiJRox7G00sftw.gif'
11 |
12 | export const GifExample = () => {
13 | const { url, bust } = useCacheBust(GIF_URL)
14 | return (
15 |
16 |
19 |
20 |
21 |
22 |
23 | )
24 | }
25 |
26 | const styles = StyleSheet.create({
27 | image: {
28 | backgroundColor: '#ddd',
29 | margin: 20,
30 | height: 100,
31 | width: 100,
32 | flex: 0,
33 | },
34 | })
35 |
--------------------------------------------------------------------------------
/ReactNativeFastImageExample/src/Icon.tsx:
--------------------------------------------------------------------------------
1 | import React from 'react'
2 | import Base from 'react-native-vector-icons/Ionicons'
3 |
4 | interface IconProps {
5 | size?: number
6 | name: string
7 | color: string
8 | }
9 |
10 | export function Icon({ size, name, color }: IconProps) {
11 | return (
12 |
18 | )
19 | }
20 |
--------------------------------------------------------------------------------
/ReactNativeFastImageExample/src/ImageGrid.tsx:
--------------------------------------------------------------------------------
1 | import React, { memo, useCallback, useEffect, useState } from 'react'
2 | import { FlatList, Text, View, LayoutChangeEvent } from 'react-native'
3 | import StatusBarUnderlay, { STATUS_BAR_HEIGHT } from './StatusBarUnderlay'
4 |
5 | const getImageUrl = (id: string, width: number, height: number) =>
6 | `https://unsplash.it/${width}/${height}?image=${id}`
7 |
8 | const MARGIN = 2
9 |
10 | export interface ImageGridItemProps {
11 | id: string
12 | ImageComponent: any
13 | }
14 |
15 | export const ImageGridItem = memo(
16 | ({ id, ImageComponent }: ImageGridItemProps) => {
17 | const uri = getImageUrl(id, 100, 100)
18 | return (
19 |
25 |
35 |
36 | )
37 | },
38 | )
39 |
40 | export interface ImageGridProps {
41 | ImageComponent: React.ComponentType
42 | }
43 |
44 | export const ImageGrid = (props: ImageGridProps) => {
45 | const [images, setImages] = useState([])
46 | const [itemHeight, setItemHeight] = useState(0)
47 | const [error, setError] = useState(null)
48 |
49 | useEffect(() => {
50 | fetch('https://unsplash.it/list')
51 | .then((res) => res.json())
52 | .then((d) => setImages(d))
53 | .catch((e) => setError(e))
54 | }, [])
55 |
56 | const onLayout = useCallback((e: LayoutChangeEvent) => {
57 | const width = e.nativeEvent.layout.width
58 | setItemHeight(width / 4)
59 | }, [])
60 |
61 | const getItemLayout = useCallback(
62 | (_: any, index: number) => {
63 | return { length: itemHeight, offset: itemHeight * index, index }
64 | },
65 | [itemHeight],
66 | )
67 |
68 | const { ImageComponent } = props
69 |
70 | const renderItem = useCallback(
71 | ({ item }: { item: any }) => {
72 | return (
73 |
74 | )
75 | },
76 | [ImageComponent],
77 | )
78 |
79 | const extractKey = useCallback((item: any) => {
80 | return item.id
81 | }, [])
82 |
83 | if (error) {
84 | return (
85 |
93 |
98 | Error fetching images.
99 |
100 |
101 | )
102 | }
103 |
104 | return (
105 |
113 |
134 |
135 |
136 | )
137 | }
138 |
--------------------------------------------------------------------------------
/ReactNativeFastImageExample/src/LocalImagesExample.tsx:
--------------------------------------------------------------------------------
1 | import React, { Component } from 'react'
2 | import {
3 | StyleSheet,
4 | View,
5 | Text,
6 | TouchableOpacity,
7 | ViewProps,
8 | } from 'react-native'
9 | import FastImage, { FastImageProps, Source } from 'react-native-fast-image'
10 | import Section from './Section'
11 | import FeatureText from './FeatureText'
12 | import FieldsBase64 from './images/fields'
13 | import { launchImageLibrary } from 'react-native-image-picker'
14 | import BulletText from './BulletText'
15 |
16 | // @ts-ignore
17 | import FieldsImage from './images/fields.jpg'
18 | // @ts-ignore
19 | import FieldsWebP from './images/fields.webp'
20 | // @ts-ignore
21 | import JellyfishGIF from './images/jellyfish.gif'
22 | // @ts-ignore
23 | import JellyfishWebP from './images/jellyfish.webp'
24 |
25 | const Image = ({ source, ...p }: FastImageProps) => (
26 |
27 | )
28 |
29 | const Row: React.ComponentType = (p: ViewProps) => (
30 |
31 | )
32 |
33 | interface ExampleProps {
34 | name: string
35 | source: any
36 | }
37 |
38 | const Example = ({ name, source }: ExampleProps) => (
39 |
40 | {name}
41 |
42 |
43 | )
44 |
45 | interface PhotoExampleState {
46 | image?: Source
47 | }
48 |
49 | class PhotoExample extends Component<{}, PhotoExampleState> {
50 | state: PhotoExampleState = {}
51 |
52 | pick = () => {
53 | launchImageLibrary({ mediaType: 'photo' }, (response) => {
54 | if (response.didCancel) {
55 | console.log('ImagePicker - User cancelled.')
56 | } else if (response.errorCode) {
57 | console.log(`ImagePicker - Error ${response.errorMessage}.`)
58 | } else {
59 | const uri = response?.assets?.[0]?.uri
60 | if (uri) {
61 | this.setState({
62 | image: { uri: uri },
63 | })
64 | }
65 | }
66 | })
67 | }
68 |
69 | render() {
70 | return (
71 |
72 | photo library
73 |
74 |
78 | Pick Photo
79 |
80 |
81 |
82 | )
83 | }
84 | }
85 |
86 | export const LocalImagesExample = () => (
87 |
88 |
89 | • Local images.
90 |
91 |
92 |
93 |
94 |
95 |
96 |
97 |
98 |
99 |
100 |
101 | )
102 |
103 | const styles = StyleSheet.create({
104 | pickPhoto: { color: 'white', fontWeight: '900' },
105 | row: {
106 | justifyContent: 'center',
107 | alignItems: 'center',
108 | marginBottom: 20,
109 | },
110 | container: {
111 | backgroundColor: '#eee',
112 | justifyContent: 'center',
113 | alignItems: 'center',
114 | paddingTop: 10,
115 | paddingBottom: 10,
116 | },
117 | imageSquare: {
118 | alignItems: 'center',
119 | justifyContent: 'center',
120 | height: 100,
121 | backgroundColor: '#ddd',
122 | margin: 20,
123 | marginTop: 10,
124 | width: 100,
125 | flex: 0,
126 | },
127 | plus: {
128 | width: 30,
129 | height: 30,
130 | position: 'absolute',
131 | bottom: 0,
132 | right: 0,
133 | },
134 | })
135 |
--------------------------------------------------------------------------------
/ReactNativeFastImageExample/src/PreloadExample.tsx:
--------------------------------------------------------------------------------
1 | import React, { useState } from 'react'
2 | import { StyleSheet, View } from 'react-native'
3 | import SectionFlex from './SectionFlex'
4 | import FastImage from 'react-native-fast-image'
5 | import Section from './Section'
6 | import FeatureText from './FeatureText'
7 | import Button from './Button'
8 | // @ts-ignore
9 | import { createImageProgress } from 'react-native-image-progress'
10 | import { useCacheBust } from './useCacheBust'
11 |
12 | const IMAGE_URL =
13 | 'https://cdn-images-1.medium.com/max/1600/1*-CY5bU4OqiJRox7G00sftw.gif'
14 |
15 | const Image = createImageProgress(FastImage)
16 |
17 | export const PreloadExample = () => {
18 | const [show, setShow] = useState(false)
19 | const { url, bust } = useCacheBust(IMAGE_URL)
20 |
21 | const preload = () => {
22 | FastImage.preload([{ uri: url }])
23 | }
24 |
25 | return (
26 |
27 |
31 |
32 | {show ? (
33 |
34 | ) : (
35 |
36 | )}
37 |
38 |
39 |
40 |
41 |
42 |
43 |
44 |
45 |
50 |
51 |
52 |
53 | )
54 | }
55 |
56 | const styles = StyleSheet.create({
57 | buttonView: { flex: 1 },
58 | section: {
59 | flexDirection: 'column',
60 | alignItems: 'center',
61 | },
62 | buttons: {
63 | flexDirection: 'row',
64 | marginHorizontal: 20,
65 | marginBottom: 10,
66 | },
67 | image: {
68 | backgroundColor: '#ddd',
69 | margin: 20,
70 | marginBottom: 10,
71 | height: 100,
72 | width: 100,
73 | },
74 | })
75 |
--------------------------------------------------------------------------------
/ReactNativeFastImageExample/src/PriorityExample.tsx:
--------------------------------------------------------------------------------
1 | import React from 'react'
2 | import { PixelRatio, StyleSheet, View } from 'react-native'
3 | import FastImage from 'react-native-fast-image'
4 | import Section from './Section'
5 | import SectionFlex from './SectionFlex'
6 | import FeatureText from './FeatureText'
7 | import { useCacheBust } from './useCacheBust'
8 |
9 | const getImageUrl = (id: string, width: number, height: number) =>
10 | `https://source.unsplash.com/${id}/${width}x${height}`
11 | const IMAGE_SIZE = 1024
12 | const IMAGE_SIZE_PX = PixelRatio.getPixelSizeForLayoutSize(IMAGE_SIZE)
13 | const IMAGE_URLS = [
14 | getImageUrl('x58soEovG_M', IMAGE_SIZE_PX, IMAGE_SIZE_PX),
15 | getImageUrl('yPI7myL5eWY', IMAGE_SIZE_PX, IMAGE_SIZE_PX),
16 | getImageUrl('S7VCcp6KCKE', IMAGE_SIZE, IMAGE_SIZE),
17 | ]
18 |
19 | export const PriorityExample = () => {
20 | const { query, bust } = useCacheBust('')
21 | return (
22 |
23 |
26 |
27 |
34 |
41 |
48 |
49 |
50 | )
51 | }
52 |
53 | const styles = StyleSheet.create({
54 | image: {
55 | flex: 1,
56 | height: 100,
57 | backgroundColor: '#ddd',
58 | margin: 10,
59 | marginVertical: 20,
60 | },
61 | })
62 |
--------------------------------------------------------------------------------
/ReactNativeFastImageExample/src/ProgressExample.tsx:
--------------------------------------------------------------------------------
1 | import React, { useState } from 'react'
2 | import { StyleSheet, View, Text } from 'react-native'
3 | import SectionFlex from './SectionFlex'
4 | import FastImage from 'react-native-fast-image'
5 | import Section from './Section'
6 | import FeatureText from './FeatureText'
7 | import { useCacheBust } from './useCacheBust'
8 |
9 | const IMAGE_URL = 'https://media.giphy.com/media/GEsoqZDGVoisw/giphy.gif'
10 |
11 | export const ProgressExample = () => {
12 | const [state, setState] = useState<{
13 | mount: number
14 | start?: number
15 | progress?: number
16 | end?: number
17 | }>({
18 | mount: Date.now(),
19 | start: undefined,
20 | progress: undefined,
21 | end: undefined,
22 | })
23 |
24 | const { url, bust } = useCacheBust(IMAGE_URL)
25 | const { mount, start, progress, end } = state
26 | return (
27 |
28 |
31 |
32 |
38 | setState((s) => ({ ...s, start: Date.now() }))
39 | }
40 | onProgress={(e) => {
41 | const p = Math.round(
42 | 100 * (e.nativeEvent.loaded / e.nativeEvent.total),
43 | )
44 | setState((s) => ({
45 | ...s,
46 | progress: p,
47 | }))
48 | }}
49 | onLoad={() => setState((s) => ({ ...s, end: Date.now() }))}
50 | onLoadEnd={() => {}}
51 | />
52 |
53 | onLoadStart
54 | {start !== undefined && ` - ${start - mount} ms`}
55 |
56 |
57 | onProgress
58 | {progress !== undefined && ` - ${progress} %`}
59 |
60 |
61 | onLoad
62 | {end !== undefined && ` - ${end - mount} ms`}
63 |
64 |
65 |
66 | )
67 | }
68 |
69 | const styles = StyleSheet.create({
70 | section: {
71 | flexDirection: 'column',
72 | alignItems: 'center',
73 | paddingBottom: 20,
74 | },
75 | image: {
76 | height: 100,
77 | backgroundColor: '#ddd',
78 | margin: 20,
79 | width: 100,
80 | flex: 0,
81 | },
82 | })
83 |
--------------------------------------------------------------------------------
/ReactNativeFastImageExample/src/ResizeModeExample.tsx:
--------------------------------------------------------------------------------
1 | import React from 'react'
2 | import { StyleSheet, View } from 'react-native'
3 | import SectionFlex from './SectionFlex'
4 | import FastImage from 'react-native-fast-image'
5 | import Section from './Section'
6 | import FeatureText from './FeatureText'
7 | import BulletText from './BulletText'
8 |
9 | const IMAGE_URL = 'https://media.giphy.com/media/GEsoqZDGVoisw/giphy.gif'
10 |
11 | const Col = (p: any) =>
12 |
13 | export const ResizeModeExample = () => (
14 |
15 |
18 |
19 |
20 |
25 | contain
26 |
27 |
28 |
33 | center
34 |
35 |
36 |
41 | stretch
42 |
43 |
44 |
49 | cover
50 |
51 |
52 |
53 | )
54 |
55 | const styles = StyleSheet.create({
56 | image: {
57 | height: 100,
58 | width: 50,
59 | backgroundColor: '#ddd',
60 | margin: 20,
61 | marginTop: 0,
62 | marginBottom: 10,
63 | flex: 0,
64 | },
65 | container: {
66 | padding: 20,
67 | },
68 | col: {
69 | alignItems: 'center',
70 | },
71 | })
72 |
--------------------------------------------------------------------------------
/ReactNativeFastImageExample/src/Section.tsx:
--------------------------------------------------------------------------------
1 | import React from 'react'
2 | import { StyleSheet, View } from 'react-native'
3 |
4 | interface SectionProps {
5 | children?: any
6 | }
7 |
8 | export default function Section({ children }: SectionProps) {
9 | return {children}
10 | }
11 |
12 | const styles = StyleSheet.create({
13 | section: {
14 | marginTop: 10,
15 | marginBottom: 10,
16 | marginLeft: 40,
17 | marginRight: 40,
18 | },
19 | })
20 |
--------------------------------------------------------------------------------
/ReactNativeFastImageExample/src/SectionFlex.tsx:
--------------------------------------------------------------------------------
1 | import React from 'react'
2 | import { StyleSheet, TouchableOpacity, View } from 'react-native'
3 |
4 | interface SectionFlexProps {
5 | style?: any
6 | onPress?: () => void
7 | children?: any
8 | }
9 |
10 | export default function SectionFlex({
11 | children,
12 | onPress,
13 | style,
14 | }: SectionFlexProps) {
15 | return onPress ? (
16 |
17 | {children}
18 |
19 | ) : (
20 | {children}
21 | )
22 | }
23 |
24 | const styles = StyleSheet.create({
25 | sectionFlex: {
26 | backgroundColor: '#eee',
27 | flexDirection: 'row',
28 | justifyContent: 'center',
29 | marginLeft: -10,
30 | marginRight: -10,
31 | },
32 | })
33 |
--------------------------------------------------------------------------------
/ReactNativeFastImageExample/src/StatusBarUnderlay.tsx:
--------------------------------------------------------------------------------
1 | import React from 'react'
2 | import { StyleSheet, View } from 'react-native'
3 | import { getStatusBarHeight } from 'react-native-status-bar-height'
4 |
5 | export const STATUS_BAR_HEIGHT = getStatusBarHeight()
6 |
7 | export default () =>
8 |
9 | const styles = StyleSheet.create({
10 | statusBarUnderlay: {
11 | position: 'absolute',
12 | top: 0,
13 | left: 0,
14 | right: 0,
15 | height: STATUS_BAR_HEIGHT,
16 | backgroundColor: 'white',
17 | },
18 | })
19 |
--------------------------------------------------------------------------------
/ReactNativeFastImageExample/src/TintColorExample.tsx:
--------------------------------------------------------------------------------
1 | import React from 'react'
2 | import { StyleSheet, View } from 'react-native'
3 | import FastImage from 'react-native-fast-image'
4 | import Section from './Section'
5 | import SectionFlex from './SectionFlex'
6 | import FeatureText from './FeatureText'
7 |
8 | // @ts-ignore
9 | import LogoImage from './images/logo.png'
10 |
11 | export const TintColorExample = () => {
12 | return (
13 |
14 |
18 |
19 |
24 |
29 |
34 |
35 |
36 | )
37 | }
38 |
39 | const styles = StyleSheet.create({
40 | image: {
41 | flex: 1,
42 | height: 100,
43 | margin: 10,
44 | },
45 | })
46 |
--------------------------------------------------------------------------------
/ReactNativeFastImageExample/src/images/fields.jpg:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/DylanVann/react-native-fast-image/9ab80fcd570b7f56da66ab20e52c9a35934067c9/ReactNativeFastImageExample/src/images/fields.jpg
--------------------------------------------------------------------------------
/ReactNativeFastImageExample/src/images/fields.webp:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/DylanVann/react-native-fast-image/9ab80fcd570b7f56da66ab20e52c9a35934067c9/ReactNativeFastImageExample/src/images/fields.webp
--------------------------------------------------------------------------------
/ReactNativeFastImageExample/src/images/jellyfish.gif:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/DylanVann/react-native-fast-image/9ab80fcd570b7f56da66ab20e52c9a35934067c9/ReactNativeFastImageExample/src/images/jellyfish.gif
--------------------------------------------------------------------------------
/ReactNativeFastImageExample/src/images/jellyfish.webp:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/DylanVann/react-native-fast-image/9ab80fcd570b7f56da66ab20e52c9a35934067c9/ReactNativeFastImageExample/src/images/jellyfish.webp
--------------------------------------------------------------------------------
/ReactNativeFastImageExample/src/images/logo.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/DylanVann/react-native-fast-image/9ab80fcd570b7f56da66ab20e52c9a35934067c9/ReactNativeFastImageExample/src/images/logo.png
--------------------------------------------------------------------------------
/ReactNativeFastImageExample/src/index.tsx:
--------------------------------------------------------------------------------
1 | import React from 'react'
2 | import { LogBox } from 'react-native'
3 | import { NavigationContainer } from '@react-navigation/native'
4 | import { createBottomTabNavigator } from '@react-navigation/bottom-tabs'
5 | import { Icon } from './Icon'
6 | import FastImageExamples from './FastImageExamples'
7 | import FastImageGrid from './FastImageGrid'
8 | import DefaultImageGrid from './DefaultImageGrid'
9 |
10 | const Tab = createBottomTabNavigator()
11 |
12 | LogBox.ignoreLogs([
13 | 'Warning: isMounted(...) is deprecated',
14 | 'Module RCTImageLoader',
15 | ])
16 |
17 | export default function App() {
18 | return (
19 |
20 |
21 | (
26 |
27 | ),
28 | }}
29 | />
30 | (
35 |
36 | ),
37 | }}
38 | />
39 | (
44 |
45 | ),
46 | }}
47 | />
48 |
49 |
50 | )
51 | }
52 |
--------------------------------------------------------------------------------
/ReactNativeFastImageExample/src/useCacheBust.tsx:
--------------------------------------------------------------------------------
1 | import { useCallback, useState } from 'react'
2 |
3 | const getNewKey = () => Math.random().toString()
4 |
5 | export const useCacheBust = (
6 | url: string,
7 | ): { bust: () => void; url: string; query: string } => {
8 | const [key, setKey] = useState(getNewKey())
9 | const bust = useCallback(() => {
10 | setKey(getNewKey())
11 | }, [])
12 | const query = `?bust=${key}`
13 | return {
14 | url: `${url}${query}`,
15 | query,
16 | bust,
17 | }
18 | }
19 |
--------------------------------------------------------------------------------
/ReactNativeFastImageExample/tsconfig.json:
--------------------------------------------------------------------------------
1 | {
2 | "compilerOptions": {
3 | /* Basic Options */
4 | "target": "esnext" /* Specify ECMAScript target version: 'ES3' (default), 'ES5', 'ES2015', 'ES2016', 'ES2017','ES2018' or 'ESNEXT'. */,
5 | "module": "commonjs" /* Specify module code generation: 'none', 'commonjs', 'amd', 'system', 'umd', 'es2015', or 'ESNext'. */,
6 | "lib": [
7 | "es2017"
8 | ] /* Specify library files to be included in the compilation. */,
9 | "allowJs": true /* Allow javascript files to be compiled. */,
10 | // "checkJs": true, /* Report errors in .js files. */
11 | "jsx": "react-native" /* Specify JSX code generation: 'preserve', 'react-native', or 'react'. */,
12 | // "declaration": true, /* Generates corresponding '.d.ts' file. */
13 | // "sourceMap": true, /* Generates corresponding '.map' file. */
14 | // "outFile": "./", /* Concatenate and emit output to single file. */
15 | // "outDir": "./", /* Redirect output structure to the directory. */
16 | // "rootDir": "./", /* Specify the root directory of input files. Use to control the output directory structure with --outDir. */
17 | // "removeComments": true, /* Do not emit comments to output. */
18 | "noEmit": true /* Do not emit outputs. */,
19 | // "incremental": true, /* Enable incremental compilation */
20 | // "importHelpers": true, /* Import emit helpers from 'tslib'. */
21 | // "downlevelIteration": true, /* Provide full support for iterables in 'for-of', spread, and destructuring when targeting 'ES5' or 'ES3'. */
22 | "isolatedModules": true /* Transpile each file as a separate module (similar to 'ts.transpileModule'). */,
23 |
24 | /* Strict Type-Checking Options */
25 | "strict": true /* Enable all strict type-checking options. */,
26 | // "noImplicitAny": true, /* Raise error on expressions and declarations with an implied 'any' type. */
27 | // "strictNullChecks": true, /* Enable strict null checks. */
28 | // "strictFunctionTypes": true, /* Enable strict checking of function types. */
29 | // "strictPropertyInitialization": true, /* Enable strict checking of property initialization in classes. */
30 | // "noImplicitThis": true, /* Raise error on 'this' expressions with an implied 'any' type. */
31 | // "alwaysStrict": true, /* Parse in strict mode and emit "use strict" for each source file. */
32 |
33 | /* Additional Checks */
34 | // "noUnusedLocals": true, /* Report errors on unused locals. */
35 | // "noUnusedParameters": true, /* Report errors on unused parameters. */
36 | // "noImplicitReturns": true, /* Report error when not all code paths in function return a value. */
37 | // "noFallthroughCasesInSwitch": true, /* Report errors for fallthrough cases in switch statement. */
38 |
39 | /* Module Resolution Options */
40 | "moduleResolution": "node" /* Specify module resolution strategy: 'node' (Node.js) or 'classic' (TypeScript pre-1.6). */,
41 | // "baseUrl": "./", /* Base directory to resolve non-absolute module names. */
42 | // "paths": {}, /* A series of entries which re-map imports to lookup locations relative to the 'baseUrl'. */
43 | // "rootDirs": [], /* List of root folders whose combined content represents the structure of the project at runtime. */
44 | // "typeRoots": [], /* List of folders to include type definitions from. */
45 | // "types": [], /* Type declaration files to be included in compilation. */
46 | "allowSyntheticDefaultImports": true /* Allow default imports from modules with no default export. This does not affect code emit, just typechecking. */,
47 | "esModuleInterop": true /* Enables emit interoperability between CommonJS and ES Modules via creation of namespace objects for all imports. Implies 'allowSyntheticDefaultImports'. */,
48 | // "preserveSymlinks": true, /* Do not resolve the real path of symlinks. */
49 | "skipLibCheck": false /* Skip type checking of declaration files. */,
50 | "resolveJsonModule": true /* Allows importing modules with a ‘.json’ extension, which is a common practice in node projects. */
51 |
52 | /* Source Map Options */
53 | // "sourceRoot": "./", /* Specify the location where debugger should locate TypeScript files instead of source locations. */
54 | // "mapRoot": "./", /* Specify the location where debugger should locate map files instead of generated locations. */
55 | // "inlineSourceMap": true, /* Emit a single file with source maps instead of having a separate file. */
56 | // "inlineSources": true, /* Emit the source alongside the sourcemaps within a single file; requires '--inlineSourceMap' or '--sourceMap' to be set. */
57 |
58 | /* Experimental Options */
59 | // "experimentalDecorators": true, /* Enables experimental support for ES7 decorators. */
60 | // "emitDecoratorMetadata": true, /* Enables experimental support for emitting type metadata for decorators. */
61 | },
62 | "exclude": [
63 | "node_modules",
64 | "babel.config.js",
65 | "metro.config.js",
66 | "jest.config.js"
67 | ]
68 | }
69 |
--------------------------------------------------------------------------------
/ReactNativeFastImageExampleServer/index.js:
--------------------------------------------------------------------------------
1 | const path = require('path')
2 | const express = require('express')
3 | const bodyParser = require('body-parser')
4 | const morgan = require('morgan')
5 |
6 | const app = express()
7 |
8 | const port = process.env.PORT || 8080
9 | const welcome = 'Test images API at http://localhost:' + port
10 | console.log(welcome)
11 |
12 | app.use(bodyParser.urlencoded({ extended: false }))
13 | app.use(bodyParser.json())
14 | app.use(morgan('dev'))
15 | app.get('/', (req, res) => res.send(welcome))
16 | app.listen(port)
17 |
18 | const authentication = (req, res, next) => {
19 | const token = req.query.token || req.headers['token']
20 | if (token) {
21 | next()
22 | } else {
23 | return res.status(403).send({ success: false })
24 | }
25 | }
26 |
27 | const staticPictures = express.static(path.join(__dirname, 'pictures'))
28 |
29 | app.use('/pictures', authentication, staticPictures)
30 |
--------------------------------------------------------------------------------
/ReactNativeFastImageExampleServer/package.json:
--------------------------------------------------------------------------------
1 | {
2 | "name": "ReactNativeFastImageExampleServer",
3 | "version": "0.0.1",
4 | "private": true,
5 | "license": "MIT",
6 | "main": "index.js",
7 | "scripts": {
8 | "start": "nodemon ./index"
9 | },
10 | "dependencies": {
11 | "body-parser": "^1.19.0",
12 | "express": "^4.17.1",
13 | "morgan": "^1.9.1",
14 | "nodemon": "^2.0.2"
15 | }
16 | }
17 |
--------------------------------------------------------------------------------
/ReactNativeFastImageExampleServer/pictures/fields.jpg:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/DylanVann/react-native-fast-image/9ab80fcd570b7f56da66ab20e52c9a35934067c9/ReactNativeFastImageExampleServer/pictures/fields.jpg
--------------------------------------------------------------------------------
/ReactNativeFastImageExampleServer/pictures/forest.jpg:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/DylanVann/react-native-fast-image/9ab80fcd570b7f56da66ab20e52c9a35934067c9/ReactNativeFastImageExampleServer/pictures/forest.jpg
--------------------------------------------------------------------------------
/ReactNativeFastImageExampleServer/pictures/harbor.jpg:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/DylanVann/react-native-fast-image/9ab80fcd570b7f56da66ab20e52c9a35934067c9/ReactNativeFastImageExampleServer/pictures/harbor.jpg
--------------------------------------------------------------------------------
/ReactNativeFastImageExampleServer/pictures/jellyfish.gif:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/DylanVann/react-native-fast-image/9ab80fcd570b7f56da66ab20e52c9a35934067c9/ReactNativeFastImageExampleServer/pictures/jellyfish.gif
--------------------------------------------------------------------------------
/ReactNativeFastImageExampleServer/pictures/jellyfish.webp:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/DylanVann/react-native-fast-image/9ab80fcd570b7f56da66ab20e52c9a35934067c9/ReactNativeFastImageExampleServer/pictures/jellyfish.webp
--------------------------------------------------------------------------------
/ReactNativeFastImageExampleServer/pictures/plankton.gif:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/DylanVann/react-native-fast-image/9ab80fcd570b7f56da66ab20e52c9a35934067c9/ReactNativeFastImageExampleServer/pictures/plankton.gif
--------------------------------------------------------------------------------
/android/build.gradle:
--------------------------------------------------------------------------------
1 | def safeExtGet(prop, fallback) {
2 | rootProject.ext.has(prop) ? rootProject.ext.get(prop) : fallback
3 | }
4 |
5 | buildscript {
6 | // The Android Gradle plugin is only required when opening the android folder stand-alone.
7 | // This avoids unnecessary downloads and potential conflicts when the library is included as a
8 | // module dependency in an application project.
9 | if (project == rootProject) {
10 | repositories {
11 | google()
12 | mavenCentral()
13 | }
14 | dependencies {
15 | classpath 'com.android.tools.build:gradle:3.5.3'
16 | }
17 | }
18 | }
19 |
20 | apply plugin: 'com.android.library'
21 |
22 | android {
23 | compileSdkVersion safeExtGet('compileSdkVersion', 28)
24 | buildToolsVersion safeExtGet('buildToolsVersion', '28.0.3')
25 | defaultConfig {
26 | minSdkVersion safeExtGet('minSdkVersion', 16)
27 | targetSdkVersion safeExtGet('targetSdkVersion', 28)
28 | versionCode 1
29 | versionName "1.0"
30 | }
31 | sourceSets {
32 | main {
33 | java {
34 | if (safeExtGet('excludeAppGlideModule', false)) {
35 | srcDir "src"
36 | exclude "**/FastImageGlideModule.java"
37 | }
38 | }
39 | }
40 | }
41 | lintOptions {
42 | abortOnError false
43 | }
44 | }
45 |
46 | repositories {
47 | mavenLocal()
48 | maven {
49 | // All of React Native (JS, Obj-C sources, Android binaries) is installed from npm
50 | url "$rootDir/../node_modules/react-native/android"
51 | }
52 | maven {
53 | // Android JSC is installed from npm
54 | url "$rootDir/../node_modules/jsc-android/dist"
55 | }
56 | google()
57 | mavenCentral()
58 | }
59 |
60 | def glideVersion = safeExtGet('glideVersion', '4.12.0')
61 |
62 | dependencies {
63 | //noinspection GradleDynamicVersion
64 | implementation 'com.facebook.react:react-native:+' // From node_modules
65 | implementation "com.github.bumptech.glide:glide:${glideVersion}"
66 | implementation "com.github.bumptech.glide:okhttp3-integration:${glideVersion}"
67 | annotationProcessor "com.github.bumptech.glide:compiler:${glideVersion}"
68 | }
69 |
--------------------------------------------------------------------------------
/android/src/main/AndroidManifest.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 |
--------------------------------------------------------------------------------
/android/src/main/java/com/dylanvann/fastimage/FastImageCacheControl.java:
--------------------------------------------------------------------------------
1 | package com.dylanvann.fastimage;
2 |
3 | public enum FastImageCacheControl {
4 | IMMUTABLE,
5 | WEB,
6 | CACHE_ONLY
7 | }
8 |
9 |
--------------------------------------------------------------------------------
/android/src/main/java/com/dylanvann/fastimage/FastImageGlideModule.java:
--------------------------------------------------------------------------------
1 | package com.dylanvann.fastimage;
2 |
3 | import com.bumptech.glide.annotation.GlideModule;
4 | import com.bumptech.glide.module.AppGlideModule;
5 |
6 | // We need an AppGlideModule to be present for progress events to work.
7 | @GlideModule
8 | public final class FastImageGlideModule extends AppGlideModule {
9 | }
10 |
--------------------------------------------------------------------------------
/android/src/main/java/com/dylanvann/fastimage/FastImageOkHttpProgressGlideModule.java:
--------------------------------------------------------------------------------
1 | package com.dylanvann.fastimage;
2 |
3 | import android.content.Context;
4 | import androidx.annotation.NonNull;
5 |
6 | import com.bumptech.glide.Glide;
7 | import com.bumptech.glide.Registry;
8 | import com.bumptech.glide.annotation.GlideModule;
9 | import com.bumptech.glide.integration.okhttp3.OkHttpUrlLoader;
10 | import com.bumptech.glide.load.model.GlideUrl;
11 | import com.bumptech.glide.module.LibraryGlideModule;
12 | import com.facebook.react.modules.network.OkHttpClientProvider;
13 |
14 | import java.io.IOException;
15 | import java.io.InputStream;
16 | import java.util.HashMap;
17 | import java.util.Map;
18 | import java.util.WeakHashMap;
19 |
20 | import okhttp3.Interceptor;
21 | import okhttp3.MediaType;
22 | import okhttp3.OkHttpClient;
23 | import okhttp3.Request;
24 | import okhttp3.Response;
25 | import okhttp3.ResponseBody;
26 | import okio.Buffer;
27 | import okio.BufferedSource;
28 | import okio.ForwardingSource;
29 | import okio.Okio;
30 | import okio.Source;
31 |
32 | @GlideModule
33 | public class FastImageOkHttpProgressGlideModule extends LibraryGlideModule {
34 |
35 | private static final DispatchingProgressListener progressListener = new DispatchingProgressListener();
36 |
37 | @Override
38 | public void registerComponents(
39 | @NonNull Context context,
40 | @NonNull Glide glide,
41 | @NonNull Registry registry
42 | ) {
43 | OkHttpClient client = OkHttpClientProvider
44 | .getOkHttpClient()
45 | .newBuilder()
46 | .addInterceptor(createInterceptor(progressListener))
47 | .build();
48 | OkHttpUrlLoader.Factory factory = new OkHttpUrlLoader.Factory(client);
49 | registry.replace(GlideUrl.class, InputStream.class, factory);
50 | }
51 |
52 | private static Interceptor createInterceptor(final ResponseProgressListener listener) {
53 | return new Interceptor() {
54 | @Override
55 | public Response intercept(Chain chain) throws IOException {
56 | Request request = chain.request();
57 | Response response = chain.proceed(request);
58 | final String key = request.url().toString();
59 | return response
60 | .newBuilder()
61 | .body(new OkHttpProgressResponseBody(key, response.body(), listener))
62 | .build();
63 | }
64 | };
65 | }
66 |
67 | static void forget(String key) {
68 | progressListener.forget(key);
69 | }
70 |
71 | static void expect(String key, FastImageProgressListener listener) {
72 | progressListener.expect(key, listener);
73 | }
74 |
75 | private interface ResponseProgressListener {
76 | void update(String key, long bytesRead, long contentLength);
77 | }
78 |
79 | private static class DispatchingProgressListener implements ResponseProgressListener {
80 | private final Map LISTENERS = new WeakHashMap<>();
81 | private final Map PROGRESSES = new HashMap<>();
82 |
83 | void forget(String key) {
84 | LISTENERS.remove(key);
85 | PROGRESSES.remove(key);
86 | }
87 |
88 | void expect(String key, FastImageProgressListener listener) {
89 | LISTENERS.put(key, listener);
90 | }
91 |
92 | @Override
93 | public void update(final String key, final long bytesRead, final long contentLength) {
94 | final FastImageProgressListener listener = LISTENERS.get(key);
95 | if (listener == null) {
96 | return;
97 | }
98 | if (contentLength <= bytesRead) {
99 | forget(key);
100 | }
101 | if (needsDispatch(key, bytesRead, contentLength, listener.getGranularityPercentage())) {
102 | listener.onProgress(key, bytesRead, contentLength);
103 | }
104 | }
105 |
106 | private boolean needsDispatch(String key, long current, long total, float granularity) {
107 | if (granularity == 0 || current == 0 || total == current) {
108 | return true;
109 | }
110 | float percent = 100f * current / total;
111 | long currentProgress = (long) (percent / granularity);
112 | Long lastProgress = PROGRESSES.get(key);
113 | if (lastProgress == null || currentProgress != lastProgress) {
114 | PROGRESSES.put(key, currentProgress);
115 | return true;
116 | } else {
117 | return false;
118 | }
119 | }
120 | }
121 |
122 | private static class OkHttpProgressResponseBody extends ResponseBody {
123 | private final String key;
124 | private final ResponseBody responseBody;
125 | private final ResponseProgressListener progressListener;
126 | private BufferedSource bufferedSource;
127 |
128 | OkHttpProgressResponseBody(
129 | String key,
130 | ResponseBody responseBody,
131 | ResponseProgressListener progressListener
132 | ) {
133 | this.key = key;
134 | this.responseBody = responseBody;
135 | this.progressListener = progressListener;
136 | }
137 |
138 | @Override
139 | public MediaType contentType() {
140 | return responseBody.contentType();
141 | }
142 |
143 | @Override
144 | public long contentLength() {
145 | return responseBody.contentLength();
146 | }
147 |
148 | @Override
149 | public BufferedSource source() {
150 | if (bufferedSource == null) {
151 | bufferedSource = Okio.buffer(source(responseBody.source()));
152 | }
153 | return bufferedSource;
154 | }
155 |
156 | private Source source(Source source) {
157 | return new ForwardingSource(source) {
158 | long totalBytesRead = 0L;
159 |
160 | @Override
161 | public long read(Buffer sink, long byteCount) throws IOException {
162 | long bytesRead = super.read(sink, byteCount);
163 | long fullLength = responseBody.contentLength();
164 | if (bytesRead == -1) {
165 | // this source is exhausted
166 | totalBytesRead = fullLength;
167 | } else {
168 | totalBytesRead += bytesRead;
169 | }
170 | progressListener.update(key, totalBytesRead, fullLength);
171 | return bytesRead;
172 | }
173 | };
174 | }
175 | }
176 | }
177 |
--------------------------------------------------------------------------------
/android/src/main/java/com/dylanvann/fastimage/FastImageProgressListener.java:
--------------------------------------------------------------------------------
1 | package com.dylanvann.fastimage;
2 |
3 | public interface FastImageProgressListener {
4 |
5 | void onProgress(String key, long bytesRead, long expectedLength);
6 |
7 | /**
8 | * Control how often the listener needs an update. 0% and 100% will always be dispatched.
9 | *
10 | * @return in percentage (0.2 = call {@link #onProgress} around every 0.2 percent of progress)
11 | */
12 | float getGranularityPercentage();
13 |
14 | }
15 |
--------------------------------------------------------------------------------
/android/src/main/java/com/dylanvann/fastimage/FastImageRequestListener.java:
--------------------------------------------------------------------------------
1 | package com.dylanvann.fastimage;
2 |
3 | import android.graphics.drawable.Drawable;
4 |
5 | import com.bumptech.glide.load.DataSource;
6 | import com.bumptech.glide.load.engine.GlideException;
7 | import com.bumptech.glide.request.RequestListener;
8 | import com.bumptech.glide.request.target.ImageViewTarget;
9 | import com.bumptech.glide.request.target.Target;
10 | import com.facebook.react.bridge.WritableMap;
11 | import com.facebook.react.bridge.WritableNativeMap;
12 | import com.facebook.react.uimanager.ThemedReactContext;
13 | import com.facebook.react.uimanager.events.RCTEventEmitter;
14 |
15 | public class FastImageRequestListener implements RequestListener {
16 | static final String REACT_ON_ERROR_EVENT = "onFastImageError";
17 | static final String REACT_ON_LOAD_EVENT = "onFastImageLoad";
18 | static final String REACT_ON_LOAD_END_EVENT = "onFastImageLoadEnd";
19 | private final String key;
20 |
21 | FastImageRequestListener(String key) {
22 | this.key = key;
23 | }
24 |
25 | private static WritableMap mapFromResource(Drawable resource) {
26 | WritableMap resourceData = new WritableNativeMap();
27 | resourceData.putInt("width", resource.getIntrinsicWidth());
28 | resourceData.putInt("height", resource.getIntrinsicHeight());
29 | return resourceData;
30 | }
31 |
32 | @Override
33 | public boolean onLoadFailed(@androidx.annotation.Nullable GlideException e, Object model, Target target, boolean isFirstResource) {
34 | FastImageOkHttpProgressGlideModule.forget(key);
35 | if (!(target instanceof ImageViewTarget)) {
36 | return false;
37 | }
38 | FastImageViewWithUrl view = (FastImageViewWithUrl) ((ImageViewTarget) target).getView();
39 | ThemedReactContext context = (ThemedReactContext) view.getContext();
40 | RCTEventEmitter eventEmitter = context.getJSModule(RCTEventEmitter.class);
41 | int viewId = view.getId();
42 | eventEmitter.receiveEvent(viewId, REACT_ON_ERROR_EVENT, new WritableNativeMap());
43 | eventEmitter.receiveEvent(viewId, REACT_ON_LOAD_END_EVENT, new WritableNativeMap());
44 | return false;
45 | }
46 |
47 | @Override
48 | public boolean onResourceReady(Drawable resource, Object model, Target target, DataSource dataSource, boolean isFirstResource) {
49 | if (!(target instanceof ImageViewTarget)) {
50 | return false;
51 | }
52 | FastImageViewWithUrl view = (FastImageViewWithUrl) ((ImageViewTarget) target).getView();
53 | ThemedReactContext context = (ThemedReactContext) view.getContext();
54 | RCTEventEmitter eventEmitter = context.getJSModule(RCTEventEmitter.class);
55 | int viewId = view.getId();
56 | eventEmitter.receiveEvent(viewId, REACT_ON_LOAD_EVENT, mapFromResource(resource));
57 | eventEmitter.receiveEvent(viewId, REACT_ON_LOAD_END_EVENT, new WritableNativeMap());
58 | return false;
59 | }
60 | }
61 |
--------------------------------------------------------------------------------
/android/src/main/java/com/dylanvann/fastimage/FastImageSource.java:
--------------------------------------------------------------------------------
1 | package com.dylanvann.fastimage;
2 |
3 | import android.content.Context;
4 | import android.content.res.Resources;
5 | import android.net.Uri;
6 | import android.text.TextUtils;
7 |
8 | import com.bumptech.glide.load.model.GlideUrl;
9 | import com.bumptech.glide.load.model.Headers;
10 | import com.facebook.react.views.imagehelper.ImageSource;
11 |
12 | import javax.annotation.Nullable;
13 |
14 | public class FastImageSource extends ImageSource {
15 | private static final String DATA_SCHEME = "data";
16 | private static final String LOCAL_RESOURCE_SCHEME = "res";
17 | private static final String ANDROID_RESOURCE_SCHEME = "android.resource";
18 | private static final String ANDROID_CONTENT_SCHEME = "content";
19 | private static final String LOCAL_FILE_SCHEME = "file";
20 | private final Headers mHeaders;
21 | private Uri mUri;
22 |
23 | public static boolean isBase64Uri(Uri uri) {
24 | return DATA_SCHEME.equals(uri.getScheme());
25 | }
26 |
27 | public static boolean isLocalResourceUri(Uri uri) {
28 | return LOCAL_RESOURCE_SCHEME.equals(uri.getScheme());
29 | }
30 |
31 | public static boolean isResourceUri(Uri uri) {
32 | return ANDROID_RESOURCE_SCHEME.equals(uri.getScheme());
33 | }
34 |
35 | public static boolean isContentUri(Uri uri) {
36 | return ANDROID_CONTENT_SCHEME.equals(uri.getScheme());
37 | }
38 |
39 | public static boolean isLocalFileUri(Uri uri) {
40 | return LOCAL_FILE_SCHEME.equals(uri.getScheme());
41 | }
42 |
43 | public FastImageSource(Context context, String source) {
44 | this(context, source, null);
45 | }
46 |
47 | public FastImageSource(Context context, String source, @Nullable Headers headers) {
48 | this(context, source, 0.0d, 0.0d, headers);
49 | }
50 |
51 | public FastImageSource(Context context, String source, double width, double height, @Nullable Headers headers) {
52 | super(context, source, width, height);
53 | mHeaders = headers == null ? Headers.DEFAULT : headers;
54 | mUri = super.getUri();
55 |
56 | if (isResource() && TextUtils.isEmpty(mUri.toString())) {
57 | throw new Resources.NotFoundException("Local Resource Not Found. Resource: '" + getSource() + "'.");
58 | }
59 |
60 | if (isLocalResourceUri(mUri)) {
61 | // Convert res:/ scheme to android.resource:// so
62 | // glide can understand the uri.
63 | mUri = Uri.parse(mUri.toString().replace("res:/", ANDROID_RESOURCE_SCHEME + "://" + context.getPackageName() + "/"));
64 | }
65 | }
66 |
67 |
68 | public boolean isBase64Resource() {
69 | return mUri != null && FastImageSource.isBase64Uri(mUri);
70 | }
71 |
72 | public boolean isResource() {
73 | return mUri != null && FastImageSource.isResourceUri(mUri);
74 | }
75 |
76 | public boolean isLocalFile() {
77 | return mUri != null && FastImageSource.isLocalFileUri(mUri);
78 | }
79 |
80 | public boolean isContentUri() {
81 | return mUri != null && FastImageSource.isContentUri(mUri);
82 | }
83 |
84 | public Object getSourceForLoad() {
85 | if (isContentUri()) {
86 | return getSource();
87 | }
88 | if (isBase64Resource()) {
89 | return getSource();
90 | }
91 | if (isResource()) {
92 | return getUri();
93 | }
94 | if (isLocalFile()) {
95 | return getUri().toString();
96 | }
97 | return getGlideUrl();
98 | }
99 |
100 | @Override
101 | public Uri getUri() {
102 | return mUri;
103 | }
104 |
105 | public Headers getHeaders() {
106 | return mHeaders;
107 | }
108 |
109 | public GlideUrl getGlideUrl() {
110 | return new GlideUrl(getUri().toString(), getHeaders());
111 | }
112 | }
113 |
--------------------------------------------------------------------------------
/android/src/main/java/com/dylanvann/fastimage/FastImageViewConverter.java:
--------------------------------------------------------------------------------
1 | package com.dylanvann.fastimage;
2 |
3 | import static com.bumptech.glide.request.RequestOptions.signatureOf;
4 |
5 | import android.content.Context;
6 | import android.graphics.Color;
7 | import android.graphics.drawable.ColorDrawable;
8 | import android.graphics.drawable.Drawable;
9 | import android.widget.ImageView;
10 | import android.widget.ImageView.ScaleType;
11 |
12 | import com.bumptech.glide.Priority;
13 | import com.bumptech.glide.load.engine.DiskCacheStrategy;
14 | import com.bumptech.glide.load.model.Headers;
15 | import com.bumptech.glide.load.model.LazyHeaders;
16 | import com.bumptech.glide.request.RequestOptions;
17 | import com.bumptech.glide.signature.ApplicationVersionSignature;
18 | import com.facebook.react.bridge.JSApplicationIllegalArgumentException;
19 | import com.facebook.react.bridge.NoSuchKeyException;
20 | import com.facebook.react.bridge.ReadableMap;
21 | import com.facebook.react.bridge.ReadableMapKeySetIterator;
22 |
23 | import java.util.HashMap;
24 | import java.util.Map;
25 |
26 | import javax.annotation.Nullable;
27 |
28 | class FastImageViewConverter {
29 | private static final Drawable TRANSPARENT_DRAWABLE = new ColorDrawable(Color.TRANSPARENT);
30 |
31 | private static final Map FAST_IMAGE_CACHE_CONTROL_MAP =
32 | new HashMap() {{
33 | put("immutable", FastImageCacheControl.IMMUTABLE);
34 | put("web", FastImageCacheControl.WEB);
35 | put("cacheOnly", FastImageCacheControl.CACHE_ONLY);
36 | }};
37 |
38 | private static final Map FAST_IMAGE_PRIORITY_MAP =
39 | new HashMap() {{
40 | put("low", Priority.LOW);
41 | put("normal", Priority.NORMAL);
42 | put("high", Priority.HIGH);
43 | }};
44 |
45 | private static final Map FAST_IMAGE_RESIZE_MODE_MAP =
46 | new HashMap() {{
47 | put("contain", ScaleType.FIT_CENTER);
48 | put("cover", ScaleType.CENTER_CROP);
49 | put("stretch", ScaleType.FIT_XY);
50 | put("center", ScaleType.CENTER_INSIDE);
51 | }};
52 |
53 | // Resolve the source uri to a file path that android understands.
54 | static @Nullable
55 | FastImageSource getImageSource(Context context, @Nullable ReadableMap source) {
56 | return source == null
57 | ? null
58 | : new FastImageSource(context, source.getString("uri"), getHeaders(source));
59 | }
60 |
61 | static Headers getHeaders(ReadableMap source) {
62 | Headers headers = Headers.DEFAULT;
63 |
64 | if (source.hasKey("headers")) {
65 | ReadableMap headersMap = source.getMap("headers");
66 | ReadableMapKeySetIterator iterator = headersMap.keySetIterator();
67 | LazyHeaders.Builder builder = new LazyHeaders.Builder();
68 |
69 | while (iterator.hasNextKey()) {
70 | String header = iterator.nextKey();
71 | String value = headersMap.getString(header);
72 |
73 | builder.addHeader(header, value);
74 | }
75 |
76 | headers = builder.build();
77 | }
78 |
79 | return headers;
80 | }
81 |
82 | static RequestOptions getOptions(Context context, FastImageSource imageSource, ReadableMap source) {
83 | // Get priority.
84 | final Priority priority = FastImageViewConverter.getPriority(source);
85 | // Get cache control method.
86 | final FastImageCacheControl cacheControl = FastImageViewConverter.getCacheControl(source);
87 | DiskCacheStrategy diskCacheStrategy = DiskCacheStrategy.AUTOMATIC;
88 | boolean onlyFromCache = false;
89 | boolean skipMemoryCache = false;
90 | switch (cacheControl) {
91 | case WEB:
92 | // If using none then OkHttp integration should be used for caching.
93 | diskCacheStrategy = DiskCacheStrategy.NONE;
94 | skipMemoryCache = true;
95 | break;
96 | case CACHE_ONLY:
97 | onlyFromCache = true;
98 | break;
99 | case IMMUTABLE:
100 | // Use defaults.
101 | break;
102 | }
103 |
104 | RequestOptions options = new RequestOptions()
105 | .diskCacheStrategy(diskCacheStrategy)
106 | .onlyRetrieveFromCache(onlyFromCache)
107 | .skipMemoryCache(skipMemoryCache)
108 | .priority(priority)
109 | .placeholder(TRANSPARENT_DRAWABLE);
110 |
111 | if (imageSource.isResource()) {
112 | // Every local resource (drawable) in Android has its own unique numeric id, which are
113 | // generated at build time. Although these ids are unique, they are not guaranteed unique
114 | // across builds. The underlying glide implementation caches these resources. To make
115 | // sure the cache does not return the wrong image, we should clear the cache when the
116 | // application version changes. Adding a cache signature for only these local resources
117 | // solves this issue: https://github.com/DylanVann/react-native-fast-image/issues/402
118 | options = options.apply(signatureOf(ApplicationVersionSignature.obtain(context)));
119 | }
120 |
121 | return options;
122 | }
123 |
124 | private static FastImageCacheControl getCacheControl(ReadableMap source) {
125 | return getValueFromSource("cache", "immutable", FAST_IMAGE_CACHE_CONTROL_MAP, source);
126 | }
127 |
128 | private static Priority getPriority(ReadableMap source) {
129 | return getValueFromSource("priority", "normal", FAST_IMAGE_PRIORITY_MAP, source);
130 | }
131 |
132 | static ScaleType getScaleType(String propValue) {
133 | return getValue("resizeMode", "cover", FAST_IMAGE_RESIZE_MODE_MAP, propValue);
134 | }
135 |
136 | private static T getValue(String propName, String defaultPropValue, Map map, String propValue) {
137 | if (propValue == null) propValue = defaultPropValue;
138 | T value = map.get(propValue);
139 | if (value == null)
140 | throw new JSApplicationIllegalArgumentException("FastImage, invalid " + propName + " : " + propValue);
141 | return value;
142 | }
143 |
144 | private static T getValueFromSource(String propName, String defaultProp, Map map, ReadableMap source) {
145 | String propValue;
146 | try {
147 | propValue = source != null ? source.getString(propName) : null;
148 | } catch (NoSuchKeyException e) {
149 | propValue = null;
150 | }
151 | return getValue(propName, defaultProp, map, propValue);
152 | }
153 | }
154 |
--------------------------------------------------------------------------------
/android/src/main/java/com/dylanvann/fastimage/FastImageViewManager.java:
--------------------------------------------------------------------------------
1 | package com.dylanvann.fastimage;
2 |
3 | import static com.dylanvann.fastimage.FastImageRequestListener.REACT_ON_ERROR_EVENT;
4 | import static com.dylanvann.fastimage.FastImageRequestListener.REACT_ON_LOAD_END_EVENT;
5 | import static com.dylanvann.fastimage.FastImageRequestListener.REACT_ON_LOAD_EVENT;
6 |
7 | import android.app.Activity;
8 | import android.content.Context;
9 | import android.content.ContextWrapper;
10 | import android.graphics.PorterDuff;
11 | import android.os.Build;
12 |
13 | import androidx.annotation.NonNull;
14 |
15 | import com.bumptech.glide.Glide;
16 | import com.bumptech.glide.RequestManager;
17 | import com.facebook.react.bridge.ReadableMap;
18 | import com.facebook.react.bridge.WritableMap;
19 | import com.facebook.react.bridge.WritableNativeMap;
20 | import com.facebook.react.common.MapBuilder;
21 | import com.facebook.react.uimanager.SimpleViewManager;
22 | import com.facebook.react.uimanager.ThemedReactContext;
23 | import com.facebook.react.uimanager.annotations.ReactProp;
24 | import com.facebook.react.uimanager.events.RCTEventEmitter;
25 | import com.facebook.react.views.imagehelper.ResourceDrawableIdHelper;
26 |
27 | import java.util.List;
28 | import java.util.Map;
29 | import java.util.WeakHashMap;
30 |
31 | import javax.annotation.Nullable;
32 |
33 | class FastImageViewManager extends SimpleViewManager implements FastImageProgressListener {
34 |
35 | static final String REACT_CLASS = "FastImageView";
36 | static final String REACT_ON_LOAD_START_EVENT = "onFastImageLoadStart";
37 | static final String REACT_ON_PROGRESS_EVENT = "onFastImageProgress";
38 | private static final Map> VIEWS_FOR_URLS = new WeakHashMap<>();
39 |
40 | @Nullable
41 | private RequestManager requestManager = null;
42 |
43 | @NonNull
44 | @Override
45 | public String getName() {
46 | return REACT_CLASS;
47 | }
48 |
49 | @NonNull
50 | @Override
51 | protected FastImageViewWithUrl createViewInstance(@NonNull ThemedReactContext reactContext) {
52 | if (isValidContextForGlide(reactContext)) {
53 | requestManager = Glide.with(reactContext);
54 | }
55 |
56 | return new FastImageViewWithUrl(reactContext);
57 | }
58 |
59 | @ReactProp(name = "source")
60 | public void setSource(FastImageViewWithUrl view, @Nullable ReadableMap source) {
61 | view.setSource(source);
62 | }
63 |
64 | @ReactProp(name = "defaultSource")
65 | public void setDefaultSource(FastImageViewWithUrl view, @Nullable String source) {
66 | view.setDefaultSource(
67 | ResourceDrawableIdHelper.getInstance()
68 | .getResourceDrawable(view.getContext(), source));
69 | }
70 |
71 | @ReactProp(name = "tintColor", customType = "Color")
72 | public void setTintColor(FastImageViewWithUrl view, @Nullable Integer color) {
73 | if (color == null) {
74 | view.clearColorFilter();
75 | } else {
76 | view.setColorFilter(color, PorterDuff.Mode.SRC_IN);
77 | }
78 | }
79 |
80 | @ReactProp(name = "resizeMode")
81 | public void setResizeMode(FastImageViewWithUrl view, String resizeMode) {
82 | final FastImageViewWithUrl.ScaleType scaleType = FastImageViewConverter.getScaleType(resizeMode);
83 | view.setScaleType(scaleType);
84 | }
85 |
86 | @Override
87 | public void onDropViewInstance(@NonNull FastImageViewWithUrl view) {
88 | // This will cancel existing requests.
89 | view.clearView(requestManager);
90 |
91 | if (view.glideUrl != null) {
92 | final String key = view.glideUrl.toString();
93 | FastImageOkHttpProgressGlideModule.forget(key);
94 | List viewsForKey = VIEWS_FOR_URLS.get(key);
95 | if (viewsForKey != null) {
96 | viewsForKey.remove(view);
97 | if (viewsForKey.size() == 0) VIEWS_FOR_URLS.remove(key);
98 | }
99 | }
100 |
101 | super.onDropViewInstance(view);
102 | }
103 |
104 | @Override
105 | public Map getExportedCustomDirectEventTypeConstants() {
106 | return MapBuilder.builder()
107 | .put(REACT_ON_LOAD_START_EVENT, MapBuilder.of("registrationName", REACT_ON_LOAD_START_EVENT))
108 | .put(REACT_ON_PROGRESS_EVENT, MapBuilder.of("registrationName", REACT_ON_PROGRESS_EVENT))
109 | .put(REACT_ON_LOAD_EVENT, MapBuilder.of("registrationName", REACT_ON_LOAD_EVENT))
110 | .put(REACT_ON_ERROR_EVENT, MapBuilder.of("registrationName", REACT_ON_ERROR_EVENT))
111 | .put(REACT_ON_LOAD_END_EVENT, MapBuilder.of("registrationName", REACT_ON_LOAD_END_EVENT))
112 | .build();
113 | }
114 |
115 | @Override
116 | public void onProgress(String key, long bytesRead, long expectedLength) {
117 | List viewsForKey = VIEWS_FOR_URLS.get(key);
118 | if (viewsForKey != null) {
119 | for (FastImageViewWithUrl view : viewsForKey) {
120 | WritableMap event = new WritableNativeMap();
121 | event.putInt("loaded", (int) bytesRead);
122 | event.putInt("total", (int) expectedLength);
123 | ThemedReactContext context = (ThemedReactContext) view.getContext();
124 | RCTEventEmitter eventEmitter = context.getJSModule(RCTEventEmitter.class);
125 | int viewId = view.getId();
126 | eventEmitter.receiveEvent(viewId, REACT_ON_PROGRESS_EVENT, event);
127 | }
128 | }
129 | }
130 |
131 | @Override
132 | public float getGranularityPercentage() {
133 | return 0.5f;
134 | }
135 |
136 | private static boolean isValidContextForGlide(final Context context) {
137 | Activity activity = getActivityFromContext(context);
138 |
139 | if (activity == null) {
140 | return false;
141 | }
142 |
143 | return !isActivityDestroyed(activity);
144 | }
145 |
146 | private static Activity getActivityFromContext(final Context context) {
147 | if (context instanceof Activity) {
148 | return (Activity) context;
149 | }
150 |
151 | if (context instanceof ThemedReactContext) {
152 | final Context baseContext = ((ThemedReactContext) context).getBaseContext();
153 | if (baseContext instanceof Activity) {
154 | return (Activity) baseContext;
155 | }
156 |
157 | if (baseContext instanceof ContextWrapper) {
158 | final ContextWrapper contextWrapper = (ContextWrapper) baseContext;
159 | final Context wrapperBaseContext = contextWrapper.getBaseContext();
160 | if (wrapperBaseContext instanceof Activity) {
161 | return (Activity) wrapperBaseContext;
162 | }
163 | }
164 | }
165 |
166 | return null;
167 | }
168 |
169 | private static boolean isActivityDestroyed(Activity activity) {
170 | if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.JELLY_BEAN_MR1) {
171 | return activity.isDestroyed() || activity.isFinishing();
172 | } else {
173 | return activity.isFinishing() || activity.isChangingConfigurations();
174 | }
175 |
176 | }
177 |
178 | @Override
179 | protected void onAfterUpdateTransaction(@NonNull FastImageViewWithUrl view) {
180 | super.onAfterUpdateTransaction(view);
181 | view.onAfterUpdate(this, requestManager, VIEWS_FOR_URLS);
182 | }
183 | }
184 |
--------------------------------------------------------------------------------
/android/src/main/java/com/dylanvann/fastimage/FastImageViewModule.java:
--------------------------------------------------------------------------------
1 | package com.dylanvann.fastimage;
2 |
3 | import android.app.Activity;
4 |
5 | import androidx.annotation.NonNull;
6 |
7 | import com.bumptech.glide.Glide;
8 | import com.bumptech.glide.load.model.GlideUrl;
9 | import com.facebook.react.bridge.Promise;
10 | import com.facebook.react.bridge.ReactApplicationContext;
11 | import com.facebook.react.bridge.ReactContextBaseJavaModule;
12 | import com.facebook.react.bridge.ReactMethod;
13 | import com.facebook.react.bridge.ReadableArray;
14 | import com.facebook.react.bridge.ReadableMap;
15 | import com.facebook.react.views.imagehelper.ImageSource;
16 |
17 | class FastImageViewModule extends ReactContextBaseJavaModule {
18 |
19 | private static final String REACT_CLASS = "FastImageView";
20 |
21 | FastImageViewModule(ReactApplicationContext reactContext) {
22 | super(reactContext);
23 | }
24 |
25 | @NonNull
26 | @Override
27 | public String getName() {
28 | return REACT_CLASS;
29 | }
30 |
31 | @ReactMethod
32 | public void preload(final ReadableArray sources) {
33 | final Activity activity = getCurrentActivity();
34 | if (activity == null) return;
35 | activity.runOnUiThread(new Runnable() {
36 | @Override
37 | public void run() {
38 | for (int i = 0; i < sources.size(); i++) {
39 | final ReadableMap source = sources.getMap(i);
40 | final FastImageSource imageSource = FastImageViewConverter.getImageSource(activity, source);
41 |
42 | Glide
43 | .with(activity.getApplicationContext())
44 | // This will make this work for remote and local images. e.g.
45 | // - file:///
46 | // - content://
47 | // - res:/
48 | // - android.resource://
49 | // - data:image/png;base64
50 | .load(
51 | imageSource.isBase64Resource() ? imageSource.getSource() :
52 | imageSource.isResource() ? imageSource.getUri() : imageSource.getGlideUrl()
53 | )
54 | .apply(FastImageViewConverter.getOptions(activity, imageSource, source))
55 | .preload();
56 | }
57 | }
58 | });
59 | }
60 |
61 | @ReactMethod
62 | public void clearMemoryCache(final Promise promise) {
63 | final Activity activity = getCurrentActivity();
64 | if (activity == null) {
65 | promise.resolve(null);
66 | return;
67 | }
68 |
69 | activity.runOnUiThread(new Runnable() {
70 | @Override
71 | public void run() {
72 | Glide.get(activity.getApplicationContext()).clearMemory();
73 | promise.resolve(null);
74 | }
75 | });
76 | }
77 |
78 | @ReactMethod
79 | public void clearDiskCache(Promise promise) {
80 | final Activity activity = getCurrentActivity();
81 | if (activity == null) {
82 | promise.resolve(null);
83 | return;
84 | }
85 |
86 | Glide.get(activity.getApplicationContext()).clearDiskCache();
87 | promise.resolve(null);
88 | }
89 | }
90 |
--------------------------------------------------------------------------------
/android/src/main/java/com/dylanvann/fastimage/FastImageViewPackage.java:
--------------------------------------------------------------------------------
1 | package com.dylanvann.fastimage;
2 |
3 | import androidx.annotation.NonNull;
4 |
5 | import com.facebook.react.ReactPackage;
6 | import com.facebook.react.bridge.NativeModule;
7 | import com.facebook.react.bridge.ReactApplicationContext;
8 | import com.facebook.react.uimanager.ViewManager;
9 |
10 | import java.util.Collections;
11 | import java.util.List;
12 |
13 | public class FastImageViewPackage implements ReactPackage {
14 | @NonNull
15 | @Override
16 | public List createNativeModules(@NonNull ReactApplicationContext reactContext) {
17 | return Collections.singletonList(new FastImageViewModule(reactContext));
18 | }
19 |
20 | @NonNull
21 | @Override
22 | public List createViewManagers(@NonNull ReactApplicationContext reactContext) {
23 | return Collections.singletonList(new FastImageViewManager());
24 | }
25 | }
26 |
--------------------------------------------------------------------------------
/android/src/main/java/com/dylanvann/fastimage/FastImageViewWithUrl.java:
--------------------------------------------------------------------------------
1 | package com.dylanvann.fastimage;
2 |
3 | import static com.dylanvann.fastimage.FastImageRequestListener.REACT_ON_ERROR_EVENT;
4 |
5 | import android.annotation.SuppressLint;
6 | import android.content.Context;
7 | import android.graphics.drawable.Drawable;
8 |
9 | import androidx.annotation.Nullable;
10 | import androidx.appcompat.widget.AppCompatImageView;
11 |
12 | import com.bumptech.glide.RequestBuilder;
13 | import com.bumptech.glide.RequestManager;
14 | import com.bumptech.glide.load.model.GlideUrl;
15 | import com.bumptech.glide.request.Request;
16 | import com.facebook.react.bridge.ReadableMap;
17 | import com.facebook.react.bridge.WritableMap;
18 | import com.facebook.react.bridge.WritableNativeMap;
19 | import com.facebook.react.uimanager.ThemedReactContext;
20 | import com.facebook.react.uimanager.events.RCTEventEmitter;
21 |
22 | import java.util.ArrayList;
23 | import java.util.Collections;
24 | import java.util.List;
25 | import java.util.Map;
26 |
27 | import javax.annotation.Nonnull;
28 |
29 | class FastImageViewWithUrl extends AppCompatImageView {
30 | private boolean mNeedsReload = false;
31 | private ReadableMap mSource = null;
32 | private Drawable mDefaultSource = null;
33 |
34 | public GlideUrl glideUrl;
35 |
36 | public FastImageViewWithUrl(Context context) {
37 | super(context);
38 | }
39 |
40 | public void setSource(@Nullable ReadableMap source) {
41 | mNeedsReload = true;
42 | mSource = source;
43 | }
44 |
45 | public void setDefaultSource(@Nullable Drawable source) {
46 | mNeedsReload = true;
47 | mDefaultSource = source;
48 | }
49 |
50 | private boolean isNullOrEmpty(final String url) {
51 | return url == null || url.trim().isEmpty();
52 | }
53 |
54 | @SuppressLint("CheckResult")
55 | public void onAfterUpdate(
56 | @Nonnull FastImageViewManager manager,
57 | @Nullable RequestManager requestManager,
58 | @Nonnull Map> viewsForUrlsMap) {
59 | if (!mNeedsReload)
60 | return;
61 |
62 | if ((mSource == null ||
63 | !mSource.hasKey("uri") ||
64 | isNullOrEmpty(mSource.getString("uri"))) &&
65 | mDefaultSource == null) {
66 |
67 | // Cancel existing requests.
68 | clearView(requestManager);
69 |
70 | if (glideUrl != null) {
71 | FastImageOkHttpProgressGlideModule.forget(glideUrl.toStringUrl());
72 | }
73 |
74 | // Clear the image.
75 | setImageDrawable(null);
76 | return;
77 | }
78 |
79 | //final GlideUrl glideUrl = FastImageViewConverter.getGlideUrl(view.getContext(), mSource);
80 | final FastImageSource imageSource = FastImageViewConverter.getImageSource(getContext(), mSource);
81 |
82 | if (imageSource != null && imageSource.getUri().toString().length() == 0) {
83 | ThemedReactContext context = (ThemedReactContext) getContext();
84 | RCTEventEmitter eventEmitter = context.getJSModule(RCTEventEmitter.class);
85 | int viewId = getId();
86 | WritableMap event = new WritableNativeMap();
87 | event.putString("message", "Invalid source prop:" + mSource);
88 | eventEmitter.receiveEvent(viewId, REACT_ON_ERROR_EVENT, event);
89 |
90 | // Cancel existing requests.
91 | clearView(requestManager);
92 |
93 | if (glideUrl != null) {
94 | FastImageOkHttpProgressGlideModule.forget(glideUrl.toStringUrl());
95 | }
96 | // Clear the image.
97 | setImageDrawable(null);
98 | return;
99 | }
100 |
101 | // `imageSource` may be null and we still continue, if `defaultSource` is not null
102 | final GlideUrl glideUrl = imageSource == null ? null : imageSource.getGlideUrl();
103 |
104 | // Cancel existing request.
105 | this.glideUrl = glideUrl;
106 | clearView(requestManager);
107 |
108 | String key = glideUrl == null ? null : glideUrl.toStringUrl();
109 |
110 | if (glideUrl != null) {
111 | FastImageOkHttpProgressGlideModule.expect(key, manager);
112 | List viewsForKey = viewsForUrlsMap.get(key);
113 | if (viewsForKey != null && !viewsForKey.contains(this)) {
114 | viewsForKey.add(this);
115 | } else if (viewsForKey == null) {
116 | List newViewsForKeys = new ArrayList<>(Collections.singletonList(this));
117 | viewsForUrlsMap.put(key, newViewsForKeys);
118 | }
119 | }
120 |
121 | ThemedReactContext context = (ThemedReactContext) getContext();
122 | if (imageSource != null) {
123 | // This is an orphan even without a load/loadend when only loading a placeholder
124 | RCTEventEmitter eventEmitter = context.getJSModule(RCTEventEmitter.class);
125 | int viewId = this.getId();
126 |
127 | eventEmitter.receiveEvent(viewId,
128 | FastImageViewManager.REACT_ON_LOAD_START_EVENT,
129 | new WritableNativeMap());
130 | }
131 |
132 | if (requestManager != null) {
133 | RequestBuilder builder =
134 | requestManager
135 | // This will make this work for remote and local images. e.g.
136 | // - file:///
137 | // - content://
138 | // - res:/
139 | // - android.resource://
140 | // - data:image/png;base64
141 | .load(imageSource == null ? null : imageSource.getSourceForLoad())
142 | .apply(FastImageViewConverter
143 | .getOptions(context, imageSource, mSource)
144 | .placeholder(mDefaultSource) // show until loaded
145 | .fallback(mDefaultSource)); // null will not be treated as error
146 |
147 | if (key != null)
148 | builder.listener(new FastImageRequestListener(key));
149 |
150 | builder.into(this);
151 | }
152 | }
153 |
154 | public void clearView(@Nullable RequestManager requestManager) {
155 | if (requestManager != null && getTag() != null && getTag() instanceof Request) {
156 | requestManager.clear(this);
157 | }
158 | }
159 | }
160 |
--------------------------------------------------------------------------------
/babel.config.js:
--------------------------------------------------------------------------------
1 | module.exports = {
2 | presets: ['module:metro-react-native-babel-preset'],
3 | }
4 |
--------------------------------------------------------------------------------
/docs/app-glide-module.md:
--------------------------------------------------------------------------------
1 | # Removing MyAppGlideModule from react-native-fast-image
2 |
3 | If you are using Glide within your application using an `AppGlideModule` then you will
4 | need to prevent the inclusion of the `AppGlideModule` in this package.
5 |
6 | To accomplish this you can add to `android/build.gradle`:
7 |
8 | ```gradle
9 | project.ext {
10 | excludeAppGlideModule = true
11 | }
12 | ```
--------------------------------------------------------------------------------
/docs/assets/priority.gif:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/DylanVann/react-native-fast-image/9ab80fcd570b7f56da66ab20e52c9a35934067c9/docs/assets/priority.gif
--------------------------------------------------------------------------------
/docs/assets/scroll.gif:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/DylanVann/react-native-fast-image/9ab80fcd570b7f56da66ab20e52c9a35934067c9/docs/assets/scroll.gif
--------------------------------------------------------------------------------
/docs/development.md:
--------------------------------------------------------------------------------
1 | # Development
2 |
3 | For now this uses a modified cli to work around issues with symlinked packages.
4 |
5 | This is how to start the example app so you can test code with it.
6 |
7 | ```bash
8 | # In the repo root folder.
9 | # Install dependencies.
10 | yarn
11 |
12 | # Link module.
13 | yarn link
14 |
15 | # Move to example folder.
16 | cd ReactNativeFastImageExample
17 |
18 | # Install dependencies.
19 | yarn
20 |
21 | # Link module.
22 | yarn link react-native-fast-image
23 |
24 | # Start packager.
25 | yarn start
26 |
27 | # Start the iOS app.
28 | yarn react-native run-ios
29 | # Start the android app.
30 | yarn react-native run-android
31 | # You will need to re-run those commands to re-compile native code.
32 | ```
--------------------------------------------------------------------------------
/docs/how-is-caching-handled.md:
--------------------------------------------------------------------------------
1 | # How is caching handled?
2 |
3 | In the readme it says "Aggressively cache images.". What does this mean?
4 |
5 | This library treats image urls as immutable.
6 | That means it assumes the data located at a given url will not change.
7 | This is ideal for performance.
8 |
9 | The way this would work in practice for something like a user profile picture is:
10 |
11 | - Request user from API.
12 | - Receive JSON representing the user containing a `profilePicture` property that is the url of the profile picture.
13 | - Display the profile picture.
14 |
15 | So what happens if the user wants to change their profile picture?
16 |
17 | - User uploads a new profile picture, it gets a new url on the backend.
18 | - Update a field in a database.
19 |
20 | Next time the app is opened:
21 |
22 | - Display the cached profile picture immediately.
23 | - Request the user json again (this time it will have the new profile picture url).
24 | - Display the new profile picture.
25 |
26 | ## How is the cache cleared?
27 |
28 | As the app is used the cache fills up. When the cache reaches its maximum size the least frequently used images will be purged from the cache. You generally do not need to manually manage the cache.
29 |
--------------------------------------------------------------------------------
/docs/other-android-versions.md:
--------------------------------------------------------------------------------
1 | # Other Android Versions
2 |
3 | If you are not on a clean React Native install you may need to change the versions of some things this library uses.
4 |
5 | If you've defined
6 | _[project-wide-properties](https://developer.android.com/studio/build/gradle-tips.html)_(**recommended**)
7 | in your root `build.gradle`, this library will detect the presence of the following properties:
8 |
9 | ```groovy
10 | buildscript {...}
11 | allprojects {...}
12 |
13 | /**
14 | + Project-wide Gradle configuration properties
15 | */
16 | ext {
17 | // You can use any of these to change project wide versions:
18 | // compileSdkVersion = 26
19 | // targetSdkVersion = 26
20 | // minSdkVersion = 16
21 | // buildToolsVersion = "26.0.3"
22 | // supportLibVersion = "27.1.1"
23 | // glideVersion = "4.7.1"
24 | }
25 | ```
26 |
--------------------------------------------------------------------------------
/docs/roadmap.md:
--------------------------------------------------------------------------------
1 | # Roadmap / Ideas
2 |
3 | ## Add `onProgress` and `onComplete` to preload.
4 |
5 | - [Add onProgress and onComplete to preload.](https://github.com/DylanVann/react-native-fast-image/pull/268)
6 | - Blocked by: [Consider switching to a different iOS image loading library.](https://github.com/DylanVann/react-native-fast-image/issues/13)
7 | - Preload API should include returning the cache path of the images.
8 | - Something like `.preload(images: {uri, ...otherOptions}[]): Promise<[{path: string}]>`
9 | - Maybe something like: [Make it possible to obtain the cache path of an image.](https://github.com/DylanVann/react-native-fast-image/pull/351)
10 |
11 | ## Add `blurRadius` prop.
12 |
13 | - [Add blurRadius property.](https://github.com/DylanVann/react-native-fast-image/pull/157)
14 | - Blocked by: needing an Android implementation that works the same.
15 |
16 | ## Add more information to `onError` callback.
17 |
18 | - We need standardized errors across iOS and Android.
19 |
--------------------------------------------------------------------------------
/docs/troubleshooting.md:
--------------------------------------------------------------------------------
1 | # Troubleshooting
2 |
3 | If you have problems you can try:
4 |
5 | - Running Clean in Xcode.
6 | - Deleting Xcode's derived data.
7 | - Removing `node_modules` then reinstalling dependencies. (`rm -rf node_modules && yarn`)
8 | - Clearing watchman's watches. (`watchman watch-del-all`)
9 | - Clearing React Native's packager cache. (`react-native start --reset-cache`)
10 | - Clearing React Native's iOS build folder. (`rm -rf ios/build`)
11 | - Updating Pod repos. (`cd ios && pod repo update`)
12 | - Reinstalling Pods. (`cd ios && pod install`)
13 |
--------------------------------------------------------------------------------
/ios/FastImage.xcodeproj/xcshareddata/xcschemes/FastImage-tvOS.xcscheme:
--------------------------------------------------------------------------------
1 |
2 |
5 |
8 |
9 |
15 |
21 |
22 |
23 |
24 |
25 |
30 |
31 |
32 |
33 |
34 |
35 |
45 |
46 |
52 |
53 |
54 |
55 |
56 |
57 |
63 |
64 |
70 |
71 |
72 |
73 |
75 |
76 |
79 |
80 |
81 |
--------------------------------------------------------------------------------
/ios/FastImage/FFFastImageSource.h:
--------------------------------------------------------------------------------
1 | #import
2 | #import
3 |
4 | typedef NS_ENUM(NSInteger, FFFPriority) {
5 | FFFPriorityLow,
6 | FFFPriorityNormal,
7 | FFFPriorityHigh
8 | };
9 |
10 | typedef NS_ENUM(NSInteger, FFFCacheControl) {
11 | FFFCacheControlImmutable,
12 | FFFCacheControlWeb,
13 | FFFCacheControlCacheOnly
14 | };
15 |
16 | // Object containing an image uri and metadata.
17 | @interface FFFastImageSource : NSObject
18 |
19 | // uri for image, or base64
20 | @property (nonatomic) NSURL* url;
21 | // priority for image request
22 | @property (nonatomic) FFFPriority priority;
23 | // headers for the image request
24 | @property (nonatomic) NSDictionary *headers;
25 | // cache control mode
26 | @property (nonatomic) FFFCacheControl cacheControl;
27 |
28 | - (instancetype)initWithURL:(NSURL *)url
29 | priority:(FFFPriority)priority
30 | headers:(NSDictionary *)headers
31 | cacheControl:(FFFCacheControl)cacheControl;
32 |
33 | @end
34 |
--------------------------------------------------------------------------------
/ios/FastImage/FFFastImageSource.m:
--------------------------------------------------------------------------------
1 | #import "FFFastImageSource.h"
2 |
3 | @implementation FFFastImageSource
4 |
5 | - (instancetype)initWithURL:(NSURL *)url
6 | priority:(FFFPriority)priority
7 | headers:(NSDictionary *)headers
8 | cacheControl:(FFFCacheControl)cacheControl
9 | {
10 | self = [super init];
11 | if (self) {
12 | _url = url;
13 | _priority = priority;
14 | _headers = headers;
15 | _cacheControl = cacheControl;
16 | }
17 | return self;
18 | }
19 |
20 | @end
21 |
--------------------------------------------------------------------------------
/ios/FastImage/FFFastImageView.h:
--------------------------------------------------------------------------------
1 | #import
2 |
3 | #import
4 | #import
5 |
6 | #import
7 | #import
8 |
9 | #import "FFFastImageSource.h"
10 |
11 | @interface FFFastImageView : SDAnimatedImageView
12 |
13 | @property (nonatomic, copy) RCTDirectEventBlock onFastImageLoadStart;
14 | @property (nonatomic, copy) RCTDirectEventBlock onFastImageProgress;
15 | @property (nonatomic, copy) RCTDirectEventBlock onFastImageError;
16 | @property (nonatomic, copy) RCTDirectEventBlock onFastImageLoad;
17 | @property (nonatomic, copy) RCTDirectEventBlock onFastImageLoadEnd;
18 | @property (nonatomic, assign) RCTResizeMode resizeMode;
19 | @property (nonatomic, strong) FFFastImageSource *source;
20 | @property (nonatomic, strong) UIImage *defaultSource;
21 | @property (nonatomic, strong) UIColor *imageColor;
22 |
23 | @end
24 |
25 |
--------------------------------------------------------------------------------
/ios/FastImage/FFFastImageView.m:
--------------------------------------------------------------------------------
1 | #import "FFFastImageView.h"
2 | #import
3 | #import
4 |
5 | @interface FFFastImageView ()
6 |
7 | @property(nonatomic, assign) BOOL hasSentOnLoadStart;
8 | @property(nonatomic, assign) BOOL hasCompleted;
9 | @property(nonatomic, assign) BOOL hasErrored;
10 | // Whether the latest change of props requires the image to be reloaded
11 | @property(nonatomic, assign) BOOL needsReload;
12 |
13 | @property(nonatomic, strong) NSDictionary* onLoadEvent;
14 |
15 | @end
16 |
17 | @implementation FFFastImageView
18 |
19 | - (id) init {
20 | self = [super init];
21 | self.resizeMode = RCTResizeModeCover;
22 | self.clipsToBounds = YES;
23 | return self;
24 | }
25 |
26 | - (void) setResizeMode: (RCTResizeMode)resizeMode {
27 | if (_resizeMode != resizeMode) {
28 | _resizeMode = resizeMode;
29 | self.contentMode = (UIViewContentMode) resizeMode;
30 | }
31 | }
32 |
33 | - (void) setOnFastImageLoadEnd: (RCTDirectEventBlock)onFastImageLoadEnd {
34 | _onFastImageLoadEnd = onFastImageLoadEnd;
35 | if (self.hasCompleted) {
36 | _onFastImageLoadEnd(@{});
37 | }
38 | }
39 |
40 | - (void) setOnFastImageLoad: (RCTDirectEventBlock)onFastImageLoad {
41 | _onFastImageLoad = onFastImageLoad;
42 | if (self.hasCompleted) {
43 | _onFastImageLoad(self.onLoadEvent);
44 | }
45 | }
46 |
47 | - (void) setOnFastImageError: (RCTDirectEventBlock)onFastImageError {
48 | _onFastImageError = onFastImageError;
49 | if (self.hasErrored) {
50 | _onFastImageError(@{});
51 | }
52 | }
53 |
54 | - (void) setOnFastImageLoadStart: (RCTDirectEventBlock)onFastImageLoadStart {
55 | if (_source && !self.hasSentOnLoadStart) {
56 | _onFastImageLoadStart = onFastImageLoadStart;
57 | onFastImageLoadStart(@{});
58 | self.hasSentOnLoadStart = YES;
59 | } else {
60 | _onFastImageLoadStart = onFastImageLoadStart;
61 | self.hasSentOnLoadStart = NO;
62 | }
63 | }
64 |
65 | - (void) setImageColor: (UIColor*)imageColor {
66 | if (imageColor != nil) {
67 | _imageColor = imageColor;
68 | if (super.image) {
69 | super.image = [self makeImage: super.image withTint: self.imageColor];
70 | }
71 | }
72 | }
73 |
74 | - (UIImage*) makeImage: (UIImage*)image withTint: (UIColor*)color {
75 | UIImage* newImage = [image imageWithRenderingMode: UIImageRenderingModeAlwaysTemplate];
76 | UIGraphicsBeginImageContextWithOptions(image.size, NO, newImage.scale);
77 | [color set];
78 | [newImage drawInRect: CGRectMake(0, 0, image.size.width, newImage.size.height)];
79 | newImage = UIGraphicsGetImageFromCurrentImageContext();
80 | UIGraphicsEndImageContext();
81 | return newImage;
82 | }
83 |
84 | - (void) setImage: (UIImage*)image {
85 | if (self.imageColor != nil) {
86 | super.image = [self makeImage: image withTint: self.imageColor];
87 | } else {
88 | super.image = image;
89 | }
90 | }
91 |
92 | - (void) sendOnLoad: (UIImage*)image {
93 | self.onLoadEvent = @{
94 | @"width": [NSNumber numberWithDouble: image.size.width],
95 | @"height": [NSNumber numberWithDouble: image.size.height]
96 | };
97 | if (self.onFastImageLoad) {
98 | self.onFastImageLoad(self.onLoadEvent);
99 | }
100 | }
101 |
102 | - (void) setSource: (FFFastImageSource*)source {
103 | if (_source != source) {
104 | _source = source;
105 | _needsReload = YES;
106 | }
107 | }
108 |
109 | - (void) setDefaultSource: (UIImage*)defaultSource {
110 | if (_defaultSource != defaultSource) {
111 | _defaultSource = defaultSource;
112 | _needsReload = YES;
113 | }
114 | }
115 |
116 | - (void) didSetProps: (NSArray*)changedProps {
117 | if (_needsReload) {
118 | [self reloadImage];
119 | }
120 | }
121 |
122 | - (void) reloadImage {
123 | _needsReload = NO;
124 |
125 | if (_source) {
126 | // Load base64 images.
127 | NSString* url = [_source.url absoluteString];
128 | if (url && [url hasPrefix: @"data:image"]) {
129 | if (self.onFastImageLoadStart) {
130 | self.onFastImageLoadStart(@{});
131 | self.hasSentOnLoadStart = YES;
132 | } else {
133 | self.hasSentOnLoadStart = NO;
134 | }
135 | // Use SDWebImage API to support external format like WebP images
136 | UIImage* image = [UIImage sd_imageWithData: [NSData dataWithContentsOfURL: _source.url]];
137 | [self setImage: image];
138 | if (self.onFastImageProgress) {
139 | self.onFastImageProgress(@{
140 | @"loaded": @(1),
141 | @"total": @(1)
142 | });
143 | }
144 | self.hasCompleted = YES;
145 | [self sendOnLoad: image];
146 |
147 | if (self.onFastImageLoadEnd) {
148 | self.onFastImageLoadEnd(@{});
149 | }
150 | return;
151 | }
152 |
153 | // Set headers.
154 | NSDictionary* headers = _source.headers;
155 | SDWebImageDownloaderRequestModifier* requestModifier = [SDWebImageDownloaderRequestModifier requestModifierWithBlock: ^NSURLRequest* _Nullable (NSURLRequest* _Nonnull request) {
156 | NSMutableURLRequest* mutableRequest = [request mutableCopy];
157 | for (NSString* header in headers) {
158 | NSString* value = headers[header];
159 | [mutableRequest setValue: value forHTTPHeaderField: header];
160 | }
161 | return [mutableRequest copy];
162 | }];
163 | SDWebImageContext* context = @{SDWebImageContextDownloadRequestModifier: requestModifier};
164 |
165 | // Set priority.
166 | SDWebImageOptions options = SDWebImageRetryFailed | SDWebImageHandleCookies;
167 | switch (_source.priority) {
168 | case FFFPriorityLow:
169 | options |= SDWebImageLowPriority;
170 | break;
171 | case FFFPriorityNormal:
172 | // Priority is normal by default.
173 | break;
174 | case FFFPriorityHigh:
175 | options |= SDWebImageHighPriority;
176 | break;
177 | }
178 |
179 | switch (_source.cacheControl) {
180 | case FFFCacheControlWeb:
181 | options |= SDWebImageRefreshCached;
182 | break;
183 | case FFFCacheControlCacheOnly:
184 | options |= SDWebImageFromCacheOnly;
185 | break;
186 | case FFFCacheControlImmutable:
187 | break;
188 | }
189 |
190 | if (self.onFastImageLoadStart) {
191 | self.onFastImageLoadStart(@{});
192 | self.hasSentOnLoadStart = YES;
193 | } else {
194 | self.hasSentOnLoadStart = NO;
195 | }
196 | self.hasCompleted = NO;
197 | self.hasErrored = NO;
198 |
199 | [self downloadImage: _source options: options context: context];
200 | } else if (_defaultSource) {
201 | [self setImage: _defaultSource];
202 | }
203 | }
204 |
205 | - (void) downloadImage: (FFFastImageSource*)source options: (SDWebImageOptions)options context: (SDWebImageContext*)context {
206 | __weak typeof(self) weakSelf = self; // Always use a weak reference to self in blocks
207 | [self sd_setImageWithURL: _source.url
208 | placeholderImage: _defaultSource
209 | options: options
210 | context: context
211 | progress: ^(NSInteger receivedSize, NSInteger expectedSize, NSURL* _Nullable targetURL) {
212 | if (weakSelf.onFastImageProgress) {
213 | weakSelf.onFastImageProgress(@{
214 | @"loaded": @(receivedSize),
215 | @"total": @(expectedSize)
216 | });
217 | }
218 | } completed: ^(UIImage* _Nullable image,
219 | NSError* _Nullable error,
220 | SDImageCacheType cacheType,
221 | NSURL* _Nullable imageURL) {
222 | if (error) {
223 | weakSelf.hasErrored = YES;
224 | if (weakSelf.onFastImageError) {
225 | weakSelf.onFastImageError(@{});
226 | }
227 | if (weakSelf.onFastImageLoadEnd) {
228 | weakSelf.onFastImageLoadEnd(@{});
229 | }
230 | } else {
231 | weakSelf.hasCompleted = YES;
232 | [weakSelf sendOnLoad: image];
233 | if (weakSelf.onFastImageLoadEnd) {
234 | weakSelf.onFastImageLoadEnd(@{});
235 | }
236 | }
237 | }];
238 | }
239 |
240 | - (void) dealloc {
241 | [self sd_cancelCurrentImageLoad];
242 | }
243 |
244 | @end
245 |
--------------------------------------------------------------------------------
/ios/FastImage/FFFastImageViewManager.h:
--------------------------------------------------------------------------------
1 | #import
2 |
3 | @interface FFFastImageViewManager : RCTViewManager
4 |
5 | @end
6 |
--------------------------------------------------------------------------------
/ios/FastImage/FFFastImageViewManager.m:
--------------------------------------------------------------------------------
1 | #import "FFFastImageViewManager.h"
2 | #import "FFFastImageView.h"
3 |
4 | #import
5 | #import
6 |
7 | @implementation FFFastImageViewManager
8 |
9 | RCT_EXPORT_MODULE(FastImageView)
10 |
11 | - (FFFastImageView*)view {
12 | return [[FFFastImageView alloc] init];
13 | }
14 |
15 | RCT_EXPORT_VIEW_PROPERTY(source, FFFastImageSource)
16 | RCT_EXPORT_VIEW_PROPERTY(defaultSource, UIImage)
17 | RCT_EXPORT_VIEW_PROPERTY(resizeMode, RCTResizeMode)
18 | RCT_EXPORT_VIEW_PROPERTY(onFastImageLoadStart, RCTDirectEventBlock)
19 | RCT_EXPORT_VIEW_PROPERTY(onFastImageProgress, RCTDirectEventBlock)
20 | RCT_EXPORT_VIEW_PROPERTY(onFastImageError, RCTDirectEventBlock)
21 | RCT_EXPORT_VIEW_PROPERTY(onFastImageLoad, RCTDirectEventBlock)
22 | RCT_EXPORT_VIEW_PROPERTY(onFastImageLoadEnd, RCTDirectEventBlock)
23 | RCT_REMAP_VIEW_PROPERTY(tintColor, imageColor, UIColor)
24 |
25 | RCT_EXPORT_METHOD(preload:(nonnull NSArray *)sources)
26 | {
27 | NSMutableArray *urls = [NSMutableArray arrayWithCapacity:sources.count];
28 |
29 | [sources enumerateObjectsUsingBlock:^(FFFastImageSource * _Nonnull source, NSUInteger idx, BOOL * _Nonnull stop) {
30 | [source.headers enumerateKeysAndObjectsUsingBlock:^(NSString *key, NSString* header, BOOL *stop) {
31 | [[SDWebImageDownloader sharedDownloader] setValue:header forHTTPHeaderField:key];
32 | }];
33 | [urls setObject:source.url atIndexedSubscript:idx];
34 | }];
35 |
36 | [[SDWebImagePrefetcher sharedImagePrefetcher] prefetchURLs:urls];
37 | }
38 |
39 | RCT_EXPORT_METHOD(clearMemoryCache:(RCTPromiseResolveBlock)resolve reject:(RCTPromiseRejectBlock)reject)
40 | {
41 | [SDImageCache.sharedImageCache clearMemory];
42 | resolve(NULL);
43 | }
44 |
45 | RCT_EXPORT_METHOD(clearDiskCache:(RCTPromiseResolveBlock)resolve reject:(RCTPromiseRejectBlock)reject)
46 | {
47 | [SDImageCache.sharedImageCache clearDiskOnCompletion:^(){
48 | resolve(NULL);
49 | }];
50 | }
51 |
52 | @end
53 |
--------------------------------------------------------------------------------
/ios/FastImage/RCTConvert+FFFastImage.h:
--------------------------------------------------------------------------------
1 | #import
2 |
3 | @class FFFastImageSource;
4 |
5 | @interface RCTConvert (FFFastImage)
6 |
7 | + (FFFastImageSource *)FFFastImageSource:(id)json;
8 |
9 | @end
10 |
--------------------------------------------------------------------------------
/ios/FastImage/RCTConvert+FFFastImage.m:
--------------------------------------------------------------------------------
1 | #import "RCTConvert+FFFastImage.h"
2 | #import "FFFastImageSource.h"
3 |
4 | @implementation RCTConvert (FFFastImage)
5 |
6 | RCT_ENUM_CONVERTER(FFFPriority, (@{
7 | @"low": @(FFFPriorityLow),
8 | @"normal": @(FFFPriorityNormal),
9 | @"high": @(FFFPriorityHigh),
10 | }), FFFPriorityNormal, integerValue);
11 |
12 | RCT_ENUM_CONVERTER(FFFCacheControl, (@{
13 | @"immutable": @(FFFCacheControlImmutable),
14 | @"web": @(FFFCacheControlWeb),
15 | @"cacheOnly": @(FFFCacheControlCacheOnly),
16 | }), FFFCacheControlImmutable, integerValue);
17 |
18 | + (FFFastImageSource *)FFFastImageSource:(id)json {
19 | if (!json) {
20 | return nil;
21 | }
22 |
23 | NSString *uriString = json[@"uri"];
24 | NSURL *uri = [self NSURL:uriString];
25 |
26 | FFFPriority priority = [self FFFPriority:json[@"priority"]];
27 | FFFCacheControl cacheControl = [self FFFCacheControl:json[@"cache"]];
28 |
29 | NSDictionary *headers = [self NSDictionary:json[@"headers"]];
30 | if (headers) {
31 | __block BOOL allHeadersAreStrings = YES;
32 | [headers enumerateKeysAndObjectsUsingBlock:^(NSString *key, id header, BOOL *stop) {
33 | if (![header isKindOfClass:[NSString class]]) {
34 | RCTLogError(@"Values of HTTP headers passed must be of type string. "
35 | "Value of header '%@' is not a string.", key);
36 | allHeadersAreStrings = NO;
37 | *stop = YES;
38 | }
39 | }];
40 | if (!allHeadersAreStrings) {
41 | // Set headers to nil here to avoid crashing later.
42 | headers = nil;
43 | }
44 | }
45 |
46 | FFFastImageSource *imageSource = [[FFFastImageSource alloc] initWithURL:uri priority:priority headers:headers cacheControl:cacheControl];
47 |
48 | return imageSource;
49 | }
50 |
51 | RCT_ARRAY_CONVERTER(FFFastImageSource);
52 |
53 | @end
54 |
--------------------------------------------------------------------------------
/package.json:
--------------------------------------------------------------------------------
1 | {
2 | "name": "react-native-fast-image",
3 | "version": "8.6.3",
4 | "description": "🚩 FastImage, performant React Native image component.",
5 | "keywords": [
6 | "cache",
7 | "cached",
8 | "fastimage",
9 | "image",
10 | "priority"
11 | ],
12 | "homepage": "https://github.com/DylanVann/react-native-fast-image#readme",
13 | "bugs": {
14 | "url": "https://github.com/DylanVann/react-native-fast-image/issues"
15 | },
16 | "repository": {
17 | "type": "git",
18 | "url": "https://github.com/DylanVann/react-native-fast-image.git"
19 | },
20 | "license": "(MIT AND Apache-2.0)",
21 | "author": "Dylan Vann (https://dylanvann.com)",
22 | "main": "dist/index.cjs.js",
23 | "module": "dist/index.js",
24 | "typings": "dist/index.d.ts",
25 | "files": [
26 | "android",
27 | "!android/build",
28 | "ios",
29 | "!ios/build",
30 | "dist",
31 | "RNFastImage.podspec"
32 | ],
33 | "scripts": {
34 | "build": "dv-scripts build && cp src/index.js.flow dist/index.js.flow && cp src/index.js.flow dist/index.cjs.js.flow",
35 | "lint": "dv-scripts lint",
36 | "release": "dv-scripts release",
37 | "test": "dv-scripts test"
38 | },
39 | "prettier": {
40 | "semi": false,
41 | "singleQuote": true,
42 | "tabWidth": 4,
43 | "trailingComma": "all"
44 | },
45 | "eslintConfig": {
46 | "extends": "dv-scripts"
47 | },
48 | "jest": {
49 | "coveragePathIgnorePatterns": [
50 | "ReactNativeFastImageExample*",
51 | "ReactNativeFastImageExampleServer*"
52 | ],
53 | "modulePathIgnorePatterns": [
54 | "ReactNativeFastImageExample*",
55 | "ReactNativeFastImageExampleServer*"
56 | ],
57 | "preset": "react-native"
58 | },
59 | "resolutions": {
60 | "@jest/create-cache-key-function": "^27"
61 | },
62 | "devDependencies": {
63 | "@babel/core": "^7.14.6",
64 | "@babel/runtime": "^7.14.6",
65 | "@types/jest": "^26.0.24",
66 | "@types/react": "^17.0.14",
67 | "@types/react-native": "^0.69.5",
68 | "@types/react-test-renderer": "^17.0.1",
69 | "dv-scripts": "^1.6.0",
70 | "eslint-config-dv-scripts": "^1.1.1",
71 | "metro-react-native-babel-preset": "^0.66.1",
72 | "prettier": "^2.3.2",
73 | "react": "17.0.2",
74 | "react-native": "0.64.2",
75 | "react-test-renderer": "17.0.2",
76 | "typescript": "^4.3.5"
77 | },
78 | "peerDependencies": {
79 | "react": "^17 || ^18",
80 | "react-native": ">=0.60.0"
81 | }
82 | }
83 |
--------------------------------------------------------------------------------
/src/__snapshots__/index.test.tsx.snap:
--------------------------------------------------------------------------------
1 | // Jest Snapshot v1, https://goo.gl/fbAQLP
2 |
3 | exports[`FastImage (Android) renders a non-existing defaultSource 1`] = `
4 |
17 |
31 |
32 | `;
33 |
34 | exports[`FastImage (Android) renders a normal defaultSource 1`] = `
35 |
48 |
61 |
62 | `;
63 |
64 | exports[`FastImage (Android) renders a normal defaultSource when fails to load source 1`] = `
65 |
78 |
95 |
96 | `;
97 |
98 | exports[`FastImage (iOS) renders 1`] = `
99 |
112 |
134 |
135 | `;
136 |
137 | exports[`FastImage (iOS) renders Image with fallback prop 1`] = `
138 |
151 |
173 |
174 | `;
175 |
176 | exports[`FastImage (iOS) renders a normal Image when not passed a uri 1`] = `
177 |
190 |
208 |
209 | `;
210 |
211 | exports[`FastImage (iOS) renders defaultSource 1`] = `
212 |
225 |
243 |
244 | `;
245 |
--------------------------------------------------------------------------------
/src/index.js.flow:
--------------------------------------------------------------------------------
1 | // @flow
2 |
3 | import type { ViewProps } from 'react-native/Libraries/Components/View/ViewPropTypes'
4 | import type { SyntheticEvent } from 'react-native/Libraries/Types/CoreEventTypes'
5 |
6 | export type OnLoadEvent = SyntheticEvent<
7 | $ReadOnly<{
8 | width: number,
9 | height: number,
10 | }>,
11 | >
12 |
13 | export type OnProgressEvent = SyntheticEvent<
14 | $ReadOnly<{|
15 | loaded: number,
16 | total: number,
17 | |}>,
18 | >
19 |
20 | export type ResizeMode = $ReadOnly<{|
21 | contain: 'contain',
22 | cover: 'cover',
23 | stretch: 'stretch',
24 | center: 'center',
25 | |}>
26 |
27 | export type Priority = $ReadOnly<{|
28 | low: 'low',
29 | normal: 'normal',
30 | high: 'high',
31 | |}>
32 |
33 | export type CacheControl = $ReadOnly<{|
34 | immutable: 'immutable',
35 | web: 'web',
36 | cacheOnly: 'cacheOnly',
37 | |}>
38 |
39 | export type ResizeModes = $Values
40 | export type Priorities = $Values
41 | export type CacheControls = $Values
42 |
43 | export type PreloadFn = (sources: Array) => void
44 | export type FastImageSource = {
45 | uri?: string,
46 | headers?: Object,
47 | priority?: Priorities,
48 | cache?: CacheControls,
49 | }
50 |
51 | export type FastImageProps = $ReadOnly<{|
52 | ...ViewProps,
53 | onError?: ?() => void,
54 | onLoad?: ?(event: OnLoadEvent) => void,
55 | onLoadEnd?: ?() => void,
56 | onLoadStart?: ?() => void,
57 | onProgress?: ?(event: OnProgressEvent) => void,
58 |
59 | source?: ?(FastImageSource | number),
60 | defaultSource?: ?number,
61 |
62 | tintColor?: number | string,
63 | resizeMode?: ?ResizeModes,
64 | fallback?: ?boolean,
65 | testID?: ?string,
66 | |}>
67 |
68 | declare export default class FastImage extends React$Component {
69 | static resizeMode: ResizeMode;
70 | static priority: Priority;
71 | static cacheControl: CacheControl;
72 | static preload: PreloadFn;
73 | static clearMemoryCache: () => Promise;
74 | static clearDiskCache: () => Promise;
75 | }
76 |
--------------------------------------------------------------------------------
/src/index.test.tsx:
--------------------------------------------------------------------------------
1 | import { StyleSheet, Platform, NativeModules } from 'react-native'
2 | import React from 'react'
3 | import renderer from 'react-test-renderer'
4 | import FastImage from './index'
5 |
6 | const style = StyleSheet.create({ image: { width: 44, height: 44 } })
7 |
8 | describe('FastImage (iOS)', () => {
9 | beforeAll(() => {
10 | Platform.OS = 'ios'
11 | NativeModules.FastImageView = {
12 | preload: Function.prototype,
13 | clearMemoryCache: Function.prototype,
14 | clearDiskCache: Function.prototype,
15 | }
16 | })
17 |
18 | it('renders', () => {
19 | const tree = renderer
20 | .create(
21 | ,
31 | )
32 | .toJSON()
33 |
34 | expect(tree).toMatchSnapshot()
35 | })
36 |
37 | it('renders a normal Image when not passed a uri', () => {
38 | const tree = renderer
39 | .create(
40 | ,
44 | )
45 | .toJSON()
46 |
47 | expect(tree).toMatchSnapshot()
48 | })
49 |
50 | it('renders Image with fallback prop', () => {
51 | const tree = renderer
52 | .create(
53 | ,
58 | )
59 | .toJSON()
60 |
61 | expect(tree).toMatchSnapshot()
62 | })
63 |
64 | it('renders defaultSource', () => {
65 | const tree = renderer
66 | .create(
67 | ,
71 | )
72 | .toJSON()
73 |
74 | expect(tree).toMatchSnapshot()
75 | })
76 |
77 | it('runs static functions', () => {
78 | FastImage.preload([
79 | {
80 | uri: 'https://facebook.github.io/react/img/logo_og.png',
81 | headers: {
82 | token: 'someToken',
83 | },
84 | priority: FastImage.priority.high,
85 | },
86 | ])
87 | FastImage.clearMemoryCache()
88 | FastImage.clearDiskCache()
89 | })
90 | })
91 |
92 | describe('FastImage (Android)', () => {
93 | beforeAll(() => {
94 | Platform.OS = 'android'
95 | })
96 |
97 | it('renders a normal defaultSource', () => {
98 | const tree = renderer
99 | .create(
100 | ,
104 | )
105 | .toJSON()
106 |
107 | expect(tree).toMatchSnapshot()
108 | })
109 |
110 | it('renders a normal defaultSource when fails to load source', () => {
111 | const tree = renderer
112 | .create(
113 | ,
120 | )
121 | .toJSON()
122 |
123 | expect(tree).toMatchSnapshot()
124 | })
125 |
126 | it('renders a non-existing defaultSource', () => {
127 | const tree = renderer
128 | .create()
129 | .toJSON()
130 |
131 | expect(tree).toMatchSnapshot()
132 | })
133 | })
134 |
--------------------------------------------------------------------------------
/src/index.tsx:
--------------------------------------------------------------------------------
1 | import React, { forwardRef, memo } from 'react'
2 | import {
3 | View,
4 | Image,
5 | NativeModules,
6 | requireNativeComponent,
7 | StyleSheet,
8 | FlexStyle,
9 | LayoutChangeEvent,
10 | ShadowStyleIOS,
11 | StyleProp,
12 | TransformsStyle,
13 | ImageRequireSource,
14 | Platform,
15 | AccessibilityProps,
16 | ViewProps,
17 | ColorValue,
18 | } from 'react-native'
19 |
20 | export type ResizeMode = 'contain' | 'cover' | 'stretch' | 'center'
21 |
22 | const resizeMode = {
23 | contain: 'contain',
24 | cover: 'cover',
25 | stretch: 'stretch',
26 | center: 'center',
27 | } as const
28 |
29 | export type Priority = 'low' | 'normal' | 'high'
30 |
31 | const priority = {
32 | low: 'low',
33 | normal: 'normal',
34 | high: 'high',
35 | } as const
36 |
37 | type Cache = 'immutable' | 'web' | 'cacheOnly'
38 |
39 | const cacheControl = {
40 | // Ignore headers, use uri as cache key, fetch only if not in cache.
41 | immutable: 'immutable',
42 | // Respect http headers, no aggressive caching.
43 | web: 'web',
44 | // Only load from cache.
45 | cacheOnly: 'cacheOnly',
46 | } as const
47 |
48 | export type Source = {
49 | uri?: string
50 | headers?: { [key: string]: string }
51 | priority?: Priority
52 | cache?: Cache
53 | }
54 |
55 | export interface OnLoadEvent {
56 | nativeEvent: {
57 | width: number
58 | height: number
59 | }
60 | }
61 |
62 | export interface OnProgressEvent {
63 | nativeEvent: {
64 | loaded: number
65 | total: number
66 | }
67 | }
68 |
69 | export interface ImageStyle extends FlexStyle, TransformsStyle, ShadowStyleIOS {
70 | backfaceVisibility?: 'visible' | 'hidden'
71 | borderBottomLeftRadius?: number
72 | borderBottomRightRadius?: number
73 | backgroundColor?: string
74 | borderColor?: string
75 | borderWidth?: number
76 | borderRadius?: number
77 | borderTopLeftRadius?: number
78 | borderTopRightRadius?: number
79 | overlayColor?: string
80 | opacity?: number
81 | }
82 |
83 | export interface FastImageProps extends AccessibilityProps, ViewProps {
84 | source?: Source | ImageRequireSource
85 | defaultSource?: ImageRequireSource
86 | resizeMode?: ResizeMode
87 | fallback?: boolean
88 |
89 | onLoadStart?(): void
90 |
91 | onProgress?(event: OnProgressEvent): void
92 |
93 | onLoad?(event: OnLoadEvent): void
94 |
95 | onError?(): void
96 |
97 | onLoadEnd?(): void
98 |
99 | /**
100 | * onLayout function
101 | *
102 | * Invoked on mount and layout changes with
103 | *
104 | * {nativeEvent: { layout: {x, y, width, height}}}.
105 | */
106 | onLayout?: (event: LayoutChangeEvent) => void
107 |
108 | /**
109 | *
110 | * Style
111 | */
112 | style?: StyleProp
113 |
114 | /**
115 | * TintColor
116 | *
117 | * If supplied, changes the color of all the non-transparent pixels to the given color.
118 | */
119 |
120 | tintColor?: ColorValue
121 |
122 | /**
123 | * A unique identifier for this element to be used in UI Automation testing scripts.
124 | */
125 | testID?: string
126 |
127 | /**
128 | * Render children within the image.
129 | */
130 | children?: React.ReactNode
131 | }
132 |
133 | const resolveDefaultSource = (
134 | defaultSource?: ImageRequireSource,
135 | ): string | number | null => {
136 | if (!defaultSource) {
137 | return null
138 | }
139 | if (Platform.OS === 'android') {
140 | // Android receives a URI string, and resolves into a Drawable using RN's methods.
141 | const resolved = Image.resolveAssetSource(
142 | defaultSource as ImageRequireSource,
143 | )
144 |
145 | if (resolved) {
146 | return resolved.uri
147 | }
148 |
149 | return null
150 | }
151 | // iOS or other number mapped assets
152 | // In iOS the number is passed, and bridged automatically into a UIImage
153 | return defaultSource
154 | }
155 |
156 | function FastImageBase({
157 | source,
158 | defaultSource,
159 | tintColor,
160 | onLoadStart,
161 | onProgress,
162 | onLoad,
163 | onError,
164 | onLoadEnd,
165 | style,
166 | fallback,
167 | children,
168 | // eslint-disable-next-line no-shadow
169 | resizeMode = 'cover',
170 | forwardedRef,
171 | ...props
172 | }: FastImageProps & { forwardedRef: React.Ref }) {
173 | if (fallback) {
174 | const cleanedSource = { ...(source as any) }
175 | delete cleanedSource.cache
176 | const resolvedSource = Image.resolveAssetSource(cleanedSource)
177 |
178 | return (
179 |
180 |
192 | {children}
193 |
194 | )
195 | }
196 |
197 | const resolvedSource = Image.resolveAssetSource(source as any)
198 | const resolvedDefaultSource = resolveDefaultSource(defaultSource)
199 |
200 | return (
201 |
202 |
215 | {children}
216 |
217 | )
218 | }
219 |
220 | const FastImageMemo = memo(FastImageBase)
221 |
222 | const FastImageComponent: React.ComponentType = forwardRef(
223 | (props: FastImageProps, ref: React.Ref) => (
224 |
225 | ),
226 | )
227 |
228 | FastImageComponent.displayName = 'FastImage'
229 |
230 | export interface FastImageStaticProperties {
231 | resizeMode: typeof resizeMode
232 | priority: typeof priority
233 | cacheControl: typeof cacheControl
234 | preload: (sources: Source[]) => void
235 | clearMemoryCache: () => Promise
236 | clearDiskCache: () => Promise
237 | }
238 |
239 | const FastImage: React.ComponentType &
240 | FastImageStaticProperties = FastImageComponent as any
241 |
242 | FastImage.resizeMode = resizeMode
243 |
244 | FastImage.cacheControl = cacheControl
245 |
246 | FastImage.priority = priority
247 |
248 | FastImage.preload = (sources: Source[]) =>
249 | NativeModules.FastImageView.preload(sources)
250 |
251 | FastImage.clearMemoryCache = () =>
252 | NativeModules.FastImageView.clearMemoryCache()
253 |
254 | FastImage.clearDiskCache = () => NativeModules.FastImageView.clearDiskCache()
255 |
256 | const styles = StyleSheet.create({
257 | imageContainer: {
258 | overflow: 'hidden',
259 | },
260 | })
261 |
262 | // Types of requireNativeComponent are not correct.
263 | const FastImageView = (requireNativeComponent as any)(
264 | 'FastImageView',
265 | FastImage,
266 | {
267 | nativeOnly: {
268 | onFastImageLoadStart: true,
269 | onFastImageProgress: true,
270 | onFastImageLoad: true,
271 | onFastImageError: true,
272 | onFastImageLoadEnd: true,
273 | },
274 | },
275 | )
276 |
277 | export default FastImage
278 |
--------------------------------------------------------------------------------
/tsconfig.json:
--------------------------------------------------------------------------------
1 | {
2 | "compilerOptions": {
3 | "target": "ESNext",
4 | "module": "ESNext",
5 | "composite": true,
6 | "incremental": true,
7 | "lib": ["ESNext"],
8 | "importHelpers": true,
9 | "declarationMap": true,
10 | "declaration": true,
11 | "emitDeclarationOnly": true,
12 | "sourceMap": false,
13 | "strict": true,
14 | "noImplicitAny": true,
15 | "skipLibCheck": true,
16 | "skipDefaultLibCheck": true,
17 | "isolatedModules": true,
18 | "noImplicitThis": true,
19 | "alwaysStrict": true,
20 | "noUnusedLocals": true,
21 | "noImplicitReturns": true,
22 | "noFallthroughCasesInSwitch": true,
23 | "forceConsistentCasingInFileNames": true,
24 | "moduleResolution": "Node",
25 | "jsx": "react",
26 | "allowSyntheticDefaultImports": true,
27 | "esModuleInterop": true,
28 | "resolveJsonModule": true,
29 | "declarationDir": "./dist",
30 | "outDir": "./dist",
31 | "rootDir": "./src"
32 | },
33 | "include": ["./src"]
34 | }
35 |
--------------------------------------------------------------------------------