diff --git a/.babelrc b/.babelrc new file mode 100644 index 0000000..6c1e0ce --- /dev/null +++ b/.babelrc @@ -0,0 +1,3 @@ +{ + "presets": ["react-native"] +} \ No newline at end of file diff --git a/CachedImage.js b/CachedImage.js index 8add2e4..daf03c8 100644 --- a/CachedImage.js +++ b/CachedImage.js @@ -3,20 +3,22 @@ const _ = require('lodash'); const React = require('react'); const ReactNative = require('react-native'); + +const PropTypes = require('prop-types'); + +const ImageCacheManagerOptionsPropTypes = require('./ImageCacheManagerOptionsPropTypes'); + const flattenStyle = ReactNative.StyleSheet.flatten; -const ImageCacheProvider = require('./ImageCacheProvider'); + +const ImageCacheManager = require('./ImageCacheManager'); const { View, - Image, + ImageBackground, ActivityIndicator, NetInfo, - Platform -} = ReactNative; - - -const { - StyleSheet + Platform, + StyleSheet, } = ReactNative; const styles = StyleSheet.create({ @@ -39,51 +41,41 @@ function getImageProps(props) { const CACHED_IMAGE_REF = 'cachedImage'; -const CachedImage = React.createClass({ - propTypes: { - renderImage: React.PropTypes.func.isRequired, - activityIndicatorProps: React.PropTypes.object.isRequired, - useQueryParamsInCacheKey: React.PropTypes.oneOfType([ - React.PropTypes.bool, - React.PropTypes.array - ]).isRequired, - resolveHeaders: React.PropTypes.func, - cacheLocation: React.PropTypes.string - }, +class CachedImage extends React.Component { + + static propTypes = { + renderImage: PropTypes.func.isRequired, + activityIndicatorProps: PropTypes.object.isRequired, - getDefaultProps() { - return { - renderImage: props => (), + // ImageCacheManager options + ...ImageCacheManagerOptionsPropTypes, + }; + + static defaultProps = { + renderImage: props => (), activityIndicatorProps: {}, - useQueryParamsInCacheKey: false, - resolveHeaders: () => Promise.resolve({}), - cacheLocation: ImageCacheProvider.LOCATION.CACHE - }; - }, + }; - setNativeProps(nativeProps) { - try { - this.refs[CACHED_IMAGE_REF].setNativeProps(nativeProps); - } catch (e) { - console.error(e); - } - }, + static contextTypes = { + getImageCacheManager: PropTypes.func, + }; - getInitialState() { + constructor(props) { + super(props); this._isMounted = false; - return { - isCacheable: false, + this.state = { + isCacheable: true, cachedImagePath: null, networkAvailable: true }; - }, - safeSetState(newState) { - if (!this._isMounted) { - return; - } - return this.setState(newState); - }, + this.getImageCacheManagerOptions = this.getImageCacheManagerOptions.bind(this); + this.getImageCacheManager = this.getImageCacheManager.bind(this); + this.safeSetState = this.safeSetState.bind(this); + this.handleConnectivityChange = this.handleConnectivityChange.bind(this); + this.processSource = this.processSource.bind(this); + this.renderLoader = this.renderLoader.bind(this); + } componentWillMount() { this._isMounted = true; @@ -97,54 +89,73 @@ const CachedImage = React.createClass({ }); this.processSource(this.props.source); - }, + } componentWillUnmount() { this._isMounted = false; NetInfo.isConnected.removeEventListener('connectionChange', this.handleConnectivityChange); - }, + } componentWillReceiveProps(nextProps) { if (!_.isEqual(this.props.source, nextProps.source)) { this.processSource(nextProps.source); } - }, + } + + setNativeProps(nativeProps) { + try { + this.refs[CACHED_IMAGE_REF].setNativeProps(nativeProps); + } catch (e) { + console.error(e); + } + } + + getImageCacheManagerOptions() { + return _.pick(this.props, _.keys(ImageCacheManagerOptionsPropTypes)); + } + + getImageCacheManager() { + // try to get ImageCacheManager from context + if (this.context && this.context.getImageCacheManager) { + return this.context.getImageCacheManager(); + } + // create a new one if context is not available + const options = this.getImageCacheManagerOptions(); + return ImageCacheManager(options); + } + + safeSetState(newState) { + if (!this._isMounted) { + return; + } + return this.setState(newState); + } handleConnectivityChange(isConnected) { this.safeSetState({ networkAvailable: isConnected }); - }, + } processSource(source) { const url = _.get(source, ['uri'], null); - if (ImageCacheProvider.isCacheable(url)) { - const options = _.pick(this.props, ['useQueryParamsInCacheKey', 'cacheGroup', 'cacheLocation']); - - // try to get the image path from cache - ImageCacheProvider.getCachedImagePath(url, options) - // try to put the image in cache if - .catch(() => ImageCacheProvider.cacheImage(url, options, this.props.resolveHeaders)) - .then(cachedImagePath => { - this.safeSetState({ - cachedImagePath - }); - }) - .catch(err => { - this.safeSetState({ - cachedImagePath: null, - isCacheable: false - }); + const options = this.getImageCacheManagerOptions(); + const imageCacheManager = this.getImageCacheManager(); + + imageCacheManager.downloadAndCacheUrl(url, options) + .then(cachedImagePath => { + this.safeSetState({ + cachedImagePath + }); + }) + .catch(err => { + // console.warn(err); + this.safeSetState({ + cachedImagePath: null, + isCacheable: false }); - this.safeSetState({ - isCacheable: true - }); - } else { - this.safeSetState({ - isCacheable: false }); - } - }, + } render() { if (this.state.isCacheable && !this.state.cachedImagePath) { @@ -153,15 +164,15 @@ const CachedImage = React.createClass({ const props = getImageProps(this.props); const style = this.props.style || styles.image; const source = (this.state.isCacheable && this.state.cachedImagePath) ? { - uri: 'file://' + this.state.cachedImagePath - } : this.props.source; + uri: 'file://' + this.state.cachedImagePath + } : this.props.source; if (this.props.fallbackSource && !this.state.cachedImagePath) { - return this.props.renderImage({ - ...props, - key: `${props.key || source.uri}error`, - style, - source: this.props.fallbackSource - }); + return this.props.renderImage({ + ...props, + key: `${props.key || source.uri}error`, + style, + source: this.props.fallbackSource + }); } return this.props.renderImage({ ...props, @@ -169,7 +180,7 @@ const CachedImage = React.createClass({ style, source }); - }, + } renderLoader() { const imageProps = getImageProps(this.props); @@ -186,11 +197,11 @@ const CachedImage = React.createClass({ // so we only show the ActivityIndicator if (!source || (Platform.OS === 'android' && flattenStyle(imageStyle).borderRadius)) { if (LoadingIndicator) { - return ( - - - - ); + return ( + + + + ); } return ( - - - : + ? + + + : ) }); } -}); -/** - * Same as ReactNaive.Image.getSize only it will not download the image if it has a cached version - * @param uri - * @param success - * @param failure - * @param options - */ -CachedImage.getSize = function getSize(uri, success, failure, options) { - if (ImageCacheProvider.isCacheable(uri)) { - ImageCacheProvider.getCachedImagePath(uri, options) - .then(imagePath => { - if (Platform.OS === 'android') { - imagePath = 'file://' + imagePath; - } - Image.getSize(imagePath, success, failure); - }) - .catch(err => { - Image.getSize(uri, success, failure); - }); - } else { - Image.getSize(uri, success, failure); - } -}; +} module.exports = CachedImage; diff --git a/CachedImageExample/.babelrc b/CachedImageExample/.babelrc index 8df53fe..a9ce136 100644 --- a/CachedImageExample/.babelrc +++ b/CachedImageExample/.babelrc @@ -1,3 +1,3 @@ { -"presets": ["react-native"] -} \ No newline at end of file + "presets": ["react-native"] +} diff --git a/CachedImageExample/.flowconfig b/CachedImageExample/.flowconfig index 5912f47..218cce9 100644 --- a/CachedImageExample/.flowconfig +++ b/CachedImageExample/.flowconfig @@ -22,6 +22,8 @@ node_modules/react-native/flow flow/ [options] +emoji=true + module.system=haste experimental.strict_type_args=true @@ -35,11 +37,13 @@ suppress_type=$FlowIssue suppress_type=$FlowFixMe suppress_type=$FixMe -suppress_comment=\\(.\\|\n\\)*\\$FlowFixMe\\($\\|[^(]\\|(\\(>=0\\.\\(3[0-5]\\|[1-2][0-9]\\|[0-9]\\).[0-9]\\)? *\\(site=[a-z,_]*react_native[a-z,_]*\\)?)\\) -suppress_comment=\\(.\\|\n\\)*\\$FlowIssue\\((\\(>=0\\.\\(3[0-5]\\|1[0-9]\\|[1-2][0-9]\\).[0-9]\\)? *\\(site=[a-z,_]*react_native[a-z,_]*\\)?)\\)?:? #[0-9]+ +suppress_comment=\\(.\\|\n\\)*\\$FlowFixMe\\($\\|[^(]\\|(\\(>=0\\.\\(4[0-7]\\|[1-3][0-9]\\|[0-9]\\).[0-9]\\)? *\\(site=[a-z,_]*react_native[a-z,_]*\\)?)\\) +suppress_comment=\\(.\\|\n\\)*\\$FlowIssue\\((\\(>=0\\.\\(4[0-7]\\|[1-3][0-9]\\|[0-9]\\).[0-9]\\)? *\\(site=[a-z,_]*react_native[a-z,_]*\\)?)\\)?:? #[0-9]+ + suppress_comment=\\(.\\|\n\\)*\\$FlowFixedInNextDeploy +suppress_comment=\\(.\\|\n\\)*\\$FlowExpectedError unsafe.enable_getters_and_setters=true [version] -^0.35.0 +^0.47.0 \ No newline at end of file diff --git a/CachedImageExample/.gitignore b/CachedImageExample/.gitignore index 93fd764..9b51814 100644 --- a/CachedImageExample/.gitignore +++ b/CachedImageExample/.gitignore @@ -34,6 +34,7 @@ local.properties # node_modules/ npm-debug.log +yarn-error.log # BUCK buck-out/ diff --git a/CachedImageExample/android/app/BUCK b/CachedImageExample/android/app/BUCK index cb3f773..5619e88 100644 --- a/CachedImageExample/android/app/BUCK +++ b/CachedImageExample/android/app/BUCK @@ -1,5 +1,3 @@ -import re - # To learn about Buck see [Docs](https://buckbuild.com/). # To run your application with Buck: # - install Buck @@ -11,8 +9,9 @@ import re # lib_deps = [] + for jarfile in glob(['libs/*.jar']): - name = 'jars__' + re.sub(r'^.*/([^/]+)\.jar$', r'\1', jarfile) + name = 'jars__' + jarfile[jarfile.rindex('/') + 1: jarfile.rindex('.jar')] lib_deps.append(':' + name) prebuilt_jar( name = name, @@ -20,7 +19,7 @@ for jarfile in glob(['libs/*.jar']): ) for aarfile in glob(['libs/*.aar']): - name = 'aars__' + re.sub(r'^.*/([^/]+)\.aar$', r'\1', aarfile) + name = 'aars__' + aarfile[aarfile.rindex('/') + 1: aarfile.rindex('.aar')] lib_deps.append(':' + name) android_prebuilt_aar( name = name, @@ -28,39 +27,39 @@ for aarfile in glob(['libs/*.aar']): ) android_library( - name = 'all-libs', - exported_deps = lib_deps + name = "all-libs", + exported_deps = lib_deps, ) android_library( - name = 'app-code', - srcs = glob([ - 'src/main/java/**/*.java', - ]), - deps = [ - ':all-libs', - ':build_config', - ':res', - ], + name = "app-code", + srcs = glob([ + "src/main/java/**/*.java", + ]), + deps = [ + ":all-libs", + ":build_config", + ":res", + ], ) android_build_config( - name = 'build_config', - package = 'com.cachedimageexample', + name = "build_config", + package = "com.cachedimageexample", ) android_resource( - name = 'res', - res = 'src/main/res', - package = 'com.cachedimageexample', + name = "res", + package = "com.cachedimageexample", + res = "src/main/res", ) android_binary( - name = 'app', - package_type = 'debug', - manifest = 'src/main/AndroidManifest.xml', - keystore = '//android/keystores:debug', - deps = [ - ':app-code', - ], + name = "app", + keystore = "//android/keystores:debug", + manifest = "src/main/AndroidManifest.xml", + package_type = "debug", + deps = [ + ":app-code", + ], ) diff --git a/CachedImageExample/android/app/build.gradle b/CachedImageExample/android/app/build.gradle index 8b80c23..daa0170 100644 --- a/CachedImageExample/android/app/build.gradle +++ b/CachedImageExample/android/app/build.gradle @@ -33,6 +33,13 @@ import com.android.build.OutputFile * // bundleInPaidRelease: true, * // bundleInBeta: true, * + * // whether to disable dev mode in custom build variants (by default only disabled in release) + * // for example: to disable dev mode in the staging build type (if configured) + * devDisabledInStaging: true, + * // The configuration property can be in the following formats + * // 'devDisabledIn${productFlavor}${buildType}' + * // 'devDisabledIn${buildType}' + * * // the root of your project, i.e. where "package.json" lives * root: "../../", * @@ -58,7 +65,7 @@ import com.android.build.OutputFile * inputExcludes: ["android/**", "ios/**"], * * // override which node gets called and with what additional arguments - * nodeExecutableAndArgs: ["node"] + * nodeExecutableAndArgs: ["node"], * * // supply additional arguments to the packager * extraPackagerArgs: [] diff --git a/CachedImageExample/android/app/proguard-rules.pro b/CachedImageExample/android/app/proguard-rules.pro index 48361a9..6e8516c 100644 --- a/CachedImageExample/android/app/proguard-rules.pro +++ b/CachedImageExample/android/app/proguard-rules.pro @@ -50,6 +50,10 @@ -dontwarn com.facebook.react.** +# TextLayoutBuilder uses a non-public Android constructor within StaticLayout. +# See libs/proxy/src/main/java/com/facebook/fbui/textlayoutbuilder/proxy for details. +-dontwarn android.text.StaticLayout + # okhttp -keepattributes Signature diff --git a/CachedImageExample/android/app/src/main/AndroidManifest.xml b/CachedImageExample/android/app/src/main/AndroidManifest.xml index 79e0fe3..57628d4 100644 --- a/CachedImageExample/android/app/src/main/AndroidManifest.xml +++ b/CachedImageExample/android/app/src/main/AndroidManifest.xml @@ -7,7 +7,7 @@ - + + android:configChanges="keyboard|keyboardHidden|orientation|screenSize" + android:windowSoftInputMode="adjustResize"> - diff --git a/CachedImageExample/android/app/src/main/java/com/cachedimageexample/MainApplication.java b/CachedImageExample/android/app/src/main/java/com/cachedimageexample/MainApplication.java index c6499da..3ac614f 100644 --- a/CachedImageExample/android/app/src/main/java/com/cachedimageexample/MainApplication.java +++ b/CachedImageExample/android/app/src/main/java/com/cachedimageexample/MainApplication.java @@ -4,13 +4,15 @@ import android.app.Application; import com.facebook.react.ReactApplication; -import com.RNFetchBlob.RNFetchBlobPackage; import com.facebook.react.ReactInstanceManager; import com.facebook.react.ReactNativeHost; import com.facebook.react.ReactPackage; import com.facebook.react.shell.MainReactPackage; + import com.facebook.soloader.SoLoader; +import com.RNFetchBlob.RNFetchBlobPackage; + import java.util.Arrays; import java.util.List; @@ -18,7 +20,7 @@ public class MainApplication extends Application implements ReactApplication { private final ReactNativeHost mReactNativeHost = new ReactNativeHost(this) { @Override - protected boolean getUseDeveloperSupport() { + public boolean getUseDeveloperSupport() { return BuildConfig.DEBUG; } diff --git a/CachedImageExample/android/build.gradle b/CachedImageExample/android/build.gradle index fcba4c5..eed9972 100644 --- a/CachedImageExample/android/build.gradle +++ b/CachedImageExample/android/build.gradle @@ -5,7 +5,7 @@ buildscript { jcenter() } dependencies { - classpath 'com.android.tools.build:gradle:1.3.1' + classpath 'com.android.tools.build:gradle:2.2.3' // NOTE: Do not place your application dependencies here; they belong // in the individual module build.gradle files diff --git a/CachedImageExample/android/gradle/wrapper/gradle-wrapper.properties b/CachedImageExample/android/gradle/wrapper/gradle-wrapper.properties index b9fbfab..dbdc05d 100644 --- a/CachedImageExample/android/gradle/wrapper/gradle-wrapper.properties +++ b/CachedImageExample/android/gradle/wrapper/gradle-wrapper.properties @@ -2,4 +2,4 @@ distributionBase=GRADLE_USER_HOME distributionPath=wrapper/dists zipStoreBase=GRADLE_USER_HOME zipStorePath=wrapper/dists -distributionUrl=https\://services.gradle.org/distributions/gradle-2.4-all.zip +distributionUrl=https\://services.gradle.org/distributions/gradle-2.14.1-all.zip diff --git a/CachedImageExample/android/keystores/BUCK b/CachedImageExample/android/keystores/BUCK index 15da20e..88e4c31 100644 --- a/CachedImageExample/android/keystores/BUCK +++ b/CachedImageExample/android/keystores/BUCK @@ -1,8 +1,8 @@ keystore( - name = 'debug', - store = 'debug.keystore', - properties = 'debug.keystore.properties', - visibility = [ - 'PUBLIC', - ], + name = "debug", + properties = "debug.keystore.properties", + store = "debug.keystore", + visibility = [ + "PUBLIC", + ], ) diff --git a/CachedImageExample/app.json b/CachedImageExample/app.json new file mode 100644 index 0000000..e0d82bf --- /dev/null +++ b/CachedImageExample/app.json @@ -0,0 +1,4 @@ +{ + "name": "CachedImageExample", + "displayName": "CachedImageExample" +} \ No newline at end of file diff --git a/CachedImageExample/index.js b/CachedImageExample/index.js index f1f2650..154c91e 100644 --- a/CachedImageExample/index.js +++ b/CachedImageExample/index.js @@ -7,27 +7,19 @@ const _ = require('lodash'); const { View, + ScrollView, Button, Dimensions, StyleSheet, AppRegistry, - ListView + ListView, } = ReactNative; -const CachedImageBase = require('react-native-cached-image'); const { - ImageCacheProvider -} = CachedImageBase; - -const cachedImageOptions = { - cacheLocation: ImageCacheProvider.LOCATION.BUNDLE -}; - -function CachedImage(props) { - return ( - - ); -} + CachedImage, + ImageCacheProvider, + ImageCacheManager, +} = require('react-native-cached-image'); const { width @@ -54,13 +46,17 @@ const styles = StyleSheet.create({ const loading = require('./loading.jpg'); +const image1 = 'https://wallpaperbrowse.com/media/images/bcf39e88-5731-43bb-9d4b-e5b3b2b1fdf2.jpg'; +const image2 = 'https://d22cb02g3nv58u.cloudfront.net/0.676.0/assets/images/icons/fun-types/full/baby-shower-full.jpg'; + const images = [ - 'https://wallpaperbrowse.com/media/images/bcf39e88-5731-43bb-9d4b-e5b3b2b1fdf2.jpg', - 'https://d22cb02g3nv58u.cloudfront.net/0.671.0/assets/images/icons/fun-types/full/wrong-image.jpg', - 'https://d22cb02g3nv58u.cloudfront.net/0.671.0/assets/images/icons/fun-types/full/bar-crawl-full.jpg', - 'https://d22cb02g3nv58u.cloudfront.net/0.671.0/assets/images/icons/fun-types/full/cheeseburger-full.jpg', - 'https://d22cb02g3nv58u.cloudfront.net/0.671.0/assets/images/icons/fun-types/full/friendsgiving-full.jpg', - 'https://d22cb02g3nv58u.cloudfront.net/0.671.0/assets/images/icons/fun-types/full/dogs-play-date-full.jpg' + 'https://d22cb02g3nv58u.cloudfront.net/0.676.0/assets/images/icons/fun-types/full/after-work-drinks-full.jpg', + 'https://i.ytimg.com/vi/b6m-XlOxjbk/hqdefault.jpg', + 'https://d22cb02g3nv58u.cloudfront.net/0.676.0/assets/images/icons/fun-types/full/wrong-image.jpg', + 'https://d22cb02g3nv58u.cloudfront.net/0.676.0/assets/images/icons/fun-types/full/bar-crawl-full.jpg', + 'https://d22cb02g3nv58u.cloudfront.net/0.676.0/assets/images/icons/fun-types/full/cheeseburger-full.jpg', + 'https://d22cb02g3nv58u.cloudfront.net/0.676.0/assets/images/icons/fun-types/full/friendsgiving-full.jpg', + 'https://d22cb02g3nv58u.cloudfront.net/0.676.0/assets/images/icons/fun-types/full/dogs-play-date-full.jpg' ]; function formatBytes(bytes, decimals) { @@ -74,37 +70,51 @@ function formatBytes(bytes, decimals) { return parseFloat((bytes / Math.pow(k, i)).toFixed(dm)) + ' ' + sizes[i]; } -const CachedImageExample = React.createClass({ +const defaultImageCacheManager = ImageCacheManager(); + +class CachedImageExample extends React.Component { + + constructor(props) { + super(props); - getInitialState() { const ds = new ListView.DataSource({rowHasChanged: (r1, r2) => r1 !== r2}); - return { + this.state = { showNextImage: false, dataSource: ds.cloneWithRows(images) }; - }, + + this.cacheImages = this.cacheImages.bind(this); + + } componentWillMount() { - ImageCacheProvider.cacheMultipleImages(images, cachedImageOptions) - .then(() => { - console.log('cacheMultipleImages Done'); - }) - .catch(err => { - console.log('cacheMultipleImages caught an error: ', err); - }); - }, + defaultImageCacheManager.downloadAndCacheUrl(image1); + } clearCache() { - ImageCacheProvider.clearCache(cachedImageOptions); - }, + defaultImageCacheManager.clearCache() + .then(() => { + ReactNative.Alert.alert('Cache cleared'); + }); + } getCacheInfo() { - ImageCacheProvider.getCacheInfo(cachedImageOptions) + defaultImageCacheManager.getCacheInfo() .then(({size, files}) => { // console.log(size, files); ReactNative.Alert.alert('Cache Info', `files: ${files.length}\nsize: ${formatBytes(size)}`); }); - }, + } + + cacheImages() { + this.setState({ + dataSource: this.state.dataSource.cloneWithRows([]) + }, () => { + this.setState({ + dataSource: this.state.dataSource.cloneWithRows(images) + }); + }); + } renderRow(uri) { return ( @@ -114,11 +124,11 @@ const CachedImageExample = React.createClass({ style={styles.image} /> ); - }, + } render() { return ( - +