Merge branch 'master' into glide-v4
58
FastImage.js
@ -1,10 +1,12 @@
|
||||
import React, { Component } from 'react'
|
||||
import PropTypes from 'prop-types'
|
||||
import {
|
||||
View,
|
||||
Image,
|
||||
NativeModules,
|
||||
requireNativeComponent,
|
||||
ViewPropTypes,
|
||||
StyleSheet,
|
||||
} from 'react-native'
|
||||
|
||||
const resolveAssetSource = require('react-native/Libraries/Image/resolveAssetSource')
|
||||
@ -24,6 +26,9 @@ class FastImage extends Component {
|
||||
onLoad,
|
||||
onError,
|
||||
onLoadEnd,
|
||||
style,
|
||||
children,
|
||||
borderRadius,
|
||||
...props
|
||||
} = this.props
|
||||
|
||||
@ -33,6 +38,7 @@ class FastImage extends Component {
|
||||
<Image
|
||||
ref={e => (this._root = e)}
|
||||
{...props}
|
||||
style={style}
|
||||
source={source}
|
||||
onLoadStart={onLoadStart}
|
||||
onProgress={onProgress}
|
||||
@ -44,21 +50,53 @@ class FastImage extends Component {
|
||||
}
|
||||
|
||||
const resolvedSource = resolveAssetSource(source)
|
||||
|
||||
if (children) {
|
||||
throw new Error(
|
||||
'The <FastImage> component cannot contain children. If you want to render content on top of the image consider using absolute positioning.',
|
||||
)
|
||||
}
|
||||
|
||||
if (!borderRadius) {
|
||||
return (
|
||||
<FastImageView
|
||||
ref={e => (this._root = e)}
|
||||
{...props}
|
||||
style={style}
|
||||
source={resolvedSource}
|
||||
onFastImageLoadStart={onLoadStart}
|
||||
onFastImageProgress={onProgress}
|
||||
onFastImageLoad={onLoad}
|
||||
onFastImageError={onError}
|
||||
onFastImageLoadEnd={onLoadEnd}
|
||||
/>
|
||||
)
|
||||
}
|
||||
|
||||
return (
|
||||
<FastImageView
|
||||
ref={e => (this._root = e)}
|
||||
{...props}
|
||||
source={resolvedSource}
|
||||
onFastImageLoadStart={onLoadStart}
|
||||
onFastImageProgress={onProgress}
|
||||
onFastImageLoad={onLoad}
|
||||
onFastImageError={onError}
|
||||
onFastImageLoadEnd={onLoadEnd}
|
||||
/>
|
||||
<View style={[style, styles.imageContainer]} borderRadius={borderRadius}>
|
||||
<FastImageView
|
||||
ref={e => (this._root = e)}
|
||||
{...props}
|
||||
style={StyleSheet.absoluteFill}
|
||||
source={resolvedSource}
|
||||
onFastImageLoadStart={onLoadStart}
|
||||
onFastImageProgress={onProgress}
|
||||
onFastImageLoad={onLoad}
|
||||
onFastImageError={onError}
|
||||
onFastImageLoadEnd={onLoadEnd}
|
||||
/>
|
||||
</View>
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
const styles = StyleSheet.create({
|
||||
imageContainer: {
|
||||
overflow: 'hidden',
|
||||
},
|
||||
})
|
||||
|
||||
FastImage.resizeMode = {
|
||||
contain: 'contain',
|
||||
cover: 'cover',
|
||||
|
@ -1,8 +1,10 @@
|
||||
import 'react-native'
|
||||
import { StyleSheet } from 'react-native'
|
||||
import React from 'react'
|
||||
import renderer from 'react-test-renderer'
|
||||
import FastImage from './FastImage.js'
|
||||
|
||||
const style = StyleSheet.create({ image: { width: 44, height: 44 } })
|
||||
|
||||
test('FastImage renders correctly.', () => {
|
||||
const tree = renderer
|
||||
.create(
|
||||
@ -14,6 +16,20 @@ test('FastImage renders correctly.', () => {
|
||||
},
|
||||
priority: FastImage.priority.high,
|
||||
}}
|
||||
style={style.image}
|
||||
/>,
|
||||
)
|
||||
.toJSON()
|
||||
|
||||
expect(tree).toMatchSnapshot()
|
||||
})
|
||||
|
||||
test('Renders a normal Image when not passed a uri.', () => {
|
||||
const tree = renderer
|
||||
.create(
|
||||
<FastImage
|
||||
source={require('./server/pictures/jellyfish.gif')}
|
||||
style={style.image}
|
||||
/>,
|
||||
)
|
||||
.toJSON()
|
||||
|
13
README.md
@ -38,6 +38,7 @@ and
|
||||
- [x] Prioritize images.
|
||||
- [x] Preload images.
|
||||
- [x] GIF support.
|
||||
- [x] Border radius property.
|
||||
|
||||
## Usage
|
||||
|
||||
@ -92,8 +93,8 @@ Headers to load the image with. e.g. `{ Authorization: 'someAuthToken' }`.
|
||||
|
||||
### `resizeMode?: enum`
|
||||
|
||||
- `FastImage.resizeMode.contain` **(Default)** - Scale the image uniformly (maintain the image's aspect ratio) so that both dimensions (width and height) of the image will be equal to or less than the corresponding dimension of the view (minus padding).
|
||||
- `FastImage.resizeMode.cover` - Scale the image uniformly (maintain the image's aspect ratio) so that both dimensions (width and height) of the image will be equal to or larger than the corresponding dimension of the view (minus padding).
|
||||
- `FastImage.resizeMode.contain` - Scale the image uniformly (maintain the image's aspect ratio) so that both dimensions (width and height) of the image will be equal to or less than the corresponding dimension of the view (minus padding).
|
||||
- `FastImage.resizeMode.cover` **(Default)** - Scale the image uniformly (maintain the image's aspect ratio) so that both dimensions (width and height) of the image will be equal to or larger than the corresponding dimension of the view (minus padding).
|
||||
- `FastImage.resizeMode.stretch` - Scale width and height independently, This may change the aspect ratio of the src.
|
||||
- `FastImage.resizeMode.center` - Do not scale the image, keep centered.
|
||||
|
||||
@ -131,12 +132,10 @@ Called when the image finishes loading, whether it was successful or an error.
|
||||
|
||||
---
|
||||
|
||||
### `children`
|
||||
### `borderRadius: number`
|
||||
|
||||
`FastImage` does not currently support children.
|
||||
Absolute positioning can be used as an alternative.
|
||||
|
||||
This is because `FastImage` supplies a `android.widget.imageview` and not a `android.view.viewgroup`.
|
||||
A border radius for the image.
|
||||
Can be used to make a circular cropped version of the image.
|
||||
|
||||
## Static Methods
|
||||
|
||||
|
@ -17,5 +17,33 @@ exports[`FastImage renders correctly. 1`] = `
|
||||
"uri": "https://facebook.github.io/react/img/logo_og.png",
|
||||
}
|
||||
}
|
||||
style={
|
||||
Object {
|
||||
"height": 44,
|
||||
"width": 44,
|
||||
}
|
||||
}
|
||||
/>
|
||||
`;
|
||||
|
||||
exports[`Renders a normal Image when not passed a uri. 1`] = `
|
||||
<Image
|
||||
onError={undefined}
|
||||
onLoad={undefined}
|
||||
onLoadEnd={undefined}
|
||||
onLoadStart={undefined}
|
||||
onProgress={undefined}
|
||||
resizeMode="cover"
|
||||
source={
|
||||
Object {
|
||||
"testUri": "../../../server/pictures/jellyfish.gif",
|
||||
}
|
||||
}
|
||||
style={
|
||||
Object {
|
||||
"height": 44,
|
||||
"width": 44,
|
||||
}
|
||||
}
|
||||
/>
|
||||
`;
|
||||
|
@ -64,7 +64,7 @@ class FastImageViewConverter {
|
||||
}};
|
||||
|
||||
public static ScaleType scaleType(String resizeMode) {
|
||||
if (resizeMode == null) resizeMode = "contain";
|
||||
if (resizeMode == null) resizeMode = "cover";
|
||||
final ImageView.ScaleType scaleType = REACT_RESIZE_MODE_MAP.get(resizeMode);
|
||||
return scaleType;
|
||||
}
|
||||
|
@ -9,6 +9,7 @@ import android.widget.ImageView;
|
||||
import com.bumptech.glide.Glide;
|
||||
import com.bumptech.glide.Priority;
|
||||
import com.bumptech.glide.RequestManager;
|
||||
import com.bumptech.glide.load.engine.DiskCacheStrategy;
|
||||
import com.bumptech.glide.load.model.GlideUrl;
|
||||
import com.bumptech.glide.request.RequestOptions;
|
||||
import com.facebook.react.bridge.ReadableMap;
|
||||
@ -100,10 +101,12 @@ class FastImageViewManager extends SimpleViewManager<ImageViewWithUrl> implement
|
||||
|
||||
RequestOptions options = new RequestOptions()
|
||||
.priority(priority)
|
||||
.dontTransform()
|
||||
.placeholder(TRANSPARENT_DRAWABLE);
|
||||
|
||||
requestManager
|
||||
.load(glideUrl)
|
||||
.diskCacheStrategy(DiskCacheStrategy.ALL)
|
||||
.apply(options)
|
||||
.listener(new FastImageRequestListener(key))
|
||||
.into(view);
|
||||
|
28
docs/how-is-caching-handled.md
Normal file
@ -0,0 +1,28 @@
|
||||
# How is caching handled?
|
||||
|
||||
In the readme it says "Aggressively cache images.". What does this mean?
|
||||
|
||||
This library treats image urls as immutable.
|
||||
That means it assumes the data located at a given url will not change.
|
||||
This is ideal for performance.
|
||||
|
||||
The way this would work in practice for something like a user profile picture is:
|
||||
|
||||
- Request user from API.
|
||||
- Receive JSON representing the user containing a `profilePicture` property that is the url of the profile picture.
|
||||
- Display the profile picture.
|
||||
|
||||
So what happens if the user wants to change their profile picture?
|
||||
|
||||
- User uploads a new profile picture, it gets a new url on the backend.
|
||||
- Update a field in a database.
|
||||
|
||||
Next time the app is opened:
|
||||
|
||||
- Display the cached profile picture immediately.
|
||||
- Request the user json again (this time it will have the new profile picture url).
|
||||
- Display the new profile picture.
|
||||
|
||||
## How is the cache cleared?
|
||||
|
||||
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.
|
@ -1,13 +1,13 @@
|
||||
import React from 'react'
|
||||
import { TabNavigator, TabBarBottom } from 'react-navigation'
|
||||
import FastImageExample from './fastImage/FastImageExample'
|
||||
import FastImageExamples from './fastImage/FastImageExamples'
|
||||
import FastImageGrid from './fastImage/FastImageGrid'
|
||||
import DefaultImageGrid from './fastImage/DefaultImageGrid'
|
||||
|
||||
const App = TabNavigator(
|
||||
{
|
||||
fastImageExample: {
|
||||
screen: FastImageExample,
|
||||
screen: FastImageExamples,
|
||||
},
|
||||
image: {
|
||||
screen: DefaultImageGrid,
|
||||
|
58
example/fastImage/BorderRadiusExample.js
Normal file
@ -0,0 +1,58 @@
|
||||
import React from 'react'
|
||||
import { StyleSheet, View } from 'react-native'
|
||||
import withCacheBust from './withCacheBust'
|
||||
import SectionFlex from './SectionFlex'
|
||||
import FastImage from 'react-native-fast-image'
|
||||
import Section from './Section'
|
||||
import FeatureText from './FeatureText'
|
||||
|
||||
const IMAGE_URL = 'https://media.giphy.com/media/GEsoqZDGVoisw/giphy.gif'
|
||||
|
||||
const BorderRadiusExample = ({ onPressReload, bust }) => (
|
||||
<View>
|
||||
<Section>
|
||||
<FeatureText text="• Border radius." />
|
||||
</Section>
|
||||
<SectionFlex onPress={onPressReload}>
|
||||
<FastImage
|
||||
style={styles.imageSquare}
|
||||
borderRadius={50}
|
||||
source={{
|
||||
uri: IMAGE_URL + bust,
|
||||
}}
|
||||
/>
|
||||
<FastImage
|
||||
style={styles.imageRectangular}
|
||||
borderRadius={50}
|
||||
source={{
|
||||
uri: IMAGE_URL + bust,
|
||||
}}
|
||||
/>
|
||||
</SectionFlex>
|
||||
</View>
|
||||
)
|
||||
|
||||
const styles = StyleSheet.create({
|
||||
imageSquare: {
|
||||
height: 100,
|
||||
backgroundColor: '#ddd',
|
||||
margin: 20,
|
||||
width: 100,
|
||||
flex: 0,
|
||||
},
|
||||
imageRectangular: {
|
||||
height: 100,
|
||||
backgroundColor: '#ddd',
|
||||
margin: 20,
|
||||
flex: 1,
|
||||
},
|
||||
plus: {
|
||||
width: 30,
|
||||
height: 30,
|
||||
position: 'absolute',
|
||||
bottom: 0,
|
||||
right: 0,
|
||||
},
|
||||
})
|
||||
|
||||
export default withCacheBust(BorderRadiusExample)
|
22
example/fastImage/Button.js
Normal file
@ -0,0 +1,22 @@
|
||||
import React from 'react'
|
||||
import { StyleSheet, Text, TouchableOpacity } from 'react-native'
|
||||
|
||||
const Button = ({ text, onPress }) => (
|
||||
<TouchableOpacity onPress={onPress}>
|
||||
<Text style={styles.button}>{text}</Text>
|
||||
</TouchableOpacity>
|
||||
)
|
||||
|
||||
const styles = StyleSheet.create({
|
||||
button: {
|
||||
backgroundColor: 'black',
|
||||
color: 'white',
|
||||
margin: 5,
|
||||
padding: 5,
|
||||
paddingLeft: 10,
|
||||
paddingRight: 10,
|
||||
borderRadius: 2,
|
||||
},
|
||||
})
|
||||
|
||||
export default Button
|
@ -1,168 +0,0 @@
|
||||
import React, { Component } from 'react'
|
||||
import {
|
||||
Button,
|
||||
PixelRatio,
|
||||
ScrollView,
|
||||
StatusBar,
|
||||
StyleSheet,
|
||||
Text,
|
||||
View,
|
||||
} from 'react-native'
|
||||
import Icon from 'react-native-vector-icons/Ionicons'
|
||||
import FastImage from 'react-native-fast-image'
|
||||
import uuid from 'uuid/v4'
|
||||
|
||||
const getImageUrl = (id, width, height) =>
|
||||
`https://source.unsplash.com/${id}/${width}x${height}`
|
||||
|
||||
const IMAGE_SIZE = 150
|
||||
const IMAGE_SIZE_PX = PixelRatio.getPixelSizeForLayoutSize(IMAGE_SIZE)
|
||||
|
||||
// The server is used to test that sending headers is working correctly.
|
||||
const USE_SERVER = false
|
||||
const TOKEN = 'someToken'
|
||||
|
||||
const getImages = () => {
|
||||
if (USE_SERVER) {
|
||||
const baseUrl = '192.168.2.11'
|
||||
return [
|
||||
`http://${baseUrl}:8080/pictures/ahmed-saffu-235616.jpg`,
|
||||
`http://${baseUrl}:8080/pictures/alex-bertha-236361.jpg`,
|
||||
`http://${baseUrl}:8080/pictures/jaromir-kavan-233699.jpg`,
|
||||
]
|
||||
}
|
||||
return [
|
||||
getImageUrl('x58soEovG_M', IMAGE_SIZE_PX, IMAGE_SIZE_PX),
|
||||
getImageUrl('yPI7myL5eWY', IMAGE_SIZE_PX, IMAGE_SIZE_PX),
|
||||
'https://cdn-images-1.medium.com/max/1600/1*-CY5bU4OqiJRox7G00sftw.gif',
|
||||
]
|
||||
}
|
||||
|
||||
const getTestProgressCallbacks = label => ({
|
||||
onLoadStart: () => console.log(`${label} - onLoadStart`),
|
||||
onProgress: e =>
|
||||
console.log(
|
||||
`${label} - onProgress - ${e.nativeEvent.loaded / e.nativeEvent.total}`,
|
||||
),
|
||||
onLoad: () => console.log(`${label} - onLoad`),
|
||||
onError: () => console.log(`${label} - onError`),
|
||||
onLoadEnd: () => console.log(`${label} - onLoadEnd`),
|
||||
})
|
||||
|
||||
const images = getImages()
|
||||
|
||||
// Preload images. This can be called anywhere.
|
||||
FastImage.preload([
|
||||
{
|
||||
uri: 'https://facebook.github.io/react/img/logo_og.png',
|
||||
headers: { Authorization: 'someAuthToken' },
|
||||
},
|
||||
{
|
||||
uri: 'https://facebook.github.io/react/img/logo_og.png',
|
||||
headers: { Authorization: 'someAuthToken' },
|
||||
},
|
||||
])
|
||||
|
||||
class FastImageExample extends Component {
|
||||
state = { bust: '?bust' }
|
||||
|
||||
onPressedReload = () => {
|
||||
// Force complete re-render and bust image cache.
|
||||
const key = uuid()
|
||||
const bust = `?bust=${key}`
|
||||
this.setState({ bust })
|
||||
}
|
||||
|
||||
render() {
|
||||
return (
|
||||
<View style={styles.container} key={this.state.bust}>
|
||||
<StatusBar
|
||||
translucent
|
||||
barStyle="dark-content"
|
||||
backgroundColor="transparent"
|
||||
/>
|
||||
<ScrollView
|
||||
style={styles.scrollContainer}
|
||||
contentContainerStyle={styles.scrollContentContainer}
|
||||
>
|
||||
<View style={styles.textContainer}>
|
||||
<Text style={styles.bold}>FastImage</Text>
|
||||
<Text>• priority (low, normal, high)</Text>
|
||||
<Text>• authentication (token)</Text>
|
||||
<Button title="Reload" onPress={this.onPressedReload} />
|
||||
</View>
|
||||
<FastImage
|
||||
style={styles.image}
|
||||
source={{
|
||||
uri: images[0] + this.state.bust,
|
||||
headers: {
|
||||
token: TOKEN,
|
||||
},
|
||||
priority: FastImage.priority.low,
|
||||
}}
|
||||
{...getTestProgressCallbacks('1')}
|
||||
/>
|
||||
<FastImage
|
||||
style={styles.image}
|
||||
source={{
|
||||
uri: images[1] + this.state.bust,
|
||||
headers: {
|
||||
token: TOKEN,
|
||||
},
|
||||
priority: FastImage.priority.normal,
|
||||
}}
|
||||
{...getTestProgressCallbacks('2')}
|
||||
/>
|
||||
<FastImage
|
||||
style={styles.image}
|
||||
source={{
|
||||
uri: images[2] + this.state.bust,
|
||||
headers: {
|
||||
token: TOKEN,
|
||||
},
|
||||
priority: FastImage.priority.high,
|
||||
}}
|
||||
{...getTestProgressCallbacks('3')}
|
||||
/>
|
||||
</ScrollView>
|
||||
</View>
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
FastImageExample.navigationOptions = {
|
||||
tabBarLabel: 'FastImage Example',
|
||||
tabBarIcon: ({ focused, tintColor }) => {
|
||||
if (focused)
|
||||
return <Icon name="ios-information-circle" size={26} color={tintColor} />
|
||||
return (
|
||||
<Icon name="ios-information-circle-outline" size={26} color={tintColor} />
|
||||
)
|
||||
},
|
||||
}
|
||||
|
||||
const styles = StyleSheet.create({
|
||||
bold: {
|
||||
fontWeight: '900',
|
||||
},
|
||||
textContainer: {
|
||||
marginTop: 40,
|
||||
marginBottom: 20,
|
||||
},
|
||||
image: {
|
||||
height: IMAGE_SIZE,
|
||||
width: IMAGE_SIZE,
|
||||
backgroundColor: '#eee',
|
||||
margin: 2,
|
||||
},
|
||||
container: {
|
||||
flex: 1,
|
||||
},
|
||||
scrollContainer: {},
|
||||
scrollContentContainer: {
|
||||
alignItems: 'center',
|
||||
flex: 0,
|
||||
},
|
||||
})
|
||||
|
||||
export default FastImageExample
|
80
example/fastImage/FastImageExamples.js
Normal file
@ -0,0 +1,80 @@
|
||||
import React from 'react'
|
||||
import { ScrollView, StatusBar, StyleSheet, Text, View } from 'react-native'
|
||||
import Icon from 'react-native-vector-icons/Ionicons'
|
||||
import Section from './Section'
|
||||
import PriorityExample from './PriorityExample'
|
||||
import GifExample from './GifExample'
|
||||
import BorderRadiusExample from './BorderRadiusExample'
|
||||
import FeatureText from './FeatureText'
|
||||
import ProgressExample from './ProgressExample'
|
||||
import PreloadExample from './PreloadExample'
|
||||
import StatusBarUnderlay, { STATUS_BAR_HEIGHT } from './StatusBarUnderlay'
|
||||
|
||||
const FastImageExample = () => (
|
||||
<View style={styles.container}>
|
||||
<StatusBar
|
||||
translucent
|
||||
barStyle="dark-content"
|
||||
backgroundColor="transparent"
|
||||
/>
|
||||
<ScrollView
|
||||
style={styles.scrollContainer}
|
||||
contentContainerStyle={styles.scrollContentContainer}
|
||||
>
|
||||
<View style={styles.contentContainer}>
|
||||
<Section>
|
||||
<Text style={styles.titleText}>🚩 FastImage</Text>
|
||||
<FeatureText text="Tap images to reload examples." />
|
||||
</Section>
|
||||
<PriorityExample />
|
||||
<GifExample />
|
||||
<BorderRadiusExample />
|
||||
<ProgressExample />
|
||||
<PreloadExample />
|
||||
</View>
|
||||
</ScrollView>
|
||||
<StatusBarUnderlay />
|
||||
</View>
|
||||
)
|
||||
|
||||
FastImageExample.navigationOptions = {
|
||||
tabBarLabel: 'FastImage Example',
|
||||
tabBarIcon: ({ focused, tintColor }) => {
|
||||
const name = focused
|
||||
? 'ios-information-circle'
|
||||
: 'ios-information-circle-outline'
|
||||
return <Icon name={name} size={26} color={tintColor} />
|
||||
},
|
||||
}
|
||||
|
||||
const styles = StyleSheet.create({
|
||||
titleText: {
|
||||
fontWeight: '900',
|
||||
marginBottom: 20,
|
||||
color: '#222',
|
||||
},
|
||||
contentContainer: {
|
||||
marginTop: 20,
|
||||
marginBottom: 20,
|
||||
},
|
||||
image: {
|
||||
flex: 1,
|
||||
height: 100,
|
||||
backgroundColor: '#ddd',
|
||||
margin: 10,
|
||||
},
|
||||
container: {
|
||||
flex: 1,
|
||||
alignItems: 'stretch',
|
||||
backgroundColor: '#fff',
|
||||
},
|
||||
scrollContainer: {
|
||||
marginTop: STATUS_BAR_HEIGHT,
|
||||
},
|
||||
scrollContentContainer: {
|
||||
alignItems: 'stretch',
|
||||
flex: 0,
|
||||
},
|
||||
})
|
||||
|
||||
export default FastImageExample
|
10
example/fastImage/FeatureText.js
Normal file
@ -0,0 +1,10 @@
|
||||
import React from 'react'
|
||||
import { StyleSheet, Text } from 'react-native'
|
||||
|
||||
export default ({ text }) => <Text style={styles.style}>{text}</Text>
|
||||
|
||||
const styles = StyleSheet.create({
|
||||
style: {
|
||||
color: '#222',
|
||||
},
|
||||
})
|
33
example/fastImage/GifExample.js
Normal file
@ -0,0 +1,33 @@
|
||||
import React from 'react'
|
||||
import { StyleSheet, View } from 'react-native'
|
||||
import withCacheBust from './withCacheBust'
|
||||
import SectionFlex from './SectionFlex'
|
||||
import FastImage from 'react-native-fast-image'
|
||||
import Section from './Section'
|
||||
import FeatureText from './FeatureText'
|
||||
|
||||
const GIF_URL =
|
||||
'https://cdn-images-1.medium.com/max/1600/1*-CY5bU4OqiJRox7G00sftw.gif'
|
||||
|
||||
const GifExample = ({ onPressReload, bust }) => (
|
||||
<View>
|
||||
<Section>
|
||||
<FeatureText text="• GIF support." />
|
||||
</Section>
|
||||
<SectionFlex onPress={onPressReload}>
|
||||
<FastImage style={styles.image} source={{ uri: GIF_URL + bust }} />
|
||||
</SectionFlex>
|
||||
</View>
|
||||
)
|
||||
|
||||
const styles = StyleSheet.create({
|
||||
image: {
|
||||
backgroundColor: '#ddd',
|
||||
margin: 10,
|
||||
height: 100,
|
||||
width: 100,
|
||||
flex: 0,
|
||||
},
|
||||
})
|
||||
|
||||
export default withCacheBust(GifExample)
|
@ -1,12 +1,6 @@
|
||||
import React, { Component } from 'react'
|
||||
import {
|
||||
StyleSheet,
|
||||
View,
|
||||
FlatList,
|
||||
Platform,
|
||||
StatusBar,
|
||||
Text,
|
||||
} from 'react-native'
|
||||
import { FlatList, StyleSheet, Text, View } from 'react-native'
|
||||
import StatusBarUnderlay, { STATUS_BAR_HEIGHT } from './StatusBarUnderlay'
|
||||
|
||||
const getImageUrl = (id, width, height) =>
|
||||
`https://unsplash.it/${width}/${height}?image=${id}`
|
||||
@ -87,24 +81,15 @@ class ImageGrid extends Component {
|
||||
keyExtractor={this._extractKey}
|
||||
getItemLayout={this._getItemLayout}
|
||||
/>
|
||||
<View style={styles.statusBarUnderlay} />
|
||||
<StatusBarUnderlay />
|
||||
</View>
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
const MARGIN = 2
|
||||
const STATUS_BAR_HEIGHT = Platform.OS === 'ios' ? 20 : StatusBar.currentHeight
|
||||
|
||||
const styles = StyleSheet.create({
|
||||
statusBarUnderlay: {
|
||||
position: 'absolute',
|
||||
top: 0,
|
||||
left: 0,
|
||||
right: 0,
|
||||
height: STATUS_BAR_HEIGHT,
|
||||
backgroundColor: 'white',
|
||||
},
|
||||
container: {
|
||||
flex: 1,
|
||||
backgroundColor: '#fff',
|
||||
|
68
example/fastImage/PreloadExample.js
Normal file
@ -0,0 +1,68 @@
|
||||
import React, { Component } from 'react'
|
||||
import { StyleSheet, View } from 'react-native'
|
||||
import SectionFlex from './SectionFlex'
|
||||
import FastImage from 'react-native-fast-image'
|
||||
import Section from './Section'
|
||||
import FeatureText from './FeatureText'
|
||||
import uuid from 'uuid/v4'
|
||||
import Button from './Button'
|
||||
|
||||
const IMAGE_URL =
|
||||
'https://cdn-images-1.medium.com/max/1600/1*-CY5bU4OqiJRox7G00sftw.gif'
|
||||
|
||||
class PreloadExample extends Component {
|
||||
state = {
|
||||
show: false,
|
||||
url: IMAGE_URL,
|
||||
}
|
||||
|
||||
bustAndPreload = () => {
|
||||
const key = uuid()
|
||||
const bust = `?bust=${key}`
|
||||
// Preload images. This can be called anywhere.
|
||||
const url = IMAGE_URL + bust
|
||||
FastImage.preload([{ uri: url }])
|
||||
this.setState({ url, show: false })
|
||||
}
|
||||
|
||||
showImage = () => {
|
||||
this.setState({ show: true })
|
||||
}
|
||||
|
||||
render() {
|
||||
return (
|
||||
<View>
|
||||
<Section>
|
||||
<FeatureText text="• Preloading." />
|
||||
</Section>
|
||||
<SectionFlex style={styles.section} onPress={this.props.onPressReload}>
|
||||
{this.state.show ? (
|
||||
<FastImage style={styles.image} source={{ uri: this.state.url }} />
|
||||
) : (
|
||||
<View style={styles.image} />
|
||||
)}
|
||||
<Button
|
||||
text="Bust cache and preload."
|
||||
onPress={this.bustAndPreload}
|
||||
/>
|
||||
<Button text="Render image." onPress={this.showImage} />
|
||||
</SectionFlex>
|
||||
</View>
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
const styles = StyleSheet.create({
|
||||
section: {
|
||||
flexDirection: 'column',
|
||||
alignItems: 'center',
|
||||
},
|
||||
image: {
|
||||
backgroundColor: '#ddd',
|
||||
margin: 10,
|
||||
height: 100,
|
||||
width: 100,
|
||||
},
|
||||
})
|
||||
|
||||
export default PreloadExample
|
59
example/fastImage/PriorityExample.js
Normal file
@ -0,0 +1,59 @@
|
||||
import React from 'react'
|
||||
import { PixelRatio, StyleSheet, View } from 'react-native'
|
||||
import withCacheBust from './withCacheBust'
|
||||
import FastImage from 'react-native-fast-image'
|
||||
import Section from './Section'
|
||||
import SectionFlex from './SectionFlex'
|
||||
import FeatureText from './FeatureText'
|
||||
|
||||
const getImageUrl = (id, width, height) =>
|
||||
`https://source.unsplash.com/${id}/${width}x${height}`
|
||||
const IMAGE_SIZE = 1024
|
||||
const IMAGE_SIZE_PX = PixelRatio.getPixelSizeForLayoutSize(IMAGE_SIZE)
|
||||
const IMAGE_URLS = [
|
||||
getImageUrl('x58soEovG_M', IMAGE_SIZE_PX, IMAGE_SIZE_PX),
|
||||
getImageUrl('yPI7myL5eWY', IMAGE_SIZE_PX, IMAGE_SIZE_PX),
|
||||
getImageUrl('S7VCcp6KCKE', IMAGE_SIZE, IMAGE_SIZE),
|
||||
]
|
||||
|
||||
const PriorityExample = ({ onPressReload, bust }) => (
|
||||
<View>
|
||||
<Section>
|
||||
<FeatureText text="• Prioritize images (low, normal, high)." />
|
||||
</Section>
|
||||
<SectionFlex onPress={onPressReload}>
|
||||
<FastImage
|
||||
style={styles.image}
|
||||
source={{
|
||||
uri: IMAGE_URLS[0] + bust,
|
||||
priority: FastImage.priority.low,
|
||||
}}
|
||||
/>
|
||||
<FastImage
|
||||
style={styles.image}
|
||||
source={{
|
||||
uri: IMAGE_URLS[1] + bust,
|
||||
priority: FastImage.priority.normal,
|
||||
}}
|
||||
/>
|
||||
<FastImage
|
||||
style={styles.image}
|
||||
source={{
|
||||
uri: IMAGE_URLS[2] + bust,
|
||||
priority: FastImage.priority.high,
|
||||
}}
|
||||
/>
|
||||
</SectionFlex>
|
||||
</View>
|
||||
)
|
||||
|
||||
const styles = StyleSheet.create({
|
||||
image: {
|
||||
flex: 1,
|
||||
height: 100,
|
||||
backgroundColor: '#ddd',
|
||||
margin: 10,
|
||||
},
|
||||
})
|
||||
|
||||
export default withCacheBust(PriorityExample)
|
49
example/fastImage/ProgressExample.js
Normal file
@ -0,0 +1,49 @@
|
||||
import React from 'react'
|
||||
import { StyleSheet, View } from 'react-native'
|
||||
import withCacheBust from './withCacheBust'
|
||||
import SectionFlex from './SectionFlex'
|
||||
import FastImage from 'react-native-fast-image'
|
||||
import Section from './Section'
|
||||
import FeatureText from './FeatureText'
|
||||
|
||||
const IMAGE_URL = 'https://media.giphy.com/media/GEsoqZDGVoisw/giphy.gif'
|
||||
|
||||
const getTestProgressCallbacks = label => ({
|
||||
onLoadStart: () => console.log(`${label} - onLoadStart`),
|
||||
onProgress: e =>
|
||||
console.log(
|
||||
`${label} - onProgress - ${e.nativeEvent.loaded / e.nativeEvent.total}`,
|
||||
),
|
||||
onLoad: () => console.log(`${label} - onLoad`),
|
||||
onError: () => console.log(`${label} - onError`),
|
||||
onLoadEnd: () => console.log(`${label} - onLoadEnd`),
|
||||
})
|
||||
|
||||
const ProgressExample = ({ onPressReload, bust }) => (
|
||||
<View>
|
||||
<Section>
|
||||
<FeatureText text="• Progress callbacks." />
|
||||
</Section>
|
||||
<SectionFlex onPress={onPressReload}>
|
||||
<FastImage
|
||||
style={styles.image}
|
||||
source={{
|
||||
uri: IMAGE_URL + bust,
|
||||
}}
|
||||
{...getTestProgressCallbacks('ProgressExample')}
|
||||
/>
|
||||
</SectionFlex>
|
||||
</View>
|
||||
)
|
||||
|
||||
const styles = StyleSheet.create({
|
||||
image: {
|
||||
height: 100,
|
||||
backgroundColor: '#ddd',
|
||||
margin: 20,
|
||||
width: 100,
|
||||
flex: 0,
|
||||
},
|
||||
})
|
||||
|
||||
export default withCacheBust(ProgressExample)
|
13
example/fastImage/Section.js
Normal file
@ -0,0 +1,13 @@
|
||||
import React from 'react'
|
||||
import { StyleSheet, View } from 'react-native'
|
||||
|
||||
export default ({ children }) => <View style={styles.section}>{children}</View>
|
||||
|
||||
const styles = StyleSheet.create({
|
||||
section: {
|
||||
marginTop: 10,
|
||||
marginBottom: 10,
|
||||
marginLeft: 40,
|
||||
marginRight: 40,
|
||||
},
|
||||
})
|
20
example/fastImage/SectionFlex.js
Normal file
@ -0,0 +1,20 @@
|
||||
import React from 'react'
|
||||
import { StyleSheet, TouchableOpacity } from 'react-native'
|
||||
|
||||
export default ({ children, onPress, style }) => (
|
||||
<TouchableOpacity style={[styles.sectionFlex, style]} onPress={onPress}>
|
||||
{children}
|
||||
</TouchableOpacity>
|
||||
)
|
||||
|
||||
const styles = StyleSheet.create({
|
||||
sectionFlex: {
|
||||
backgroundColor: '#eee',
|
||||
flexDirection: 'row',
|
||||
justifyContent: 'center',
|
||||
marginTop: 10,
|
||||
marginBottom: 10,
|
||||
marginLeft: -10,
|
||||
marginRight: -10,
|
||||
},
|
||||
})
|
18
example/fastImage/StatusBarUnderlay.js
Normal file
@ -0,0 +1,18 @@
|
||||
import React from 'react'
|
||||
import { Platform, StatusBar, StyleSheet, View } from 'react-native'
|
||||
|
||||
export const STATUS_BAR_HEIGHT =
|
||||
Platform.OS === 'ios' ? 20 : StatusBar.currentHeight
|
||||
|
||||
export default () => <View style={styles.statusBarUnderlay} />
|
||||
|
||||
const styles = StyleSheet.create({
|
||||
statusBarUnderlay: {
|
||||
position: 'absolute',
|
||||
top: 0,
|
||||
left: 0,
|
||||
right: 0,
|
||||
height: STATUS_BAR_HEIGHT,
|
||||
backgroundColor: 'white',
|
||||
},
|
||||
})
|
28
example/fastImage/withCacheBust.js
Normal file
@ -0,0 +1,28 @@
|
||||
import React, { Component } from 'react'
|
||||
import uuid from 'uuid/v4'
|
||||
|
||||
export default BaseComponent => {
|
||||
class WithCacheBust extends Component {
|
||||
state = { bust: '?bust' }
|
||||
|
||||
onPressReload = () => {
|
||||
// Force complete re-render and bust image cache.
|
||||
const key = uuid()
|
||||
const bust = `?bust=${key}`
|
||||
this.setState({ bust })
|
||||
}
|
||||
|
||||
render() {
|
||||
return (
|
||||
<BaseComponent
|
||||
bust={this.state.bust}
|
||||
onPressReload={this.onPressReload}
|
||||
/>
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
WithCacheBust.displayName = `withCacheBust${BaseComponent.displayName}`
|
||||
|
||||
return WithCacheBust
|
||||
}
|
101
index.d.ts
vendored
Normal file
@ -0,0 +1,101 @@
|
||||
import * as React from 'react'
|
||||
import { FlexStyle, LayoutChangeEvent, ShadowStyleIOS, StyleProp, TransformsStyle } from 'react-native'
|
||||
|
||||
declare namespace FastImage {
|
||||
|
||||
namespace priority {
|
||||
type low = 'low'
|
||||
type normal = 'normal'
|
||||
type high = 'high'
|
||||
}
|
||||
|
||||
namespace resizeMode {
|
||||
type contain = 'contain'
|
||||
type cover = 'cover'
|
||||
type stretch = 'stretch'
|
||||
type center = 'center'
|
||||
}
|
||||
|
||||
export type Priority =
|
||||
FastImage.priority.low |
|
||||
FastImage.priority.normal |
|
||||
FastImage.priority.high
|
||||
|
||||
export type ResizeMode =
|
||||
FastImage.resizeMode.contain |
|
||||
FastImage.resizeMode.cover |
|
||||
FastImage.resizeMode.stretch |
|
||||
FastImage.resizeMode.center
|
||||
}
|
||||
|
||||
export type FastImageSource = {
|
||||
uri?: string,
|
||||
headers?: object
|
||||
priority?: FastImage.Priority
|
||||
}
|
||||
|
||||
export interface ImageStyle extends FlexStyle, TransformsStyle, ShadowStyleIOS {
|
||||
backfaceVisibility?: 'visible' | 'hidden'
|
||||
backgroundColor?: string
|
||||
borderColor?: string
|
||||
overlayColor?: string
|
||||
tintColor?: string
|
||||
opacity?: number
|
||||
}
|
||||
|
||||
export interface FastImageProperties {
|
||||
source: FastImageSource | number
|
||||
resizeMode?: FastImage.ResizeMode
|
||||
|
||||
onLoadStart?(): void
|
||||
|
||||
onProgress?(event: any): void
|
||||
|
||||
onLoad?(): void
|
||||
|
||||
onError?(): void
|
||||
|
||||
onLoadEnd?(): void
|
||||
|
||||
/**
|
||||
* onLayout function
|
||||
*
|
||||
* Invoked on mount and layout changes with
|
||||
*
|
||||
* {nativeEvent: { layout: {x, y, width, height}}}.
|
||||
*/
|
||||
onLayout?: (event: LayoutChangeEvent) => void;
|
||||
|
||||
/**
|
||||
*
|
||||
* Style
|
||||
*/
|
||||
style?: StyleProp<ImageStyle>;
|
||||
|
||||
/**
|
||||
* A unique identifier for this element to be used in UI Automation testing scripts.
|
||||
*/
|
||||
testID?: string;
|
||||
}
|
||||
|
||||
interface FastImageStatic extends React.ComponentClass<FastImageProperties> {
|
||||
resizeMode: {
|
||||
contain: FastImage.resizeMode.contain
|
||||
cover: FastImage.resizeMode.cover
|
||||
stretch: FastImage.resizeMode.stretch
|
||||
center: FastImage.resizeMode.center
|
||||
}
|
||||
|
||||
priority: {
|
||||
low: FastImage.priority.low
|
||||
normal: FastImage.priority.normal
|
||||
high: FastImage.priority.high
|
||||
}
|
||||
|
||||
preload(sources: FastImageSource[]): void
|
||||
}
|
||||
|
||||
declare var FastImage: FastImageStatic
|
||||
type FastImage = FastImageStatic
|
||||
|
||||
export default FastImage
|
@ -1,6 +1,6 @@
|
||||
{
|
||||
"name": "react-native-fast-image",
|
||||
"version": "2.0.0",
|
||||
"version": "2.2.4",
|
||||
"description": "🚩 FastImage, performant React Native image component.",
|
||||
"keywords": [
|
||||
"cache",
|
||||
@ -19,7 +19,8 @@
|
||||
"ios",
|
||||
"android",
|
||||
"utils",
|
||||
"FastImage.js"
|
||||
"FastImage.js",
|
||||
"index.d.ts"
|
||||
],
|
||||
"main": "FastImage.js",
|
||||
"directories": {
|
||||
@ -49,10 +50,6 @@
|
||||
"react-native": "^0.50.4",
|
||||
"react-test-renderer": "16.0.0"
|
||||
},
|
||||
"engines": {
|
||||
"node": ">= 9.2.0",
|
||||
"yarn": ">= 1.3.2"
|
||||
},
|
||||
"jest": {
|
||||
"preset": "react-native",
|
||||
"modulePathIgnorePatterns": [
|
||||
|
Before Width: | Height: | Size: 8.9 MiB After Width: | Height: | Size: 8.9 MiB |
Before Width: | Height: | Size: 12 MiB After Width: | Height: | Size: 12 MiB |
Before Width: | Height: | Size: 16 MiB After Width: | Height: | Size: 16 MiB |
BIN
server/pictures/jellyfish.gif
Normal file
After Width: | Height: | Size: 913 KiB |
BIN
server/pictures/plankton.gif
Normal file
After Width: | Height: | Size: 804 KiB |