diff --git a/lib/FullScreenContainer.js b/lib/FullScreenContainer.js
index b22020b..399f9e0 100644
--- a/lib/FullScreenContainer.js
+++ b/lib/FullScreenContainer.js
@@ -3,9 +3,11 @@ import PropTypes from 'prop-types';
import {
DeviceEventEmitter,
Dimensions,
- FlatList,
+ ListView,
View,
+ ViewPagerAndroid,
StyleSheet,
+ Platform,
StatusBar,
TouchableWithoutFeedback,
ViewPropTypes
@@ -19,17 +21,18 @@ export default class FullScreenContainer extends React.Component {
static propTypes = {
style: ViewPropTypes.style,
+ dataSource: PropTypes.instanceOf(ListView.DataSource).isRequired,
mediaList: PropTypes.array.isRequired,
/*
* opens grid view
*/
onGridButtonTap: PropTypes.func,
-
+
/*
* Display top bar
*/
displayTopBar: PropTypes.bool,
-
+
/*
* updates top bar title
*/
@@ -60,14 +63,8 @@ export default class FullScreenContainer extends React.Component {
onActionButton: PropTypes.func,
onPhotoLongPress: PropTypes.func,
delayLongPress: PropTypes.number,
-
- /**
- * Use a custom button in the bottom bar to the left of the Share button,
- * without having to recreate the entire bottom bar and pass it in with the
- * `bottomBarComponent` prop. The visibility of the Share button can still
- * be controlled with `displayActionButton`.
- */
- customBottomBarButton: PropTypes.element,
+ enablePinchToZoom: PropTypes.bool,
+ customBottomBarButton: PropTypes.element,
};
static defaultProps = {
@@ -80,14 +77,16 @@ export default class FullScreenContainer extends React.Component {
onGridButtonTap: () => {},
onPhotoLongPress: () => {},
delayLongPress: 1000,
+ enablePinchToZoom: true,
};
constructor(props, context) {
super(props, context);
- this._renderItem = this._renderItem.bind(this);
+ this._renderRow = this._renderRow.bind(this);
this._toggleControls = this._toggleControls.bind(this);
this._onScroll = this._onScroll.bind(this);
+ this._onPageSelected = this._onPageSelected.bind(this);
this._onNextButtonTapped = this._onNextButtonTapped.bind(this);
this._onPhotoLongPress = this._onPhotoLongPress.bind(this);
this._onPreviousButtonTapped = this._onPreviousButtonTapped.bind(this);
@@ -111,14 +110,19 @@ export default class FullScreenContainer extends React.Component {
}
openPage(index, animated) {
- if (!this.flatListView) {
+ if (!this.scrollView) {
return;
}
- this.flatListView.scrollToIndex({
- index,
+ if (Platform.OS === 'ios') {
+ const screenWidth = Dimensions.get('window').width;
+ this.scrollView.scrollTo({
+ x: index * screenWidth,
animated,
});
+ } else {
+ this.scrollView.setPageWithoutAnimation(index);
+ }
this._updatePageIndex(index);
}
@@ -130,10 +134,7 @@ export default class FullScreenContainer extends React.Component {
}, () => {
this._triggerPhotoLoad(index);
- const { customTitle, mediaList } = this.props;
-
- const rowCount = mediaList.length;
- const newTitle = customTitle ? customTitle(index + 1, rowCount) : `${index + 1} of ${rowCount}`;
+ const newTitle = `${index + 1} of ${this.props.dataSource.getRowCount()}`;
this.props.updateTitle(newTitle);
});
}
@@ -166,7 +167,7 @@ export default class FullScreenContainer extends React.Component {
_onNextButtonTapped() {
let nextIndex = this.state.currentIndex + 1;
// go back to the first item when there is no more next item
- if (nextIndex > this.props.mediaList.length - 1) {
+ if (nextIndex > this.props.dataSource.getRowCount() - 1) {
nextIndex = 0;
}
this.openPage(nextIndex, false);
@@ -176,7 +177,7 @@ export default class FullScreenContainer extends React.Component {
let prevIndex = this.state.currentIndex - 1;
// go to the last item when there is no more previous item
if (prevIndex < 0) {
- prevIndex = this.props.mediaList.length - 1;
+ prevIndex = this.props.dataSource.getRowCount() - 1;
}
this.openPage(prevIndex, false);
}
@@ -204,6 +205,11 @@ export default class FullScreenContainer extends React.Component {
const { currentIndex } = this.state;
let newIndex = page;
+ // handle ViewPagerAndroid argument
+ if (typeof newIndex === 'object') {
+ newIndex = newIndex.nativeEvent.position;
+ }
+
if (currentIndex !== newIndex) {
this._updatePageIndex(newIndex);
@@ -212,14 +218,13 @@ export default class FullScreenContainer extends React.Component {
}
}
}
-
_onPhotoLongPress() {
const onPhotoLongPress = this.props.onPhotoLongPress;
const { currentMedia, currentIndex } = this.state;
onPhotoLongPress(currentMedia, currentIndex);
}
- _renderItem({ item, index }) {
+ _renderRow(media: Object, sectionID: number, rowID: number) {
const {
displaySelectionButtons,
onMediaSelection,
@@ -227,53 +232,57 @@ export default class FullScreenContainer extends React.Component {
} = this.props;
return (
-
-
+ this.photoRefs[rowID] = ref}
+ lazyLoad
+ useCircleProgress={useCircleProgress}
+ uri={media.photo}
+ displaySelectionButtons={displaySelectionButtons}
+ selected={media.selected}
+ enablePinchToZoom={this.props.enablePinchToZoom}
+ onSelection={(isSelected) => {
+ onMediaSelection(rowID, isSelected);
+ }}
onPress={this._toggleControls}
onLongPress={this._onPhotoLongPress}
- delayLongPress={this.props.delayLongPress}>
- this.photoRefs[index] = ref}
- lazyLoad
- useCircleProgress={useCircleProgress}
- uri={item.photo}
- displaySelectionButtons={displaySelectionButtons}
- selected={item.selected}
- onSelection={(isSelected) => onMediaSelection(item, index, isSelected)}
- />
-
+ delayLongPress={this.props.delayLongPress}
+ />
);
}
- getItemLayout = (data, index) => (
- { length: Dimensions.get('window').width, offset: Dimensions.get('window').width * index, index }
- )
-
_renderScrollableContent() {
- const { mediaList } = this.props;
+ const { dataSource, mediaList } = this.props;
+
+ if (Platform.OS === 'android') {
+ return (
+ this.scrollView = scrollView}
+ onPageSelected={this._onPageSelected}
+ >
+ {mediaList.map((child, idx) => this._renderRow(child, 0, idx))}
+
+ );
+ }
return (
- this.flatListView = flatListView}
- data={mediaList}
- renderItem={this._renderItem}
+ this.scrollView = scrollView}
+ dataSource={dataSource}
+ renderRow={this._renderRow}
onScroll={this._onScroll}
- keyExtractor={this._keyExtractor}
horizontal
pagingEnabled
showsHorizontalScrollIndicator={false}
showsVerticalScrollIndicator={false}
directionalLockEnabled
scrollEventThrottle={16}
- getItemLayout={this.getItemLayout}
- initialScrollIndex={this.state.currentIndex}
/>
);
}
- _keyExtractor = item => item.id || item.thumb || item.photo;
-
render() {
const {
displayNavArrows,
@@ -281,7 +290,7 @@ export default class FullScreenContainer extends React.Component {
displayActionButton,
onGridButtonTap,
enableGrid,
- customBottomBarButton,
+ customBottomBarButton,
} = this.props;
const { controlsDisplayed, currentMedia } = this.state;
const BottomBarComponent = this.props.bottomBarComponent || BottomBar;
@@ -289,11 +298,7 @@ export default class FullScreenContainer extends React.Component {
return (
{this._renderScrollableContent()}
);
}
+
}
const styles = StyleSheet.create({
diff --git a/lib/GridContainer.js b/lib/GridContainer.js
index 4209fb9..0b0820e 100755
--- a/lib/GridContainer.js
+++ b/lib/GridContainer.js
@@ -2,15 +2,13 @@ import React from 'react';
import PropTypes from 'prop-types';
import {
Dimensions,
- FlatList,
+ ListView,
TouchableHighlight,
View,
StyleSheet,
ViewPropTypes
} from 'react-native';
-import { ifIphoneX } from 'react-native-iphone-x-helper';
-
import Constants from './constants';
import { Photo } from './media';
@@ -21,8 +19,8 @@ export default class GridContainer extends React.Component {
static propTypes = {
style: ViewPropTypes.style,
- mediaList: PropTypes.array.isRequired,
square: PropTypes.bool,
+ dataSource: PropTypes.instanceOf(ListView.DataSource).isRequired,
displaySelectionButtons: PropTypes.bool,
onPhotoTap: PropTypes.func,
itemPerRow: PropTypes.number,
@@ -44,9 +42,15 @@ export default class GridContainer extends React.Component {
itemPerRow: 3,
};
- keyExtractor = item => item.id || item.thumb || item.photo;
+ constructor(props, context) {
+ super(props, context);
+
+ this._renderRow = this._renderRow.bind(this);
- renderItem = ({ item, index }) => {
+ this.state = {};
+ }
+
+ _renderRow(media: Object, sectionID: number, rowID: number) {
const {
displaySelectionButtons,
onPhotoTap,
@@ -59,7 +63,7 @@ export default class GridContainer extends React.Component {
const photoWidth = (screenWidth / itemPerRow) - (ITEM_MARGIN * 2);
return (
- onPhotoTap(index)}>
+ onPhotoTap(parseInt(rowID, 10))}>
onMediaSelection(item, index, isSelected)}
+ uri={media.thumb || media.photo}
+ selected={media.selected}
+ enablePinchToZoom={false}
+ onSelection={(isSelected) => {
+ onMediaSelection(rowID, isSelected);
+ }}
/>
@@ -78,18 +85,18 @@ export default class GridContainer extends React.Component {
}
render() {
- const { mediaList } = this.props;
+ const { dataSource } = this.props;
return (
-
);
@@ -100,9 +107,14 @@ export default class GridContainer extends React.Component {
const styles = StyleSheet.create({
container: {
flex: 1,
- paddingTop: ifIphoneX(18, 0),
paddingBottom: Constants.TOOLBAR_HEIGHT,
},
+ list: {
+ justifyContent: 'flex-start',
+ alignItems: 'flex-start',
+ flexDirection: 'row',
+ flexWrap: 'wrap',
+ },
row: {
justifyContent: 'center',
margin: 1,
diff --git a/lib/bar/BarContainer.js b/lib/bar/BarContainer.js
index a881af2..7ae9c41 100644
--- a/lib/bar/BarContainer.js
+++ b/lib/bar/BarContainer.js
@@ -7,8 +7,6 @@ import {
ViewPropTypes
} from 'react-native';
-import { getBottomSpace } from 'react-native-iphone-x-helper';
-
const BAR_POSITIONS = {
TOP: 'top',
BOTTOM: 'bottom',
@@ -37,9 +35,9 @@ class BarContainer extends Component {
};
}
- componentDidUpdate() {
+ componentWillReceiveProps(nextProps) {
Animated.timing(this.state.animation, {
- toValue: this.props.displayed ? 1 : 0,
+ toValue: nextProps.displayed ? 1 : 0,
duration: 300,
}).start();
}
@@ -55,7 +53,7 @@ class BarContainer extends Component {
styles.container,
isBottomBar ? styles.bottomBar : styles.topBar,
{
- height: height + (isBottomBar ? getBottomSpace() : 0),
+ height,
opacity: this.state.animation,
transform: [{
translateY: this.state.animation.interpolate({
@@ -86,7 +84,6 @@ const styles = StyleSheet.create({
},
bottomBar: {
bottom: 0,
- paddingBottom: getBottomSpace(),
},
});
diff --git a/lib/bar/BottomBar.js b/lib/bar/BottomBar.js
index 57e269c..2fe31f9 100644
--- a/lib/bar/BottomBar.js
+++ b/lib/bar/BottomBar.js
@@ -1,5 +1,6 @@
import React from 'react';
import PropTypes from 'prop-types';
+
import {
Image,
Text,
@@ -7,6 +8,7 @@ import {
TouchableOpacity,
View,
} from 'react-native';
+import { Icon } from 'native-base';
import { BarContainer, BAR_POSITIONS } from './BarContainer';
@@ -64,10 +66,7 @@ export default class BottomBar extends React.Component {
style={styles.button}
onPress={onPrev}
>
-
+
);
const rightArrow = (
@@ -76,10 +75,7 @@ export default class BottomBar extends React.Component {
style={styles.button}
onPress={onNext}
>
-
+
);
@@ -100,11 +96,8 @@ export default class BottomBar extends React.Component {
if (displayGridButton) {
return (
-
-
+
+
);
}
@@ -123,7 +116,7 @@ export default class BottomBar extends React.Component {
if (displayActionButton) {
components.push(
-
+
);
}
@@ -198,4 +191,4 @@ const styles = StyleSheet.create({
flex: 0,
paddingTop: 8,
}
-});
+});
\ No newline at end of file
diff --git a/lib/bar/TopBar.js b/lib/bar/TopBar.js
index 2af3e8b..c0daca2 100644
--- a/lib/bar/TopBar.js
+++ b/lib/bar/TopBar.js
@@ -8,8 +8,6 @@ import {
Platform,
} from 'react-native';
-import { ifIphoneX } from 'react-native-iphone-x-helper';
-
import { BarContainer } from './BarContainer';
export default class TopBar extends React.Component {
@@ -60,7 +58,7 @@ export default class TopBar extends React.Component {
{this.renderBackButton()}
{title}
@@ -74,7 +72,7 @@ const styles = StyleSheet.create({
flex: 1,
flexDirection: 'row',
justifyContent: 'center',
- paddingTop: ifIphoneX(48, 30),
+ paddingTop: 30,
},
text: {
fontSize: 18,
@@ -84,7 +82,7 @@ const styles = StyleSheet.create({
position: 'absolute',
flexDirection: 'row',
left: 0,
- top: 16 + ifIphoneX(18, 0),
+ top: 16,
},
backText: {
paddingTop: 14,
diff --git a/lib/index.js b/lib/index.js
index 6b3078b..46ab02b 100755
--- a/lib/index.js
+++ b/lib/index.js
@@ -3,6 +3,7 @@ import PropTypes from 'prop-types';
import {
Animated,
Dimensions,
+ ListView,
View,
StyleSheet,
ViewPropTypes
@@ -112,14 +113,13 @@ export default class PhotoBrowser extends React.Component {
*/
onPhotoLongPress: PropTypes.func,
delayPhotoLongPress: PropTypes.number,
+
- /**
- * Use a custom button in the bottom bar to the left of the Share button,
- * without having to recreate the entire bottom bar and pass it in with the
- * `bottomBarComponent` prop. The visibility of the Share button can still
- * be controlled with `displayActionButton`.
+ /*
+ * Whether pinch to zoom is enabled or disabled
*/
- customBottomBarButton: PropTypes.element,
+ enablePinchToZoom: PropTypes.bool,
+ customBottomBarButton: PropTypes.element,
};
static defaultProps = {
@@ -138,6 +138,7 @@ export default class PhotoBrowser extends React.Component {
displayTopBar: true,
onPhotoLongPress: () => {},
delayPhotoLongPress: 1000,
+ enablePinchToZoom: true,
gridOffset: 0,
};
@@ -146,12 +147,15 @@ export default class PhotoBrowser extends React.Component {
this._onGridPhotoTap = this._onGridPhotoTap.bind(this);
this._onGridButtonTap = this._onGridButtonTap.bind(this);
+ this._onMediaSelection = this._onMediaSelection.bind(this);
this._updateTitle = this._updateTitle.bind(this);
this._toggleTopBar = this._toggleTopBar.bind(this);
- const { startOnGrid, initialIndex } = props;
+ const { mediaList, startOnGrid, initialIndex } = props;
this.state = {
+ dataSource: this._createDataSource(mediaList),
+ mediaList,
isFullScreen: !startOnGrid,
fullScreenAnim: new Animated.Value(startOnGrid ? 0 : 1),
currentIndex: initialIndex,
@@ -159,6 +163,21 @@ export default class PhotoBrowser extends React.Component {
};
}
+ componentWillReceiveProps(nextProps) {
+ const mediaList = nextProps.mediaList;
+ this.setState({
+ dataSource: this._createDataSource(mediaList),
+ mediaList,
+ });
+ }
+
+ _createDataSource(list) {
+ const dataSource = new ListView.DataSource({
+ rowHasChanged: (r1, r2) => r1 !== r2,
+ });
+ return dataSource.cloneWithRows(list);
+ }
+
_onGridPhotoTap(index) {
this.refs.fullScreenContainer.openPage(index, false);
this._toggleFullScreen(true);
@@ -168,6 +187,25 @@ export default class PhotoBrowser extends React.Component {
this._toggleFullScreen(false);
}
+ _onMediaSelection(index, isSelected) {
+ const {
+ mediaList: oldMediaList,
+ dataSource,
+ } = this.state;
+ const newMediaList = oldMediaList.slice();
+ const selectedMedia = {
+ ...oldMediaList[index],
+ selected: isSelected,
+ };
+ newMediaList[index] = selectedMedia;
+
+ this.setState({
+ dataSource: dataSource.cloneWithRows(newMediaList),
+ mediaList: newMediaList,
+ });
+ this.props.onSelectionChanged(selectedMedia, index, isSelected);
+ }
+
_updateTitle(title) {
this.setState({ title });
}
@@ -195,7 +233,6 @@ export default class PhotoBrowser extends React.Component {
render() {
const {
- mediaList,
alwaysShowControls,
displayNavArrows,
alwaysDisplayStatusBar,
@@ -209,10 +246,10 @@ export default class PhotoBrowser extends React.Component {
style,
square,
gridOffset,
- customTitle,
- onSelectionChanged
} = this.props;
const {
+ dataSource,
+ mediaList,
isFullScreen,
fullScreenAnim,
currentIndex,
@@ -238,10 +275,10 @@ export default class PhotoBrowser extends React.Component {
@@ -251,6 +288,7 @@ export default class PhotoBrowser extends React.Component {
fullScreenContainer = (
);
}
@@ -286,7 +325,7 @@ export default class PhotoBrowser extends React.Component {
diff --git a/lib/media/Photo.js b/lib/media/Photo.js
index 2f6c2c9..916aee8 100644
--- a/lib/media/Photo.js
+++ b/lib/media/Photo.js
@@ -8,6 +8,8 @@ import {
TouchableWithoutFeedback,
ActivityIndicator,
Platform,
+ ScrollView,
+ Text,
} from 'react-native';
import * as Progress from 'react-native-progress';
@@ -75,6 +77,17 @@ export default class Photo extends Component {
* iOS only
*/
useCircleProgress: PropTypes.bool,
+
+ /*
+ * Whether or not the user can pinch to zoom in on a photo
+ */
+ enablePinchToZoom: PropTypes.bool,
+
+ onPress: PropTypes.func,
+
+ onLongPress: PropTypes.func,
+
+ delayLongPress: PropTypes.number,
};
static defaultProps = {
@@ -82,6 +95,7 @@ export default class Photo extends Component {
thumbnail: false,
lazyLoad: false,
selected: false,
+ enablePinchToZoom: true,
};
constructor(props) {
@@ -92,13 +106,12 @@ export default class Photo extends Component {
this._onLoad = this._onLoad.bind(this);
this._toggleSelection = this._toggleSelection.bind(this);
- const { lazyLoad, uri, selected } = props;
+ const { lazyLoad, uri } = props;
this.state = {
uri: lazyLoad ? null : uri,
progress: 0,
error: false,
- selected,
};
}
@@ -133,8 +146,9 @@ export default class Photo extends Component {
}
_toggleSelection() {
- this.props.onSelection(!this.state.selected);
- this.setState(prevState => ({ selected: !prevState.selected}))
+ // onSelection is resolved in index.js
+ // and refreshes the dataSource with new media object
+ this.props.onSelection(!this.props.selected);
}
_renderProgressIndicator() {
@@ -175,8 +189,8 @@ export default class Photo extends Component {
}
_renderSelectionButton() {
- const { selected, progress } = this.state;
- const { displaySelectionButtons, thumbnail } = this.props;
+ const { progress } = this.state;
+ const { displaySelectionButtons, selected, thumbnail } = this.props;
// do not display selection before image is loaded
if (!displaySelectionButtons || progress < 1) {
@@ -236,19 +250,34 @@ export default class Photo extends Component {
width: width || screen.width,
height: height || screen.height,
};
+
+ const props = {
+ ...this.props,
+ style: [styles.image, sizeStyle],
+ source,
+ onProgress: this._onProgress,
+ onError: this._onError,
+ onLoad: this._onLoad,
+ resizeMode
+ };
return (
{error ? this._renderErrorIcon() : this._renderProgressIndicator()}
-
+ {
+ this.props.enablePinchToZoom ?
+
+
+
+
+
+ :
+
+ }
{this._renderSelectionButton()}
);