Map.js 11 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335
  1. import React, { useEffect, useRef, useState } from 'react';
  2. import {Image, StyleSheet, View, TouchableOpacity, ScrollView, Text, Alert} from 'react-native';
  3. import MapboxGL from '@react-native-mapbox-gl/maps';
  4. import 'react-native-get-random-values'
  5. import { seedLandmarkData, useMockState } from '../contexts/MockContext';
  6. import Modal from 'react-native-modalbox';
  7. import LandmarkDetails from '../components/LandmarkDetails';
  8. import {Icons} from '../globals.js';
  9. import Icon from 'react-native-vector-icons/FontAwesome';
  10. import VoiceView from '../components/VoiceView';
  11. import RNLocation from 'react-native-location';
  12. import { polygon, point, booleanPointInPolygon } from '@turf/turf';
  13. import ModalDropdown from 'react-native-modal-dropdown';
  14. MapboxGL.setAccessToken('pk.eyJ1IjoiY2Rtb3NzIiwiYSI6ImNrbmhuOXJzcDIyd20ycW1pYm8xaGI0aGUifQ.j04Sp636N9Wg4N9j9t2tXw');
  15. RNLocation.configure({
  16. distanceFilter: 1
  17. });
  18. const places = {
  19. type: 'FeatureCollection',
  20. features: [
  21. {
  22. type: 'Feature',
  23. id: 'test1',
  24. properties: {
  25. name: 'University of Alberta'
  26. },
  27. geometry: {
  28. "type": "Polygon",
  29. "coordinates": [
  30. [
  31. [
  32. -113.53271484375,
  33. 53.517705925148846
  34. ],
  35. [
  36. -113.52018356323242,
  37. 53.515511461129655
  38. ],
  39. [
  40. -113.52044105529785,
  41. 53.52311504872178
  42. ],
  43. [
  44. -113.51529121398926,
  45. 53.522757863745845
  46. ],
  47. [
  48. -113.5151195526123,
  49. 53.52576832854355
  50. ],
  51. [
  52. -113.53134155273438,
  53. 53.53143150445749
  54. ],
  55. [
  56. -113.53271484375,
  57. 53.517705925148846
  58. ]
  59. ]
  60. ]
  61. }
  62. }
  63. ]
  64. }
  65. const Map = ({navigation, route}) => {
  66. const { dispatch, state } = useMockState();
  67. //const [center, setCenter] = useState([-113.4982408198804, 53.56484760459073]);
  68. const [center, setCenter] = useState([]);
  69. const [tempPoint, setTempPoint] = useState(null);
  70. const [selectedPlaces, setPlaces] = useState([]);
  71. const [voiceActive, setVoice] = useState(false);
  72. const landmarkAddModal = useRef();
  73. const mapMenuModal = useRef();
  74. const landmarkDetailsModal = useRef();
  75. const voiceModal = useRef();
  76. const map = useRef();
  77. useEffect(() => {
  78. // const unsubscribePermission = RNLocation.subscribeToPermissionUpdates(currentPermission => {
  79. // dispatch({ type:"UPDATE_LOCATION_PERMISSION", payload: currentPermission });
  80. // });
  81. // const unsubscribeLocation = RNLocation.subscribeToLocationUpdates(locations => {
  82. // dispatch({ type:"UPDATE_LOCATION", payload: locations });
  83. // });
  84. async function handleLocation() {
  85. if (state.landmarks == null) {
  86. const landmarks = seedLandmarkData();
  87. dispatch({ type:"UPDATE_LANDMARKS", payload: landmarks });
  88. }
  89. if (state.location == null && !state.locationPermission) {
  90. dispatch({type: 'UPDATE_LOCATION_PERMISSION', payload: await RNLocation.requestPermission({
  91. ios: "whenInUse",
  92. android: {
  93. detail: "fine",
  94. rationale: {
  95. title: "We need to access your location",
  96. message: "We use your location to provide convenient features such as geo-activated notifications and adding landmarks via voice",
  97. buttonPositive: "Grant permission",
  98. buttonNegative: "No thanks"
  99. }
  100. }
  101. })})
  102. }
  103. }
  104. handleLocation();
  105. return() => {
  106. // unsubscribePermission();
  107. // unsubscribeLocation();
  108. }
  109. },[state]);
  110. const changeSelectedLandmark = (landmark) => {
  111. dispatch({ type:"UPDATE_SELECTED_LANDMARK", payload: landmark});
  112. }
  113. const showLandmarkDetails = (landmark) => {
  114. changeSelectedLandmark(landmark);
  115. landmarkDetailsModal.current.open();
  116. }
  117. const openVoiceModal = () => {
  118. if (!state.locationPermission) {
  119. Alert.alert('You need to provide location permission to use this feature.')
  120. }
  121. else {
  122. RNLocation.getLatestLocation({timeout: 100}).then(location => {
  123. dispatch({ type:"UPDATE_LOCATION", payload: location});
  124. voiceModal.current.open();
  125. setVoice(true);
  126. })
  127. }
  128. }
  129. const handleMapTouch = (e) => {
  130. setTempPoint(e.geometry.coordinates);
  131. changeSelectedLandmark({
  132. longitude: e.geometry.coordinates[0],
  133. latitude: e.geometry.coordinates[1],
  134. icon: 'barrier',
  135. title: '',
  136. desc: ''
  137. })
  138. landmarkAddModal.current.open();
  139. }
  140. const handlePlaceTouch = (e) => {
  141. setTempPoint([e.coordinates.longitude, e.coordinates.latitude]);
  142. const touchedPlaces = e.features.filter(f => {
  143. return booleanPointInPolygon(point([e.coordinates.longitude, e.coordinates.latitude]), polygon(f.geometry.coordinates));
  144. });
  145. if (touchedPlaces.length == 0) {
  146. changeSelectedLandmark({
  147. longitude: e.coordinates.longitude,
  148. latitude: e.coordinates.latitude,
  149. icon: 'barrier',
  150. title: '',
  151. desc: ''
  152. });
  153. landmarkAddModal.current.open();
  154. }
  155. else {
  156. setPlaces(touchedPlaces);
  157. mapMenuModal.current.open();
  158. }
  159. }
  160. const addLandmark = () => {
  161. landmarkAddModal.current.close();
  162. navigation.navigate("LandmarkForm");
  163. }
  164. const removeTempPoint = () => {
  165. setTempPoint(null);
  166. }
  167. const editLandmark = () => {
  168. navigation.navigate("LandmarkForm");
  169. }
  170. const closeModal = (message) => {
  171. landmarkDetailsModal.current.close();
  172. if (message != null) {
  173. Alert.alert(message);
  174. }
  175. };
  176. const clearModals = () => {
  177. landmarkDetailsModal.current.close();
  178. voiceModal.current.close();
  179. landmarkAddModal.current.close();
  180. };
  181. return(
  182. <View style={styles.container}>
  183. <View style={styles.mapHeader}>
  184. <TouchableOpacity style={{width: '25%', height: '100%', justifyContent: 'center'}} onPress={() => {clearModals(); navigation.jumpTo('Account');}}>
  185. <Image style={{width: 40, height: 40, borderRadius: 100, marginLeft: 20}} source={require('../assets/default-pfp.png')}/>
  186. </TouchableOpacity>
  187. <Text style={{color: 'white', fontSize: 20, marginRight: 20}}>atlas</Text>
  188. </View>
  189. <View style={styles.mapContainer}>
  190. <MapboxGL.MapView
  191. ref={map}
  192. style={styles.mapbox}
  193. onPress={e => handleMapTouch(e)}
  194. // onLongPress={p => promptAddPlace(p.geometry.coordinates)}
  195. >
  196. <MapboxGL.UserLocation/>
  197. <MapboxGL.Camera followUserLocation followUserMode={'normal'} zoomLevel={9} centerCoordinate={center} />
  198. {tempPoint &&
  199. <MapboxGL.PointAnnotation id="temp" coordinate={tempPoint} />
  200. }
  201. {state.landmarks.map(landmark => {
  202. return (
  203. <MapboxGL.MarkerView key={landmark.id} id={landmark.id} coordinate={[landmark.longitude, landmark.latitude]} >
  204. <View style={{width: 25, height: 20}}>
  205. <TouchableOpacity onPress={() => showLandmarkDetails(landmark)} >
  206. <Image source={Icons[landmark.icon]} style={{width: 22, height: 30}} />
  207. </TouchableOpacity>
  208. </View>
  209. </MapboxGL.MarkerView>
  210. )})}
  211. <MapboxGL.ShapeSource onPress={e => handlePlaceTouch(e)} id="test" shape={places}>
  212. <MapboxGL.FillLayer id="test" style={{fillColor: 'black', fillOpacity: .3}}/>
  213. </MapboxGL.ShapeSource>
  214. </MapboxGL.MapView>
  215. <TouchableOpacity style={styles.micBtn} name="microphone" onPress={openVoiceModal}>
  216. <Icon name='microphone' size={20} color='white' />
  217. </TouchableOpacity>
  218. </View>
  219. {state.locationPermission ? <VoiceView ref={voiceModal} changeSelectedLandmark={changeSelectedLandmark} editLandmark={editLandmark} style={styles.voiceModal} onPress={openVoiceModal}/> : null}
  220. <Modal position={"bottom"} ref={landmarkDetailsModal} style={{height: 520}} backButtonClose={true} >
  221. <LandmarkDetails closeModal={closeModal} editLandmark={editLandmark}/>
  222. </Modal>
  223. <Modal position={"bottom"} ref={mapMenuModal} style={styles.menuModal} backButtonClose={true}>
  224. <Text style={styles.addTitle}>What would you like to do?</Text>
  225. <View style={{justifyContent: 'space-between', alignItems: 'flex-end',}}>
  226. <TouchableOpacity style={{marginRight: 30, marginTop: 20}} onPress={addLandmark}>
  227. <Text style={{color: 'white'}}>Add landmark</Text>
  228. </TouchableOpacity>
  229. <ModalDropdown style={{marginRight: 30, marginTop: 20}} defaultValue={selectedPlaces.map(p => p.properties.name)[0]} defaultIndex={0} options={selectedPlaces.map(p => p.properties.name)}/>
  230. <TouchableOpacity style={{marginRight: 30, marginTop: 20}} onPress={() => landmarkAddModal.current.close()}>
  231. <Text style={{color: 'white'}}>Cancel</Text>
  232. </TouchableOpacity>
  233. </View>
  234. </Modal>
  235. <Modal style={styles.addModal} position={"bottom"} ref={landmarkAddModal} onClosed={removeTempPoint} backButtonClose={true}>
  236. <Text style={styles.addTitle}>Add landmark here?</Text>
  237. <View style={{flexDirection: 'row', justifyContent: 'flex-end', alignItems: 'center', }}>
  238. <TouchableOpacity style={{marginRight: 40, height: 50}} onPress={() => landmarkAddModal.current.close()}>
  239. <Text style={{color: 'white'}}>Cancel</Text>
  240. </TouchableOpacity>
  241. <TouchableOpacity style={{marginRight: 40, height: 50}} onPress={addLandmark}>
  242. <Text style={{color: 'white'}}>Add</Text>
  243. </TouchableOpacity>
  244. </View>
  245. </Modal>
  246. </View>
  247. )
  248. }
  249. const styles = StyleSheet.create({
  250. container: {
  251. flex: 1
  252. },
  253. mapHeader: {
  254. justifyContent: 'space-between',
  255. flexDirection: 'row',
  256. alignItems: 'center',
  257. height: '9%',
  258. zIndex: 5,
  259. backgroundColor: '#df3f3f',
  260. },
  261. mapContainer: {
  262. height: '100%',
  263. width: '100%',
  264. backgroundColor: 'white',
  265. },
  266. markerContainer: {
  267. height: 50,
  268. width: 50,
  269. backgroundColor: 'white',
  270. },
  271. markerImg: {
  272. height: 25,
  273. width: 25
  274. },
  275. mapbox: {
  276. flex: 1,
  277. },
  278. micBtn: {
  279. position: 'absolute',
  280. backgroundColor: '#df3f3f',
  281. justifyContent: 'center',
  282. alignItems: 'center',
  283. borderRadius: 40,
  284. bottom: 70,
  285. right: 20,
  286. zIndex: 5,
  287. width: 60,
  288. height: 60,
  289. },
  290. menuModal: {
  291. backgroundColor: '#df3f3f',
  292. height: 200,
  293. },
  294. addModal: {
  295. backgroundColor: '#df3f3f',
  296. height: 100,
  297. },
  298. voiceModal: {
  299. backgroundColor: '#df3f3f',
  300. height: 200,
  301. },
  302. addTitle: {
  303. color: 'white',
  304. fontSize: 20,
  305. marginTop: 20,
  306. marginLeft: 20
  307. }
  308. })
  309. export default Map;