diff --git a/android/app/build.gradle b/android/app/build.gradle
index e8b35a5..937c2de 100644
--- a/android/app/build.gradle
+++ b/android/app/build.gradle
@@ -91,7 +91,7 @@ android {
applicationId "com.aretherecookies"
minSdkVersion 16
targetSdkVersion 22
- versionCode 1
+ versionCode 6
versionName "1.0"
ndk {
abiFilters "armeabi-v7a", "x86"
diff --git a/android/app/src/main/AndroidManifest.xml b/android/app/src/main/AndroidManifest.xml
index 6af0fe5..2640918 100644
--- a/android/app/src/main/AndroidManifest.xml
+++ b/android/app/src/main/AndroidManifest.xml
@@ -2,13 +2,13 @@
xmlns:tools="http://schemas.android.com/tools"
package="com.aretherecookies"
android:versionCode="1"
- android:versionName="1.0">
+ android:versionName="1.0.1">
-
+
}
tweenDuration={150}>
-
-
-
-
-
-
-
- {
- if (!AuthManager.isLoggedIn()) {
- return ;
- }
- return ;
- }}
- />
-
+
+
+
+
+
+
+
+
+
+
+ {
+ if (!AuthManager.isLoggedIn()) {
+ return ;
+ }
+ return ;
+ }}
+ />
+
+
diff --git a/js/apis/FoodItemsApi.js b/js/apis/FoodItemsApi.js
index e12174d..c04eea0 100644
--- a/js/apis/FoodItemsApi.js
+++ b/js/apis/FoodItemsApi.js
@@ -27,7 +27,7 @@ export type RawFoodItem = {
export type FoodItemsForLocation = {
orderby: string,
filter: FoodItemsFilter,
- fooditems: Array,
+ fooditems: ?Array,
};
export const getFoodItems = memoize(
@@ -38,7 +38,9 @@ export const getFoodItems = memoize(
loc: Position,
filter: FilterRecord,
}): Promise => {
- const { coords: { latitude: lat, longitude: lng } } = loc;
+ const {
+ coords: { latitude: lat, longitude: lng },
+ } = loc;
const { orderby, categories, radius } = filter;
try {
diff --git a/js/apis/PositionApi.js b/js/apis/PositionApi.js
new file mode 100644
index 0000000..803e577
--- /dev/null
+++ b/js/apis/PositionApi.js
@@ -0,0 +1,27 @@
+// @flow
+import { emitter } from '../streams/LocationStream';
+
+export const getCurrentPosition = () => {
+ // $FlowFixMe: maximumAge not found on object literal
+ navigator.geolocation.getCurrentPosition(
+ (pos: Position) => emitter(pos),
+ err => {
+ throw err;
+ },
+ {
+ enableHighAccuracy: false,
+ timeout: 2000,
+ }
+ );
+};
+
+// TODO actually implement geolocation for zipcode into lat/lng
+export const getPositionFromZip = () => {
+ const dummyPos: any = {
+ coords: {
+ latitude: 30.267,
+ longitude: -97.7485,
+ },
+ };
+ emitter(dummyPos);
+};
diff --git a/js/components/FoodItemList.js b/js/components/FoodItemList.js
index c9854de..4d6b6e3 100644
--- a/js/components/FoodItemList.js
+++ b/js/components/FoodItemList.js
@@ -3,11 +3,12 @@ import React, { Component } from 'react';
import { View } from 'react-native';
import { type SetSeq } from 'immutable';
import FoodItemRecord from '../records/FoodItemRecord';
-import { pure, compose } from 'recompose';
-import { withFoodItemsAsSeq } from '../enhancers/foodItemEnhancers';
+import { pure } from 'recompose';
import R from 'ramda';
-const matchString = R.memoize((match = '', str = '') => str.toLowerCase().includes(match.toLowerCase()));
+const matchString = R.memoize((match = '', str = '') =>
+ str.toLowerCase().includes(match.toLowerCase())
+);
const filterBy = (filter?: string = '') => (foodItemsSeq: SetSeq) => {
if (!filter) {
@@ -27,7 +28,8 @@ const sortByDistance = (foodItemsSeq: SetSeq) => {
return foodItemsSeq.sort((left, right) => left.distance - right.distance);
};
-const intoArray = (foodItemsSeq: SetSeq) => foodItemsSeq.toArray();
+const intoArray = (foodItemsSeq: SetSeq) =>
+ foodItemsSeq ? foodItemsSeq.toArray() : [];
class FoodItemList extends Component {
static displayName = 'FoodItemList';
@@ -37,16 +39,21 @@ class FoodItemList extends Component {
limit?: number,
foodItemsSeq: SetSeq,
renderFoodItem: (foodItem: typeof FoodItemRecord) => Component<*, *, *>,
+ foodItemsLoading: boolean,
};
render() {
const { filter, foodItemsSeq, renderFoodItem, limit } = this.props;
+
+ if (!foodItemsSeq) {
+ return null;
+ }
+
const getItems = R.compose(intoArray, limitBy(limit), sortByDistance, filterBy(filter));
const items = getItems(foodItemsSeq);
+
return {items.map(renderFoodItem)};
}
}
-const enhance = compose(pure, withFoodItemsAsSeq);
-
-export default enhance(FoodItemList);
+export default pure(FoodItemList);
diff --git a/js/components/ItemTile.js b/js/components/ItemTile.js
index 7f1be29..b03dded 100644
--- a/js/components/ItemTile.js
+++ b/js/components/ItemTile.js
@@ -42,7 +42,7 @@ export const SubText = ({ children, style = {} }: { children?: string, style?: O
};
export const TileBox = ({ children, style = {} }: { children?: any, style?: Object }) => (
-
+
{children}
);
diff --git a/js/components/PlaceTile.js b/js/components/PlaceTile.js
index baf0dab..af1c59d 100644
--- a/js/components/PlaceTile.js
+++ b/js/components/PlaceTile.js
@@ -6,7 +6,7 @@ import { List } from 'immutable';
import FoodItemRecord from '../records/FoodItemRecord';
import typeof PlaceRecord from '../records/PlaceRecord';
import { Link } from 'react-router-native';
-import { Thumbnail, StrongText, SubText } from './ItemTile';
+import { TileBox, Thumbnail, StrongText, SubText } from './ItemTile';
import { routeWithTitle } from '../helpers/RouteHelpers';
import { getCategories } from '../helpers/CategoryHelpers';
import { type Map } from 'immutable';
@@ -47,13 +47,15 @@ export default pure(({ place, foodItems }: PlaceTileProps) => {
to={routeWithTitle(`/place/${place.id || ''}`, place.name)}
underlayColor={theme.itemTile.pressHighlightColor}
>
-
-
-
- {`${place.name} - ${distance} mi`}
- {getCategoriesText(foodItems)}
- {getHoursText(place.hours)}
-
+
+
+
+
+ {`${place.name} - ${distance} mi`}
+ {getCategoriesText(foodItems)}
+ {getHoursText(place.hours)}
+
+
);
diff --git a/js/components/RadiusPicker.js b/js/components/RadiusPicker.js
index 62d40c4..0fae8a9 100644
--- a/js/components/RadiusPicker.js
+++ b/js/components/RadiusPicker.js
@@ -19,7 +19,7 @@ const renderItem = (selected: number) => (item: number) => (
type radiusPickerProps = { selected: number, onValueChange: Function, style: Object };
const radiusPicker = pure(({ selected, onValueChange, style }: radiusPickerProps) => (
-
+
{CHOICES.map(renderItem(selected))}
diff --git a/js/enhancers/foodItemEnhancers.js b/js/enhancers/foodItemEnhancers.js
index 7b711d0..0ef1aed 100644
--- a/js/enhancers/foodItemEnhancers.js
+++ b/js/enhancers/foodItemEnhancers.js
@@ -11,7 +11,7 @@ export const withFoodItems = mapPropsStream(props$ =>
props$.combineLatest(FoodItems$, (props, foodItems) => {
return {
...props,
- foodItemsMap: foodItems,
+ foodItemsMap: foodItems && foodItems,
};
})
);
@@ -20,7 +20,7 @@ export const withFoodItemsAsSeq = mapPropsStream(props$ =>
props$.combineLatest(FoodItems$, (props, foodItems) => {
return {
...props,
- foodItemsSeq: foodItems.valueSeq(),
+ foodItemsSeq: foodItems && foodItems.valueSeq(),
};
})
);
@@ -33,10 +33,9 @@ export const withFoodItemIdFromRoute = withProps((props: { match: { params: { id
export const withFoodItem = compose(
withFoodItems,
withFoodItemIdFromRoute,
- withProps((props: { foodItemsMap: Map, foodItemId: number }) => {
+ withProps((props: { foodItemsMap: ?Map, foodItemId: number }) => {
const { foodItemsMap, foodItemId } = props;
- const foodItem = foodItemsMap.get(foodItemId);
- return { foodItem };
+ return { foodItem: foodItemsMap && foodItemsMap.get(foodItemId) };
})
);
@@ -49,10 +48,12 @@ export const withFoodItemPlaceId = withProps((props: { foodItem: FoodItemRecord
export const withFoodItemsGroupedByPlace = compose(
withFoodItems,
- withProps((props: { foodItemsMap: Map }) => {
- const foodItemsByPlace = props.foodItemsMap.groupBy(foodItem => foodItem.placeId);
+ withProps((props: { foodItemsMap: ?Map }) => {
+ if (!props.foodItemsMap) {
+ return {};
+ }
return {
- foodItemsByPlace,
+ foodItemsByPlace: props.foodItemsMap.groupBy(foodItem => foodItem.placeId),
};
})
);
diff --git a/js/modals/FilterModal.js b/js/modals/FilterModal.js
index a52ebc0..062f00c 100644
--- a/js/modals/FilterModal.js
+++ b/js/modals/FilterModal.js
@@ -69,29 +69,29 @@ const FilterModal = (props: Props) => {
})}
- Sort By
+ Sort By
- Search Radius
+ Search Radius
-
+
- Cancel
+ CANCEL
- Apply
+ APPLY
diff --git a/js/pages/FoodItemDetail.js b/js/pages/FoodItemDetail.js
index 7ccc76e..e34e157 100644
--- a/js/pages/FoodItemDetail.js
+++ b/js/pages/FoodItemDetail.js
@@ -77,7 +77,7 @@ const contentTileStyle = {
};
type Props = {
- foodItem: FoodItemRecord,
+ foodItem: ?FoodItemRecord,
place: PlaceRecord,
currentImage: number,
quantityModalOpen: boolean,
@@ -196,12 +196,18 @@ export default compose(
withState('imagesLoading', 'setImagesLoading', true),
withHandlers({
addPhoto: ({ addImage, foodItem, setImagesLoading }: Props) => async () => {
+ if (!foodItem) {
+ return;
+ }
const imageUri = await openImagePicker();
setImagesLoading(true);
await addImage({ foodItemId: foodItem.id, imageUri });
setImagesLoading(false);
},
updateAmount: ({ updateQuantity, foodItem }: Props) => (quantity: Quantity) => {
+ if (!foodItem) {
+ return;
+ }
updateQuantity({ foodItemId: foodItem.id, quantity });
},
toggleQuantityModal: ({ quantityModalOpen, setQuantityModalOpen }) => () => {
diff --git a/js/pages/FoodList.js b/js/pages/FoodList.js
index 5b4169d..de04db0 100644
--- a/js/pages/FoodList.js
+++ b/js/pages/FoodList.js
@@ -7,6 +7,9 @@ import { routeWithTitle } from '../helpers/RouteHelpers';
import FoodItemList from '../components/FoodItemList';
import typeof FoodItemRecord from '../records/FoodItemRecord';
import FilterModal from '../modals/FilterModal';
+import { withFoodItemsAsSeq } from '../enhancers/foodItemEnhancers';
+import { type SetSeq } from 'immutable';
+import Spinner from 'react-native-loading-spinner-overlay';
import theme from '../ui-theme';
@@ -14,13 +17,14 @@ const renderFoodItem = (foodItem: FoodItemRecord) => (
);
-export default class FoodList extends Component {
+class FoodList extends Component {
static displayName = 'FoodList';
props: {
pushRoute: Object => void,
isFilterModalOpen: boolean,
toggleFilterModal: (val: boolean) => void,
+ foodItemsSeq: SetSeq,
};
addFoodItem = () => {
@@ -45,12 +49,13 @@ export default class FoodList extends Component {
};
render() {
- const { isFilterModalOpen } = this.props;
+ const { isFilterModalOpen, foodItemsSeq } = this.props;
return (
+
-
+
{
+ return (
+
+
+
+ We need to use your location to bring you the best experience possible.
+
+
+
+
+
+ I'd rather not.
+
+
+ );
+};
+
+export default LandingPage;
diff --git a/js/pages/List.js b/js/pages/List.js
index 118c189..b3f2978 100644
--- a/js/pages/List.js
+++ b/js/pages/List.js
@@ -5,10 +5,16 @@ import Places from './Places';
import ScrollableTabView from 'react-native-scrollable-tab-view';
import theme from '../ui-theme.js';
import { type RoutingContextFlowTypes, routingContextPropTypes } from '../routes';
+import { getSearch } from '../helpers/RouteHelpers';
+import { getCurrentPosition, getPositionFromZip } from '../apis/PositionApi';
const tabs = ['food', 'places'];
-const getTabIndex = ({ match: { params: { type = '' } } }: RoutingContextFlowTypes): number => {
+const getTabIndex = ({
+ match: {
+ params: { type = '' },
+ },
+}: RoutingContextFlowTypes): number => {
return tabs.indexOf(type) || 0;
};
@@ -28,6 +34,17 @@ class List extends Component {
tabView: TabView;
+ // TODO convert this component to SFC using recompose
+ // also, figure out a less lifecyle-y way to do this request
+ componentDidMount() {
+ const { positionBy = 'zip' } = getSearch(this.context);
+ if (positionBy === 'location') {
+ getCurrentPosition();
+ } else {
+ getPositionFromZip();
+ }
+ }
+
updateTabRoute = ({ i }: { i: number }) => {
const currentTab = getTabIndex(this.props);
@@ -68,8 +85,7 @@ class List extends Component {
tabBarInactiveTextColor={theme.topTabs.selectedTextColor}
prerenderingSiblingsNumber={Infinity}
onChangeTab={this.updateTabRoute}
- initialPage={getTabIndex(this.props)}
- >
+ initialPage={getTabIndex(this.props)}>
diff --git a/js/streams/FoodItemsStream.js b/js/streams/FoodItemsStream.js
index 1c7ee74..ee315a1 100644
--- a/js/streams/FoodItemsStream.js
+++ b/js/streams/FoodItemsStream.js
@@ -27,34 +27,51 @@ const manualUpdate$ = foodItemSubject.scan(
Map()
);
-const fetchedFoodItems$ = location$
- .combineLatest(Filter$)
- .mergeMap(([loc, filter]: [Position, FilterRecord]) => getFoodItems({ loc, filter }))
- .map(({ fooditems = [] }: FoodItemsForLocation) => {
- return fooditems.map(createFoodItem).reduce(setById, new Map());
+const fetchedFoodItems$ = Filter$.combineLatest(location$)
+ .mergeMap(([filter, loc]: [Position, FilterRecord]) => {
+ if (loc) {
+ return getFoodItems({ loc, filter });
+ }
+ return Promise.resolve({});
+ })
+ .map(({ fooditems }: FoodItemsForLocation) => {
+ if (fooditems) {
+ return fooditems.map(createFoodItem).reduce(setById, new Map());
+ }
+ return null;
});
export default fetchedFoodItems$
.combineLatest(manualUpdate$, (foodItemMap: Map, manualUpdates) => {
- return foodItemMap.mergeDeep(manualUpdates);
+ if (foodItemMap) {
+ return foodItemMap.mergeDeep(manualUpdates);
+ }
})
.combineLatest(
Quantity$,
(
- foodItems: Map,
+ foodItems: ?Map,
quantitiesFromStream: Map
) => {
- return foodItems.mergeDeepWith(
- (foodItem, foodItemQuantities) => foodItem.merge(foodItemQuantities),
- quantitiesFromStream
- );
+ if (foodItems) {
+ return foodItems.mergeDeepWith(
+ (foodItem, foodItemQuantities) => foodItem.merge(foodItemQuantities),
+ quantitiesFromStream
+ );
+ }
}
)
.combineLatest(
Image$,
- (foodItems: Map, latestFromImages$: Map) =>
- // $FlowFixMe this type is incompatible with the expected param type of object type
- foodItems.mergeDeepWith((foodItem: FoodItemRecord, imageFragment: ImageFragment) => {
- return foodItem.set('images', imageFragment.images);
- }, latestFromImages$)
+ (foodItems: ?Map, latestFromImages$: Map) => {
+ if (foodItems) {
+ return foodItems.mergeDeepWith(
+ // $FlowFixMe
+ (foodItem: FoodItemRecord, imageFragment: ImageFragment) => {
+ return foodItem.set('images', imageFragment.images);
+ },
+ latestFromImages$
+ );
+ }
+ }
);
diff --git a/js/streams/LocationStream.js b/js/streams/LocationStream.js
index 4d6541b..bd3ec1e 100644
--- a/js/streams/LocationStream.js
+++ b/js/streams/LocationStream.js
@@ -1,16 +1,12 @@
// @flow
-import { Observable, type Observer } from 'rxjs';
+import { ReplaySubject } from 'rxjs';
-export default Observable.create((obs: Observer): Observable => {
- // $FlowFixMe: property maximumAge not found on object literal
- navigator.geolocation.getCurrentPosition(
- (pos: Position) => obs.next(pos),
- err => {
- throw err;
- },
- {
- enableHighAccuracy: false,
- timeout: 2000,
- }
- );
-});
+const multicaster: ReplaySubject = new ReplaySubject();
+
+export function emitter(val: ?Position) {
+ multicaster.next(val);
+}
+
+emitter(null);
+
+export default multicaster;
diff --git a/js/streams/PlacesStream.js b/js/streams/PlacesStream.js
index 6aa8c19..6c168c7 100644
--- a/js/streams/PlacesStream.js
+++ b/js/streams/PlacesStream.js
@@ -22,7 +22,7 @@ const safeGetPlaceDetails = memoize((placeId: string): Promise
});
const Places$ = foodItems$
- .mergeMap((foodItems: Map): Observable => {
+ .mergeMap((foodItems: Map = Map()): Observable => {
return Observable.from(foodItems.toArray().map(foodItem => foodItem.placeId));
})
.distinct()
diff --git a/js/ui-theme.js b/js/ui-theme.js
index 5aa4454..40eab0d 100644
--- a/js/ui-theme.js
+++ b/js/ui-theme.js
@@ -7,15 +7,18 @@ import { COLOR } from 'react-native-material-ui';
export const primaryColor = '#6d5354';
+export const palette = {
+ primaryColor,
+ accentColor: '#0E6E9E',
+ disabledColor: COLOR.grey500,
+ facebook: '#3B5998',
+ google: '#DB4437',
+ errorColor: '#B92D00',
+};
+
export default {
- palette: {
- primaryColor,
- accentColor: '#0E6E9E',
- disabledColor: COLOR.grey500,
- facebook: '#3B5998',
- google: '#DB4437',
- errorColor: '#B92D00',
- },
+ statusBarColor: '#412A2B',
+ palette: palette,
toolbar: {
titleText: { color: COLOR.white },
leftElement: { color: COLOR.white },
@@ -26,6 +29,11 @@ export default {
elevation: 0,
},
},
+ actionButton: {
+ speedDialActionIcon: {
+ backgroundColor: '#48A0CC',
+ },
+ },
checkbox: {
icon: {
color: '#0E6E9E',
@@ -45,14 +53,29 @@ export default {
selectedTextColor: 'rgba(255, 255, 255, 0.7)',
backgroundColor: primaryColor,
},
+ modalButton: {
+ fontSize: 14,
+ fontWeight: '500',
+ color: palette.accentColor,
+ paddingLeft: 30,
+ },
+ modalDropDown: {
+ /* fontSize: 16,
+ fontWeight: 'bold',
+ color: 'black', */
+ height: 40,
+ width: 150,
+ },
itemTile: {
thumbnailSize: 50,
- thumbnailColor: COLOR.grey500,
+ thumbnailColor: COLOR.grey400,
itemNameStyle: {
+ fontSize: 16,
color: COLOR.black,
},
itemPlaceStyle: {
- color: COLOR.grey500,
+ color: COLOR.grey700,
+ paddingTop: 3,
},
availableCountStyle: {
fontSize: 25,
diff --git a/package.json b/package.json
index 4b3c530..e35ed47 100644
--- a/package.json
+++ b/package.json
@@ -1,12 +1,17 @@
{
"name": "aretherecookies",
+<<<<<<< HEAD
"version": "1.6.0",
+=======
+ "version": "1.0.1",
+>>>>>>> master
"private": true,
"scripts": {
"start": "node node_modules/react-native/local-cli/cli.js start",
"test": "jest",
"lint": "flow && eslint js",
- "android": "react-native run-android"
+ "android:dev": "react-native run-android && react-native start",
+ "android:release": "cd android && ./gradlew assembleRelease"
},
"dependencies": {
"babel-preset-es2015": "^6.24.0",
diff --git a/static/atc-cookie-logo.png b/static/atc-cookie-logo.png
new file mode 100644
index 0000000..a23251a
Binary files /dev/null and b/static/atc-cookie-logo.png differ