Gitlab - Argos ALM by PALO IT

Challenge React Native

parent 53be98c1
# Learn more https://docs.github.com/en/get-started/getting-started-with-git/ignoring-files
# dependencies
node_modules/
# Expo
.expo/
dist/
web-build/
# Native
*.orig.*
*.jks
*.p8
*.p12
*.key
*.mobileprovision
# Metro
.metro-health-check*
# debug
npm-debug.*
yarn-debug.*
yarn-error.*
# macOS
.DS_Store
*.pem
# local env files
.env*.local
# typescript
*.tsbuildinfo
import React from 'react';
import { StatusBar } from 'expo-status-bar';
import { StyleSheet, Text, View } from 'react-native';
import { NavigationContainer } from '@react-navigation/native';
import { createBottomTabNavigator } from '@react-navigation/bottom-tabs';
import { createNativeStackNavigator } from '@react-navigation/native-stack';
import Ionicons from 'react-native-vector-icons/Ionicons';
import NewPlace from './src/screens/NewPlace';
import MapScreen from './src/screens/MapScreen';
import ListScreen from './src/screens/ListScreen';
import CameraScreen from './src/screens/CameraScreen';
import DetailScreen from './src/screens/DetailScreen';
import { Provider } from './src/context/PlaceContext';
const Tab = createBottomTabNavigator();
const MapStack = createNativeStackNavigator();
const HomeStack = createNativeStackNavigator();
const HomeStackScreen = () => {
return(
<HomeStack.Navigator>
<HomeStack.Screen name="List" component={ListScreen} />
<HomeStack.Screen name='Detail' component={DetailScreen} />
<HomeStack.Screen name='New Place' component={NewPlace} />
<HomeStack.Screen name='Camera' component={CameraScreen} />
</HomeStack.Navigator>
);
};
const MapStackScreen = () => {
return (
<MapStack.Navigator>
<MapStack.Screen name='Map View' component={MapScreen} />
<MapStack.Group screenOptions={{ presentation: 'modal' }} >
<HomeStack.Screen name='Detail' component={DetailScreen} />
</MapStack.Group>
</MapStack.Navigator>
);
}
export default function App() {
return (
<Provider>
<NavigationContainer>
<StatusBar style="auto" />
<Tab.Navigator
screenOptions={ ({ route }) =>({
tabBarIcon: ({ focused, color, size }) => {
let iconName;
if (route.name == 'Home') {
iconName = focused ? 'ios-home' : 'ios-home-outline';
} else if (route.name == 'Map') {
iconName = focused ? 'ios-map' : 'ios-map-outline';
}
return <Ionicons name={iconName} size={size} color={color} />;
},
tabBarActiveTintColor: 'tomato',
tabBarInactiveTintColor: 'gray',
headerShown: false
})}
>
<Tab.Screen name='Home' component={HomeStackScreen} />
<Tab.Screen name='Map' component={MapStackScreen} />
</Tab.Navigator>
</NavigationContainer>
</Provider>
);
}
const styles = StyleSheet.create({});
{
"expo": {
"name": "Challenge",
"slug": "Challenge",
"version": "1.0.0",
"orientation": "portrait",
"icon": "./assets/icon.png",
"userInterfaceStyle": "light",
"splash": {
"image": "./assets/splash.png",
"resizeMode": "contain",
"backgroundColor": "#ffffff"
},
"assetBundlePatterns": [
"**/*"
],
"ios": {
"supportsTablet": true
},
"android": {
"adaptiveIcon": {
"foregroundImage": "./assets/adaptive-icon.png",
"backgroundColor": "#ffffff"
}
},
"web": {
"favicon": "./assets/favicon.png"
}
}
}
module.exports = function(api) {
api.cache(true);
return {
presets: ['babel-preset-expo'],
};
};
This diff is collapsed.
{
"name": "challenge",
"version": "1.0.0",
"main": "node_modules/expo/AppEntry.js",
"scripts": {
"start": "expo start",
"android": "expo start --android",
"ios": "expo start --ios",
"web": "expo start --web"
},
"dependencies": {
"@react-native-async-storage/async-storage": "^1.19.3",
"@react-navigation/bottom-tabs": "^6.5.10",
"@react-navigation/native": "^6.1.9",
"@react-navigation/native-stack": "^6.9.15",
"expo": "~49.0.15",
"expo-camera": "^13.6.0",
"expo-location": "^16.3.0",
"expo-media-library": "^15.6.0",
"expo-status-bar": "~1.6.0",
"react": "18.2.0",
"react-native": "0.72.6",
"react-native-maps": "^1.8.0",
"react-native-uuid": "^2.0.1"
},
"devDependencies": {
"@babel/core": "^7.20.0"
},
"private": true
}
import React from "react";
import { View, Text, StyleSheet, TouchableOpacity } from "react-native";
import Ionicons from 'react-native-vector-icons/Ionicons';
const CButton = ( {title, icon, color, onPress} ) => {
return (
<View>
<TouchableOpacity onPress={() => onPress()} style={styles.container}>
<Ionicons name={icon} style={[styles.icon, {color: color}]}/>
<Text style={[styles.title, {color: color}]}>{title}</Text>
</TouchableOpacity>
</View>
);
};
const styles = StyleSheet.create({
container: {
flexDirection: 'column',
justifyContent: 'center',
alignContent: 'center',
alignItems: 'center'
},
title: {
fontSize: 9
},
icon: {
width: 16,
height: 16,
fontSize: 18
}
});
export default CButton;
\ No newline at end of file
import React from "react";
import { View, StyleSheet, FlatList, TouchableOpacity } from "react-native";
import CardView from "./CardView";
const CardList = ({ items, navigation }) => {
return (
<View style={styles.container}>
<FlatList
style={styles.list}
data={items}
keyExtractor={ place => place.id }
horizontal={true}
showsHorizontalScrollIndicator={false}
renderItem={({ item }) => {
return(
<TouchableOpacity onPress={() => navigation.navigate('Detail', {id: item.id})}>
<CardView
place={item}
/>
</TouchableOpacity>
)
}}
/>
</View>
);
};
const styles = StyleSheet.create({
container: {
flex: 1,
marginHorizontal: 16
},
list: {
flex: 1
}
});
export default CardList;
\ No newline at end of file
import React, { useContext } from "react";
import { View, Text, StyleSheet } from "react-native";
import { Context } from "../context/PlaceContext";
const CardView = ({ place }) => {
const { state } = useContext(Context);
const index = state.findIndex( item => item.id == place.id );
return (
<View style={styles.container}>
<View style={styles.index}>
<Text style={styles.indexText}>{index + 1}</Text>
</View>
<View style={styles.nameContainer}>
<Text style={styles.name}>{place.name}</Text>
</View>
</View>
);
};
const styles = StyleSheet.create({
container: {
flex: 1,
marginHorizontal: 8,
marginVertical: 16,
backgroundColor: 'white',
borderRadius: 16,
flexDirection: 'row',
shadowOffset: {
width: 0,
height: 0
},
shadowOpacity: 0.5,
shadowRadius: 8
},
index: {
backgroundColor: 'tomato',
borderRadius: 24,
width: 48,
height: 48,
justifyContent: 'center',
margin: 16
},
indexText: {
color: 'white',
textAlign: 'center',
fontSize: 18,
fontWeight: 'bold',
},
nameContainer: {
justifyContent: 'center',
marginEnd: 16
},
name: {
fontSize: 24
}
});
export default CardView;
\ No newline at end of file
import React from "react";
import { View, Text, StyleSheet } from "react-native";
import { Entypo } from '@expo/vector-icons';
const EmptyState = () => {
return (
<View style={styles.container}>
<Entypo name="emoji-sad" style={styles.imageStyle}/>
<Text style={styles.titleStyle}>No places to show</Text>
<Text style={styles.content}>Please tap on the "+" button to register a new place.</Text>
</View>
);
};
const styles = StyleSheet.create({
container: {
margin: 16,
justifyContent: 'center',
alignItems: 'center'
},
titleStyle: {
margin: 8,
fontSize: 24,
fontWeight: 'bold',
color: '#BDBDBD'
},
imageStyle: {
fontSize: 60,
color: '#BDBDBD',
marginTop: 16
},
content: {
color: '#BDBDBD',
marginVertical: 24,
marginHorizontal: 16,
fontSize: 17,
textAlign: 'center'
}
});
export default EmptyState;
\ No newline at end of file
import React from "react";
import { View, Text, StyleSheet, TouchableOpacity } from "react-native";
import { Ionicons } from '@expo/vector-icons';
const IconButton = ({ title, icon, color, onPress }) => {
return <View>
<TouchableOpacity onPress={onPress} style={[styles.buttonStyle, {backgroundColor: color}]}>
<Ionicons name={icon} size={28} color='#F1F1F1'/>
<Text style={styles.titleStyle}>{title}</Text>
</TouchableOpacity>
</View>
};
IconButton.defaultProps = {
color: '#2196f3'
};
const styles = StyleSheet.create({
buttonStyle: {
height: 46,
flexDirection: 'row',
alignItems: 'center',
justifyContent: 'center',
paddingHorizontal: 24,
borderRadius: 8,
marginHorizontal: 16,
marginVertical: 8
},
titleStyle: {
fontWeight: '600',
fontSize: 20,
color: "#F1F1F1",
marginStart: 8
}
});
export default IconButton;
\ No newline at end of file
import React from "react";
import { View, Text, StyleSheet } from "react-native";
const LocationCell = ({ latitude, longitude }) => {
return (
<View style={styles.container}>
<Text style={styles.text} >Latitude: {latitude}</Text>
<Text style={styles.text} >Longitude: {longitude}</Text>
</View>
);
};
const styles = StyleSheet.create({
container: {
marginHorizontal: 16,
marginVertical: 8
},
text: {
fontSize: 16,
color: '#1B1A28',
marginVertical: 2
}
});
export default LocationCell;
\ No newline at end of file
import React from "react";
import { View, StyleSheet, TouchableOpacity } from "react-native";
import { Ionicons } from '@expo/vector-icons';
const MButton = ({ icon, color, onPress }) => {
return (
<View>
<TouchableOpacity onPress={onPress}>
<Ionicons style={[styles.icon, {color: color}]} name={icon} />
</TouchableOpacity>
</View>
);
};
MButton.defaultProps = {
color: 'tomato'
}
const styles = StyleSheet.create({
icon: {
fontSize: 40
}
});
export default MButton;
\ No newline at end of file
import React from "react";
import { View, Text, StyleSheet, Image, TouchableOpacity } from "react-native";
import { Ionicons } from '@expo/vector-icons';
const PlaceCell = ({ place, onDelete }) => {
return (
<View style={styles.cell}>
<Image
style={styles.image}
source={{ uri: place.photo }}
/>
<View>
<Text style={styles.title} >{place.name}</Text>
<Text style={styles.info} >Registered at: {place.date}</Text>
<TouchableOpacity onPress={onDelete}>
<Ionicons name="ios-trash-outline" style={styles.delete} />
</TouchableOpacity>
</View>
<View style={{ position: 'absolute', right: 8, top: 32}}>
<Ionicons name="ios-chevron-forward" style={styles.chevron} />
</View>
</View>
);
};
const styles = StyleSheet.create({
cell: {
backgroundColor: 'white',
borderRadius: 16,
marginHorizontal: 16,
marginVertical: 8,
flexDirection: 'row'
},
image: {
width: 80,
height: 80,
borderRadius: 8,
backgroundColor: 'black',
margin: 8
},
title: {
fontSize: 18,
fontWeight: 'bold',
marginVertical: 8
},
info: {
fontSize: 11,
fontWeight: '400',
color: '#BDBDBD'
},
delete: {
fontSize: 22,
color: 'red',
marginTop: 8,
width: 24
},
chevron: {
fontSize: 24,
color: '#BDBDBD',
alignSelf: 'center'
}
});
export default PlaceCell;
\ No newline at end of file
import React from "react";
import { View, StyleSheet, TextInput } from "react-native";
import { Ionicons } from '@expo/vector-icons';
const SearchView = ({ value, setValue, callback }) => {
return (
<View style={styles.container}>
<View style={{ flexDirection: 'row' }}>
<Ionicons name="ios-search-outline" style={styles.icon} />
<TextInput
style={styles.textField}
placeholder='Search by name'
value={value}
onChangeText={(newText) => {
setValue(newText);
if (callback) {
callback(newText);
}
}}
/>
</View>
</View>
);
};
const styles = StyleSheet.create({
container: {
backgroundColor: 'white',
margin: 16,
paddingHorizontal: 16,
borderRadius: 24,
shadowRadius: 4,
shadowOffset: { width: 0, height: 2},
shadowColor: '#000',
shadowOpacity: 0.1
},
icon: {
fontSize: 24,
color: '#BDBDBD',
paddingVertical: 8
},
textField: {
flex: 1,
marginHorizontal: 8
}
});
export default SearchView;
\ No newline at end of file
import createDataContext from "./createDataContext";
import uuid from 'react-native-uuid';
import AsyncStorage from '@react-native-async-storage/async-storage';
const placeReducer = (state, action) => {
switch (action.type) {
case 'add_place':
return [...state,
{
id: uuid.v4(),
name: action.payload.name,
latitude: action.payload.latitude,
longitude: action.payload.longitude,
date: action.payload.date,
photo: action.payload.photo
}
];
case 'delete_place':
return state.filter( place => place.id !== action.payload );
case 'get_places':
return action.payload;
case 'sort_places':
switch (action.payload.type) {
case 'name':
return action.payload.data.sort((a, b) => a.name > b.name ? 1 : -1);
case 'date':
return action.payload.data.sort((a, b) => a.date > b.date ? 1 : -1);
default:
return action.payload.data;
}
case 'filter_places':
const data = action.payload.data;
const text = action.payload.text;
if (text.length > 0) {
console.log(data.length);
console.log(text);
return data.filter(item => item.name.startsWith(text));
}
return data;
default:
return state;
}
};
const addPlace = dispatch => {
return async (place, callback) => {
try {
const jsonValue = await AsyncStorage.getItem('Keys.SavedPlaces');
const data = jsonValue != null ? JSON.parse(jsonValue) : [];
const newData = [...data, place];
const newJson = JSON.stringify(newData);
await AsyncStorage.setItem('Keys.SavedPlaces', newJson);
} catch (e) {
console.log('----------------> ERROR <----------------');
console.log(e);
console.log('--------------> END ERROR <--------------');
}
dispatch({ type: 'add_place', payload: place });
if (callback) {
callback();
}
};
};
const deletePlace = dispatch => {
return async (id, callback) => {
try {
const jsonValue = await AsyncStorage.getItem('Keys.SavedPlaces');
const data = jsonValue != null ? JSON.parse(jsonValue) : [];
const filtered = data.filter( place => place.id !== id );
const newJson = JSON.stringify(filtered);
await AsyncStorage.setItem('Keys.SavedPlaces', newJson);
} catch (e) {
console.log('----------------> ERROR <----------------');
console.log(e);
console.log('--------------> END ERROR <--------------');
}
console.log(id);
dispatch({ type: 'delete_place', payload: id });
if (callback) {
callback();
}
};
};
const getPlaces = dispatch => {
return async () => {
try {
const jsonValue = await AsyncStorage.getItem('Keys.SavedPlaces');
const data = jsonValue != null ? JSON.parse(jsonValue) : [];
dispatch({ type: 'get_places', payload: data });
} catch (e) {
console.log('----------------> ERROR <----------------');
console.log(e);
console.log('--------------> END ERROR <--------------');
}
}
};
const sortPlaces = dispatch => {
return async (type) => {
try {
const jsonValue = await AsyncStorage.getItem('Keys.SavedPlaces');
const data = jsonValue != null ? JSON.parse(jsonValue) : [];
dispatch({ type: 'sort_places', payload: {data, type} });
} catch (e) {
console.log('----------------> ERROR <----------------');
console.log(e);
console.log('--------------> END ERROR <--------------');
}
}
};
const filterPlaces = dispatch => {
return async (text) => {
try {
const jsonValue = await AsyncStorage.getItem('Keys.SavedPlaces');
const data = jsonValue != null ? JSON.parse(jsonValue) : [];
dispatch({ type: 'filter_places', payload: {data, text} });
} catch (e) {
console.log('----------------> ERROR <----------------');
console.log(e);
console.log('--------------> END ERROR <--------------');
}
}
};
export const { Context, Provider } = createDataContext(
placeReducer,
{ addPlace, deletePlace, getPlaces, sortPlaces, filterPlaces },
[
// {
// id: uuid.v4(),
// name: "Home",
// latitude: 19.2843824,
// longitude: -98.9422586,
// date: "16/10/2023",
// photo: "https://www.livehome3d.com/assets/img/social/how-to-design-a-house.jpg"
// }
]
);
\ No newline at end of file
import React, { useReducer } from "react";
export default (reducer, actions, initialState) => {
const Context = React.createContext();
const Provider = ({ children }) => {
const [state, dispatch] = useReducer(reducer, initialState);
const boundActions = {};
for (let key in actions) {
boundActions[key] = actions[key](dispatch);
}
return <Context.Provider value={{ state, ...boundActions }}>
{children}
</Context.Provider>
}
return { Context, Provider };
};
\ No newline at end of file
import React, { useState, useEffect, useRef } from "react";
import { View, Text, StyleSheet, Image } from "react-native";
import { Camera } from "expo-camera";
import IconButton from "../components/IconButton";
import * as MediaLibrary from 'expo-media-library';
const CameraScreen = ({navigation, route}) => {
const [hasCameraPermissions, setCameraPermissions] = useState(null);
const [image, setImage] = useState(null);
const cameraRef = useRef(null);
const callback = route.params.callback;
useEffect(() => {
(async () => {
MediaLibrary.requestPermissionsAsync();
const cameraStatus = await Camera.requestMicrophonePermissionsAsync();
setCameraPermissions(cameraStatus.status == 'granted');
})();
}, []);
const takePicture = async () => {
if (cameraRef) {
try {
const data = await cameraRef.current.takePictureAsync();
console.log(data);
setImage(data.uri);
} catch(e) {
console.log(e);
}
}
}
const saveImage = async () => {
if (image) {
try {
await MediaLibrary.createAssetAsync(image);
console.log('image saved!');
callback(image);
navigation.pop();
} catch (e) {
console.log(e);
}
}
}
if (hasCameraPermissions == false) {
return <Text>No access to the camera</Text>
}
return (
<View style={styles.container}>
{
!image ?
<Camera
style={styles.cameraStyle}
type={Camera.Constants.Type.back}
flashMode={Camera.Constants.FlashMode.off}
ref={cameraRef}
/>
:
<Image source={{uri: image}} style={styles.cameraStyle}/>
}
<View>
{ image ?
<View
style={styles.buttonContainer}>
<IconButton
title={"Re-take"}
icon="ios-repeat-outline"
onPress={() => setImage(null)}
/>
<IconButton
title={"Save"}
icon="ios-save"
color={"#4caf50"}
onPress={saveImage}
/>
</View>
:
<View style={{marginBottom: 16}}>
<IconButton
title={'Take picture'}
onPress={takePicture}
/>
</View>
}
</View>
</View>
);
};
const styles = StyleSheet.create({
container: {
flex: 1,
backgroundColor: '#f1f1f1',
justifyContent: 'center'
},
cameraStyle: {
flex: 1
},
buttonContainer: {
flexDirection: 'row',
justifyContent: 'space-around',
padding: 16,
marginBottom: 16
}
});
export default CameraScreen;
\ No newline at end of file
import React, { useContext } from "react";
import { View, Text, StyleSheet, Image } from "react-native";
import { Context } from "../context/PlaceContext";
const DetailScreen = ( { navigation, route } ) => {
const { state } = useContext(Context);
const id = route.params.id;
const place = state.find((place) => place.id === id);
return (
<View>
<Text style={styles.title}>{place.name}</Text>
<Image style={styles.photo} source={{ uri: place.photo }} />
<View style={styles.row}>
<Text style={styles.info} >ID:</Text>
<Text style={styles.value} >{place.id}</Text>
</View>
<View style={styles.row}>
<Text style={styles.info}>Registered at:</Text>
<Text style={styles.value} >{place.date}</Text>
</View>
<View style={styles.row}>
<Text style={styles.info}>Latitude:</Text>
<Text style={styles.value} >{place.latitude}</Text>
</View>
<View style={styles.row}>
<Text style={styles.info}>Longitude:</Text>
<Text style={styles.value} >{place.longitude}</Text>
</View>
</View>
);
};
const styles = StyleSheet.create({
title: {
fontSize: 24,
fontWeight: '600',
marginVertical: 16,
textAlign: 'center'
},
photo: {
width: '85%',
borderRadius: 16,
marginBottom: 16,
aspectRatio: 0.75,
alignSelf: 'center'
},
row: {
marginVertical: 4,
flexDirection: 'row',
marginHorizontal: 16,
},
info: {
marginEnd: 8,
fontSize: 16,
color: '#757575'
},
value: {
fontSize: 16,
fontWeight: '600'
}
});
export default DetailScreen;
\ No newline at end of file
import React, { useEffect, useContext, useState } from "react";
import { View, StyleSheet, FlatList, Alert, TouchableOpacity } from "react-native";
import { Context } from "../context/PlaceContext";
import CButton from '../components/CButton';
import EmptyState from '../components/EmptyState';
import PlaceCell from "../components/PlaceCell";
import IconButton from "../components/IconButton";
import SearchView from "../components/SearchView";
const ListScreen = ({ navigation }) => {
const [sortType, setSortType] = useState('name');
const { state, getPlaces, sortPlaces, deletePlace, filterPlaces } = useContext(Context);
const [showSearch, setShowSearch] = useState(false);
const [searchText, setSearchText] = useState('');
const updateSorting = () => {
const type = sortType === 'name' ? 'date' : 'name';
setSortType(type);
sortPlaces(type);
};
const deleteConfirmation = ( id ) => {
Alert.alert(
'Delete place?',
'Are you sure to delete this place?, you won\'t be able to recover it ',
[{
text: 'Cancel',
onPress: () => console.log('Deletion Canceled'),
style: 'cancel'
}, {
text: 'Yes',
onPress: () => deletePlace(id),
style: 'destructive'
}]
);
};
const filterResults = ( text ) => {
filterPlaces(text);
};
useEffect(() => {
navigation.setOptions({
headerRight: () => (
<CButton
title={'Nuevo'}
icon={'ios-add'}
color={'tomato'}
onPress={() => navigation.navigate('New Place')}
/>
),
headerLeft: () => (
<CButton
title={'search'}
icon={'ios-search'}
color={'tomato'}
onPress={() => {
setSearchText('');
setShowSearch(!showSearch);
}}
/>
)
});
getPlaces();
// navigation.addListener('didFocus', () => {
// getPlaces();
// });
}, [navigation, showSearch]);
return (
<View style={styles.container}>
{ showSearch ?
<SearchView
value={searchText}
setValue={setSearchText}
callback={(text) => filterResults(text)}
/>
:
null
}
{
(state.length > 0) ?
<View style={{flex: 1}}>
<FlatList
style={styles.list}
data={state}
keyExtractor={ place => place.id }
renderItem={({ item }) => {
return(
<TouchableOpacity
onPress={() => navigation.navigate('Detail', {id: item.id})}
>
<PlaceCell
place={item}
onDelete={() => {
deleteConfirmation(item.id);
}}
/>
</TouchableOpacity>
)
}}
/>
<View style={styles.bottom}>
<IconButton
title={`Sort by ${sortType}`}
icon={'ios-swap-vertical'}
onPress={updateSorting}
/>
</View>
</View>
:
<EmptyState />
}
</View>
);
};
const styles = StyleSheet.create({
container: {
flex: 1
},
list: {
marginBottom: 68
},
bottom: {
position: 'absolute',
bottom: 8,
width: '100%'
}
});
export default ListScreen;
\ No newline at end of file
import React, { useEffect, useState, useContext, createRef } from "react";
import { View, StyleSheet, Text } from "react-native";
import MapView, { Marker } from "react-native-maps";
import * as Location from 'expo-location';
import { Context } from "../context/PlaceContext";
import MButton from '../components/MButton';
import CardList from "../components/CardList";
const MapScreen = ({ navigation }) => {
const { state, getPlaces } = useContext(Context);
const mapRef = createRef();
const [location, setLocation] = useState(null);
const [hasLocationPermissions, setHasLocationPermissions] = useState(false);
const [mapRegion, setMapRegion] = useState(null);
const indexFor = ( item ) => {
return state.findIndex( place => place.id == item.id);
};
useEffect(() => {
(async () => {
getPlaces();
let { status } = await Location.requestForegroundPermissionsAsync();
if (status == 'granted') {
setHasLocationPermissions(true);
const loc = await Location.getCurrentPositionAsync();
console.log(loc);
setLocation(loc);
} else {
setHasLocationPermissions(false);
console.log('Permission not granted :(');
}
})();
}, []);
let markers = state.map((place) => {
return (
<Marker
title={place.name}
coordinate={{latitude: place.latitude, longitude: place.longitude}}
key={`map-${place.id}`}
onPress={() => navigation.navigate('Detail', {id: place.id})}
>
<View style={styles.pin}>
<Text style={styles.index}>{indexFor(place) + 1}</Text>
</View>
</Marker>
);
});
return (
<View style={styles.container}>
<MapView
style={styles.map}
mapRegion={mapRegion}
showsUserLocation={true}
ref={mapRef}
initialRegion={{
latitude: 19.4296568,
longitude: -99.2028261,
latitudeDelta: 0.02,
longitudeDelta: 0.02
}}
>
{ markers }
</MapView>
<View style={styles.topButtons}>
<MButton
icon={'ios-navigate-circle-sharp'}
onPress={() => {
mapRef.current.animateToRegion({
latitude: location.coords.latitude,
longitude: location.coords.longitude,
latitudeDelta: 0.02,
longitudeDelta: 0.02
})
}}
/>
</View>
<View style={styles.bottomList}>
<CardList
items={state}
navigation={navigation}
/>
</View>
</View>
);
};
const styles = StyleSheet.create({
container: {
flex: 1
},
map: {
width: '100%',
height: '100%'
},
topButtons: {
position: 'absolute',
top: 16,
margin: 16
},
bottomList: {
position: 'absolute',
bottom: 0,
height: 110
},
pin: {
backgroundColor: 'tomato',
width: 40,
height: 40,
borderRadius: 20,
justifyContent: 'center',
shadowOffset: {
width: 0,
height: 0
},
shadowOpacity: 0.5,
shadowRadius: 4
},
index: {
fontSize: 18,
fontWeight: 'bold',
color: 'white',
textAlign: 'center'
}
});
export default MapScreen;
\ No newline at end of file
import React, { useEffect, useState, useContext } from "react";
import { View, Text, StyleSheet, TextInput, Image } from "react-native";
import { Context } from "../context/PlaceContext";
import * as Location from 'expo-location';
import uuid from 'react-native-uuid';
import { Ionicons } from '@expo/vector-icons';
import LocationCell from "../components/LocationCell";
import IconButton from '../components/IconButton';
const NewPlace = ({ navigation }) => {
const { addPlace } = useContext(Context);
const [name, setName] = useState('');
const [image, setImage] = useState(null);
const [location, setLocation] = useState(null);
const [hasLocationPermissions, setHasLocationPermissions] = useState(false);
const pictureTaken = (image) => {
setImage(image);
};
const savePlace = () => {
if (hasLocationPermissions) {
if (name && name.trim().length > 0) {
if (image) {
if (location) {
console.log('Saving place...');
addPlace({
id: uuid.v4(),
name,
latitude: 19.284444, //location.coords.latitude,
longitude: -98.942530, //location.coords.longitude,
date: new Date().toLocaleString(),
photo: image
},
() => navigation.pop()
);
} else {
console.log('No location gotten');
alert('Please wait until we get your current location');
}
} else {
console.log('No picture taken');
alert('Please take a picture first');
}
} else {
console.log('No name entered');
alert('Please enter a name first');
}
} else {
console.log('No location permissions');
alert('We need location permissions to continue');
}
};
useEffect(() => {
(async () => {
let { status } = await Location.requestForegroundPermissionsAsync();
if (status == 'granted') {
setHasLocationPermissions(true);
const loc = await Location.getCurrentPositionAsync();
setLocation(loc);
} else {
setHasLocationPermissions(false);
console.log('Location Permissions Denied :(');
}
})();
}, []);
return (
<View>
<Text style={styles.title}>Register a new Place</Text>
<Text style={styles.hint}>Name</Text>
<TextInput
value={name}
autoCorrect={false}
style={styles.input}
autoCapitalize='sentences'
placeholder='Name for the place'
onChangeText={ (newName) => setName(newName) }
/>
{
location ?
<LocationCell
latitude={location.coords.latitude}
longitude={location.coords.longitude}
/>
:
<Text style={styles.message}>Getting location....</Text>
}
{
image ?
<Image source={{uri: image}} style={styles.image} />
:
<View style={styles.preview}>
<Ionicons name="ios-image" size={60} color="#BDBDBD" style={{alignSelf: 'center'}}/>
<Text style={styles.previewText}>The picture will be shown here</Text>
</View>
}
<IconButton
title={image ? 'Re-take picture' : 'Take a picture'}
icon={'ios-camera'}
color={'tomato'}
onPress={ () => navigation.navigate('Camera', { callback: pictureTaken })}
/>
<IconButton
title={'Save place'}
icon={'ios-save'}
onPress={ savePlace }
/>
</View>
);
};
const styles = StyleSheet.create({
view: {
backgroundColor: '#F1F1F1'
},
title: {
fontSize: 24,
fontWeight: 'bold',
alignSelf: 'center',
marginVertical: 8
},
message: {
margin: 16,
fontSize: 18,
color: '#BDBDBD',
alignSelf: 'center'
},
hint: {
marginHorizontal: 16,
marginTop: 8,
fontSize: 18,
color: '#1B1A28',
fontWeight: '600',
},
input: {
height: 46,
fontSize: 18,
borderWidth: 1,
borderRadius: 8,
color: '#1B1A28',
marginVertical: 8,
marginHorizontal: 16,
paddingHorizontal: 16,
borderColor: '#C7C7C7',
backgroundColor: 'white',
},
preview: {
width: 225,
height: 300,
borderWidth: 2,
borderRadius: 16,
marginVertical: 16,
alignSelf: 'center',
borderStyle: 'dashed',
borderColor: '#BDBDBD',
justifyContent: 'center'
},
previewText: {
margin: 16,
fontSize: 18,
color: '#BDBDBD',
textAlign: 'center'
},
image: {
width: 225,
height: 300,
borderRadius: 16,
marginVertical: 16,
alignSelf: 'center',
backgroundColor: 'white'
}
});
export default NewPlace;
\ No newline at end of file
Markdown is supported
0%
or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment