index.tsx 7.1 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246
  1. import {
  2. View,
  3. Platform,
  4. TouchableOpacity,
  5. Text
  6. } from 'react-native';
  7. import React, { useEffect, useState, useRef, useMemo } from 'react';
  8. import MapView, { UrlTile, Geojson } from 'react-native-maps';
  9. import * as turf from '@turf/turf';
  10. import * as FileSystem from 'expo-file-system';
  11. import MenuIcon from '../../../../assets/icons/menu.svg';
  12. import SearchIcon from '../../../../assets/icons/search.svg';
  13. import RadarIcon from '../../../../assets/icons/radar.svg';
  14. import LocationIcon from '../../../../assets/icons/location.svg';
  15. import CloseSvg from '../../../../assets/icons/close.svg';
  16. import regions from '../../../../assets/geojson/nm2022.json';
  17. import dareRegions from '../../../../assets/geojson/mqp.json';
  18. import NetInfo from "@react-native-community/netinfo";
  19. import { getFirstDatabase, getSecondDatabase } from '../../../db';
  20. import RegionPopup from '../../../components/RegionPopup';
  21. import { BottomTabNavigationProp } from '@react-navigation/bottom-tabs';
  22. import { styles } from './style';
  23. import { findRegionInDataset, calculateMapRegion } from '../../../utils/mapHelpers';
  24. import { getData } from '../../../modules/map/regionData';
  25. const tilesBaseURL = 'https://maps.nomadmania.com/tiles_osm';
  26. const localTileDir = `${FileSystem.cacheDirectory}tiles`;
  27. const gridUrl = 'https://maps.nomadmania.com/tiles_nm/grid';
  28. const localGridDir = `${FileSystem.cacheDirectory}tiles/grid`;
  29. const visitedTiles = 'https://maps.nomadmania.com/tiles_nm/user_visited/51363';
  30. const localVisitedDir = `${FileSystem.cacheDirectory}tiles/user_visited`;
  31. const dareTiles = 'https://maps.nomadmania.com/tiles_nm/regions_mqp';
  32. const localDareDir = `${FileSystem.cacheDirectory}tiles/regions_mqp`;
  33. interface Region {
  34. id: number;
  35. name: string;
  36. region_photos: string;
  37. visitors_count: number;
  38. }
  39. interface MapScreenProps {
  40. navigation: BottomTabNavigationProp<any>;
  41. }
  42. const MapScreen: React.FC<MapScreenProps> = ({ navigation }) => {
  43. const mapRef = useRef<MapView>(null);
  44. const [isConnected, setIsConnected] = useState<boolean | null>(true);
  45. const [selectedRegion, setSelectedRegion] = useState(null);
  46. const [popupVisible, setPopupVisible] = useState<boolean | null>(false);
  47. const [regionData, setRegionData] = useState<Region | null>(null);
  48. const [userAvatars, setUserAvatars] = useState<string[]>([]);
  49. useEffect(() => {
  50. navigation.setOptions({
  51. tabBarStyle: {
  52. display: popupVisible ? 'none' : 'flex',
  53. position: 'absolute',
  54. ...Platform.select({
  55. android: {
  56. height: 58,
  57. },
  58. }),
  59. }
  60. });
  61. }, [popupVisible, navigation]);
  62. const handleRegionData = (regionData: Region, avatars: string[]) => {
  63. setRegionData(regionData);
  64. setUserAvatars(avatars);
  65. };
  66. const handleMapPress = async (event: { nativeEvent: { coordinate: { latitude: any; longitude: any; }; }; }) => {
  67. const { latitude, longitude } = event.nativeEvent.coordinate;
  68. const point = turf.point([longitude, latitude]);
  69. setUserAvatars([]);
  70. let db = getSecondDatabase();
  71. let tableName = 'places';
  72. let foundRegion = findRegionInDataset(dareRegions, point);
  73. if (!foundRegion) {
  74. foundRegion = findRegionInDataset(regions, point);
  75. db = getFirstDatabase();
  76. tableName = 'regions';
  77. }
  78. if (foundRegion) {
  79. const id = foundRegion.properties?.id;
  80. setSelectedRegion({
  81. type: 'FeatureCollection',
  82. features: [{
  83. geometry: foundRegion.geometry,
  84. properties: {
  85. ...foundRegion.properties,
  86. fill: "rgba(57, 115, 172, 0.2)",
  87. stroke: "#3973AC",
  88. },
  89. type: 'Feature',
  90. }]
  91. });
  92. await getData(db, id, tableName, handleRegionData)
  93. .then(() => {
  94. setPopupVisible(true);
  95. })
  96. .catch(error => {
  97. console.error("Error fetching data", error);
  98. });
  99. const bounds = turf.bbox(foundRegion);
  100. const region = calculateMapRegion(bounds);
  101. mapRef.current?.animateToRegion(region, 1000);
  102. } else {
  103. handleClosePopup();
  104. }
  105. };
  106. useEffect(() => {
  107. const unsubscribe = NetInfo.addEventListener(state => {
  108. setIsConnected(state.isConnected);
  109. });
  110. return () => unsubscribe();
  111. }, []);
  112. const renderMapTiles = (
  113. url: string,
  114. cacheDir: string,
  115. zIndex: number,
  116. opacity = 1
  117. ) => (
  118. <UrlTile
  119. urlTemplate={`${url}/{z}/{x}/{y}`}
  120. maximumZ={15}
  121. maximumNativeZ={13}
  122. tileCachePath={`${cacheDir}`}
  123. shouldReplaceMapContent
  124. minimumZ={0}
  125. offlineMode={!isConnected}
  126. opacity={opacity}
  127. zIndex={zIndex}
  128. />
  129. );
  130. function renderGeoJSON() {
  131. if (!selectedRegion) return null;
  132. return (
  133. <Geojson
  134. geojson={selectedRegion}
  135. fillColor="rgba(57, 115, 172, 0.2)"
  136. strokeColor="#3973ac"
  137. strokeWidth={Platform.OS == 'android' ? 3 : 2}
  138. zIndex={3}
  139. />
  140. );
  141. };
  142. const handleClosePopup = () => {
  143. setPopupVisible(false);
  144. setSelectedRegion(null);
  145. }
  146. const renderedGeoJSON = useMemo(() => renderGeoJSON(), [selectedRegion]);
  147. return (
  148. <View style={styles.container}>
  149. <MapView
  150. ref={mapRef}
  151. showsMyLocationButton={false}
  152. showsCompass={false}
  153. zoomControlEnabled={false}
  154. onPress={handleMapPress}
  155. style={styles.map}
  156. mapType={Platform.OS == 'android' ? 'none' : 'standard'}
  157. offlineMode={!isConnected}
  158. maxZoomLevel={15}
  159. minZoomLevel={0}
  160. >
  161. {renderedGeoJSON}
  162. {renderMapTiles(tilesBaseURL, localTileDir, 1)}
  163. {renderMapTiles(gridUrl, localGridDir, 2)}
  164. {renderMapTiles(visitedTiles, localVisitedDir, 2, 0.5)}
  165. {renderMapTiles(dareTiles, localDareDir, 2, 0.5)}
  166. </MapView>
  167. {popupVisible && regionData ? (
  168. <>
  169. <TouchableOpacity
  170. style={[ styles.cornerButton, styles.topLeftButton, styles.closeLeftButton ]}
  171. onPress={handleClosePopup}
  172. >
  173. <CloseSvg
  174. fill="white"
  175. width={13}
  176. height={13}
  177. />
  178. <Text style={styles.textClose}>Close</Text>
  179. </TouchableOpacity>
  180. <TouchableOpacity style={[ styles.cornerButton, styles.topRightButton, styles.bottomButton ]}>
  181. <LocationIcon />
  182. </TouchableOpacity>
  183. <RegionPopup
  184. region={regionData}
  185. userAvatars={userAvatars}
  186. onMarkVisited={() => console.log('Mark as visited')}
  187. />
  188. </>
  189. ) : (
  190. <>
  191. <TouchableOpacity style={[styles.cornerButton, styles.topLeftButton]}>
  192. <MenuIcon />
  193. </TouchableOpacity>
  194. <TouchableOpacity style={[styles.cornerButton, styles.topRightButton]}>
  195. <SearchIcon />
  196. </TouchableOpacity>
  197. <TouchableOpacity style={[styles.cornerButton, styles.bottomButton, styles.bottomLeftButton]}>
  198. <RadarIcon />
  199. </TouchableOpacity>
  200. <TouchableOpacity style={[styles.cornerButton, styles.bottomButton, styles.bottomRightButton]}>
  201. <LocationIcon />
  202. </TouchableOpacity>
  203. </>
  204. )}
  205. </View>
  206. );
  207. }
  208. export default MapScreen;