Skip to content

Instantly share code, notes, and snippets.

@jittuu
Created November 19, 2018 05:52

Revisions

  1. jittuu created this gist Nov 19, 2018.
    193 changes: 193 additions & 0 deletions PhotoStateList.tsx
    Original file line number Diff line number Diff line change
    @@ -0,0 +1,193 @@
    import React from 'react';
    import {
    Dimensions,
    Image,
    ListRenderItemInfo,
    StyleSheet,
    Text,
    TouchableWithoutFeedback,
    View,
    ViewStyle,
    } from 'react-native';
    import * as Animatable from 'react-native-animatable';
    import firebase, { RNFirebase } from 'react-native-firebase';
    import Progress from 'react-native-progress';
    import { NavigationInjectedProps } from 'react-navigation';
    import { FirebaseInfiniteFlatList } from 'src/components';
    import { Photo, PhotoState } from 'src/store';
    import theme from 'src/theme';

    interface PhotoViewProps {
    photoState: PhotoState;
    no?: number;
    handler: (photoState: PhotoState) => void;
    }
    interface PhotoViewState {
    animation: string | undefined;
    }
    class PhotoView extends React.Component<PhotoViewProps, PhotoViewState> {
    constructor(props: PhotoViewProps) {
    super(props);

    this.state = { animation: undefined };
    }

    onAnimationEnd = () => {
    this.setState({ animation: undefined });
    }

    componentWillReceiveProps(nextProps: PhotoViewProps) {
    if (this.props.no !== nextProps.no) {
    this.setState({ animation: 'zoomIn' });
    }
    }

    handlePress = () => {
    const { photoState, handler } = this.props;
    handler(photoState);
    }

    render() {
    const { photoState: item, no } = this.props;
    const src = item.status === 'uploading' ? item.path : (item.thumbnail || item.url);
    return (
    <TouchableWithoutFeedback onPress={this.handlePress}>
    <View>
    <Image style={styles.image} source={{ uri: src }} />
    {item.status === 'uploading' &&
    <Progress.Circle
    style={styles.progress}
    progress={item.progress}
    thickness={4}
    />}
    <Animatable.View
    animation={this.state.animation}
    onAnimationEnd={this.onAnimationEnd}
    style={[styles.circle, !!no && styles.circleFill]}
    duration={300}
    useNativeDriver={true}
    >
    {!!no && <Text style={{ color: '#fff' }}>{no}</Text>}
    </Animatable.View>
    </View>
    </TouchableWithoutFeedback>
    );
    }
    }

    class PhotoStateList extends FirebaseInfiniteFlatList<PhotoState> { }

    interface P extends NavigationInjectedProps {
    selectedPhotos: Photo[];
    onChange: (selectedPhotos: Photo[]) => void;
    }

    export default class extends React.Component<P> {
    query: RNFirebase.firestore.Query;
    flatList: PhotoStateList | null;

    constructor(props: P) {
    super(props);

    this.flatList = null;

    this.query = firebase.firestore()
    .collection('photos')
    .orderBy('createdOn', 'desc');

    this.state = { selectedPhotoIds: [] };
    }

    onItemPress = ({ itemId }: { itemId: string }) => {
    const { navigation: { navigate } } = this.props;
    navigate('ItemDetails', { itemId });
    }

    keyExtractor = (i: PhotoState) => i.id;

    docMapper = (doc: RNFirebase.firestore.DocumentSnapshot): PhotoState => {
    const { url, thumbnail, createdOn } = doc.data() as Photo;
    return {
    id: doc.id || 'UNKNOWN',
    status: 'uploaded',
    url,
    thumbnail,
    createdOn,
    };
    }

    onPhotoSelect = (photoState: PhotoState) => {
    const { selectedPhotos, onChange } = this.props;
    if (selectedPhotos.some(sp => sp.id === photoState.id)) {
    onChange(selectedPhotos.filter(sp => sp.id !== photoState.id));
    } else if (photoState.status === 'uploaded') {
    const { id, createdOn, thumbnail, url } = photoState;
    onChange(selectedPhotos.concat([{
    id, url, thumbnail, createdOn,
    }]));
    }
    }

    refreshData = () => {
    if (this.flatList) {
    this.flatList.refreshData();
    }
    }

    renderItem = ({ item }: ListRenderItemInfo<PhotoState>) => {
    const { selectedPhotos } = this.props;
    const foundIdx = selectedPhotos.findIndex(sp => sp.id === item.id);
    return (
    <PhotoView
    photoState={item}
    no={foundIdx > -1 ? foundIdx + 1 : undefined}
    handler={this.onPhotoSelect}
    />
    );
    }

    render() {
    return (
    <PhotoStateList
    ref={ls => this.flatList = ls}
    extraData={this.props.selectedPhotos}
    query={this.query}
    pageSize={100}
    keyExtractor={this.keyExtractor}
    renderItem={this.renderItem}
    docMapper={this.docMapper}
    numColumns={4}
    />
    );
    }
    }

    const { width } = Dimensions.get('window');
    const styles = StyleSheet.create({
    image: {
    marginLeft: 2,
    marginVertical: 2,
    width: ((width - 36) / 4),
    height: ((width - 36) / 4) * 1.2,
    } as ViewStyle,
    progress: {
    position: 'absolute',
    bottom: 10,
    right: 10,
    },
    circle: {
    alignItems: 'center',
    justifyContent: 'center',
    width: 20,
    height: 20,
    borderRadius: 10,
    borderWidth: StyleSheet.hairlineWidth,
    borderColor: theme.primaryColor,
    position: 'absolute',
    top: 6,
    right: 4,
    } as ViewStyle,
    circleFill: {
    backgroundColor: theme.primaryColor,
    } as ViewStyle,
    });