Commit 781ff39f authored by Milan John Paul Digiuseppe's avatar Milan John Paul Digiuseppe

Merge branch 'discover-exploration' into 'master'

Discover exploration

See merge request !5
parents 139503ff b438eb50
[
{
"elementType": "geometry",
"stylers": [
{
"color": "#ebe3cd"
}
]
},
{
"elementType": "geometry.stroke",
"stylers": [
{
"saturation": 35
},
{
"weight": 2.5
}
]
},
{
"elementType": "labels.text.fill",
"stylers": [
{
"color": "#523735"
}
]
},
{
"elementType": "labels.text.stroke",
"stylers": [
{
"color": "#f5f1e6"
}
]
},
{
"featureType": "administrative",
"elementType": "geometry",
"stylers": [
{
"visibility": "off"
}
]
},
{
"featureType": "administrative",
"elementType": "geometry.stroke",
"stylers": [
{
"color": "#c9b2a6"
}
]
},
{
"featureType": "administrative.land_parcel",
"stylers": [
{
"visibility": "off"
}
]
},
{
"featureType": "administrative.land_parcel",
"elementType": "geometry.stroke",
"stylers": [
{
"color": "#dcd2be"
}
]
},
{
"featureType": "administrative.land_parcel",
"elementType": "labels.text.fill",
"stylers": [
{
"color": "#ae9e90"
}
]
},
{
"featureType": "administrative.neighborhood",
"stylers": [
{
"visibility": "off"
}
]
},
{
"featureType": "landscape.natural",
"elementType": "geometry",
"stylers": [
{
"color": "#dfd2ae"
}
]
},
{
"featureType": "poi",
"stylers": [
{
"visibility": "off"
}
]
},
{
"featureType": "poi",
"elementType": "geometry",
"stylers": [
{
"color": "#dfd2ae"
}
]
},
{
"featureType": "poi",
"elementType": "labels.text",
"stylers": [
{
"visibility": "off"
}
]
},
{
"featureType": "poi",
"elementType": "labels.text.fill",
"stylers": [
{
"color": "#93817c"
}
]
},
{
"featureType": "poi.park",
"elementType": "geometry.fill",
"stylers": [
{
"color": "#a5b076"
}
]
},
{
"featureType": "poi.park",
"elementType": "labels.text.fill",
"stylers": [
{
"color": "#447530"
}
]
},
{
"featureType": "road",
"elementType": "geometry",
"stylers": [
{
"color": "#f5f1e6"
}
]
},
{
"featureType": "road",
"elementType": "labels",
"stylers": [
{
"visibility": "off"
}
]
},
{
"featureType": "road",
"elementType": "labels.icon",
"stylers": [
{
"visibility": "off"
}
]
},
{
"featureType": "road.arterial",
"elementType": "geometry",
"stylers": [
{
"color": "#fdfcf8"
}
]
},
{
"featureType": "road.highway",
"elementType": "geometry",
"stylers": [
{
"color": "#f8c967"
}
]
},
{
"featureType": "road.highway",
"elementType": "geometry.stroke",
"stylers": [
{
"color": "#e9bc62"
}
]
},
{
"featureType": "road.highway.controlled_access",
"elementType": "geometry",
"stylers": [
{
"color": "#e98d58"
}
]
},
{
"featureType": "road.highway.controlled_access",
"elementType": "geometry.stroke",
"stylers": [
{
"color": "#db8555"
}
]
},
{
"featureType": "road.local",
"elementType": "labels.text.fill",
"stylers": [
{
"color": "#806b63"
}
]
},
{
"featureType": "transit",
"stylers": [
{
"visibility": "off"
}
]
},
{
"featureType": "transit.line",
"elementType": "geometry",
"stylers": [
{
"color": "#dfd2ae"
}
]
},
{
"featureType": "transit.line",
"elementType": "labels.text.fill",
"stylers": [
{
"color": "#8f7d77"
}
]
},
{
"featureType": "transit.line",
"elementType": "labels.text.stroke",
"stylers": [
{
"color": "#ebe3cd"
}
]
},
{
"featureType": "transit.station",
"elementType": "geometry",
"stylers": [
{
"color": "#dfd2ae"
}
]
},
{
"featureType": "water",
"elementType": "geometry.fill",
"stylers": [
{
"color": "#b9d3c2"
}
]
},
{
"featureType": "water",
"elementType": "labels.text",
"stylers": [
{
"visibility": "off"
}
]
},
{
"featureType": "water",
"elementType": "labels.text.fill",
"stylers": [
{
"color": "#92998d"
}
]
}
]
import React from 'react';
import { View, StyleSheet } from 'react-native';
import { Icon } from 'react-native-elements';
import theme, { Color } from '../theme';
export const MARKER_ICONS = [
'cat',
'crow',
'dog',
'dove',
'dragon',
'fish',
'frog',
'ghost',
'hippo',
'horse',
'kiwi-bird',
'otter',
'paw',
'spider',
] as const;
export type MarkerIcon = typeof MARKER_ICONS[number];
const ICON_SIZE = 24;
const BUBBLE_SIZE = 60;
const BORDER_COLOR = theme.colors.black;
const BORDER_WIDTH = 2;
const styles = StyleSheet.create({
bubble: {
height: BUBBLE_SIZE,
width: BUBBLE_SIZE,
justifyContent: 'center',
alignItems: 'center',
borderRadius: BUBBLE_SIZE,
borderColor: BORDER_COLOR,
borderWidth: BORDER_WIDTH,
},
pin: {
alignSelf: 'center',
width: BORDER_WIDTH,
height: 20,
backgroundColor: BORDER_COLOR,
},
});
// TODO: make props required, save icon/color in cache or randomize
interface Props {
icon?: MarkerIcon;
backgroundColor?: Color;
}
const CustomMarker: React.FC<Props> = ({
icon = 'cat',
backgroundColor = theme.colors.pink,
}) => (
<View>
<View style={[styles.bubble, { backgroundColor }]}>
<Icon
color={theme.colors.white}
name={icon}
type={'font-awesome-5'}
size={ICON_SIZE}
/>
</View>
<View style={styles.pin} />
</View>
);
export default CustomMarker;
......@@ -2,6 +2,7 @@ import React, { useCallback } from 'react';
import { StyleSheet, View } from 'react-native';
import { Overlay } from 'react-native-elements';
import { useDispatch, useSelector } from 'react-redux';
import { reverseGeocodeAsync } from 'expo-location';
import { geoSearch } from '../store/caches/CachesActions';
import { getUserLatLng } from '../store/location/LocationSelectors';
import { signOutAsync } from '../store/auth/AuthActions';
......@@ -23,10 +24,24 @@ interface Props {
const DevModal: React.FC<Props> = ({ isOpen, onClose }) => {
const dispatch = useDispatch();
const userLocation = useSelector(getUserLatLng);
const geosearch = useCallback(() => {
dispatch(geoSearch(userLocation, '1'));
onClose();
}, [onClose, dispatch, userLocation]);
const geocode = useCallback(async () => {
const results = await reverseGeocodeAsync(userLocation);
const {
country,
region,
city,
street,
name,
} = results[0];
alert(`${name} ${street} ${city} ${region} ${country}`); // eslint-disable-line no-alert
}, []);
const signout = useCallback(() => {
dispatch(signOutAsync());
onClose();
......@@ -39,6 +54,10 @@ const DevModal: React.FC<Props> = ({ isOpen, onClose }) => {
title={'geosearch'}
onPress={geosearch}
/>
<GeoButton
title={'geo code'}
onPress={geocode}
/>
<GeoButton
title={'sign out'}
onPress={signout}
......
import React, { useEffect } from 'react';
import MapView from 'react-native-maps';
import { StyleSheet, Keyboard } from 'react-native';
import { useSelector, useDispatch } from 'react-redux';
import { getUserLatLng } from '../store/location/LocationSelectors';
import { geoSearch } from '../store/caches/CachesActions';
import customMapStyle from '../CustomMapStyle.json';
const styles = StyleSheet.create({
map: {
...StyleSheet.absoluteFillObject,
},
});
const DiscoverMap: React.FC = ({ children }) => {
const dispatch = useDispatch();
const userLocation = useSelector(getUserLatLng);
// const onUserLocationChange = useCallback((e: EventUserLocation) => {
// const { latitude, longitude } = e.nativeEvent.coordinate;
// setUserLocation({ latitude, longitude });
// }, [setUserLocation]);
useEffect(() => {
dispatch(geoSearch(userLocation, '1'));
}, []); // TODO: update deps
return (
<MapView
provider={'google'}
style={styles.map}
showsUserLocation
camera={{
center: {
latitude: userLocation.latitude,
longitude: userLocation.longitude,
},
pitch: 80,
heading: 0,
altitude: 0, // not used for Google Maps
zoom: 18,
}}
showsPointsOfInterest={false}
customMapStyle={customMapStyle}
loadingEnabled // TODO: test
pitchEnabled={false}
zoomEnabled={false}
scrollEnabled={false}
onPress={() => Keyboard.dismiss()}
>
{children}
</MapView>
);
};
export default DiscoverMap;
import React, { useCallback } from 'react';
import { View, ViewStyle } from 'react-native';
import { useNavigation } from '@react-navigation/native';
import MenuButton from './MenuButton';
import theme from '../theme';
interface Props {
style?: ViewStyle;
}
const DiscoverMenu: React.FC<Props> = ({ style }) => {
const { navigate } = useNavigation();
const onSavedPress = useCallback(() => navigate('Saved'), [navigate]);
const onCreatePress = useCallback(() => navigate('Create'), [navigate]);
return (
<View style={style}>
<MenuButton
onPress={onSavedPress}
icon={'book'}
backgroundColor={'blue'}
style={{ marginBottom: theme.spacing.medium }}
/>
<MenuButton
onPress={onCreatePress}
icon={'feather-alt'}
/>
</View>
);
};
export default DiscoverMenu;
import React from 'react';
import { Callout, Marker } from 'react-native-maps';
import GeoText from './GeoText';
import theme from '../theme';
import theme, { Color } from '../theme';
import CustomMarker, { MarkerIcon } from './CustomMarker';
interface Props {
lat: number;
lng: number;
message?: string;
icon?: MarkerIcon;
color?: Color;
onPress?: () => void;
}
const GeoMarker: React.FC<Props> = ({
lat, lng, message, onPress,
lat,
lng,
message,
icon,
color,
onPress,
}) => (
<Marker
coordinate={{ latitude: lat, longitude: lng }}
>
<Marker coordinate={{ latitude: lat, longitude: lng }}>
<CustomMarker icon={icon} backgroundColor={color} />
{ message && onPress && (
<Callout
onPress={onPress}
......
import React, { useState, useEffect } from 'react';
import { Icon } from 'react-native-elements';
import {
Text, View, StyleSheet, ViewStyle, TextInput,
} from 'react-native';
import theme from '../theme';
// Google Place Autocomplete types: https://developers.google.com/places/web-service/autocomplete
interface Prediction {
description: string;
place_id: string;
}
interface AutocompleteResponse {
status: string;
predictions: Prediction[];
}
const HEIGHT = 40;
const styles = StyleSheet.create({
container: {
width: '70%',
},
shadow: {
shadowColor: '#000',
shadowOffset: {
width: 0,
height: 2,
},
shadowOpacity: 0.25,
shadowRadius: 3.84,
elevation: 5,
},
searchBar: {
backgroundColor: theme.colors.white,
padding: theme.spacing.small,
borderRadius: HEIGHT / 2,
height: HEIGHT,
flexDirection: 'row',
alignItems: 'center',
},
dropdown: {
backgroundColor: theme.colors.white,
marginTop: theme.spacing.small,
borderRadius: 6,
padding: theme.spacing.small,
fontSize: 20,
},
searchResult: {
padding: theme.spacing.small,
},
});
interface Props {
style?: ViewStyle;
}
const GOOGLE_API_KEY = 'AIzaSyB7ueZ5fnM1SwpWwoUi88H5k3_tkLIoz5I';
const GeoSearchBar: React.FC<Props> = ({ style }) => {
const [text, setText] = useState<string>('');
const [predictions, setPredictions] = useState<Prediction[]>([]);
useEffect(() => {
const fetchPredictions = async () => {
const response = await fetch('https://maps.googleapis.com/maps/api/place/autocomplete/'
+ 'json?'
+ `input=${encodeURIComponent(text)}`
+ `&key=${encodeURIComponent(GOOGLE_API_KEY)}`,
{
method: 'GET',
headers: {
Accept: 'application/json',
'Content-Type': 'application/json',
},
});
const result: AutocompleteResponse = await response.json();
if (result.status === 'OK') {
console.log(result);
setPredictions(result.predictions);
} else {
console.warn(`fetch predictions failed: ${result}`);
}
};
if (text.length >= 1) {
fetchPredictions();
} else {
setPredictions([]);
}
}, [text]);
return (
<View style={[style, { display: 'flex', alignItems: 'center' }]}>
<View style={styles.container}>
<View style={[styles.searchBar, styles.shadow]}>
<Icon
color={theme.colors.mediumgray}
name={'search'}
type={'font-awesome-5'}
size={20}
style={{ marginLeft: 4 }}
/>
<TextInput
value={text}
onChangeText={setText}
placeholder={'Search ...'}
style={{ marginLeft: theme.spacing.small, fontSize: 20, flex: 1 }}
/>
</View>
{predictions.length > 0 && (
<View style={[styles.dropdown, styles.shadow]}>
{predictions.map((prediction) => (
<View style={styles.searchResult} key={prediction.place_id}>
<Text>{prediction.description}</Text>
</View>
))}
</View>
)}
</View>
</View>
);
};
export default GeoSearchBar;
import React from 'react';
import { View, StyleSheet, ViewStyle } from 'react-native';
import { View, ViewStyle } from 'react-native';
import { Icon } from 'react-native-elements';
import { TouchableOpacity } from 'react-native-gesture-handler';
import theme from '../theme';
import theme, { Color, COLOR_MAP } from '../theme';
const SIZE = 34;
const styles = StyleSheet.create({
background: {
backgroundColor: theme.colors.green,
borderRadius: SIZE,
},
icon: {