image preview modal

This commit is contained in:
Bart Akeley 2017-08-05 20:38:29 -05:00
parent c8ebf2b366
commit a781ecc8ec
6 changed files with 127 additions and 62 deletions

View file

@ -4,6 +4,7 @@ import { Text, View, TouchableOpacity, Image } from 'react-native';
import { Icon } from 'react-native-material-ui';
import uiTheme from '../ui-theme.js';
import ReactNativeImagePicker from 'react-native-image-picker';
import { type List } from 'immutable';
const { palette: { accentColor } } = uiTheme;
@ -19,12 +20,12 @@ export const ImageThumb = ({ uri, onPress }: { uri?: string, onPress: () => void
<Image source={{ uri }} style={{ height: 35, width: 35, margin: 5 }} />
</TouchableOpacity>;
export const ImagePicker = ({ children, onCreateNew }: { children?: any, onCreateNew: Function }) =>
export const ImagePicker = ({ children, onCreateNew }: { children?: List<any>, onCreateNew: Function }) =>
<View style={{ flexDirection: 'row', marginTop: 10 }}>
<TouchableOpacity onPress={openImagePicker(onCreateNew)}>
<View style={{ flexDirection: 'row' }}>
<Icon name="insert-photo" size={35} color={accentColor} style={{ margin: 5 }} />
{(!children || !children.length) &&
{(!children || !children.size) &&
<View style={{ flexDirection: 'column', justifyContent: 'center' }}>
<Text style={{ fontSize: 16 }}>Add Images</Text>
</View>}

View file

@ -0,0 +1,38 @@
// @flow
import React from 'react';
import { TouchableOpacity, Image } from 'react-native';
import { ActionButton } from 'react-native-material-ui';
const backdropStyle = {
position: 'absolute',
top: 0,
bottom: 0,
left: 0,
right: 0,
backgroundColor: 'black',
flexDirection: 'column',
justifyContent: 'center',
};
const imageStyle = {
flex: 2,
resizeMode: 'contain',
};
type Props = {
onClose: () => void,
onDelete: () => void,
imageSrc: string,
};
const ImagePreviewModal = ({ onClose, onDelete, imageSrc }: Props) => {
return (
<TouchableOpacity onPress={onClose} style={backdropStyle}>
<Image style={imageStyle} source={{ uri: imageSrc }} />
<ActionButton icon="delete" onPress={onDelete} />
</TouchableOpacity>
);
};
ImagePreviewModal.displayName = 'ImagePreviewModal';
export default ImagePreviewModal;

View file

@ -6,6 +6,7 @@ import { Divider } from 'react-native-material-ui';
import FoodItemRecord from '../records/FoodItemRecord';
import PlaceRecord from '../records/PlaceRecord';
import NameModal from '../modals/FoodItemNameModal';
import ImagePreviewModal from '../modals/ImagePreviewModal';
import QuantityPicker from '../components/QuantityPicker';
import { compose, branch, withState, withHandlers, renderComponent, mapProps } from 'recompose';
import RNGooglePlaces from 'react-native-google-places';
@ -52,12 +53,12 @@ type Props = {
setModalsVisible: Function,
place: typeof PlaceRecord,
setPlace: (place: typeof PlaceRecord) => void,
removeImageAtIndex: (i: number) => () => void,
updatePlace: (place: GooglePlaceObject) => void,
addImage: (uri: string) => void,
setImagePreview: (index?: number) => void,
};
const CreateFoodItem = (props: Props) => {
const { foodItem, toggleNameModal, setPropOfFoodItem, place, updatePlace, removeImageAtIndex, addImage } = props;
const { foodItem, toggleNameModal, setPropOfFoodItem, place, updatePlace, addImage, setImagePreview } = props;
return (
<View style={{ ...theme.page.container, backgroundColor: 'white', padding: 10 }}>
@ -77,14 +78,14 @@ const CreateFoodItem = (props: Props) => {
<Divider />
<ImagePicker onCreateNew={addImage}>
{foodItem.images.map((image, index) =>
<ImageThumb key={index} uri={image} onPress={removeImageAtIndex(index)} />
<ImageThumb key={index} uri={image} onPress={() => setImagePreview(index)} />
)}
</ImagePicker>
</View>
);
};
const enhanceNameModal = compose(
const NameModalComp = compose(
renderComponent,
mapProps(({ toggleNameModal, setPropOfFoodItem }: Props) => {
return {
@ -92,57 +93,73 @@ const enhanceNameModal = compose(
onUpdate: setPropOfFoodItem('name'),
};
})
);
)(NameModal);
const ImagePreviewComp = compose(
renderComponent,
mapProps(({ foodItem, setFoodItem, setImagePreview, imagePreview }) => {
return {
onClose: () => setImagePreview(-1),
onDelete: () => {
setFoodItem(foodItem.deleteIn(['images', imagePreview]));
setImagePreview(-1);
},
imageSrc: foodItem.images.get(imagePreview),
};
})
)(ImagePreviewModal);
const setPropOfFoodItem = ({ foodItem, setFoodItem }: Props) => (prop: string) => (value: any) => {
setFoodItem(foodItem.set(prop, value));
};
const addImage = ({ foodItem, setFoodItem }: Props) => (uri: string) => {
setFoodItem(foodItem.update('images', images => images.push(uri)));
};
const updatePlace = ({ setPlace, foodItem, setFoodItem }: Props) => ({
placeID,
latitude,
longitude,
name,
address,
phoneNumber,
website,
}) => {
setPlace(
new PlaceRecord({
id: placeID,
name,
address,
latitude,
longitude,
phoneNumber,
website,
})
);
setFoodItem(
foodItem.merge({
placeId: placeID,
latitude,
longitude,
})
);
};
const toggleNameModal = ({ nameModalOpen, setNameModalOpen }) => () => setNameModalOpen(!nameModalOpen);
export default compose(
withState('foodItem', 'setFoodItem', new FoodItemRecord()),
withState('place', 'setPlace', new PlaceRecord()),
withState('nameModalOpen', 'setNameModalOpen', false),
withState('imagePreview', 'setImagePreview', -1),
withHandlers({
setPropOfFoodItem: ({ foodItem, setFoodItem }: Props) => (prop: string) => (value: any) => {
setFoodItem(foodItem.set(prop, value));
},
addImage: ({ foodItem, setFoodItem }: Props) => (uri: string) => {
setFoodItem(foodItem.update('images', images => [uri].concat(images)));
},
removeImageAtIndex: ({ foodItem, setFoodItem }: Props) => (index: number) => () => {
setFoodItem(
foodItem.update('images', (images: Array<string>) => {
images.splice(index, 1);
return images;
})
);
},
updatePlace: ({ setPlace, foodItem, setFoodItem }: Props) => ({
placeID,
latitude,
longitude,
name,
address,
phoneNumber,
website,
}) => {
setPlace(
new PlaceRecord({
id: placeID,
name,
address,
latitude,
longitude,
phoneNumber,
website,
})
);
setFoodItem(
foodItem.merge({
placeId: placeID,
latitude,
longitude,
})
);
},
toggleNameModal: ({ nameModalOpen, setNameModalOpen }) => () => setNameModalOpen(!nameModalOpen),
setPropOfFoodItem,
addImage,
updatePlace,
toggleNameModal,
}),
branch(({ nameModalOpen }) => !!nameModalOpen, enhanceNameModal(NameModal))
branch(({ nameModalOpen }) => !!nameModalOpen, NameModalComp),
branch(({ imagePreview }) => imagePreview > -1, ImagePreviewComp)
)(CreateFoodItem);

View file

@ -68,15 +68,15 @@ export class FoodItemDetail extends Component {
return (
<View style={{ ...theme.page.container }}>
<View style={{ flex: 3 }}>
{viewableImages.length === 1 &&
<Image style={stretchedStyle} source={{ uri: viewableImages[0] }} />}
{viewableImages.length > 1 &&
{viewableImages.size === 1 &&
<Image style={stretchedStyle} source={{ uri: viewableImages.get(0) }} />}
{viewableImages.size > 1 &&
<Carousel autoplay={false} onAnimateNextPage={this.changeCurrentImage} style={stretchedStyle}>
{viewableImages.map(uri =>
<Image key={uri} style={{ flex: 1, resizeMode: 'stretch' }} source={{ uri }} />
)}
</Carousel>}
<CountBadge currentImage={this.state.currentImage + 1} totalCount={viewableImages.length} />
<CountBadge currentImage={this.state.currentImage + 1} totalCount={viewableImages.size} />
</View>
<View style={{ flex: 1, marginBottom: 10, ...contentTileStyle }}>
<View style={{ marginTop: 15 }}>

View file

@ -1,5 +1,5 @@
//@flow
import { Record } from 'immutable';
import { fromJS, List, Record } from 'immutable';
export const QUANTITY_NONE: 'none' = 'none';
export const QUANTITY_FEW: 'few' = 'few';
@ -28,7 +28,7 @@ export type FoodItem = {
distance: number,
quantity: Quantity,
category: Category,
images: Array<string>,
images: List<string>,
thumbImage: ?string,
titleImage: ?string,
};
@ -42,9 +42,18 @@ const FoodRecordDefaults: FoodItem = {
distance: 999,
quantity: QUANTITY_MANY,
category: CATEGORY_DESSERTS,
images: [],
images: new List(),
thumbImage: '',
titleImage: '',
};
export default Record(FoodRecordDefaults, 'FoodItemRecord');
const FoodItemRecord = Record(FoodRecordDefaults, 'FoodItemRecord');
export const createFoodItem = (foodItemRaw: Object) => {
return new FoodItemRecord({
...foodItemRaw,
images: fromJS(foodItemRaw.images),
});
};
export default FoodItemRecord;

View file

@ -1,5 +1,5 @@
//@flow
import FoodItemRecord from '../records/FoodItemRecord';
import { createFoodItem } from '../records/FoodItemRecord';
import { Observable } from 'rxjs';
import { setById } from '../helpers/ImmutableHelpers';
import { Map } from 'immutable';
@ -110,4 +110,4 @@ const DUMMY_DATA = [
export const foodItemsRaw$ = Observable.from(DUMMY_DATA);
export default foodItemsRaw$.map(FoodItemRecord).scan(setById, Map());
export default foodItemsRaw$.map(createFoodItem).scan(setById, Map());