diff --git a/android/app/build.gradle b/android/app/build.gradle
index d88f4ac..8edcc31 100644
--- a/android/app/build.gradle
+++ b/android/app/build.gradle
@@ -131,7 +131,7 @@ android {
applicationId "com.aretherecookies"
minSdkVersion rootProject.ext.minSdkVersion
targetSdkVersion rootProject.ext.targetSdkVersion
- versionCode 103
+ versionCode 104
versionName "1.0"
}
splits {
diff --git a/android/app/src/main/AndroidManifest.xml b/android/app/src/main/AndroidManifest.xml
index acc37e9..2353538 100644
--- a/android/app/src/main/AndroidManifest.xml
+++ b/android/app/src/main/AndroidManifest.xml
@@ -4,7 +4,6 @@
-
3.1.0)
- GooglePlaces (~> 3.1.0)
- React
- - react-native-image-picker (1.1.0):
- - React
- react-native-maps (0.25.0):
- React
- React-RCTActionSheet (0.60.5):
@@ -98,10 +97,18 @@ PODS:
- React-Core (= 0.60.5)
- React-RCTWebSocket (0.60.5):
- React-Core (= 0.60.5)
+ - RNCAsyncStorage (1.6.2):
+ - React
+ - RNImageCropPicker (0.25.2):
+ - QBImagePickerController
+ - React-Core
+ - React-RCTImage
+ - RSKImageCropper
- RNSnackbar (2.0.2):
- React
- RNVectorIcons (6.6.0):
- React
+ - RSKImageCropper (2.2.3)
- yoga (0.60.5.React)
DEPENDENCIES:
@@ -118,7 +125,6 @@ DEPENDENCIES:
- React-jsinspector (from `../node_modules/react-native/ReactCommon/jsinspector`)
- "react-native-geolocation (from `../node_modules/@react-native-community/geolocation`)"
- react-native-google-places (from `../node_modules/react-native-google-places`)
- - react-native-image-picker (from `../node_modules/react-native-image-picker`)
- react-native-maps (from `../node_modules/react-native-maps`)
- React-RCTActionSheet (from `../node_modules/react-native/Libraries/ActionSheetIOS`)
- React-RCTAnimation (from `../node_modules/react-native/Libraries/NativeAnimation`)
@@ -130,6 +136,8 @@ DEPENDENCIES:
- React-RCTText (from `../node_modules/react-native/Libraries/Text`)
- React-RCTVibration (from `../node_modules/react-native/Libraries/Vibration`)
- React-RCTWebSocket (from `../node_modules/react-native/Libraries/WebSocket`)
+ - "RNCAsyncStorage (from `../node_modules/@react-native-community/async-storage`)"
+ - RNImageCropPicker (from `../node_modules/react-native-image-crop-picker`)
- RNSnackbar (from `../node_modules/react-native-snackbar`)
- RNVectorIcons (from `../node_modules/react-native-vector-icons`)
- yoga (from `../node_modules/react-native/ReactCommon/yoga`)
@@ -139,6 +147,8 @@ SPEC REPOS:
- boost-for-react-native
- GoogleMaps
- GooglePlaces
+ - QBImagePickerController
+ - RSKImageCropper
EXTERNAL SOURCES:
A0Auth0:
@@ -167,8 +177,6 @@ EXTERNAL SOURCES:
:path: "../node_modules/@react-native-community/geolocation"
react-native-google-places:
:path: "../node_modules/react-native-google-places"
- react-native-image-picker:
- :path: "../node_modules/react-native-image-picker"
react-native-maps:
:path: "../node_modules/react-native-maps"
React-RCTActionSheet:
@@ -191,6 +199,10 @@ EXTERNAL SOURCES:
:path: "../node_modules/react-native/Libraries/Vibration"
React-RCTWebSocket:
:path: "../node_modules/react-native/Libraries/WebSocket"
+ RNCAsyncStorage:
+ :path: "../node_modules/@react-native-community/async-storage"
+ RNImageCropPicker:
+ :path: "../node_modules/react-native-image-crop-picker"
RNSnackbar:
:path: "../node_modules/react-native-snackbar"
RNVectorIcons:
@@ -206,6 +218,7 @@ SPEC CHECKSUMS:
glog: 1f3da668190260b06b429bb211bfbee5cd790c28
GoogleMaps: 5c13302e6fe6bb6e686b267196586b91cd594225
GooglePlaces: e874db179f2675c4f3eeda0b686b540273a578b0
+ QBImagePickerController: d54cf93db6decf26baf6ed3472f336ef35cae022
React: 53c53c4d99097af47cf60594b8706b4e3321e722
React-Core: ba421f6b4f4cbe2fb17c0b6fc675f87622e78a64
React-cxxreact: 8384287780c4999351ad9b6e7a149d9ed10a2395
@@ -215,7 +228,6 @@ SPEC CHECKSUMS:
React-jsinspector: e08662d1bf5b129a3d556eb9ea343a3f40353ae4
react-native-geolocation: a7b94614afbd5fd8350e0233a2025c8228fc8041
react-native-google-places: 34e976a0e13cb55cc9603ef2fa6764ed534af806
- react-native-image-picker: 7a85cf7b0a53845f03ae52fb4592a2748ded069b
react-native-maps: 190c02ca533fddac5bb49cf17bdece3644612107
React-RCTActionSheet: b0f1ea83f4bf75fb966eae9bfc47b78c8d3efd90
React-RCTAnimation: 359ba1b5690b1e87cc173558a78e82d35919333e
@@ -227,10 +239,13 @@ SPEC CHECKSUMS:
React-RCTText: b074d89033583d4f2eb5faf7ea2db3a13c7553a2
React-RCTVibration: 2105b2e0e2b66a6408fc69a46c8a7fb5b2fdade0
React-RCTWebSocket: cd932a16b7214898b6b7f788c8bddb3637246ac4
+ RNCAsyncStorage: 60a80e72d95bf02a01cace55d3697d9724f0d77f
+ RNImageCropPicker: f675353bbe18f66113a39b319c0aeb36655a6e4c
RNSnackbar: f6d5a0f66f5e75794e1d94b62827b78ce37bd530
RNVectorIcons: 0bb4def82230be1333ddaeee9fcba45f0b288ed4
+ RSKImageCropper: a446db0e8444a036b34f3c43db01b2373baa4b2a
yoga: 312528f5bbbba37b4dcea5ef00e8b4033fdd9411
-PODFILE CHECKSUM: 702016785759f22e0f5c7560c54f1797f93efccf
+PODFILE CHECKSUM: ee2999c6a9ba049d4cee2b2a6ec09d970f38352a
COCOAPODS: 1.8.0.beta.2
diff --git a/ios/aretherecookies.xcodeproj/project.pbxproj b/ios/aretherecookies.xcodeproj/project.pbxproj
index e3a7ac4..1817894 100644
--- a/ios/aretherecookies.xcodeproj/project.pbxproj
+++ b/ios/aretherecookies.xcodeproj/project.pbxproj
@@ -518,6 +518,7 @@
"${PODS_ROOT}/Target Support Files/Pods-aretherecookies/Pods-aretherecookies-resources.sh",
"${PODS_ROOT}/GoogleMaps/Maps/Frameworks/GoogleMaps.framework/Resources/GoogleMaps.bundle",
"${PODS_ROOT}/GooglePlaces/Frameworks/GooglePlaces.framework/Resources/GooglePlaces.bundle",
+ "${PODS_CONFIGURATION_BUILD_DIR}/QBImagePickerController/QBImagePicker.bundle",
"${PODS_ROOT}/../../node_modules/react-native-vector-icons/Fonts/AntDesign.ttf",
"${PODS_ROOT}/../../node_modules/react-native-vector-icons/Fonts/Entypo.ttf",
"${PODS_ROOT}/../../node_modules/react-native-vector-icons/Fonts/EvilIcons.ttf",
@@ -534,11 +535,13 @@
"${PODS_ROOT}/../../node_modules/react-native-vector-icons/Fonts/Octicons.ttf",
"${PODS_ROOT}/../../node_modules/react-native-vector-icons/Fonts/SimpleLineIcons.ttf",
"${PODS_ROOT}/../../node_modules/react-native-vector-icons/Fonts/Zocial.ttf",
+ "${PODS_ROOT}/RSKImageCropper/RSKImageCropper/RSKImageCropperStrings.bundle",
);
name = "[CP] Copy Pods Resources";
outputPaths = (
"${TARGET_BUILD_DIR}/${UNLOCALIZED_RESOURCES_FOLDER_PATH}/GoogleMaps.bundle",
"${TARGET_BUILD_DIR}/${UNLOCALIZED_RESOURCES_FOLDER_PATH}/GooglePlaces.bundle",
+ "${TARGET_BUILD_DIR}/${UNLOCALIZED_RESOURCES_FOLDER_PATH}/QBImagePicker.bundle",
"${TARGET_BUILD_DIR}/${UNLOCALIZED_RESOURCES_FOLDER_PATH}/AntDesign.ttf",
"${TARGET_BUILD_DIR}/${UNLOCALIZED_RESOURCES_FOLDER_PATH}/Entypo.ttf",
"${TARGET_BUILD_DIR}/${UNLOCALIZED_RESOURCES_FOLDER_PATH}/EvilIcons.ttf",
@@ -555,6 +558,7 @@
"${TARGET_BUILD_DIR}/${UNLOCALIZED_RESOURCES_FOLDER_PATH}/Octicons.ttf",
"${TARGET_BUILD_DIR}/${UNLOCALIZED_RESOURCES_FOLDER_PATH}/SimpleLineIcons.ttf",
"${TARGET_BUILD_DIR}/${UNLOCALIZED_RESOURCES_FOLDER_PATH}/Zocial.ttf",
+ "${TARGET_BUILD_DIR}/${UNLOCALIZED_RESOURCES_FOLDER_PATH}/RSKImageCropperStrings.bundle",
);
runOnlyForDeploymentPostprocessing = 0;
shellPath = /bin/sh;
diff --git a/ios/aretherecookies/Info.plist b/ios/aretherecookies/Info.plist
index 8abbaa2..bf2296d 100644
--- a/ios/aretherecookies/Info.plist
+++ b/ios/aretherecookies/Info.plist
@@ -38,7 +38,7 @@
NSLocationWhenInUseUsageDescription
-
+
UIAppFonts
MaterialIcons.ttf
@@ -75,6 +75,8 @@
UIInterfaceOrientationLandscapeLeft
UIInterfaceOrientationLandscapeRight
+ NSPhotoLibraryUsageDescription
+ Photos can be uploaded to create new food items for business you love!
UIViewControllerBasedStatusBarAppearance
diff --git a/js/apis/ImagesApi.js b/js/apis/ImagesApi.js
index 44fe327..78e1d39 100644
--- a/js/apis/ImagesApi.js
+++ b/js/apis/ImagesApi.js
@@ -1,37 +1,37 @@
// @flow
-import type { ImageRaw } from "../records/ImageRecord";
-import { fetchRequest, fetchRequestBinary } from "./FetchApi";
+import type { ImageRaw } from '../records/ImageRecord';
+import { fetchRequest, fetchRequestBinary } from './FetchApi';
export const getImages = (foodItemId: string): Promise> => {
return fetchRequest({
endpoint: `/images/${foodItemId}`,
- method: "GET"
+ method: 'GET',
});
};
export const addImage = async ({
foodItemId,
username,
- imageUri
+ imageUri,
}: {
foodItemId: string,
username: string,
- imageUri: string
+ imageUri: string,
}) => {
const body = new FormData();
// $FlowFixMe - react-native does different stuff with FormData
- body.append("photo", {
+ body.append('photo', {
uri: imageUri,
- type: "image/jpeg",
- name: "photo.jpg"
+ type: 'image/jpeg',
+ name: 'photo.jpg',
});
- body.append("username", username);
+ body.append('username', username);
const res = await fetchRequestBinary({
endpoint: `/images/${foodItemId}`,
- body
+ body,
});
if (!res.ok) {
diff --git a/js/enhancers/createFoodItemEnhancers.js b/js/enhancers/createFoodItemEnhancers.js
index d0b7ad1..2b0b191 100644
--- a/js/enhancers/createFoodItemEnhancers.js
+++ b/js/enhancers/createFoodItemEnhancers.js
@@ -19,7 +19,11 @@ export const withCreateFoodItemState = mapPropsStream(props$ => {
// insert new item into db and cast it into FoodItemRecord
const newItem = buildFoodItem(await createFoodItem(foodItem));
- Snackbar.show({ title: foodItem.name + ' added' });
+ Snackbar.show({
+ title: foodItem.name + ' added',
+ backgroundColor: 'black',
+ color: 'white',
+ });
// notify food items state of new item
emitFoodItemsState(newItem);
@@ -32,7 +36,12 @@ export const withCreateFoodItemState = mapPropsStream(props$ => {
} catch (err) {
const error = formatError(err);
- Snackbar.show({ title: error, duration: Snackbar.LENGTH_LONG });
+ Snackbar.show({
+ title: error,
+ duration: Snackbar.LENGTH_LONG,
+ backgroundColor: 'black',
+ color: 'white',
+ });
throw error;
}
diff --git a/js/helpers/ImagePickerHelpers.js b/js/helpers/ImagePickerHelpers.js
index 5f70284..a3fb724 100644
--- a/js/helpers/ImagePickerHelpers.js
+++ b/js/helpers/ImagePickerHelpers.js
@@ -1,13 +1,11 @@
// @flow
-import * as ReactNativeImagePicker from 'react-native-image-picker';
+import ImagePicker from 'react-native-image-crop-picker';
const IMAGE_MAX_WIDTH = 512;
-const IMAGE_QUALITY = 0.8;
+const IMAGE_MAX_HEIGHT = 384;
export const openImagePicker = () =>
- new Promise((resolve, reject) =>
- ReactNativeImagePicker.showImagePicker(
- { maxWidth: IMAGE_MAX_WIDTH, quality: IMAGE_QUALITY },
- ({ didCancel, error, uri }) => (!didCancel && !error ? resolve(uri) : reject(error))
- )
- );
+ ImagePicker.openPicker({
+ width: IMAGE_MAX_WIDTH,
+ height: IMAGE_MAX_HEIGHT,
+ });
diff --git a/js/pages/CreateFoodItem.js b/js/pages/CreateFoodItem.js
index 3d24fa9..959c52c 100644
--- a/js/pages/CreateFoodItem.js
+++ b/js/pages/CreateFoodItem.js
@@ -39,7 +39,7 @@ type Props = {
place: ?PlaceRecord,
updatePlace: (place: GooglePlaceObject) => void,
addImage: (uri: string) => void,
- setImagePreview: (index?: number) => void,
+ setImagePreview: (imageURI?: string) => void,
loading: boolean,
setLoading: (arg: boolean) => void,
emitPlace: (place: Object) => void,
@@ -116,8 +116,8 @@ const CreateFoodItem = (props: Props) => {
placeholder="Quantity"
/>
- {foodItem.images.map((image, index) => (
- setImagePreview(index)} />
+ {foodItem.images.map((imageURI: string) => (
+ setImagePreview(imageURI)} />
))}
@@ -131,7 +131,7 @@ const ImagePreviewComp = compose(
onClose: () => setImagePreview(-1),
onDelete: () => {
setFoodItem(foodItem.deleteIn(['images', imagePreview]));
- setImagePreview(-1);
+ setImagePreview(null);
},
imageSrc: foodItem.images.get(imagePreview),
};
@@ -143,11 +143,11 @@ const setPropOfFoodItem = ({ foodItem, setFoodItem }: Props) => (prop: string) =
};
const addImage = ({ foodItem, setFoodItem }: Props) => async () => {
- const uri = await openImagePicker();
+ const { path } = await openImagePicker();
Snackbar.show({
title: 'Photo added.',
});
- setFoodItem(foodItem.update('images', images => images.add(uri)));
+ setFoodItem(foodItem.update('images', images => images.add(path)));
};
const updatePlace = ({ foodItem, setFoodItem, emitPlace }: Props) => placeDetails => {
@@ -175,7 +175,7 @@ export default compose(
withPlaceForFoodItem,
withPlaceActions,
withState('nameModalOpen', 'setNameModalOpen', false),
- withState('imagePreview', 'setImagePreview', -1),
+ withState('imagePreview', 'setImagePreview', null),
withState('categoryModalOpen', 'setCategoryModalOpen', false),
withState('quantityModalOpen', 'setQuantityModalOpen', false),
withHandlers({
@@ -211,7 +211,7 @@ export default compose(
onUpdateProp: 'setName',
})
),
- branch(({ imagePreview }) => imagePreview > -1, ImagePreviewComp),
+ branch(({ imagePreview }) => !!imagePreview, ImagePreviewComp),
branch(
({ categoryModalOpen }) => !!categoryModalOpen,
wrapModalComponent({
diff --git a/js/pages/FoodItemDetail.js b/js/pages/FoodItemDetail.js
index a308dc2..10420cd 100644
--- a/js/pages/FoodItemDetail.js
+++ b/js/pages/FoodItemDetail.js
@@ -60,7 +60,11 @@ const FoodItemImages = ({
)}
{visibleImages.size > 1 && (
-
+
{visibleImages.map(image => (
{
return ;
}
- if (quantityModalOpen) {
- return (
-
- );
- }
-
return (
@@ -188,6 +186,9 @@ export const FoodItemDetail = (props: Props) => {
/>
)}
+ {quantityModalOpen && (
+
+ )}
);
};
@@ -208,18 +209,26 @@ export default compose(
if (!foodItem) {
return;
}
- const imageUri = await openImagePicker();
+ const { path: imageUri } = await openImagePicker();
setImagesLoading(true);
await addImage({ foodItemId: foodItem.id, imageUri });
setImagesLoading(false);
- setTimeout(() => Snackbar.show({ title: 'Food updated.' }), 700);
+ setTimeout(
+ () =>
+ Snackbar.show({
+ title: 'Food updated.',
+ backgroundColor: 'black',
+ color: 'white',
+ }),
+ 700
+ );
},
updateAmount: ({ updateQuantity, foodItem }: Props) => async (quantity: Quantity) => {
if (!foodItem) {
return;
}
await updateQuantity({ foodItemId: foodItem.id, quantity });
- Snackbar.show({ title: 'Food updated.' });
+ Snackbar.show({ title: 'Food updated.', backgroundColor: 'black', color: 'white' });
},
toggleQuantityModal: ({ quantityModalOpen, setQuantityModalOpen }) => () => {
setQuantityModalOpen(!quantityModalOpen);
diff --git a/js/pages/FoodMap.js b/js/pages/FoodMap.js
index bcc10ba..45cb89e 100644
--- a/js/pages/FoodMap.js
+++ b/js/pages/FoodMap.js
@@ -28,13 +28,15 @@ type Props = {
};
const FoodMap = ({ foodItemsMap, region, onRegionChange, pushRoute, initialRegion }: Props) => {
- return !foodItemsMap ? null : (
+ if (!foodItemsMap) {
+ return null;
+ }
+ return (
+ onRegionChangeComplete={onRegionChange}>
{foodItemsMap
.map((foodItem, id) => {
return (
diff --git a/js/pages/Nav.js b/js/pages/Nav.js
index 4066e43..8049efd 100644
--- a/js/pages/Nav.js
+++ b/js/pages/Nav.js
@@ -34,30 +34,55 @@ const Nav = (props: Props) => {
icon="local-pizza"
label="Food"
onPress={setRoute('/list/food')}
+ style={{
+ container: {
+ minWidth: 40,
+ },
+ }}
/>
diff --git a/js/pages/ProfilePage.js b/js/pages/ProfilePage.js
index c0a99d4..bc4ce82 100644
--- a/js/pages/ProfilePage.js
+++ b/js/pages/ProfilePage.js
@@ -109,7 +109,7 @@ export default compose(
await AsyncStorage.setItem('zipcode', props.zipcode);
props.setLoading(false);
getLocation();
- Snackbar.show({ title: 'Zipcode updated.' });
+ Snackbar.show({ title: 'Zipcode updated.', backgroundColor: 'black', color: 'white' });
},
}),
withHandlers({
diff --git a/js/records/ImageRecord.js b/js/records/ImageRecord.js
index d80aa8b..45bf698 100644
--- a/js/records/ImageRecord.js
+++ b/js/records/ImageRecord.js
@@ -1,5 +1,5 @@
//@flow
-import { Record } from 'immutable';
+import { Record, OrderedSet } from 'immutable';
export type ImageRaw = {
url: string,
@@ -15,7 +15,7 @@ const ImageRecord = Record({
foodItemId: '',
});
-export type ImageFragment = { id: string, images: Set };
+export type ImageFragment = { id: string, images: OrderedSet };
export const buildImageRecord = (imageRaw: ImageRaw) => {
return new ImageRecord({
diff --git a/js/streams/ImagesStream.js b/js/streams/ImagesStream.js
index fd28697..c5020c0 100644
--- a/js/streams/ImagesStream.js
+++ b/js/streams/ImagesStream.js
@@ -1,10 +1,10 @@
//@flow
-import { Map, Set } from 'immutable';
+import { Map, OrderedSet } from 'immutable';
import { ReplaySubject } from 'rxjs';
-import ImageRecord, { buildImageRecord } from '../records/ImageRecord';
+import { buildImageRecord, type ImageFragment } from '../records/ImageRecord';
import type { ImageRaw } from '../records/ImageRecord';
-const observable: ReplaySubject = new ReplaySubject();
+const observable: ReplaySubject = new ReplaySubject();
export function emit(val: ?ImageRaw) {
observable.next(val);
@@ -13,15 +13,18 @@ export function emit(val: ?ImageRaw) {
// force our observable to emit an initial empty map so that food items will load
emit(null);
-export default observable.scan((imagesByFoodItemId: Map, image: ImageRaw) => {
- if (!image || !image.food_item_id) {
- return imagesByFoodItemId;
- }
+export default observable.scan(
+ (imagesByFoodItemId: Map, image: ImageRaw) => {
+ if (!image || !image.food_item_id) {
+ return imagesByFoodItemId;
+ }
- return imagesByFoodItemId.update(image.food_item_id, ({ images = Set() } = {}) => {
- return {
- id: image.food_item_id,
- images: images.add(buildImageRecord(image)),
- };
- });
-}, new Map());
+ return imagesByFoodItemId.update(image.food_item_id, ({ images = OrderedSet() } = {}) => {
+ return {
+ id: image.food_item_id,
+ images: images.add(buildImageRecord(image)),
+ };
+ });
+ },
+ new Map()
+);
diff --git a/package.json b/package.json
index 395020d..32002a4 100644
--- a/package.json
+++ b/package.json
@@ -25,7 +25,7 @@
"react-native-auth0": "^1.5.0",
"react-native-geolocation-service": "^3.1.0",
"react-native-google-places": "^3.1.2",
- "react-native-image-picker": "^1.1.0",
+ "react-native-image-crop-picker": "^0.25.2",
"react-native-loading-spinner-overlay": "^1.0.1",
"react-native-looped-carousel": "^0.1.13",
"react-native-maps": "^0.25.0",
diff --git a/yarn.lock b/yarn.lock
index 14da4dc..e2483b3 100644
--- a/yarn.lock
+++ b/yarn.lock
@@ -5775,10 +5775,10 @@ react-native-google-places@^3.1.2:
resolved "https://registry.yarnpkg.com/react-native-google-places/-/react-native-google-places-3.1.2.tgz#6aa11f76da6bd58aa9ec1d1b3e2c85edb4ced2ce"
integrity sha512-aKPbGHga3k/Vkfw9NMf/1t0sivdqxn4qzCbEyAfmhxO7Mlqv9Nljjnez6rp6MZGwh89ss3/a9lhM4ulyZ6o0ew==
-react-native-image-picker@^1.1.0:
- version "1.1.0"
- resolved "https://registry.yarnpkg.com/react-native-image-picker/-/react-native-image-picker-1.1.0.tgz#c2a0523886edb4cf2cdc008ad0a71433142a4d53"
- integrity sha512-/KjHf4NNAjl6XM7FQuqvGDz1wB9sRdLf86+2yksLW/QTRR7CitX4TLCM8ZF9CX6Y0MsCTndkZia3zWE+nt/GiA==
+react-native-image-crop-picker@^0.25.2:
+ version "0.25.2"
+ resolved "https://registry.yarnpkg.com/react-native-image-crop-picker/-/react-native-image-crop-picker-0.25.2.tgz#241e598e6bd601248c319483c19508f9bc9a203e"
+ integrity sha512-LZX9gJb5q1q6Oo1jXj1FyNEEDkvfynZMeE3M9jhFjJdvknMf/eui7GlmSokzQ4A1MLxKmcLSVDGw2L1HTBAO6A==
react-native-loading-spinner-overlay@^1.0.1:
version "1.0.1"