Verified Commit c6d1ef2f authored by Sébastiaan Versteeg's avatar Sébastiaan Versteeg
Browse files

Turn gallery into separate screen

parent 320150f1
......@@ -7,6 +7,7 @@ export const PHOTOS_ALBUM_SUCCESS = 'PHOTOS_ALBUM_SUCCESS';
export const PHOTOS_ALBUM_FAILURE = 'PHOTOS_ALBUM_FAILURE';
export const PHOTOS_ALBUM_FETCHING = 'PHOTOS_ALBUM_FETCHING';
export const PHOTOS_PHOTO_SHOW = 'PHOTOS_PHOTO_SHOW';
export const PHOTOS_GALLERY_OPEN = 'PHOTOS_GALLERY_OPEN';
export function openAlbums() {
return {
......@@ -31,6 +32,13 @@ export function fetchingAlbums() {
};
}
export function openGallery(selection) {
return {
type: PHOTOS_GALLERY_OPEN,
payload: { selection },
};
}
export function openAlbum(pk) {
return {
type: PHOTOS_ALBUM_OPEN,
......
......@@ -15,8 +15,9 @@ import Profile from './ui/screens/profile/ProfileScreenConnector';
import Pizza from './ui/screens/pizza/PizzaScreenConnector';
import Registration from './ui/screens/events/RegistrationScreenConnector';
import MemberList from './ui/screens/memberList/MemberListScreenConnector';
import Photos from './ui/screens/photos/AlbumsOverviewScreenContainer';
import PhotoAlbum from './ui/screens/photos/AlbumDetailScreenContainer';
import Photos from './ui/screens/photos/AlbumsOverviewScreenConnector';
import PhotoAlbum from './ui/screens/photos/AlbumDetailScreenConnector';
import PhotoGallery from './ui/screens/photos/AlbumGalleryScreenConnector';
import SplashScreen from './ui/screens/splash/SplashScreen';
import Settings from './ui/screens/settings/SettingsScreenConnector';
import EventAdmin from './ui/screens/events/EventAdminScreenConnector';
......@@ -38,6 +39,7 @@ const SignedInNavigator = createStackNavigator({
Profile,
Pizza,
PhotoAlbum,
PhotoGallery,
Registration,
EventAdmin,
}, {
......
......@@ -69,6 +69,14 @@ export default function photos(state = initialState, action = {}) {
selection: undefined,
},
};
case photosActions.PHOTOS_GALLERY_OPEN:
return {
...state,
album: {
...state.album,
selection: action.payload.selection,
},
};
default:
return state;
}
......
......@@ -10,6 +10,7 @@ import * as pizzaActions from '../actions/pizza';
import * as loginActions from '../actions/session';
import * as eventActions from '../actions/event';
import * as calendarActions from '../actions/calendar';
import * as photosActions from '../actions/photos';
export const parseURL = (url) => {
const matches = new RegExp(`^${siteURL}(/[^?]+)(?:\\?(.+))?`).exec(url);
......@@ -65,6 +66,16 @@ const deepLink = function* deepLink(action) {
action: calendarActions.open,
args: [],
},
{
regexp: new RegExp('^/photos/([0-9]+)/$'),
action: photosActions.openAlbum,
args: [],
},
{
regexp: new RegExp('^/photos/$'),
action: photosActions.openAlbums,
args: [],
},
];
for (let i = 0; i < patterns.length; i += 1) {
......
......@@ -45,6 +45,7 @@ export default function* () {
yield takeEvery(pizzaActions.PIZZA, navigate, 'Pizza');
yield takeEvery(photosActions.PHOTOS_ALBUMS_OPEN, navigate, 'Photos');
yield takeEvery(photosActions.PHOTOS_ALBUM_OPEN, navigate, 'PhotoAlbum');
yield takeEvery(photosActions.PHOTOS_GALLERY_OPEN, navigate, 'PhotoGallery');
yield takeEvery(sessionActions.SIGNED_IN, navigate, 'SignedIn');
yield takeEvery([sessionActions.TOKEN_INVALID, sessionActions.SIGN_OUT], navigate, 'Auth');
}
......@@ -25,8 +25,10 @@ function* loadAlbums() {
try {
const response = yield call(apiRequest, 'photos/albums', data, params);
yield put(photosActions.successAlbums(response.results, response.next));
yield put(photosActions.successAlbums(
response.results.filter(item => item.accessible && !item.hidden && item.cover != null),
response.next,
));
} catch (error) {
yield put(photosActions.failureAlbums());
}
......
export const photosStore = state => state.photos;
export const albumsData = state => photosStore(state).albums.data;
export const albumsStatus = state => photosStore(state).albums.status;
export const isFetchingAlbums = state => photosStore(state).albums.fetching;
export const albumData = state => photosStore(state).album.data;
export const albumSelection = state => photosStore(state).album.selection;
export const albumStatus = state => photosStore(state).album.status;
export const isFetchingAlbum = state => photosStore(state).album.fetching;
import React, { Component } from 'react';
import React from 'react';
import PropTypes from 'prop-types';
import {
Dimensions, FlatList, TouchableOpacity, View,
} from 'react-native';
import ImageViewer from 'react-native-image-zoom-viewer';
import Icon from 'react-native-vector-icons/MaterialIcons';
import { Dimensions, FlatList, View } from 'react-native';
import { STATUS_FAILURE, STATUS_INITIAL } from '../../../reducers/photos';
import LoadingScreen from '../../components/loadingScreen/LoadingScreen';
import ErrorScreen from '../../components/errorScreen/ErrorScreen';
import styles from './style/AlbumDetailScreen';
import PhotoListItem from './PhotoListItem';
import PhotoViewContainer from './PhotoListItemContainer';
import Colors from '../../style/Colors';
import StandardHeader from '../../components/standardHeader/StandardHeader';
const windowWidth = Dimensions.get('window').width;
export const itemSize = (windowWidth - 48) / 3;
class AlbumDetailScreen extends Component {
constructor(props) {
super(props);
this.state = {
gallery: [],
selection: null,
};
}
static getDerivedStateFromProps(nextProps, prevState) {
const { token, photos } = nextProps;
const gallerySources = [];
if (photos && photos.length > 0) {
const keys = Object.keys(photos);
for (let i = 0; i < keys.length; i += 1) {
const photo = photos[i];
if (!photo.hidden) {
gallerySources.push({
url: photo.file.large,
props: {
headers: {
Authorization: `Token ${token}`,
},
},
});
const AlbumDetailScreen = ({
t, fetching, status, photos, openGallery,
}) => {
let content = (
<View style={styles.wrapper}>
<FlatList
style={styles.flatList}
contentContainerStyle={styles.listContainer}
data={photos.filter(p => !p.hidden)}
renderItem={
data => (
<PhotoListItem
photo={data.item}
size={itemSize}
style={styles.listItem}
onPress={() => openGallery(data.index)}
/>
)
}
}
}
return {
...prevState,
gallery: gallerySources,
};
}
openGallery(index) {
this.setState({ selection: index });
}
closeGallery() {
this.setState({ selection: null });
}
render() {
const {
t, fetching, status, photos,
} = this.props;
keyExtractor={item => item.pk}
numColumns={3}
/>
</View>
);
let content = (
if (fetching && status === STATUS_INITIAL) {
content = (<LoadingScreen />);
} else if (!fetching && status === STATUS_FAILURE) {
content = (
<View style={styles.wrapper}>
<FlatList
style={styles.flatList}
contentContainerStyle={styles.listContainer}
data={photos.filter(p => !p.hidden)}
renderItem={
data => (
<PhotoViewContainer
photo={data.item}
size={itemSize}
style={styles.listItem}
onPress={() => this.openGallery(data.index)}
/>
)
}
keyExtractor={item => item.pk}
numColumns={3}
/>
</View>
);
if (fetching && status === STATUS_INITIAL) {
content = (<LoadingScreen />);
} else if (!fetching && status === STATUS_FAILURE) {
content = (
<View style={styles.wrapper}>
<ErrorScreen message={t('Sorry! We couldn\'t load any data.')} />
</View>
);
} else if (this.state.selection !== null) {
return (
<View style={styles.screenWrapper}>
<View style={styles.galleryWrapper}>
<ImageViewer
index={this.state.selection}
imageUrls={this.state.gallery}
/>
<TouchableOpacity
style={styles.closeGalleryTouchable}
onPress={() => this.closeGallery()}
>
<Icon
name="close"
style={styles.icon}
size={24}
color={Colors.white}
/>
</TouchableOpacity>
</View>
</View>
);
}
return (
<View style={styles.screenWrapper}>
<StandardHeader />
{content}
<ErrorScreen message={t('Sorry! We couldn\'t load any data.')} />
</View>
);
}
}
return (
<View style={styles.screenWrapper}>
<StandardHeader />
{content}
</View>
);
};
AlbumDetailScreen.propTypes = {
openGallery: PropTypes.func.isRequired,
fetching: PropTypes.bool.isRequired,
status: PropTypes.string.isRequired,
photos: PropTypes.arrayOf(PhotoListItem.propTypes.photo),
// eslint-disable-next-line react/no-unused-prop-types
token: PropTypes.string.isRequired,
t: PropTypes.func.isRequired,
};
......
import { connect } from 'react-redux';
import { withTranslation } from 'react-i18next';
import AlbumDetailScreen from './AlbumDetailScreen';
import * as photosActions from '../../../actions/photos';
import { albumData, albumStatus, isFetchingAlbum } from '../../../selectors/photos';
const mapStateToProps = state => ({
photos: albumData(state).photos,
status: albumStatus(state),
fetching: isFetchingAlbum(state),
});
const mapDispatchToProps = {
openGallery: photosActions.openGallery,
};
export default connect(mapStateToProps, mapDispatchToProps)(withTranslation(['screens/photos/AlbumDetail'])(AlbumDetailScreen));
import { connect } from 'react-redux';
import { withTranslation } from 'react-i18next';
import AlbumDetailScreen from './AlbumDetailScreen';
import { tokenSelector } from '../../../utils/url';
const mapStateToProps = state => ({
photos: state.photos.album.data.photos,
status: state.photos.album.status,
fetching: state.photos.album.fetching,
token: tokenSelector(state),
});
export default connect(mapStateToProps)(withTranslation(['screens/photos/AlbumDetail'])(AlbumDetailScreen));
import { TouchableOpacity, View } from 'react-native';
import ImageViewer from 'react-native-image-zoom-viewer';
import Icon from 'react-native-vector-icons/MaterialIcons';
import React from 'react';
import PropTypes from 'prop-types';
import Colors from '../../style/Colors';
import styles from './style/AlbumDetailScreen';
const AlbumGalleryScreen = ({ photos, goBack, selection }) => (
<View style={styles.screenWrapper}>
<View style={styles.galleryWrapper}>
<ImageViewer
index={selection}
imageUrls={photos}
/>
<TouchableOpacity
style={styles.closeGalleryTouchable}
onPress={goBack}
>
<Icon
name="close"
style={styles.icon}
size={24}
color={Colors.white}
/>
</TouchableOpacity>
</View>
</View>
);
AlbumGalleryScreen.propTypes = {
goBack: PropTypes.func.isRequired,
selection: PropTypes.number.isRequired,
photos: PropTypes.arrayOf(PropTypes.shape({
url: PropTypes.string.isRequired,
})).isRequired,
};
export default AlbumGalleryScreen;
import { connect } from 'react-redux';
import { withTranslation } from 'react-i18next';
import AlbumGalleryScreen from './AlbumGalleryScreen';
import * as navigationActions from '../../../actions/navigation';
import { albumData, albumSelection } from '../../../selectors/photos';
const mapStateToProps = state => ({
photos: Object.values(albumData(state).photos)
.filter(photo => !photo.hidden)
.map(photo => ({
url: photo.file.large,
})),
selection: albumSelection(state),
});
const mapDispatchToProps = {
goBack: navigationActions.goBack,
};
export default connect(mapStateToProps, mapDispatchToProps)(withTranslation(['screens/photos/AlbumDetail'])(AlbumGalleryScreen));
......@@ -16,15 +16,12 @@ const AlbumListItem = (props) => {
<SquareView style={props.style} size={props.size}>
<TouchableHighlight
style={styles.image}
onPress={() => props.openAlbum(props.album.pk)}
onPress={() => props.onPress(props.album.pk)}
>
<ImageBackground
style={styles.image}
source={{
uri: props.album.cover.file.small,
headers: {
Authorization: `Token ${props.token}`,
},
}}
>
<LinearGradient colors={['#55000000', '#000000']} style={styles.overlayGradient} />
......@@ -54,8 +51,7 @@ AlbumListItem.propTypes = {
hidden: PropTypes.bool.isRequired,
cover: PropTypes.shape(PhotoListItem.propTypes.photo),
}).isRequired,
openAlbum: PropTypes.func.isRequired,
token: PropTypes.string.isRequired,
onPress: PropTypes.func.isRequired,
};
AlbumListItem.defaultProps = {
......
import { connect } from 'react-redux';
import AlbumListItem from './AlbumListItem';
import * as photosActions from '../../../actions/photos';
import { tokenSelector } from '../../../utils/url';
const mapStateToProps = state => ({
albums: state.photos.albums.data,
status: state.photos.albums.status,
fetching: state.photos.albums.fetching,
token: tokenSelector(state),
});
const mapDispatchToProps = dispatch => ({
openAlbum: pk => dispatch(photosActions.openAlbum(pk)),
});
export default connect(mapStateToProps, mapDispatchToProps)(AlbumListItem);
......@@ -6,7 +6,6 @@ import LoadingScreen from '../../components/loadingScreen/LoadingScreen';
import ErrorScreen from '../../components/errorScreen/ErrorScreen';
import AlbumListItem from './AlbumListItem';
import styles from './style/AlbumsOverviewScreen';
import AlbumListItemContainer from './AlbumListItemContainer';
import { withStandardHeader } from '../../components/standardHeader/StandardHeader';
const windowWidth = Dimensions.get('window').width;
......@@ -14,7 +13,9 @@ const numColumns = 2;
export const albumSize = (windowWidth - 48) / numColumns;
const AlbumsOverviewScreen = (props) => {
const { t, fetching, status } = props;
const {
t, fetching, status, openAlbum,
} = props;
if (fetching && status === STATUS_INITIAL) {
return <LoadingScreen />;
}
......@@ -34,13 +35,19 @@ const AlbumsOverviewScreen = (props) => {
data={props.albums}
renderItem={
({ item }) => (
<AlbumListItemContainer album={item} size={albumSize} style={styles.listItem} />
<AlbumListItem
album={item}
size={albumSize}
style={styles.listItem}
onPress={openAlbum}
/>
)
}
keyExtractor={item => item.pk}
numColumns={numColumns}
/>
</View>);
</View>
);
};
......@@ -48,6 +55,7 @@ AlbumsOverviewScreen.propTypes = {
fetching: PropTypes.bool.isRequired,
status: PropTypes.string.isRequired,
albums: PropTypes.arrayOf(AlbumListItem.propTypes.album).isRequired,
openAlbum: PropTypes.func.isRequired,
t: PropTypes.func.isRequired,
};
......
import { connect } from 'react-redux';
import { withTranslation } from 'react-i18next';
import AlbumsOverview from './AlbumsOverviewScreen';
import { albumsData, albumsStatus, isFetchingAlbums } from '../../../selectors/photos';
import * as photosActions from '../../../actions/photos';
const mapStateToProps = state => ({
albums: albumsData(state),
status: albumsStatus(state),
fetching: isFetchingAlbums(state),
});
const mapDispatchToProps = {
openAlbum: photosActions.openAlbum,
};
export default connect(mapStateToProps, mapDispatchToProps)(withTranslation(['screens/photos/AlbumsOverview'])(AlbumsOverview));
import { connect } from 'react-redux';
import { withTranslation } from 'react-i18next';
import AlbumsOverview from './AlbumsOverviewScreen';
const mapStateToProps = state => ({
albums: state.photos.albums.data,
status: state.photos.albums.status,
fetching: state.photos.albums.fetching,
});
export default connect(mapStateToProps, () => ({}))(withTranslation(['screens/photos/AlbumsOverview'])(AlbumsOverview));
......@@ -5,17 +5,14 @@ import SquareView from '../../components/memberView/SquareView';
import styles from './style/PhotoListItem';
const PhotoListItem = props => (
<TouchableHighlight
style={styles.touchable}
onPress={() => props.onPress()}
>
<SquareView style={props.style} size={props.size} >
<SquareView style={props.style} size={props.size}>
<TouchableHighlight
style={styles.touchable}
onPress={() => props.onPress()}
>
<Image
source={{
uri: props.photo.file.small,
headers: {
Authorization: `Token ${props.token}`,
},
}}
style={[
styles.image,
......@@ -26,8 +23,8 @@ const PhotoListItem = props => (
},
]}
/>
</SquareView>
</TouchableHighlight>
</TouchableHighlight>
</SquareView>
);
PhotoListItem.propTypes = {
......@@ -46,7 +43,6 @@ PhotoListItem.propTypes = {
}).isRequired,
size: PropTypes.string,
}).isRequired,
token: PropTypes.string.isRequired,
onPress: PropTypes.func.isRequired,
};
......
import { connect } from 'react-redux';
import PhotoListItem from './PhotoListItem';
import { tokenSelector } from '../../../utils/url';
const mapStateToProps = state => ({
token: tokenSelector(state),
});
export default connect(mapStateToProps)(PhotoListItem);
Markdown is supported
0% or .
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment