Commit 44dff6e4 authored by Milan DiGiuseppe's avatar Milan DiGiuseppe

GeoMarker, cache modal, dev modal, theme

parent 5b3095a4
......@@ -36,11 +36,14 @@
"react/jsx-props-no-spreading": "off",
"react/destructuring-assignment": "off",
"react/prop-types": "off",
"react/jsx-curly-brace-presence": ["error", { "props": "always" }],
// "react-hooks/exhaustive-deps": "error",
"@typescript-eslint/indent": [2, 2],
"@typescript-eslint/explicit-function-return-type": "off",
"@typescript-eslint/no-non-null-assertion": "off",
"@typescript-eslint/no-unused-vars": "error",
"import/extensions": [0, {}],
"import/prefer-default-export": "off",
"import/no-extraneous-dependencies": [
2,
{ "devDependencies": ["**/test.tsx", "**/test.ts"] }
......
import React from 'react';
import { Provider } from 'react-redux';
import { Icon } from 'react-native-elements'
import { Icon } from 'react-native-elements';
import { createAppContainer } from 'react-navigation';
import { createBottomTabNavigator } from 'react-navigation-tabs';
import { configureStore } from "@reduxjs/toolkit";
import { configureStore } from '@reduxjs/toolkit';
import DiscoverScreen from './src/screens/DiscoverScreen';
import MessageCreateScreen from './src/screens/MessageCreateScreen';
import SavedMessagesScreen from './src/screens/SavedMessagesScreen';
......@@ -13,8 +13,8 @@ import RootReducer from './src/store/RootReducer';
const TabNavigator = createBottomTabNavigator(
{
Saved: SavedMessagesScreen,
Discover: DiscoverScreen,
Saved: SavedMessagesScreen,
Create: MessageCreateScreen,
},
{
......@@ -25,15 +25,15 @@ const TabNavigator = createBottomTabNavigator(
if (routeName === 'Saved') {
iconName = 'bookmark';
} else if (routeName === 'Discover') {
iconName = `compass`;
iconName = 'compass';
} else if (routeName === 'Create') {
iconName = `pencil`;
iconName = 'pencil';
}
return (
<Icon
name={iconName}
type='font-awesome'
type="font-awesome"
color={theme.colors.green}
/>
);
......
import React from 'react';
import { StyleSheet, Text, View } from 'react-native';
import { StyleSheet, View } from 'react-native';
import { Overlay, Button } from 'react-native-elements';
import theme from '../theme';
import SimpleHeader from './SimpleHeader';
import GeoText from './GeoText';
import { Cache } from '../store/caches/CachesState';
const { colors } = theme;
interface Props {
show: boolean;
user: string;
content: string;
onSave: () => void;
onReport: () => void;
}
const styles = StyleSheet.create({
overlay: {
borderRadius: 8,
borderColor: theme.colors.blue,
borderWidth: 1,
borderStyle: 'solid',
paddingVertical: theme.spacing.small,
paddingHorizontal: theme.spacing.medium,
width: '75%',
height: '40%',
},
buttonContainer: {
flex: 1,
},
......@@ -24,40 +25,46 @@ const styles = StyleSheet.create({
},
});
const MessageFoundModal: React.FC<Props> = ({ show, user, content, onSave, onReport }) => (
interface Props {
show: boolean;
cache: Cache;
onClose: () => void;
onSave: () => void;
onReport: () => void;
}
const MessageFoundModal: React.FC<Props> = ({
show, cache, onClose, onSave, onReport,
}) => (
<Overlay
isVisible={show}
overlayStyle={{ padding: 0 }}
overlayStyle={styles.overlay}
onBackdropPress={onClose}
>
<>
<SimpleHeader title="Found Cache!" />
{show ? (
<View style={{ flex: 1, justifyContent: 'space-between' }}>
<View style={{ padding: 10 }}>
<GeoText text={user} variant={'textBold'} />
<GeoText text={content} />
<View>
<GeoText text={cache.name} variant={'formHeader'} />
<GeoText text={cache.message} style={{ marginTop: theme.spacing.medium }} />
</View>
<View style={{ flexDirection: 'row' }}>
<Button
title="REPORT"
containerStyle={styles.buttonContainer}
buttonStyle={[
styles.button,
{ backgroundColor: colors.red }
]}
title={'Report'}
type={'outline'}
containerStyle={[styles.buttonContainer, { marginRight: theme.spacing.small }]}
buttonStyle={styles.button}
onPress={onReport}
/>
<Button
title="SAVE"
title={'Save'}
type={'solid'}
containerStyle={styles.buttonContainer}
buttonStyle={[
styles.button,
{ backgroundColor: colors.green }
]}
buttonStyle={styles.button}
onPress={onSave}
/>
</View>
</View>
</>
) : <View />}
</Overlay>
);
......
import React, { useCallback } from 'react';
import { StyleSheet, View } from 'react-native';
import { Overlay, Button } from 'react-native-elements';
import { useDispatch, useSelector } from 'react-redux';
import { geoSearch } from '../store/caches/CachesActions';
import { getUserLatLng } from '../store/location/LocationSelectors';
const styles = StyleSheet.create({
overlay: {
flex: 1,
justifyContent: 'center',
alignItems: 'center',
},
button: {
width: '100%',
},
});
interface Props {
isOpen: boolean;
onClose: () => void;
}
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]);
return (
<Overlay isVisible={isOpen} fullScreen>
<View style={styles.overlay}>
<Button
title={'geosearch'}
onPress={geosearch}
type={'solid'}
containerStyle={styles.button}
/>
<Button
title={'Close'}
onPress={onClose}
type={'outline'}
containerStyle={styles.button}
/>
</View>
</Overlay>
);
};
export default DevModal;
import React, { useCallback } from 'react';
import { Callout, Marker } from 'react-native-maps';
import { Cache } from '../store/caches/CachesState';
import GeoText from './GeoText';
import theme from '../theme';
interface Props {
cache: Cache;
onPress: (cache: Cache) => void;
}
const GeoMarker: React.FC<Props> = ({ cache, onPress }) => {
const handlePress = useCallback(() => onPress(cache), [onPress, cache]);
return (
<Marker
key={cache.id}
coordinate={{ latitude: cache.lat, longitude: cache.lng }}
>
<Callout
onPress={handlePress}
tooltip={false}
style={{ width: 200, padding: theme.spacing.small }}
>
<GeoText
text={cache.message}
variant={'text'}
style={{ flex: 1, flexWrap: 'wrap' }}
numberOfLines={3}
/>
</Callout>
</Marker>
);
};
export default GeoMarker;
import React from 'react';
import { Text, TextStyle, StyleSheet } from 'react-native';
import {
Text, TextStyle, StyleSheet, TextProps,
} from 'react-native';
import theme, { Color } from '../theme';
type Variant = 'header' | 'formHeader' | 'textBold' | 'text' | 'textWeak';
......@@ -13,7 +16,7 @@ const fontWeights = {
bold: '700',
normal: '500',
weak: '200',
};
} as const;
const styles = StyleSheet.create({
header: {
......@@ -21,7 +24,7 @@ const styles = StyleSheet.create({
},
formHeader: {
fontSize: fontSizes.h3,
// fontWeight: fontWeights.bold,
fontWeight: fontWeights.bold,
},
textBold: {
fontSize: fontSizes.text,
......@@ -34,17 +37,25 @@ const styles = StyleSheet.create({
textWeak: {
fontSize: fontSizes.text,
fontWeight: fontWeights.weak,
}
},
});
interface Props {
text: string;
style?: TextStyle;
variant?: Variant;
color?: Color;
style?: TextStyle;
}
const GeoText: React.FC<Props> = ({ text, style, variant = 'text' }) => (
<Text style={[styles[variant], style]}>{text}</Text>
const GeoText: React.FC<Props & TextProps> = ({
text, variant = 'text', color = theme.colors.black, style, ...textProps
}) => (
<Text
style={[styles[variant], style, { color }]}
{...textProps}
>
{text}
</Text>
);
export default GeoText;
......@@ -31,9 +31,9 @@ const SavedMessage: React.FC<Props> = ({
}) => (
<View style={styles.container}>
<Text>
<GeoText variant="textBold" text={`${name} `} />
<GeoText variant="text" text={`${message} `} />
<GeoText variant="textWeak" text={moment(found).fromNow()} />
<GeoText variant={'textBold'} text={`${name} `} />
<GeoText variant={'text'} text={`${message} `} />
<GeoText variant={'textWeak'} text={moment(found).fromNow()} />
</Text>
</View>
);
......
import React, { useState } from 'react';
import { Header } from 'react-native-elements';
import theme from '../theme';
import DevModal from './DevModal';
const { colors, spacing } = theme;
interface Props {
title: string;
}
const ScreenHeader: React.FC<Props> = ({ title }) => {
const [devModalOpen, setDevModalOpen] = useState(false);
return (
<>
<Header
leftContainerStyle={{ display: 'none' }}
centerComponent={{
text: title,
style: {
fontSize: 32,
color: colors.white,
width: '100%',
},
}}
rightComponent={{
text: 'dev',
onPress: () => setDevModalOpen(true),
style: {
color: colors.white,
},
}}
containerStyle={{
paddingHorizontal: spacing.screenPadding,
backgroundColor: colors.green,
}}
/>
<DevModal isOpen={devModalOpen} onClose={() => setDevModalOpen(false)} />
</>
);
};
export default ScreenHeader;
import React from 'react';
import { Header } from 'react-native-elements';
import theme from '../theme';
const { colors, spacing } = theme;
interface Props {
title: string;
}
const SimpleHeader: React.FC<Props> = ({ title }) => (
<Header
leftContainerStyle={{ display: 'none' }}
centerComponent={{
text: title,
style: {
fontSize: 32,
color: colors.white,
width: '100%',
},
}}
containerStyle={{
paddingHorizontal: spacing.screenPadding,
backgroundColor: colors.green,
}}
/>
);
export default SimpleHeader;
import React, { ReactElement, useCallback } from 'react';
import {
StyleSheet,
Text,
TouchableOpacity,
View,
ListRenderItemInfo,
......@@ -9,6 +8,7 @@ import {
} from 'react-native';
import { SwipeListView } from 'react-native-swipe-list-view';
import theme from '../theme';
import GeoText from './GeoText';
const DELETE_BTN_WIDTH = 75;
......@@ -37,7 +37,7 @@ const RowItemHidden = <T extends unknown>({ item, onDelete }: RowItemHiddenProps
style={styles.backRightBtn}
onPress={() => onDelete(item)}
>
<Text style={{ color: theme.colors.white }}>Delete</Text>
<GeoText text={'Delete'} color={'white'} variant={'text'} />
</TouchableOpacity>
</View>
);
......
import React, { useState, useCallback, useEffect } from 'react';
import { useSelector, useDispatch } from 'react-redux';
import { StyleSheet, View } from 'react-native';
import theme from '../theme';
import MessageFoundModal from '../components/MessageFoundModal';
import { Cache, ReportRequest } from '../store/caches/CachesState';
import MapView from 'react-native-maps';
import CacheFoundModal from '../components/CacheFoundModal';
import { Cache } from '../store/caches/CachesState';
import { getDiscoveredCaches } from '../store/caches/CachesSelectors';
import { getUserLatLng } from '../store/location/LocationSelectors';
import { saveCache, reportCache, geoSearch } from '../store/caches/CachesActions';
import MapView, { Marker, LatLng, EventUserLocation } from 'react-native-maps';
import SimpleHeader from '../components/SimpleHeader';
import ScreenHeader from '../components/ScreenHeader';
import GeoMarker from '../components/GeoMarker';
import { sampleCaches } from '../SampleCaches';
const styles = StyleSheet.create({
background: {
......@@ -23,59 +24,65 @@ const styles = StyleSheet.create({
const DiscoverScreen: React.FC = () => {
const dispatch = useDispatch();
const discoveredCaches: Cache[] = useSelector(getDiscoveredCaches);
const [isCacheModalOpen, setCacheModalOpen] = useState(false);
const [openCache, setOpenCache] = useState<Cache | null>(sampleCaches[0]);
// const currMsg = (discoveredCaches.length > 0) ? discoveredCaches[0] : null;
// const [showOverlay, setShowOverlay] = useState(true);
const onClose = useCallback(() => {
setCacheModalOpen(false);
setOpenCache(null);
}, [setCacheModalOpen, setOpenCache]);
const onSave = useCallback((cache: Cache) => {
dispatch(saveCache({ ...cache, found: new Date().toISOString() }));
onClose();
}, [dispatch, onClose]);
const onReport = useCallback((id) => {
dispatch(reportCache(id, '1', 'reported'));
onClose();
}, [dispatch, onClose]);
// const onSave = useCallback(() => {
// setShowOverlay(false);
// dispatch(saveCache({ ...currMsg!, found: new Date().toISOString() }));
// setTimeout(() => setShowOverlay(true), 500)
// }, [setShowOverlay, currMsg]);
// const onReport = useCallback(() => {
// setShowOverlay(false);
// dispatch(reportCache(currMsg.id, 1, 'reported'));
// setTimeout(() => setShowOverlay(true), 500);
// }, []);
const [userLocation, setUserLocation] = useState<LatLng>(useSelector(getUserLatLng));
const onUserLocationChange = useCallback((e: EventUserLocation) => {
const { latitude, longitude } = e.nativeEvent.coordinate;
setUserLocation({ latitude, longitude });
}, [setUserLocation]);
const onCalloutPress = useCallback((cache: Cache) => {
setOpenCache(cache);
setCacheModalOpen(true);
}, []);
const userLocation = useSelector(getUserLatLng);
// const onUserLocationChange = useCallback((e: EventUserLocation) => {
// const { latitude, longitude } = e.nativeEvent.coordinate;
// setUserLocation({ latitude, longitude });
// }, [setUserLocation]);
useEffect(() => {
dispatch(geoSearch(userLocation.latitude, userLocation.longitude, '1'));
}, []);
dispatch(geoSearch(userLocation, '1'));
}, []); // TODO: update deps
return (
<>
<SimpleHeader title={'Discover'} />
<ScreenHeader title={'Discover'} />
<View style={{ flex: 1 }}>
<CacheFoundModal
show={isCacheModalOpen}
cache={openCache!}
onClose={onClose}
onSave={() => onSave(openCache!)}
onReport={() => onReport(openCache!.id)}
/>
<MapView
provider={'google'}
style={styles.map}
showsUserLocation={true}
showsUserLocation
region={{
latitude: userLocation.latitude,
longitude: userLocation.longitude,
latitudeDelta: 0.0100,
longitudeDelta: 0.0025,
}}
onUserLocationChange={onUserLocationChange}
// onUserLocationChange={onUserLocationChange}
showsPointsOfInterest={false}
zoomEnabled={false}
mapType={'mutedStandard'}
// followsUserLocation={true}
>
{discoveredCaches.map(cache => (
<Marker
key={cache.id}
coordinate={{ latitude: cache.lat, longitude: cache.lng }}
title={cache.name}
description={cache.message}
/>
{discoveredCaches.map((cache) => (
<GeoMarker key={cache.id} cache={cache} onPress={onCalloutPress} />
))}
</MapView>
</View>
......
......@@ -8,9 +8,9 @@ import {
Text,
View,
} from 'react-native';
import { Button, Header, Input } from 'react-native-elements'
import { Button, Input } from 'react-native-elements';
import theme from '../theme';
import SimpleHeader from '../components/SimpleHeader';
import ScreenHeader from '../components/ScreenHeader';
import Spacer from '../components/Spacer';
import { getCurrentPosition } from '../locationUtils';
import GeoText from '../components/GeoText';
......@@ -25,12 +25,9 @@ const styles = StyleSheet.create({
formEntry: {
paddingBottom: spacing.medium * 2,
},
formHeader: {
color: colors.green,
},
submitButton: {
backgroundColor: colors.green,
paddingVertical: spacing.medium
paddingVertical: spacing.medium,
},
});
......@@ -65,26 +62,26 @@ const MessageCreateScreen: React.FC = () => {
return (
<>
<SimpleHeader title="Create Cache" />
<ScreenHeader title={'Create Cache'} />
<Spacer />
<View style={{ flex: 1, justifyContent: 'space-between' }}>
<View style={styles.form}>
<View style={styles.formEntry}>
<GeoText variant={'formHeader'} text={'Message'} style={styles.formHeader} />
<GeoText variant={'formHeader'} text={'Message'} color={'green'} />
<Input
value={text}
containerStyle={{ paddingHorizontal: 0 }}
onChangeText={setText}
placeholder={"leave a message here!"}
placeholder={'leave a message here!'}
/>
</View>
<View style={styles.formEntry}>
<GeoText variant={'formHeader'} text={'Duration'} style={styles.formHeader} />
<GeoText variant={'formHeader'} text={'Duration'} color={'green'} />
<Picker
selectedValue={duration}
onValueChange={value => setDuration(value)}
onValueChange={(value) => setDuration(value)}
>
{durationOptions.map(hours => (
{durationOptions.map((hours) => (
<Picker.Item
label={`${hours} ${hours === 1 ? 'hour' : 'hours'}`}
value={hours}
......@@ -96,13 +93,13 @@ const MessageCreateScreen: React.FC = () => {
<View
style={[
styles.formEntry,
{ flexDirection: 'row', justifyContent: 'space-between', paddingBottom: spacing.small }
{ flexDirection: 'row', justifyContent: 'space-between', paddingBottom: spacing.small },
]}
>
<GeoText variant={'formHeader'} text={'Anonymous?'} style={styles.formHeader} />
<GeoText variant={'formHeader'} text={'Anonymous?'} color={'green'} />
<Switch
value={anonymous}
onValueChange={value => setAnonymous(value)}
onValueChange={(value) => setAnonymous(value)}
/>
</View>
{!anonymous && (
......@@ -113,13 +110,13 @@ const MessageCreateScreen: React.FC = () => {
)}
</View>
<Button
title="CREATE"
title={'CREATE'}
onPress={onCreate}
buttonStyle={styles.submitButton}
/>
</View>
</>
)
);
};
export default MessageCreateScreen;
......@@ -3,7 +3,7 @@ import { useSelector, useDispatch } from 'react-redux';
import { StyleSheet, View } from 'react-native';
import theme from '../theme';
import SavedMessage from '../components/SavedMessage';
import SimpleHeader from '../components/SimpleHeader';
import ScreenHeader from '../components/ScreenHeader';
import { Cache } from '../store/caches/CachesState';
import GeoText from '../components/GeoText';
import {
......@@ -34,7 +34,12 @@ interface SectionHeaderProps {
const SectionHeader: React.FC<SectionHeaderProps> = ({ title }) => (
<View style={styles.header}>
<GeoText variant="formHeader" text={title} style={{ color: colors.green, textTransform: 'uppercase', fontSize: 18 }} />
<GeoText
variant={'formHeader'}
text={title}
style={{ textTransform: 'uppercase' }}
color={'green'}
/>
</View>
);
......@@ -76,7 +81,7 @@ const SavedMessagesScreen: React.FC = () => {
return (
<>
<SimpleHeader title="Saved Caches" />
<ScreenHeader title={'Saved Caches'} />
<SwipeableList
keyExtractor={(cache) => cache.id}
sections={listSections}
......
import { Dispatch } from '@reduxjs/toolkit';
import { LatLng } from 'react-native-maps';
import cachesSlice from './CachesSlice';
import { Cache } from './CachesState';
import { Dispatch } from '@reduxjs/toolkit';
export const {
addSaved,