Skip to content

Commit

Permalink
Update to V0.4.0 (#47)
Browse files Browse the repository at this point in the history
* Fix scrolling issue, and use flat-list for performance gain

* Add key extractor

* Remove extraData property

* Add custom-image property

* Update example using react-native-fast-image

* Abstract logic into own generic component

* Update test

* Update deps and test

* Remove node v5 from travis

* Clean up Masonry

* Bump ver

* Update column and test

* Update readme.md

* Update readme.md

* Update readme.md

* Update readme.md
  • Loading branch information
brh55 committed Sep 15, 2017
1 parent 2eb9b9e commit efb97d4
Show file tree
Hide file tree
Showing 16 changed files with 247 additions and 101 deletions.
1 change: 0 additions & 1 deletion .travis.yml
Expand Up @@ -2,4 +2,3 @@ language: node_js
node_js:
- v7
- v6
- v5
6 changes: 5 additions & 1 deletion __tests__/__snapshots__/masonry.test.js.snap
Expand Up @@ -3,11 +3,15 @@
exports[`SNAPSHOT: All functionality should match prev snapshot 1`] = `
<View
onLayout={[Function]}
style={
Object {
"flex": 1,
}
}
>
<RCTScrollView
contentContainerStyle={
Object {
"flex": 1,
"flexDirection": "row",
"justifyContent": "space-between",
"width": "100%",
Expand Down
9 changes: 3 additions & 6 deletions __tests__/brick.test.js
Expand Up @@ -26,12 +26,9 @@ test('Render Brick', () => {

test('PRIVATE FUNC: Renders image tag', () => {
const imageTag = _getImageTag(mock[0], 9);
expect(imageTag).toEqual(
<Image
key='http://test1.jpg'
source={{ uri: 'http://test1.jpg' }}
resizeMethod="auto"
style={{ width: 100, height: 200, marginTop: 9 }} />);
const imageTree = renderer.create(imageTag).toJSON();
expect(imageTree.type).toEqual('Image');
expect(imageTree.props.source.uri).toEqual('http://test1.jpg');
});

test('PRIVATE FUNC: Renders touchable tag properly', () => {
Expand Down
1 change: 0 additions & 1 deletion __tests__/masonry.test.js
Expand Up @@ -22,7 +22,6 @@ test('Render masonry correct', () => {

// Crucial styles for the grid to work
expect(scrollView.props.contentContainerStyle).toEqual({
flex: 1,
justifyContent: 'space-between',
flexDirection: 'row',
width: '100%'
Expand Down
30 changes: 23 additions & 7 deletions components/Brick.js
@@ -1,5 +1,6 @@
import React, { Component } from 'react';
import { View, Image, TouchableHighlight } from 'react-native';
import Injector from 'react-native-injectable-component';

export default function Brick (props) {
// Avoid margins for first element
Expand All @@ -17,14 +18,29 @@ export default function Brick (props) {
}

// _getImageTag :: Image, Gutter -> ImageTag
export function _getImageTag (image, gutter = 0) {
export function _getImageTag (props, gutter = 0) {
const imageProps = {
key: props.uri,
source: {
uri: props.uri
},
resizeMethod: 'auto',
style: {
...props.imageContainerStyle,

width: props.width,
height: props.height,
marginTop: gutter,
}
};

return (
<Image
key={image.uri}
source={{ uri: image.uri }}
resizeMethod='auto'
style={{ width: image.width, height: image.height, marginTop: gutter, ...image.imageContainerStyle }} />
);
<Injector
defaultComponent={Image}
defaultProps={imageProps}
injectant={props.customImageComponent}
injectantProps={props.customImageProps} />
)
}

// _getTouchableUnit :: Image, Number -> TouchableTag
Expand Down
56 changes: 43 additions & 13 deletions components/Column.js
@@ -1,5 +1,5 @@
import React, { Component } from 'react';
import { View, Image, TouchableHighlight } from 'react-native';
import { View, Image, TouchableHighlight, FlatList } from 'react-native';
import styles from '../styles/main';
import PropTypes from 'prop-types';
import Brick from './Brick';
Expand All @@ -12,6 +12,12 @@ export default class Column extends Component {
parentDimensions: PropTypes.object,
columnKey: PropTypes.string,
imageContainerStyle: PropTypes.object,
customImageComponent: PropTypes.func,
customImageProps: PropTypes.object
};

static defaultProps = {
imageContainerStyle: {}
};

constructor(props) {
Expand Down Expand Up @@ -53,7 +59,7 @@ export default class Column extends Component {

// Resize image while maintain aspect ratio
// _resizeByColumns :: ImgDimensions , parentDimensions, nColumns -> AdjustedDimensions
_resizeByColumns (imgDimensions, parentDimensions, nColumns=2) {
_resizeByColumns (imgDimensions = { width: 0, height: 0 }, parentDimensions, nColumns=2) {
const { height, width } = parentDimensions;

// The gutter is 1% of the available view width
Expand All @@ -78,30 +84,54 @@ export default class Column extends Component {
}

// Renders the "bricks" within the columns
// _renderBricks :: [images] -> [TouchableTag || ImageTag...]
_renderBricks (bricks) {
return bricks.map((brick, index) => {
const gutter = (index === 0) ? 0 : brick.gutter;
const key = `RN-MASONRY-BRICK-${brick.column}-${index}`;
const { imageContainerStyle } = this.props;
const props = { ...brick, gutter, key, imageContainerStyle };
// _renderBrick :: images -> [TouchableTag || ImageTag...]
_renderBrick = (data) => {
// Example Data Structure
// {
// "item": {
// "uri": "https://img.buzzfeed.com/buzzfeed-static/static/2016-01/14/20/campaign_images/webdr15/which-delicious-mexican-food-item-are-you-based-o-2-20324-1452822970-1_dblbig.jpg",
// "column": 0,
// "dimensions": {
// "width": 625,
// "height": 415
// },
// "width": 180.675,
// "height": 119.96820000000001,
// "gutter": 3.65
// },
// "index": 9
// }
const brick = data.item;
const gutter = (data.index === 0) ? 0 : brick.gutter;
const key = `RN-MASONRY-BRICK-${brick.column}-${data.index}`;
const { imageContainerStyle, customImageComponent, customImageProps } = this.props;
const props = { ...brick, gutter, key, imageContainerStyle, customImageComponent, customImageProps };

return (
<Brick
{...props} />
);
});
}

// _keyExtractor :: item -> id
_keyExtractor = (item) => (item.id || item.key);

render() {
return (
<View
key={this.props.columnKey}
style={[
{ width: this.state.columnWidth },
{
width: this.state.columnWidth,
overflow: 'hidden'
},
styles.masonry__column
]}>
{this._renderBricks(this.state.images)}
<FlatList
key={this.props.columnKey}
data={this.state.images}
keyExtractor={this._keyExtractor}
renderItem={this._renderBrick}
/>
</View>
)
}
Expand Down
38 changes: 21 additions & 17 deletions components/Masonry.js
Expand Up @@ -24,6 +24,8 @@ export default class Masonry extends Component {
columns: PropTypes.number,
sorted: PropTypes.bool,
imageContainerStyle: PropTypes.object,
customImageComponent: PropTypes.func,
customImageProps: PropTypes.object
};

static defaultProps = {
Expand Down Expand Up @@ -80,18 +82,18 @@ export default class Masonry extends Component {
.map((brick, index) => assignObjectIndex(index, brick))
.map(brick => resolveImage(brick))
.map(resolveTask => resolveTask.fork(
(err) => console.warn('Image failed to load'),
(resolvedBrick) => {
this.setState(state => {
const sortedData = _insertIntoColumn(resolvedBrick, state._sortedData, this.props.sorted);

return {
dataSource: state.dataSource.cloneWithRows(sortedData),
_sortedData: sortedData,
_resolvedData: [...state._resolvedData, resolvedBrick]
}
});;
}));
(err) => console.warn('Image failed to load'),
(resolvedBrick) => {
this.setState(state => {
const sortedData = _insertIntoColumn(resolvedBrick, state._sortedData, this.props.sorted);

return {
dataSource: state.dataSource.cloneWithRows(sortedData),
_sortedData: sortedData,
_resolvedData: [...state._resolvedData, resolvedBrick]
}
});;
}));
}

_setParentDimensions(event) {
Expand All @@ -107,7 +109,7 @@ export default class Masonry extends Component {

render() {
return (
<View onLayout={(event) => this._setParentDimensions(event)}>
<View style={{flex: 1}} onLayout={(event) => this._setParentDimensions(event)}>
<ListView
contentContainerStyle={styles.masonry__container}
dataSource={this.state.dataSource}
Expand All @@ -118,6 +120,8 @@ export default class Masonry extends Component {
columns={this.props.columns}
parentDimensions={this.state.dimensions}
imageContainerStyle={this.props.imageContainerStyle}
customImageComponent={this.props.customImageComponent}
customImageProps={this.props.customImageProps}
key={`RN-MASONRY-COLUMN-${rowID}`}/> }
/>
</View>
Expand All @@ -134,12 +138,12 @@ export function _insertIntoColumn (resolvedBrick, dataSet, sorted) {

if (column) {
// Append to existing "row"/"column"
const bricks = [...column, resolvedBrick]
if(sorted) {
const bricks = [...column, resolvedBrick];
if (sorted) {
// Sort bricks according to the index of their original array position
bricks = bricks.sort((a, b) => { return (a.index < b.index) ? -1 : 1; });
bricks = bricks.sort((a, b) => (a.index < b.index) ? -1 : 1);
}
dataCopy[columnIndex] = bricks
dataCopy[columnIndex] = bricks;
} else {
// Pass it as a new "row" for the data source
dataCopy = [...dataCopy, [resolvedBrick]];
Expand Down
32 changes: 22 additions & 10 deletions example/App/Container/app.js
Expand Up @@ -14,6 +14,7 @@ import {
Image,
Slider
} from 'react-native';
import FastImage from 'react-native-fast-image';
import Masonry from 'react-native-masonry';

// list of images
Expand Down Expand Up @@ -45,10 +46,10 @@ const data = [
}
},
{
uri: 'https://s-media-cache-ak0.pinimg.com/736x/b1/21/df/b121df29b41b771d6610dba71834e512.jpg'
uri: 'https://s-media-cache-ak0.pinimg.com/736x/b1/21/df/b121df29b41b771d6610dba71834e512.jpg',
},
{
uri: 'https://encrypted-tbn0.gstatic.com/images?q=tbn:ANd9GcTQpD8mz-2Wwix8hHbGgR-mCFQVFTF7TF7hU05BxwLVO1PS5j-rZA'
uri: 'https://encrypted-tbn0.gstatic.com/images?q=tbn:ANd9GcTQpD8mz-2Wwix8hHbGgR-mCFQVFTF7TF7hU05BxwLVO1PS5j-rZA',
},
{
uri: 'https://s-media-cache-ak0.pinimg.com/736x/5a/15/0c/5a150cf9d5a825c8b5871eefbeda8d14.jpg'
Expand Down Expand Up @@ -117,11 +118,11 @@ export default class example extends Component {

render() {
return (
<ScrollView style={{backgroundColor: '#f4f4f4'}}>
<View style={styles.center}>
<View style={{flex: 1, backgroundColor: '#f4f4f4'}}>
<View style={[styles.center, styles.header]}>
<Text style={{ fontWeight: '800', fontSize: 20 }}>Masonry Demo</Text>
</View>
<View style={[styles.center, { marginTop: 10, marginBottom: 25 }]}>
<View style={[styles.center, styles.buttonGroup, { marginTop: 10, marginBottom: 25 }]}>
<TouchableHighlight style={styles.button} onPress={() => this.setState({ columns: 2 })}>
<Text>2 Column</Text>
</TouchableHighlight>
Expand All @@ -135,7 +136,7 @@ export default class example extends Component {
<Text>9 Columns</Text>
</TouchableHighlight>
</View>
<View style={[styles.center, { marginTop: 10, marginBottom: 25, flexDirection: 'column'}]}>
<View style={[styles.center, styles.slider, { marginTop: 10, marginBottom: 25, flexDirection: 'column'}]}>
<View style={{paddingLeft: 10}}>
<Text>Dynamically adjust padding: {this.state.padding}</Text>
</View>
Expand All @@ -148,13 +149,14 @@ export default class example extends Component {
onValueChange={(value) => this.setState({padding: value})} />
</View>
</View>
<View style={{height: '100%', padding: this.state.padding}}>
<View style={{flex: 1, flexGrow: 10, padding: this.state.padding}}>
<Masonry
sorted
bricks={data}
columns={this.state.columns}/>
columns={this.state.columns}
customImageComponent={FastImage} />
</View>
</ScrollView>
</View>
);
}
}
Expand All @@ -164,7 +166,17 @@ const styles = StyleSheet.create({
justifyContent: 'center',
alignItems: 'center',
backgroundColor: '#f4f4f4',
flex: 1
flex: 1,
flexBasis: '10%'
},
header: {
flexGrow: 1
},
buttonGroup: {
flexGrow: 1
},
slider: {
flexGrow: 1
},
button: {
backgroundColor: '#dbdcdb',
Expand Down
1 change: 1 addition & 0 deletions example/android/app/build.gradle
Expand Up @@ -126,6 +126,7 @@ android {
}

dependencies {
compile project(':react-native-fast-image')
compile fileTree(dir: "libs", include: ["*.jar"])
compile "com.android.support:appcompat-v7:23.0.1"
compile "com.facebook.react:react-native:+" // From node_modules
Expand Down
Expand Up @@ -3,6 +3,7 @@
import android.app.Application;

import com.facebook.react.ReactApplication;
import com.dylanvann.fastimage.FastImageViewPackage;
import com.facebook.react.ReactNativeHost;
import com.facebook.react.ReactPackage;
import com.facebook.react.shell.MainReactPackage;
Expand All @@ -22,7 +23,8 @@ public boolean getUseDeveloperSupport() {
@Override
protected List<ReactPackage> getPackages() {
return Arrays.<ReactPackage>asList(
new MainReactPackage()
new MainReactPackage(),
new FastImageViewPackage()
);
}
};
Expand Down
2 changes: 2 additions & 0 deletions example/android/settings.gradle
@@ -1,3 +1,5 @@
rootProject.name = 'example'
include ':react-native-fast-image'
project(':react-native-fast-image').projectDir = new File(rootProject.projectDir, '../node_modules/react-native-fast-image/android')

include ':app'

0 comments on commit efb97d4

Please sign in to comment.