forked from cemaden-educacao/WPD-MobileApp
Daniel D'Angelo Resende Barros
4 years ago
48 changed files with 9828 additions and 104 deletions
-
1src/.gitignore
-
14src/App.js
-
BINsrc/app/assets/chuva_peq.png
-
BINsrc/app/assets/ddangelorb.png
-
BINsrc/app/assets/defesa_civil.png
-
BINsrc/app/assets/pontos_alagamento_peq.png
-
BINsrc/app/assets/previsao_tempo.png
-
35src/app/components/Button.js
-
35src/app/components/CategoryPickerItem.js
-
27src/app/components/Icon.js
-
78src/app/components/ImageInput.js
-
40src/app/components/ImageInputList.js
-
98src/app/components/Picker.js
-
20src/app/components/PickerItem.js
-
23src/app/components/Screen.js
-
14src/app/components/Text.js
-
40src/app/components/TextInput.js
-
16src/app/components/forms/ErrorMessage.js
-
16src/app/components/forms/Form.js
-
23src/app/components/forms/FormField.js
-
34src/app/components/forms/FormImagePicker.js
-
33src/app/components/forms/FormPicker.js
-
12src/app/components/forms/SubmitButton.js
-
5src/app/components/forms/index.js
-
69src/app/components/lists/ListItem.js
-
30src/app/components/lists/ListItemDeleteAction.js
-
18src/app/components/lists/ListItemSeparator.js
-
3src/app/components/lists/index.js
-
2src/app/config/colors.js
-
12src/app/config/styles.js
-
25src/app/hooks/useLocation.js
-
14src/app/navigation/AccountNavigator.js
-
77src/app/navigation/AppNavigator.js
-
13src/app/navigation/FeedNavigator.js
-
13src/app/navigation/MessagesNavigator.js
-
35src/app/navigation/NewListingButton.js
-
13src/app/navigation/OfficialMessagesNavigator.js
-
11src/app/navigation/navigationTheme.js
-
9src/app/navigation/routes.js
-
36src/app/screens/AccountScreen.js
-
63src/app/screens/MapFeedScreen.js
-
70src/app/screens/MessagesScreen.js
-
29src/app/screens/OfficialMessagesScreen.js
-
109src/app/screens/SharingDataScreen.js
-
53src/app/screens/ViewImageScreen.js
-
7956src/package-lock.json
-
20src/package.json
-
688src/yarn.lock
@ -0,0 +1 @@ |
|||
node_modules |
@ -1,7 +1,13 @@ |
|||
import React from 'react'; |
|||
import React from "react"; |
|||
import { NavigationContainer } from "@react-navigation/native"; |
|||
|
|||
import ViewImageScreen from "./app/screens/ViewImageScreen"; |
|||
import navigationTheme from "./app/navigation/navigationTheme"; |
|||
import AppNavigator from "./app/navigation/AppNavigator"; |
|||
|
|||
export default function App() { |
|||
return <ViewImageScreen />; |
|||
} |
|||
return ( |
|||
<NavigationContainer theme={navigationTheme}> |
|||
<AppNavigator /> |
|||
</NavigationContainer> |
|||
); |
|||
} |
After Width: 50 | Height: 51 | Size: 8.4 KiB |
After Width: 700 | Height: 727 | Size: 793 KiB |
After Width: 225 | Height: 225 | Size: 5.9 KiB |
After Width: 50 | Height: 49 | Size: 8.4 KiB |
After Width: 714 | Height: 924 | Size: 56 KiB |
@ -0,0 +1,35 @@ |
|||
import React from "react"; |
|||
import { StyleSheet, Text, TouchableOpacity } from "react-native"; |
|||
|
|||
import colors from "../config/colors"; |
|||
|
|||
function AppButton({ title, onPress, color = "primary" }) { |
|||
return ( |
|||
<TouchableOpacity |
|||
style={[styles.button, { backgroundColor: colors[color] }]} |
|||
onPress={onPress} |
|||
> |
|||
<Text style={styles.text}>{title}</Text> |
|||
</TouchableOpacity> |
|||
); |
|||
} |
|||
|
|||
const styles = StyleSheet.create({ |
|||
button: { |
|||
backgroundColor: colors.primary, |
|||
borderRadius: 25, |
|||
justifyContent: "center", |
|||
alignItems: "center", |
|||
padding: 15, |
|||
width: "100%", |
|||
marginVertical: 10, |
|||
}, |
|||
text: { |
|||
color: colors.white, |
|||
fontSize: 18, |
|||
textTransform: "uppercase", |
|||
fontWeight: "bold", |
|||
}, |
|||
}); |
|||
|
|||
export default AppButton; |
@ -0,0 +1,35 @@ |
|||
import React from "react"; |
|||
import { View, StyleSheet, TouchableOpacity } from "react-native"; |
|||
|
|||
import Icon from "./Icon"; |
|||
import Text from "./Text"; |
|||
|
|||
function CategoryPickerItem({ item, onPress }) { |
|||
return ( |
|||
<View style={styles.container}> |
|||
<TouchableOpacity onPress={onPress}> |
|||
<Icon |
|||
backgroundColor={item.backgroundColor} |
|||
name={item.icon} |
|||
size={120} |
|||
/> |
|||
</TouchableOpacity> |
|||
<Text style={styles.label}>{item.label}</Text> |
|||
</View> |
|||
); |
|||
} |
|||
|
|||
const styles = StyleSheet.create({ |
|||
container: { |
|||
paddingHorizontal: 30, |
|||
paddingVertical: 15, |
|||
alignItems: "center", |
|||
width: "50%", |
|||
}, |
|||
label: { |
|||
marginTop: 5, |
|||
textAlign: "center", |
|||
}, |
|||
}); |
|||
|
|||
export default CategoryPickerItem; |
@ -0,0 +1,27 @@ |
|||
import React from "react"; |
|||
import { View } from "react-native"; |
|||
import { MaterialCommunityIcons } from "@expo/vector-icons"; |
|||
|
|||
function Icon({ |
|||
name, |
|||
size = 40, |
|||
backgroundColor = "#000", |
|||
iconColor = "#fff", |
|||
}) { |
|||
return ( |
|||
<View |
|||
style={{ |
|||
width: size, |
|||
height: size, |
|||
borderRadius: size / 2, |
|||
backgroundColor, |
|||
justifyContent: "center", |
|||
alignItems: "center", |
|||
}} |
|||
> |
|||
<MaterialCommunityIcons name={name} color={iconColor} size={size * 0.5} /> |
|||
</View> |
|||
); |
|||
} |
|||
|
|||
export default Icon; |
@ -0,0 +1,78 @@ |
|||
import React, { useEffect } from "react"; |
|||
import { |
|||
View, |
|||
StyleSheet, |
|||
Image, |
|||
TouchableWithoutFeedback, |
|||
Alert, |
|||
} from "react-native"; |
|||
import { MaterialCommunityIcons } from "@expo/vector-icons"; |
|||
import * as ImagePicker from "expo-image-picker"; |
|||
|
|||
import colors from "../config/colors"; |
|||
|
|||
function ImageInput({ imageUri, onChangeImage }) { |
|||
useEffect(() => { |
|||
requestPermission(); |
|||
}, []); |
|||
|
|||
const requestPermission = async () => { |
|||
const { granted } = await ImagePicker.requestCameraRollPermissionsAsync(); |
|||
if (!granted) alert("You need to enable permission to access the library."); |
|||
}; |
|||
|
|||
const handlePress = () => { |
|||
if (!imageUri) selectImage(); |
|||
else |
|||
Alert.alert("Delete", "Are you sure you want to delete this image?", [ |
|||
{ text: "Yes", onPress: () => onChangeImage(null) }, |
|||
{ text: "No" }, |
|||
]); |
|||
}; |
|||
|
|||
const selectImage = async () => { |
|||
try { |
|||
const result = await ImagePicker.launchImageLibraryAsync({ |
|||
mediaTypes: ImagePicker.MediaTypeOptions.Images, |
|||
quality: 0.5, |
|||
}); |
|||
if (!result.cancelled) onChangeImage(result.uri); |
|||
} catch (error) { |
|||
console.log("Error reading an image", error); |
|||
} |
|||
}; |
|||
|
|||
return ( |
|||
<TouchableWithoutFeedback onPress={handlePress}> |
|||
<View style={styles.container}> |
|||
{!imageUri && ( |
|||
<MaterialCommunityIcons |
|||
color={colors.medium} |
|||
name="camera" |
|||
size={40} |
|||
/> |
|||
)} |
|||
{imageUri && <Image source={{ uri: imageUri }} style={styles.image} />} |
|||
</View> |
|||
</TouchableWithoutFeedback> |
|||
); |
|||
} |
|||
|
|||
const styles = StyleSheet.create({ |
|||
container: { |
|||
alignItems: "center", |
|||
backgroundColor: colors.light, |
|||
borderRadius: 15, |
|||
height: 100, |
|||
justifyContent: "center", |
|||
marginVertical: 10, |
|||
overflow: "hidden", |
|||
width: 100, |
|||
}, |
|||
image: { |
|||
height: "100%", |
|||
width: "100%", |
|||
}, |
|||
}); |
|||
|
|||
export default ImageInput; |
@ -0,0 +1,40 @@ |
|||
import React, { useRef } from "react"; |
|||
import { View, StyleSheet, ScrollView } from "react-native"; |
|||
import ImageInput from "./ImageInput"; |
|||
|
|||
function ImageInputList({ imageUris = [], onRemoveImage, onAddImage }) { |
|||
const scrollView = useRef(); |
|||
|
|||
return ( |
|||
<View> |
|||
<ScrollView |
|||
ref={scrollView} |
|||
horizontal |
|||
onContentSizeChange={() => scrollView.current.scrollToEnd()} |
|||
> |
|||
<View style={styles.container}> |
|||
{imageUris.map((uri) => ( |
|||
<View key={uri} style={styles.image}> |
|||
<ImageInput |
|||
imageUri={uri} |
|||
onChangeImage={() => onRemoveImage(uri)} |
|||
/> |
|||
</View> |
|||
))} |
|||
<ImageInput onChangeImage={(uri) => onAddImage(uri)} /> |
|||
</View> |
|||
</ScrollView> |
|||
</View> |
|||
); |
|||
} |
|||
|
|||
const styles = StyleSheet.create({ |
|||
container: { |
|||
flexDirection: "row", |
|||
}, |
|||
image: { |
|||
marginRight: 10, |
|||
}, |
|||
}); |
|||
|
|||
export default ImageInputList; |
@ -0,0 +1,98 @@ |
|||
import React, { useState } from "react"; |
|||
import { |
|||
View, |
|||
StyleSheet, |
|||
TouchableWithoutFeedback, |
|||
Modal, |
|||
Button, |
|||
FlatList, |
|||
} from "react-native"; |
|||
import { MaterialCommunityIcons } from "@expo/vector-icons"; |
|||
|
|||
import Text from "./Text"; |
|||
import defaultStyles from "../config/styles"; |
|||
import PickerItem from "./PickerItem"; |
|||
import Screen from "./Screen"; |
|||
|
|||
function AppPicker({ |
|||
icon, |
|||
items, |
|||
numberOfColumns = 1, |
|||
onSelectItem, |
|||
PickerItemComponent = PickerItem, |
|||
placeholder, |
|||
selectedItem, |
|||
width = "100%", |
|||
}) { |
|||
const [modalVisible, setModalVisible] = useState(false); |
|||
|
|||
return ( |
|||
<> |
|||
<TouchableWithoutFeedback onPress={() => setModalVisible(true)}> |
|||
<View style={[styles.container, { width }]}> |
|||
{icon && ( |
|||
<MaterialCommunityIcons |
|||
name={icon} |
|||
size={20} |
|||
color={defaultStyles.colors.medium} |
|||
style={styles.icon} |
|||
/> |
|||
)} |
|||
{selectedItem ? ( |
|||
<Text style={styles.text}>{selectedItem.label}</Text> |
|||
) : ( |
|||
<Text style={styles.placeholder}>{placeholder}</Text> |
|||
)} |
|||
|
|||
<MaterialCommunityIcons |
|||
name="chevron-down" |
|||
size={20} |
|||
color={defaultStyles.colors.medium} |
|||
/> |
|||
</View> |
|||
</TouchableWithoutFeedback> |
|||
<Modal visible={modalVisible} animationType="slide"> |
|||
<Screen> |
|||
<Button title="Close" onPress={() => setModalVisible(false)} /> |
|||
<FlatList |
|||
data={items} |
|||
keyExtractor={(item) => item.value.toString()} |
|||
numColumns={numberOfColumns} |
|||
renderItem={({ item }) => ( |
|||
<PickerItemComponent |
|||
item={item} |
|||
label={item.label} |
|||
onPress={() => { |
|||
setModalVisible(false); |
|||
onSelectItem(item); |
|||
}} |
|||
/> |
|||
)} |
|||
/> |
|||
</Screen> |
|||
</Modal> |
|||
</> |
|||
); |
|||
} |
|||
|
|||
const styles = StyleSheet.create({ |
|||
container: { |
|||
backgroundColor: defaultStyles.colors.light, |
|||
borderRadius: 25, |
|||
flexDirection: "row", |
|||
padding: 15, |
|||
marginVertical: 10, |
|||
}, |
|||
icon: { |
|||
marginRight: 10, |
|||
}, |
|||
placeholder: { |
|||
color: defaultStyles.colors.medium, |
|||
flex: 1, |
|||
}, |
|||
text: { |
|||
flex: 1, |
|||
}, |
|||
}); |
|||
|
|||
export default AppPicker; |
@ -0,0 +1,20 @@ |
|||
import React from "react"; |
|||
import { TouchableOpacity, StyleSheet } from "react-native"; |
|||
|
|||
import Text from "./Text"; |
|||
|
|||
function PickerItem({ item, onPress }) { |
|||
return ( |
|||
<TouchableOpacity onPress={onPress}> |
|||
<Text style={styles.text}>{item.label}</Text> |
|||
</TouchableOpacity> |
|||
); |
|||
} |
|||
|
|||
const styles = StyleSheet.create({ |
|||
text: { |
|||
padding: 20, |
|||
}, |
|||
}); |
|||
|
|||
export default PickerItem; |
@ -0,0 +1,23 @@ |
|||
import React from "react"; |
|||
import Constants from "expo-constants"; |
|||
import { StyleSheet, SafeAreaView, View } from "react-native"; |
|||
|
|||
function Screen({ children, style }) { |
|||
return ( |
|||
<SafeAreaView style={[styles.screen, style]}> |
|||
<View style={[styles.view, style]}>{children}</View> |
|||
</SafeAreaView> |
|||
); |
|||
} |
|||
|
|||
const styles = StyleSheet.create({ |
|||
screen: { |
|||
paddingTop: Constants.statusBarHeight, |
|||
flex: 1, |
|||
}, |
|||
view: { |
|||
flex: 1, |
|||
}, |
|||
}); |
|||
|
|||
export default Screen; |
@ -0,0 +1,14 @@ |
|||
import React from "react"; |
|||
import { Text } from "react-native"; |
|||
|
|||
import defaultStyles from "../config/styles"; |
|||
|
|||
function AppText({ children, style, ...otherProps }) { |
|||
return ( |
|||
<Text style={[defaultStyles.text, style]} {...otherProps}> |
|||
{children} |
|||
</Text> |
|||
); |
|||
} |
|||
|
|||
export default AppText; |
@ -0,0 +1,40 @@ |
|||
import React from "react"; |
|||
import { View, TextInput, StyleSheet } from "react-native"; |
|||
import { MaterialCommunityIcons } from "@expo/vector-icons"; |
|||
|
|||
import defaultStyles from "../config/styles"; |
|||
|
|||
function AppTextInput({ icon, width = "100%", ...otherProps }) { |
|||
return ( |
|||
<View style={[styles.container, { width }]}> |
|||
{icon && ( |
|||
<MaterialCommunityIcons |
|||
name={icon} |
|||
size={20} |
|||
color={defaultStyles.colors.medium} |
|||
style={styles.icon} |
|||
/> |
|||
)} |
|||
<TextInput |
|||
placeholderTextColor={defaultStyles.colors.medium} |
|||
style={defaultStyles.text} |
|||
{...otherProps} |
|||
/> |
|||
</View> |
|||
); |
|||
} |
|||
|
|||
const styles = StyleSheet.create({ |
|||
container: { |
|||
backgroundColor: defaultStyles.colors.light, |
|||
borderRadius: 25, |
|||
flexDirection: "row", |
|||
padding: 15, |
|||
marginVertical: 10, |
|||
}, |
|||
icon: { |
|||
marginRight: 10, |
|||
}, |
|||
}); |
|||
|
|||
export default AppTextInput; |
@ -0,0 +1,16 @@ |
|||
import React from "react"; |
|||
import { StyleSheet } from "react-native"; |
|||
|
|||
import Text from "../Text"; |
|||
|
|||
function ErrorMessage({ error, visible }) { |
|||
if (!visible || !error) return null; |
|||
|
|||
return <Text style={styles.error}>{error}</Text>; |
|||
} |
|||
|
|||
const styles = StyleSheet.create({ |
|||
error: { color: "red" }, |
|||
}); |
|||
|
|||
export default ErrorMessage; |
@ -0,0 +1,16 @@ |
|||
import React from "react"; |
|||
import { Formik } from "formik"; |
|||
|
|||
function AppForm({ initialValues, onSubmit, validationSchema, children }) { |
|||
return ( |
|||
<Formik |
|||
initialValues={initialValues} |
|||
onSubmit={onSubmit} |
|||
validationSchema={validationSchema} |
|||
> |
|||
{() => <>{children}</>} |
|||
</Formik> |
|||
); |
|||
} |
|||
|
|||
export default AppForm; |
@ -0,0 +1,23 @@ |
|||
import React from "react"; |
|||
import { useFormikContext } from "formik"; |
|||
|
|||
import TextInput from "../TextInput"; |
|||
import ErrorMessage from "./ErrorMessage"; |
|||
|
|||
function AppFormField({ name, width, ...otherProps }) { |
|||
const { setFieldTouched, handleChange, errors, touched } = useFormikContext(); |
|||
|
|||
return ( |
|||
<> |
|||
<TextInput |
|||
onBlur={() => setFieldTouched(name)} |
|||
onChangeText={handleChange(name)} |
|||
width={width} |
|||
{...otherProps} |
|||
/> |
|||
<ErrorMessage error={errors[name]} visible={touched[name]} /> |
|||
</> |
|||
); |
|||
} |
|||
|
|||
export default AppFormField; |
@ -0,0 +1,34 @@ |
|||
import React from "react"; |
|||
import { useFormikContext } from "formik"; |
|||
|
|||
import ErrorMessage from "./ErrorMessage"; |
|||
import ImageInputList from "../ImageInputList"; |
|||
|
|||
function FormImagePicker({ name }) { |
|||
const { errors, setFieldValue, touched, values } = useFormikContext(); |
|||
const imageUris = values[name]; |
|||
|
|||
const handleAdd = (uri) => { |
|||
setFieldValue(name, [...imageUris, uri]); |
|||
}; |
|||
|
|||
const handleRemove = (uri) => { |
|||
setFieldValue( |
|||
name, |
|||
imageUris.filter((imageUri) => imageUri !== uri) |
|||
); |
|||
}; |
|||
|
|||
return ( |
|||
<> |
|||
<ImageInputList |
|||
imageUris={imageUris} |
|||
onAddImage={handleAdd} |
|||
onRemoveImage={handleRemove} |
|||
/> |
|||
<ErrorMessage error={errors[name]} visible={touched[name]} /> |
|||
</> |
|||
); |
|||
} |
|||
|
|||
export default FormImagePicker; |
@ -0,0 +1,33 @@ |
|||
import React from "react"; |
|||
import { useFormikContext } from "formik"; |
|||
|
|||
import Picker from "../Picker"; |
|||
import ErrorMessage from "./ErrorMessage"; |
|||
|
|||
function AppFormPicker({ |
|||
items, |
|||
name, |
|||
numberOfColumns, |
|||
PickerItemComponent, |
|||
placeholder, |
|||
width, |
|||
}) { |
|||
const { errors, setFieldValue, touched, values } = useFormikContext(); |
|||
|
|||
return ( |
|||
<> |
|||
<Picker |
|||
items={items} |
|||
numberOfColumns={numberOfColumns} |
|||
onSelectItem={(item) => setFieldValue(name, item)} |
|||
PickerItemComponent={PickerItemComponent} |
|||
placeholder={placeholder} |
|||
selectedItem={values[name]} |
|||
width={width} |
|||
/> |
|||
<ErrorMessage error={errors[name]} visible={touched[name]} /> |
|||
</> |
|||
); |
|||
} |
|||
|
|||
export default AppFormPicker; |
@ -0,0 +1,12 @@ |
|||
import React from "react"; |
|||
import { useFormikContext } from "formik"; |
|||
|
|||
import Button from "../Button"; |
|||
|
|||
function SubmitButton({ title }) { |
|||
const { handleSubmit } = useFormikContext(); |
|||
|
|||
return <Button title={title} onPress={handleSubmit} />; |
|||
} |
|||
|
|||
export default SubmitButton; |
@ -0,0 +1,5 @@ |
|||
export { default as Form } from "./Form"; |
|||
export { default as FormField } from "./FormField"; |
|||
export { default as FormPicker } from "./FormPicker"; |
|||
export { default as ErrorMessage } from "./ErrorMessage"; |
|||
export { default as SubmitButton } from "./SubmitButton"; |
@ -0,0 +1,69 @@ |
|||
import React from "react"; |
|||
import { View, StyleSheet, Image, TouchableHighlight } from "react-native"; |
|||
import { MaterialCommunityIcons } from "@expo/vector-icons"; |
|||
import Swipeable from "react-native-gesture-handler/Swipeable"; |
|||
|
|||
import Text from "../Text"; |
|||
import colors from "../../config/colors"; |
|||
|
|||
function ListItem({ |
|||
title, |
|||
subTitle, |
|||
image, |
|||
IconComponent, |
|||
onPress, |
|||
renderRightActions, |
|||
}) { |
|||
return ( |
|||
<Swipeable renderRightActions={renderRightActions}> |
|||
<TouchableHighlight underlayColor={colors.light} onPress={onPress}> |
|||
<View style={styles.container}> |
|||
{IconComponent} |
|||
{image && <Image style={styles.image} source={image} />} |
|||
<View style={styles.detailsContainer}> |
|||
<Text style={styles.title} numberOfLines={1}> |
|||
{title} |
|||
</Text> |
|||
{subTitle && ( |
|||
<Text style={styles.subTitle} numberOfLines={2}> |
|||
{subTitle} |
|||
</Text> |
|||
)} |
|||
</View> |
|||
<MaterialCommunityIcons |
|||
color={colors.medium} |
|||
name="chevron-right" |
|||
size={25} |
|||
/> |
|||
</View> |
|||
</TouchableHighlight> |
|||
</Swipeable> |
|||
); |
|||
} |
|||
|
|||
const styles = StyleSheet.create({ |
|||
container: { |
|||
alignItems: "center", |
|||
flexDirection: "row", |
|||
padding: 15, |
|||
backgroundColor: colors.white, |
|||
}, |
|||
detailsContainer: { |
|||
flex: 1, |
|||
marginLeft: 10, |
|||
justifyContent: "center", |
|||
}, |
|||
image: { |
|||
width: 70, |
|||
height: 70, |
|||
borderRadius: 35, |
|||
}, |
|||
subTitle: { |
|||
color: colors.medium, |
|||
}, |
|||
title: { |
|||
fontWeight: "500", |
|||
}, |
|||
}); |
|||
|
|||
export default ListItem; |
@ -0,0 +1,30 @@ |
|||
import React from "react"; |
|||
import { View, StyleSheet, TouchableWithoutFeedback } from "react-native"; |
|||
import { MaterialCommunityIcons } from "@expo/vector-icons"; |
|||
|
|||
import colors from "../../config/colors"; |
|||
|
|||
function ListItemDeleteAction({ onPress }) { |
|||
return ( |
|||
<TouchableWithoutFeedback onPress={onPress}> |
|||
<View style={styles.container}> |
|||
<MaterialCommunityIcons |
|||
name="trash-can" |
|||
size={35} |
|||
color={colors.white} |
|||
/> |
|||
</View> |
|||
</TouchableWithoutFeedback> |
|||
); |
|||
} |
|||
|
|||
const styles = StyleSheet.create({ |
|||
container: { |
|||
backgroundColor: colors.danger, |
|||
width: 70, |
|||
justifyContent: "center", |
|||
alignItems: "center", |
|||
}, |
|||
}); |
|||
|
|||
export default ListItemDeleteAction; |
@ -0,0 +1,18 @@ |
|||
import React from "react"; |
|||
import { StyleSheet, View } from "react-native"; |
|||
|
|||
import colors from "../../config/colors"; |
|||
|
|||
function ListItemSeparator() { |
|||
return <View style={styles.separator} />; |
|||
} |
|||
|
|||
const styles = StyleSheet.create({ |
|||
separator: { |
|||
width: "100%", |
|||
height: 1, |
|||
backgroundColor: colors.light, |
|||
}, |
|||
}); |
|||
|
|||
export default ListItemSeparator; |
@ -0,0 +1,3 @@ |
|||
export { default as ListItem } from "./ListItem"; |
|||
export { default as ListItemDeleteAction } from "./ListItemDeleteAction"; |
|||
export { default as ListItemSeparator } from "./ListItemSeparator"; |
@ -0,0 +1,12 @@ |
|||
import { Platform } from "react-native"; |
|||
|
|||
import colors from "./colors"; |
|||
|
|||
export default { |
|||
colors, |
|||
text: { |
|||
color: colors.dark, |
|||
fontSize: 18, |
|||
fontFamily: Platform.OS === "android" ? "Roboto" : "Avenir", |
|||
}, |
|||
}; |
@ -0,0 +1,25 @@ |
|||
import { useEffect, useState } from "react"; |
|||
import * as Location from "expo-location"; |
|||
|
|||
export default useLocation = () => { |
|||
const [location, setLocation] = useState(); |
|||
|
|||
const getLocation = async () => { |
|||
try { |
|||
const { granted } = await Location.requestPermissionsAsync(); |
|||
if (!granted) return; |
|||
const { |
|||
coords: { latitude, longitude }, |
|||
} = await Location.getLastKnownPositionAsync(); |
|||
setLocation({ latitude, longitude }); |
|||
} catch (error) { |
|||
console.log(error); |
|||
} |
|||
}; |
|||
|
|||
useEffect(() => { |
|||
getLocation(); |
|||
}, []); |
|||
|
|||
return location; |
|||
}; |
@ -0,0 +1,14 @@ |
|||
import React from "react"; |
|||
import { createStackNavigator } from "@react-navigation/stack"; |
|||
import AccountScreen from "../screens/AccountScreen"; |
|||
import MessagesScreen from "../screens/MessagesScreen"; |
|||
|
|||
const Stack = createStackNavigator(); |
|||
|
|||
const AccountNavigator = () => ( |
|||
<Stack.Navigator> |
|||
<Stack.Screen name="Perfil" component={AccountScreen} /> |
|||
</Stack.Navigator> |
|||
); |
|||
|
|||
export default AccountNavigator; |
@ -0,0 +1,77 @@ |
|||
import React from "react"; |
|||
import { createBottomTabNavigator } from "@react-navigation/bottom-tabs"; |
|||
import { MaterialCommunityIcons } from "@expo/vector-icons"; |
|||
|
|||
import AccountNavigator from "./AccountNavigator"; |
|||
import FeedNavigator from "./FeedNavigator"; |
|||
import MessagesNavigator from "./MessagesNavigator" |
|||
import OfficialMessagesNavigator from "./OfficialMessagesNavigator" |
|||
import SharingDataScreen from "../screens/SharingDataScreen"; |
|||
import NewListingButton from "./NewListingButton"; |
|||
import routes from "./routes"; |
|||
|
|||
const Tab = createBottomTabNavigator(); |
|||
|
|||
const AppNavigator = () => ( |
|||
<Tab.Navigator> |
|||
<Tab.Screen |
|||
name="Home" |
|||
component={FeedNavigator} |
|||
options={{ |
|||
tabBarIcon: ({ color, size }) => ( |
|||
<MaterialCommunityIcons name="home" color={color} size={size} /> |
|||
), |
|||
}} |
|||
/> |
|||
<Tab.Screen |
|||
name="Oficial" |
|||
component={OfficialMessagesNavigator} |
|||
options={{ |
|||
tabBarIcon: ({ color, size }) => ( |
|||
<MaterialCommunityIcons name="check-circle" color={color} size={size} /> |
|||
), |
|||
}} |
|||
/> |
|||
|
|||
<Tab.Screen |
|||
name="SharingData" |
|||
component={SharingDataScreen} |
|||
options={({ navigation }) => ({ |
|||
tabBarButton: () => ( |
|||
<NewListingButton |
|||
onPress={() => navigation.navigate(routes.SHARING_DATA)} |
|||
/> |
|||
), |
|||
tabBarIcon: ({ color, size }) => ( |
|||
<MaterialCommunityIcons |
|||
name="plus-circle" |
|||
color={color} |
|||
size={size} |
|||
/> |
|||
), |
|||
})} |
|||
/> |
|||
|
|||
|
|||
<Tab.Screen |
|||
name="Notificação" |
|||
component={MessagesNavigator} |
|||
options={{ |
|||
tabBarIcon: ({ color, size }) => ( |
|||
<MaterialCommunityIcons name="bell-outline" color={color} size={size} /> |
|||
), |
|||
}} |
|||
/> |
|||
<Tab.Screen |
|||
name="Perfil" |
|||
component={AccountNavigator} |
|||
options={{ |
|||
tabBarIcon: ({ color, size }) => ( |
|||
<MaterialCommunityIcons name="account" color={color} size={size} /> |
|||
), |
|||
}} |
|||
/> |
|||
</Tab.Navigator> |
|||
); |
|||
|
|||
export default AppNavigator; |
@ -0,0 +1,13 @@ |
|||
import React from "react"; |
|||
import { createStackNavigator } from "@react-navigation/stack"; |
|||
import MapFeedScreen from "../screens/MapFeedScreen"; |
|||
|
|||
const Stack = createStackNavigator(); |
|||
|
|||
const FeedNavigator = () => ( |
|||
<Stack.Navigator> |
|||
<Stack.Screen name="Home" component={MapFeedScreen} /> |
|||
</Stack.Navigator> |
|||
); |
|||
|
|||
export default FeedNavigator; |
@ -0,0 +1,13 @@ |
|||
import React from "react"; |
|||
import { createStackNavigator } from "@react-navigation/stack"; |
|||
import MessagesScreen from "../screens/MessagesScreen"; |
|||
|
|||
const Stack = createStackNavigator(); |
|||
|
|||
const MessagesNavigator = () => ( |
|||
<Stack.Navigator> |
|||
<Stack.Screen name="Notificação" component={MessagesScreen} /> |
|||
</Stack.Navigator> |
|||
); |
|||
|
|||
export default MessagesNavigator; |
@ -0,0 +1,35 @@ |
|||
import React from "react"; |
|||
import { View, StyleSheet, TouchableOpacity } from "react-native"; |
|||
import { MaterialCommunityIcons } from "@expo/vector-icons"; |
|||
|
|||
import colors from "../config/colors"; |
|||
|
|||
function NewListingButton({ onPress }) { |
|||
return ( |
|||
<TouchableOpacity onPress={onPress}> |
|||
<View style={styles.container}> |
|||
<MaterialCommunityIcons |
|||
name="plus-circle" |
|||
color={colors.white} |
|||
size={40} |
|||
/> |
|||
</View> |
|||
</TouchableOpacity> |
|||
); |
|||
} |
|||
|
|||
const styles = StyleSheet.create({ |
|||
container: { |
|||
alignItems: "center", |
|||
backgroundColor: colors.primary, |
|||
borderColor: colors.white, |
|||
borderRadius: 40, |
|||
borderWidth: 10, |
|||
bottom: 20, |
|||
height: 80, |
|||
justifyContent: "center", |
|||
width: 80, |
|||
}, |
|||
}); |
|||
|
|||
export default NewListingButton; |
@ -0,0 +1,13 @@ |
|||
import React from "react"; |
|||
import { createStackNavigator } from "@react-navigation/stack"; |
|||
import OfficialMessagesScreen from "../screens/OfficialMessagesScreen"; |
|||
|
|||
const Stack = createStackNavigator(); |
|||
|
|||
const OfficialMessagesNavigator = () => ( |
|||
<Stack.Navigator> |
|||
<Stack.Screen name="Previsão do Tempo" component={OfficialMessagesScreen} /> |
|||
</Stack.Navigator> |
|||
); |
|||
|
|||
export default OfficialMessagesNavigator; |
@ -0,0 +1,11 @@ |
|||
import { DefaultTheme } from "@react-navigation/native"; |
|||
import colors from "../config/colors"; |
|||
|
|||
export default { |
|||
...DefaultTheme, |
|||
colors: { |
|||
...DefaultTheme.colors, |
|||
primary: colors.primary, |
|||
background: colors.white, |
|||
}, |
|||
}; |
@ -0,0 +1,9 @@ |
|||
export default Object.freeze({ |
|||
LISTING_DETAILS: "ListingDetails", |
|||
LISTING_EDIT: "ListingEdit", |
|||
LOGIN: "Login", |
|||
MESSAGES: "Messages", |
|||
REGISTER: "Register", |
|||
SHARING_DATA: "SharingData", |
|||
}); |
|||
|
@ -0,0 +1,36 @@ |
|||
import React from "react"; |
|||
import { StyleSheet, View } from "react-native"; |
|||
|
|||
import { ListItem } from "../components/lists"; |
|||
import colors from "../config/colors"; |
|||
import Icon from "../components/Icon"; |
|||
import Screen from "../components/Screen"; |
|||
|
|||
function AccountScreen({ navigation }) { |
|||
return ( |
|||
<Screen style={styles.screen}> |
|||
<View style={styles.container}> |
|||
<ListItem |
|||
title="Daniel Barros" |
|||
subTitle="danieldrb@gmail.com" |
|||
image={require("../assets/ddangelorb.png")} |
|||
/> |
|||
</View> |
|||
<ListItem |
|||
title="Log Out" |
|||
IconComponent={<Icon name="logout" backgroundColor="#ffe66d" />} |
|||
/> |
|||
</Screen> |
|||
); |
|||
} |
|||
|
|||
const styles = StyleSheet.create({ |
|||
screen: { |
|||
backgroundColor: colors.light, |
|||
}, |
|||
container: { |
|||
marginVertical: 20, |
|||
}, |
|||
}); |
|||
|
|||
export default AccountScreen; |
@ -0,0 +1,63 @@ |
|||
import React from "react"; |
|||
import { StyleSheet, View } from "react-native"; |
|||
import MapView, { Marker } from "react-native-maps"; |
|||
|
|||
import colors from "../config/colors"; |
|||
|
|||
function MapFeedScreen(props) { |
|||
return ( |
|||
<View style={styles.container}> |
|||
<MapView |
|||
style={styles.mapStyle} |
|||
initialRegion={{ |
|||
latitude: -23.657090, |
|||
longitude: -46.699260, |
|||
latitudeDelta: 0.0922, |
|||
longitudeDelta: 0.0421, |
|||
}}> |
|||
<Marker |
|||
coordinate={{ latitude: -23.657090, longitude: -46.699260 }} |
|||
image={require("../assets/chuva_peq.png")} |
|||
/> |
|||
<Marker |
|||
coordinate={{ latitude: -23.656282, longitude: -46.682768 }} |
|||
image={require("../assets/chuva_peq.png")} |
|||
/> |
|||
<Marker |
|||
coordinate={{ latitude: -23.666712, longitude: -46.687650 }} |
|||
image={require("../assets/chuva_peq.png")} |
|||
/> |
|||
<Marker |
|||
coordinate={{ latitude: -23.660848, longitude: -46.704396 }} |
|||
image={require("../assets/chuva_peq.png")} |
|||
/> |
|||
|
|||
<Marker |
|||
coordinate={{ latitude: -23.634700, longitude: -46.721960 }} |
|||
image={require("../assets/pontos_alagamento_peq.png")} |
|||
/> |
|||
<Marker |
|||
coordinate={{ latitude: -23.650861, longitude: -46.721775 }} |
|||
image={require("../assets/pontos_alagamento_peq.png")} |
|||
/> |
|||
</MapView> |
|||
</View> |
|||
); |
|||
} |
|||
|
|||
const styles = StyleSheet.create({ |
|||
container: { |
|||
backgroundColor: colors.black, |
|||
flex: 1, |
|||
}, |
|||
mapStyle: { |
|||
position: 'absolute', |
|||
top: 0, |
|||
left: 0, |
|||
right: 0, |
|||
bottom: 0 |
|||
}, |
|||
}); |
|||
|
|||
export default MapFeedScreen; |
|||
|
@ -0,0 +1,70 @@ |
|||
import React, { useState } from "react"; |
|||
import { FlatList, StyleSheet } from "react-native"; |
|||
|
|||
import Screen from "../components/Screen"; |
|||
import { |
|||
ListItem, |
|||
ListItemDeleteAction, |
|||
ListItemSeparator, |
|||
} from "../components/lists"; |
|||
|
|||
const initialMessages = [ |
|||
{ |
|||
id: 1, |
|||
title: "Defesa Civil", |
|||
description: "Enviado um aviso de chuvas intensas na região do M'Boi Mirim. Há possibilidade ...", |
|||
image: require("../assets/defesa_civil.png"), |
|||
}, |
|||
{ |
|||
id: 2, |
|||
title: "Defesa Civil", |
|||
description: |
|||
"Enviado um aviso de alagamento na região de Pinheiros. Há possibilidade de alagamentos. Evite a região.", |
|||
image: require("../assets/defesa_civil.png"), |
|||
}, |
|||
]; |
|||
|
|||
function MessagesScreen(props) { |
|||
const [messages, setMessages] = useState(initialMessages); |
|||
const [refreshing, setRefreshing] = useState(false); |
|||
|
|||
const handleDelete = (message) => { |
|||
setMessages(messages.filter((m) => m.id !== message.id)); |
|||
}; |
|||
|
|||
return ( |
|||
<Screen> |
|||
<FlatList |
|||
data={messages} |
|||
keyExtractor={(message) => message.id.toString()} |
|||
renderItem={({ item }) => ( |
|||
<ListItem |
|||
title={item.title} |
|||
subTitle={item.description} |
|||
image={item.image} |
|||
onPress={() => console.log("Message selected", item)} |
|||
renderRightActions={() => ( |
|||
<ListItemDeleteAction onPress={() => handleDelete(item)} /> |
|||
)} |
|||
/> |
|||
)} |
|||
ItemSeparatorComponent={ListItemSeparator} |
|||
refreshing={refreshing} |
|||
onRefresh={() => { |
|||
setMessages([ |
|||
{ |
|||
id: 2, |
|||
title: "T2", |
|||
description: "D2", |
|||
image: require("../assets/ddangelorb.png"), |
|||
}, |
|||
]); |
|||
}} |
|||
/> |
|||
</Screen> |
|||
); |
|||
} |
|||
|
|||
const styles = StyleSheet.create({}); |
|||
|
|||
export default MessagesScreen; |
@ -0,0 +1,29 @@ |
|||
import React from "react"; |
|||
import { Image, StyleSheet, View } from "react-native"; |
|||
|
|||
import colors from "../config/colors"; |
|||
|
|||
function OfficialMessagesScreen(props) { |
|||
return ( |
|||
<View style={styles.container}> |
|||
<Image |
|||
resizeMode="contain" |
|||
style={styles.image} |
|||
source={require("../assets/previsao_tempo.png")} |
|||
/> |
|||
</View> |
|||
); |
|||
} |
|||
|
|||
const styles = StyleSheet.create({ |
|||
container: { |
|||
backgroundColor: colors.black, |
|||
flex: 1, |
|||
}, |
|||
image: { |
|||
width: "100%", |
|||
height: "100%", |
|||
}, |
|||
}); |
|||
|
|||
export default OfficialMessagesScreen; |
@ -0,0 +1,109 @@ |
|||
import React from "react"; |
|||
import { StyleSheet } from "react-native"; |
|||
import * as Yup from "yup"; |
|||
|
|||
import { |
|||
Form, |
|||
FormField, |
|||
FormPicker as Picker, |
|||
SubmitButton, |
|||
} from "../components/forms"; |
|||
import CategoryPickerItem from "../components/CategoryPickerItem"; |
|||
import Screen from "../components/Screen"; |
|||
import FormImagePicker from "../components/forms/FormImagePicker"; |
|||
import useLocation from "../hooks/useLocation"; |
|||
|
|||
const validationSchema = Yup.object().shape({ |
|||
title: Yup.string().required().min(1).label("Title"), |
|||
price: Yup.number().required().min(1).max(10000).label("Price"), |
|||
description: Yup.string().label("Description"), |
|||
category: Yup.object().required().nullable().label("Category"), |
|||
images: Yup.array().min(1, "Please select at least one image."), |
|||
}); |
|||
|
|||
const categories = [ |
|||
{ |
|||
backgroundColor: "#2e53ff", |
|||
icon: "wave", |
|||
label: "Pontos de alagamento", |
|||
value: 1, |
|||
}, |
|||
{ |
|||
backgroundColor: "#2e53ff", |
|||
icon: "weather-pouring", |
|||
label: "Chuva", |
|||
value: 2, |
|||
}, |
|||
{ |
|||
backgroundColor: "#2e53ff", |
|||
icon: "waves", |
|||
label: "Diário do pluviômetro", |
|||
value: 3, |
|||
}, |
|||
{ |
|||
backgroundColor: "#2e53ff", |
|||
icon: "waves", |
|||
label: "Diário do pluviômetro", |
|||
value: 3, |
|||
}, |
|||
{ |
|||
backgroundColor: "#2e53ff", |
|||
icon: "water-percent", |
|||
label: "Nível do rio", |
|||
value: 4, |
|||
}, |
|||
]; |
|||
|
|||
function SharingDataScreen() { |
|||
const location = useLocation(); |
|||
|
|||
return ( |
|||
<Screen style={styles.container}> |
|||
<Form |
|||
initialValues={{ |
|||
title: "", |
|||
price: "", |
|||
description: "", |
|||
category: null, |
|||
images: [], |
|||
}} |
|||
onSubmit={(values) => console.log(location)} |
|||
validationSchema={validationSchema} |
|||
> |
|||
<FormImagePicker name="images" /> |
|||
<FormField maxLength={255} name="title" placeholder="Título" /> |
|||
<FormField |
|||
keyboardType="numeric" |
|||
maxLength={8} |
|||
name="price" |
|||
placeholder="Valor" |
|||
width={120} |
|||
/> |
|||
<Picker |
|||
items={categories} |
|||
name="category" |
|||
numberOfColumns={3} |
|||
PickerItemComponent={CategoryPickerItem} |
|||
placeholder="Categoria" |
|||
width="50%" |
|||
/> |
|||
<FormField |
|||
maxLength={255} |
|||
multiline |
|||
name="description" |
|||
numberOfLines={3} |
|||
placeholder="Descrição" |
|||
/> |
|||
<SubmitButton title="Post" /> |
|||
</Form> |
|||
</Screen> |
|||
); |
|||
} |
|||
|
|||
const styles = StyleSheet.create({ |
|||
container: { |
|||
padding: 10, |
|||
}, |
|||
}); |
|||
|
|||
export default SharingDataScreen; |
@ -1,53 +0,0 @@ |
|||
import React from "react"; |
|||
import { Image, StyleSheet, View } from "react-native"; |
|||
import MapView from 'react-native-maps'; |
|||
|
|||
import colors from "../config/colors"; |
|||
|
|||
function ViewImageScreen(props) { |
|||
return ( |
|||
<View style={styles.container}> |
|||
<View style={styles.closeIcon}></View> |
|||
<View style={styles.deleteIcon}></View> |
|||
<MapView style={styles.mapStyle} /> |
|||
<Image |
|||
resizeMode="contain" |
|||
style={styles.image} |
|||
source={require("../assets/wp6.jpg")} |
|||
/> |
|||
</View> |
|||
); |
|||
} |
|||
|
|||
const styles = StyleSheet.create({ |
|||
closeIcon: { |
|||
width: 50, |
|||
height: 50, |
|||
backgroundColor: colors.primary, |
|||
position: "absolute", |
|||
top: 40, |
|||
left: 30, |
|||
}, |
|||
container: { |
|||
backgroundColor: colors.black, |
|||
flex: 1, |
|||
}, |
|||
deleteIcon: { |
|||
width: 50, |
|||
height: 50, |
|||
backgroundColor: colors.secondary, |
|||
position: "absolute", |
|||
top: 40, |
|||
right: 30, |
|||
}, |
|||
image: { |
|||
width: "100%", |
|||
height: "100%", |
|||
}, |
|||
mapStyle: { |
|||
width: 300, |
|||
height: 300, |
|||
}, |
|||
}); |
|||
|
|||
export default ViewImageScreen; |
7956
src/package-lock.json
File diff suppressed because it is too large
View File
File diff suppressed because it is too large
View File
688
src/yarn.lock
File diff suppressed because it is too large
View File
File diff suppressed because it is too large
View File
Write
Preview
Loading…
Cancel
Save
Reference in new issue