index.tsx 42 KB

12345678910111213141516171819202122232425262728293031323334353637383940414243444546474849505152535455565758596061626364656667686970717273747576777879808182838485868788899091929394959697989910010110210310410510610710810911011111211311411511611711811912012112212312412512612712812913013113213313413513613713813914014114214314414514614714814915015115215315415515615715815916016116216316416516616716816917017117217317417517617717817918018118218318418518618718818919019119219319419519619719819920020120220320420520620720820921021121221321421521621721821922022122222322422522622722822923023123223323423523623723823924024124224324424524624724824925025125225325425525625725825926026126226326426526626726826927027127227327427527627727827928028128228328428528628728828929029129229329429529629729829930030130230330430530630730830931031131231331431531631731831932032132232332432532632732832933033133233333433533633733833934034134234334434534634734834935035135235335435535635735835936036136236336436536636736836937037137237337437537637737837938038138238338438538638738838939039139239339439539639739839940040140240340440540640740840941041141241341441541641741841942042142242342442542642742842943043143243343443543643743843944044144244344444544644744844945045145245345445545645745845946046146246346446546646746846947047147247347447547647747847948048148248348448548648748848949049149249349449549649749849950050150250350450550650750850951051151251351451551651751851952052152252352452552652752852953053153253353453553653753853954054154254354454554654754854955055155255355455555655755855956056156256356456556656756856957057157257357457557657757857958058158258358458558658758858959059159259359459559659759859960060160260360460560660760860961061161261361461561661761861962062162262362462562662762862963063163263363463563663763863964064164264364464564664764864965065165265365465565665765865966066166266366466566666766866967067167267367467567667767867968068168268368468568668768868969069169269369469569669769869970070170270370470570670770870971071171271371471571671771871972072172272372472572672772872973073173273373473573673773873974074174274374474574674774874975075175275375475575675775875976076176276376476576676776876977077177277377477577677777877978078178278378478578678778878979079179279379479579679779879980080180280380480580680780880981081181281381481581681781881982082182282382482582682782882983083183283383483583683783883984084184284384484584684784884985085185285385485585685785885986086186286386486586686786886987087187287387487587687787887988088188288388488588688788888989089189289389489589689789889990090190290390490590690790890991091191291391491591691791891992092192292392492592692792892993093193293393493593693793893994094194294394494594694794894995095195295395495595695795895996096196296396496596696796896997097197297397497597697797897998098198298398498598698798898999099199299399499599699799899910001001100210031004100510061007100810091010101110121013101410151016101710181019102010211022102310241025102610271028102910301031103210331034103510361037103810391040104110421043104410451046104710481049105010511052105310541055105610571058105910601061106210631064106510661067106810691070107110721073107410751076107710781079108010811082108310841085108610871088108910901091109210931094109510961097109810991100110111021103110411051106110711081109111011111112111311141115111611171118111911201121112211231124112511261127112811291130113111321133113411351136113711381139114011411142114311441145114611471148114911501151115211531154115511561157115811591160116111621163116411651166116711681169117011711172117311741175117611771178117911801181118211831184118511861187118811891190119111921193119411951196119711981199120012011202120312041205120612071208120912101211121212131214121512161217121812191220122112221223122412251226122712281229123012311232123312341235123612371238123912401241124212431244124512461247124812491250125112521253125412551256125712581259126012611262126312641265126612671268126912701271127212731274127512761277127812791280128112821283128412851286128712881289129012911292129312941295129612971298129913001301130213031304130513061307130813091310131113121313131413151316131713181319132013211322132313241325132613271328132913301331133213331334133513361337133813391340134113421343134413451346134713481349135013511352135313541355135613571358135913601361136213631364136513661367136813691370137113721373137413751376
  1. import {
  2. Dimensions,
  3. Linking,
  4. Platform,
  5. Text,
  6. TextInput,
  7. TouchableOpacity,
  8. View,
  9. Image,
  10. StatusBar
  11. } from 'react-native';
  12. import React, { useEffect, useRef, useState, useCallback } from 'react';
  13. import MapLibreGL, { CameraRef, MapViewRef } from '@maplibre/maplibre-react-native';
  14. import { styles } from './style';
  15. import { SafeAreaView } from 'react-native-safe-area-context';
  16. import { Colors } from 'src/theme';
  17. import { storage, StoreType } from 'src/storage';
  18. import { RegionPayload } from '@maplibre/maplibre-react-native/javascript/components/MapView';
  19. import * as turf from '@turf/turf';
  20. import * as Location from 'expo-location';
  21. import SearchIcon from 'assets/icons/search.svg';
  22. import LocationIcon from 'assets/icons/location.svg';
  23. import CloseSvg from 'assets/icons/close.svg';
  24. import FilterIcon from 'assets/icons/filter.svg';
  25. import ProfileIcon from 'assets/icons/bottom-navigation/profile.svg';
  26. import RegionPopup from 'src/components/RegionPopup';
  27. import { useRegion } from 'src/contexts/RegionContext';
  28. import { qualityOptions } from '../TravelsScreen/utils/constants';
  29. import { AvatarWithInitials, EditNmModal, WarningModal } from 'src/components';
  30. import { API_HOST, MAP_HOST, VECTOR_MAP_HOST } from 'src/constants';
  31. import { NAVIGATION_PAGES } from 'src/types';
  32. import Animated, {
  33. Easing,
  34. useAnimatedStyle,
  35. useSharedValue,
  36. withTiming
  37. } from 'react-native-reanimated';
  38. import { getData } from 'src/modules/map/regionData';
  39. import {
  40. getCountriesDatabase,
  41. getFirstDatabase,
  42. getSecondDatabase,
  43. refreshDatabases
  44. } from 'src/db';
  45. import { fetchUserData, fetchUserDataDare, useGetListRegionsQuery } from '@api/regions';
  46. import { SQLiteDatabase } from 'expo-sqlite/legacy';
  47. import { useFocusEffect } from '@react-navigation/native';
  48. import { useGetUniversalSearch } from '@api/search';
  49. import { fetchCountryUserData, useGetListCountriesQuery } from '@api/countries';
  50. import SearchModal from './UniversalSearch';
  51. import EditModal from '../TravelsScreen/Components/EditSlowModal';
  52. import CheckSvg from 'assets/icons/mark.svg';
  53. import moment from 'moment';
  54. import {
  55. usePostGetVisitedCountriesIdsQuery,
  56. usePostGetVisitedDareIdsQuery,
  57. usePostGetVisitedRegionsIdsQuery,
  58. usePostGetVisitedSeriesIdsQuery
  59. } from '@api/maps';
  60. import FilterModal from './FilterModal';
  61. import { useGetListDareQuery } from '@api/myDARE';
  62. import { useGetIconsQuery, usePostSetToggleItem } from '@api/series';
  63. import MarkerItem from './MarkerItem';
  64. import { usePostGetUsersLocationQuery, usePostUpdateLocationMutation } from '@api/location';
  65. MapLibreGL.setAccessToken(null);
  66. MapLibreGL.Logger.setLogLevel('error');
  67. const generateFilter = (ids: number[]) => {
  68. return ids?.length ? ['any', ...ids.map((id) => ['==', 'id', id])] : ['==', 'id', -1];
  69. };
  70. // to do refactor
  71. let regions_visited = {
  72. id: 'regions_visited',
  73. type: 'fill',
  74. source: 'regions',
  75. 'source-layer': 'regions',
  76. style: {
  77. fillColor: 'rgba(255, 126, 0, 1)',
  78. fillOpacity: 0.6,
  79. fillOutlineColor: 'rgba(14, 80, 109, 1)'
  80. },
  81. filter: generateFilter([]),
  82. maxzoom: 12
  83. };
  84. let countries_visited = {
  85. id: 'countries_visited',
  86. type: 'fill',
  87. source: 'countries',
  88. 'source-layer': 'countries',
  89. style: {
  90. fillColor: 'rgba(255, 126, 0, 1)',
  91. fillOpacity: 0.6,
  92. fillOutlineColor: 'rgba(14, 80, 109, 1)'
  93. },
  94. filter: generateFilter([]),
  95. maxzoom: 10
  96. };
  97. let dare_visited = {
  98. id: 'dare_visited',
  99. type: 'fill',
  100. source: 'dare',
  101. 'source-layer': 'dare',
  102. style: {
  103. fillColor: 'rgba(255, 126, 0, 1)',
  104. fillOpacity: 0.6,
  105. fillOutlineColor: 'rgba(255, 126, 0, 1)'
  106. },
  107. filter: generateFilter([]),
  108. maxzoom: 12
  109. };
  110. let regions = {
  111. id: 'regions',
  112. type: 'fill',
  113. source: 'regions',
  114. 'source-layer': 'regions',
  115. style: {
  116. fillColor: 'rgba(15, 63, 79, 0)',
  117. fillOutlineColor: 'rgba(14, 80, 109, 1)'
  118. },
  119. filter: ['all'],
  120. maxzoom: 16
  121. };
  122. let countries = {
  123. id: 'countries',
  124. type: 'fill',
  125. source: 'countries',
  126. 'source-layer': 'countries',
  127. style: {
  128. fillColor: 'rgba(15, 63, 79, 0)',
  129. fillOutlineColor: 'rgba(14, 80, 109, 1)'
  130. },
  131. filter: ['all'],
  132. maxzoom: 16
  133. };
  134. let dare = {
  135. id: 'dare',
  136. type: 'fill',
  137. source: 'dare',
  138. 'source-layer': 'dare',
  139. style: {
  140. fillColor: 'rgba(14, 80, 109, 0.6)',
  141. fillOutlineColor: 'rgba(14, 80, 109, 1)'
  142. },
  143. filter: ['all'],
  144. maxzoom: 16
  145. };
  146. let selected_region = {
  147. id: 'selected_region',
  148. type: 'fill',
  149. source: 'regions',
  150. 'source-layer': 'regions',
  151. style: {
  152. fillColor: 'rgba(57, 115, 172, 0.3)',
  153. fillOutlineColor: '#3973ac'
  154. },
  155. maxzoom: 12
  156. };
  157. let series_layer = {
  158. id: 'series_layer',
  159. type: 'symbol',
  160. source: 'nomadmania_series',
  161. 'source-layer': 'series',
  162. layout: {
  163. 'icon-image': '{series_id}',
  164. 'icon-size': 0.1,
  165. 'text-anchor': 'top',
  166. 'text-field': '{series_name} - {name}',
  167. 'text-font': ['Noto Sans Regular'],
  168. 'text-max-width': 9,
  169. 'text-offset': [0, 0.6],
  170. 'text-padding': 2,
  171. 'text-size': 12,
  172. visibility: 'visible',
  173. 'text-optional': true,
  174. 'text-ignore-placement': true,
  175. 'text-allow-overlap': true
  176. },
  177. paint: {
  178. 'text-color': '#666',
  179. 'text-halo-blur': 0.5,
  180. 'text-halo-color': '#ffffff',
  181. 'text-halo-width': 1
  182. },
  183. filter: generateFilter([])
  184. };
  185. let series_visited = {
  186. id: 'series_visited',
  187. type: 'symbol',
  188. source: 'nomadmania_series',
  189. 'source-layer': 'series',
  190. layout: {
  191. 'icon-image': '{series_id}v',
  192. 'icon-size': 0.15,
  193. 'text-anchor': 'top',
  194. 'text-field': '{series_name} - {name}',
  195. 'text-font': ['Noto Sans Regular'],
  196. 'text-max-width': 9,
  197. 'text-offset': [0, 0.6],
  198. 'text-padding': 2,
  199. 'text-size': 12,
  200. visibility: 'visible',
  201. 'text-optional': true,
  202. 'text-ignore-placement': true,
  203. 'text-allow-overlap': true
  204. },
  205. paint: {
  206. 'text-color': '#666',
  207. 'text-halo-blur': 0.5,
  208. 'text-halo-color': '#ffffff',
  209. 'text-halo-width': 1
  210. },
  211. filter: generateFilter([])
  212. };
  213. const INITIAL_REGION = {
  214. latitude: 0,
  215. longitude: 0,
  216. latitudeDelta: 180,
  217. longitudeDelta: 180
  218. };
  219. const MapScreen: any = ({ navigation, route }: { navigation: any; route: any }) => {
  220. const userId = storage.get('uid', StoreType.STRING) as string;
  221. const token = storage.get('token', StoreType.STRING) as string;
  222. const { data: regionsList } = useGetListRegionsQuery(true);
  223. const { data: countriesList } = useGetListCountriesQuery(true);
  224. const { data: dareList } = useGetListDareQuery(true);
  225. const [tilesType, setTilesType] = useState({ label: 'NM regions', value: 0 });
  226. const tilesTypes = [
  227. { label: 'NM regions', value: 0 },
  228. { label: 'UN countries', value: 1 },
  229. { label: 'DARE places', value: 2 }
  230. ];
  231. const [type, setType] = useState<'regions' | 'countries' | 'dare'>('regions');
  232. const [seriesFilter, setSeriesFilter] = useState<any>({
  233. visible: true,
  234. groups: [],
  235. applied: false,
  236. status: -1
  237. });
  238. const [regionsFilter, setRegionsFilter] = useState<any>({
  239. visitedLabel: 'by',
  240. year: moment().year()
  241. });
  242. const [showNomads, setShowNomads] = useState(
  243. (storage.get('showNomads', StoreType.BOOLEAN) as boolean) ?? false
  244. );
  245. const { mutateAsync: updateLocation } = usePostUpdateLocationMutation();
  246. const { data: visitedRegionIds, refetch: refetchVisitedRegions } =
  247. usePostGetVisitedRegionsIdsQuery(
  248. token,
  249. regionsFilter.visitedLabel,
  250. regionsFilter.year,
  251. +userId,
  252. type === 'regions' && !!userId
  253. );
  254. const { data: visitedCountryIds, refetch: refetchVisitedCountries } =
  255. usePostGetVisitedCountriesIdsQuery(
  256. token,
  257. regionsFilter.visitedLabel,
  258. regionsFilter.year,
  259. +userId,
  260. type === 'countries' && !!userId
  261. );
  262. const { data: visitedDareIds, refetch: refetchVisitedDare } = usePostGetVisitedDareIdsQuery(
  263. token,
  264. +userId,
  265. type === 'dare' && !!userId
  266. );
  267. const { data: visitedSeriesIds } = usePostGetVisitedSeriesIdsQuery(token, !!userId);
  268. const { data: seriesIcons } = useGetIconsQuery(true);
  269. const userInfo = storage.get('currentUserData', StoreType.STRING) as string;
  270. const { mutateAsync: mutateUserData } = fetchUserData();
  271. const { mutateAsync: mutateUserDataDare } = fetchUserDataDare();
  272. const { mutateAsync: mutateCountriesData } = fetchCountryUserData();
  273. const [selectedRegion, setSelectedRegion] = useState<number | null>(null);
  274. const [initialRegion, setInitialRegion] = useState(INITIAL_REGION);
  275. const [regionPopupVisible, setRegionPopupVisible] = useState<boolean | null>(false);
  276. const [regionData, setRegionData] = useState<any | null>(null);
  277. const [location, setLocation] = useState<any | null>(null);
  278. const [userAvatars, setUserAvatars] = useState<string[]>([]);
  279. const [userInfoData, setUserInfoData] = useState<any>(null);
  280. const [selectedMarker, setSelectedMarker] = useState<any>(null);
  281. const [askLocationVisible, setAskLocationVisible] = useState<boolean>(false);
  282. const [openSettingsVisible, setOpenSettingsVisible] = useState<boolean>(false);
  283. const [isWarningModalVisible, setIsWarningModalVisible] = useState<boolean>(false);
  284. const [isEditSlowModalVisible, setIsEditSlowModalVisible] = useState<boolean>(false);
  285. const [isEditModalVisible, setIsEditModalVisible] = useState(false);
  286. const [isFilterVisible, setIsFilterVisible] = useState(false);
  287. const [modalState, setModalState] = useState({
  288. selectedFirstYear: 2021,
  289. selectedLastYear: 2021,
  290. selectedQuality: qualityOptions[2],
  291. selectedNoOfVisits: 1,
  292. years: [],
  293. id: null
  294. });
  295. const [isConnected, setIsConnected] = useState<boolean | null>(true);
  296. const [isExpanded, setIsExpanded] = useState(false);
  297. const [search, setSearch] = useState('');
  298. const { data: searchData } = useGetUniversalSearch(search, search.length > 0);
  299. const [searchInput, setSearchInput] = useState('');
  300. const [searchVisible, setSearchVisible] = useState<boolean>(false);
  301. const [index, setIndex] = useState<number>(0);
  302. const width = useSharedValue(48);
  303. const usableWidth = Dimensions.get('window').width - 32;
  304. const { handleUpdateNM, handleUpdateDare, handleUpdateSlow, userData, setUserData } = useRegion();
  305. const [db1, setDb1] = useState<SQLiteDatabase | null>(null);
  306. const [db2, setDb2] = useState<SQLiteDatabase | null>(null);
  307. const [db3, setDb3] = useState<SQLiteDatabase | null>(null);
  308. const [regionsVisitedFilter, setRegionsVisitedFilter] = useState(generateFilter([]));
  309. const [countriesVisitedFilter, setCountriesVisitedFilter] = useState(generateFilter([]));
  310. const [dareVisitedFilter, setDareVisitedFilter] = useState(generateFilter([]));
  311. const [seriesVisitedFilter, setSeriesVisitedFilter] = useState(generateFilter([]));
  312. const [seriesNotVisitedFilter, setSeriesNotVisitedFilter] = useState(generateFilter([]));
  313. const [regionsVisited, setRegionsVisited] = useState<any[]>([]);
  314. const [countriesVisited, setCountriesVisited] = useState<any[]>([]);
  315. const [dareVisited, setDareVisited] = useState<any[]>([]);
  316. const [seriesVisited, setSeriesVisited] = useState<any[]>([]);
  317. const [images, setImages] = useState<any>({});
  318. const { mutateAsync: updateSeriesItem } = usePostSetToggleItem();
  319. const [nomads, setNomads] = useState<GeoJSON.FeatureCollection | null>(null);
  320. const { data: usersLocation } = usePostGetUsersLocationQuery(
  321. token,
  322. !!token && showNomads && Boolean(location)
  323. );
  324. useEffect(() => {
  325. if (!showNomads) {
  326. setNomads(null);
  327. }
  328. }, [showNomads]);
  329. useEffect(() => {
  330. if (usersLocation) {
  331. console.log('usersLocation', usersLocation);
  332. setNomads(usersLocation.geojson);
  333. }
  334. }, [usersLocation]);
  335. useEffect(() => {
  336. let loadedImages: any = {};
  337. if (seriesIcons) {
  338. seriesIcons.data.forEach(async (icon) => {
  339. const id = icon.id;
  340. const img = API_HOST + '/static/img/series_new2/' + icon.new_icon_png;
  341. const imgVisited = API_HOST + '/static/img/series_new2/' + icon.new_icon_visited_png;
  342. if (!img || !imgVisited) return;
  343. try {
  344. const iconImage = { uri: img };
  345. const visitedIconImage = { uri: imgVisited };
  346. loadedImages[id] = iconImage;
  347. loadedImages[`${id}v`] = visitedIconImage;
  348. } catch (error) {
  349. console.error(`Error loading icon for series_id ${id}:`, error);
  350. }
  351. });
  352. }
  353. if (nomads && nomads.features) {
  354. nomads.features.forEach((feature) => {
  355. const user_id = `user_${feature.properties?.id}`;
  356. const avatarUrl = `${API_HOST}${feature.properties?.avatar}`;
  357. if (avatarUrl) {
  358. loadedImages[user_id] = { uri: avatarUrl };
  359. if (feature.properties) {
  360. feature.properties.icon_key = user_id;
  361. }
  362. }
  363. });
  364. }
  365. setImages(loadedImages);
  366. }, [nomads, seriesIcons]);
  367. useEffect(() => {
  368. const loadDatabases = async () => {
  369. const firstDb = await getFirstDatabase();
  370. const secondDb = await getSecondDatabase();
  371. const countriesDb = await getCountriesDatabase();
  372. setDb1(firstDb);
  373. setDb2(secondDb);
  374. setDb3(countriesDb);
  375. };
  376. if (!db1 || !db2 || !db3) {
  377. loadDatabases();
  378. }
  379. }, [db1, db2, db3]);
  380. useEffect(() => {
  381. const savedFilterSettings = storage.get('filterSettings', StoreType.STRING) as string;
  382. if (savedFilterSettings) {
  383. const filterSettings = JSON.parse(savedFilterSettings);
  384. setTilesType(filterSettings.tilesType);
  385. setType(filterSettings.type);
  386. setRegionsFilter({
  387. visitedLabel: filterSettings.selectedVisible.value === 1 ? 'in' : 'by',
  388. year: filterSettings.selectedYear.value
  389. });
  390. setSeriesFilter(filterSettings.seriesFilter);
  391. }
  392. }, []);
  393. useFocusEffect(
  394. useCallback(() => {
  395. if (token) {
  396. refetchVisitedRegions();
  397. refetchVisitedCountries();
  398. refetchVisitedDare();
  399. }
  400. }, [navigation])
  401. );
  402. // to do refactor
  403. useEffect(() => {
  404. if (visitedRegionIds) {
  405. setRegionsVisited(visitedRegionIds.ids);
  406. }
  407. }, [visitedRegionIds]);
  408. useEffect(() => {
  409. if (visitedCountryIds) {
  410. setCountriesVisited(visitedCountryIds.ids);
  411. }
  412. }, [visitedCountryIds]);
  413. useEffect(() => {
  414. if (visitedDareIds) {
  415. setDareVisited(visitedDareIds.ids);
  416. }
  417. }, [visitedDareIds]);
  418. useEffect(() => {
  419. if (visitedSeriesIds && token) {
  420. setSeriesVisited(visitedSeriesIds.ids);
  421. }
  422. }, [visitedSeriesIds]);
  423. useEffect(() => {
  424. if (regionsVisited && regionsVisited.length) {
  425. setRegionsVisitedFilter(generateFilter(regionsVisited));
  426. }
  427. }, [regionsVisited]);
  428. useEffect(() => {
  429. if (countriesVisited && countriesVisited.length) {
  430. setCountriesVisitedFilter(generateFilter(countriesVisited));
  431. }
  432. }, [countriesVisited]);
  433. useEffect(() => {
  434. if (dareVisited && dareVisited.length) {
  435. setDareVisitedFilter(generateFilter(dareVisited));
  436. }
  437. }, [dareVisited]);
  438. useEffect(() => {
  439. if (!seriesFilter.visible) {
  440. setSeriesVisitedFilter(generateFilter([]));
  441. setSeriesNotVisitedFilter(generateFilter([]));
  442. return;
  443. }
  444. if (seriesFilter.applied) {
  445. if (seriesVisited?.length) {
  446. setSeriesVisitedFilter([
  447. 'all',
  448. ['any', ...seriesVisited.map((id) => ['==', 'id', id])],
  449. ['any', ...seriesFilter.groups.map((groupId: number) => ['==', 'series_id', groupId])]
  450. ]);
  451. setSeriesNotVisitedFilter([
  452. 'all',
  453. ['all', ...seriesVisited.map((id) => ['!=', 'id', id])],
  454. ['any', ...seriesFilter.groups.map((groupId: number) => ['==', 'series_id', groupId])]
  455. ]);
  456. } else {
  457. setSeriesNotVisitedFilter([
  458. 'any',
  459. ...seriesFilter.groups.map((groupId: number) => ['==', 'series_id', groupId])
  460. ]);
  461. }
  462. } else {
  463. setSeriesVisitedFilter(['any', ...seriesVisited.map((id) => ['==', 'id', id])]);
  464. setSeriesNotVisitedFilter(['all', ...seriesVisited.map((id) => ['!=', 'id', id])]);
  465. }
  466. }, [seriesVisited, seriesFilter]);
  467. useEffect(() => {
  468. if (route.params?.id && route.params?.type && db1 && db2 && db3) {
  469. handleFindRegion(route.params?.id, route.params?.type);
  470. }
  471. }, [route, db1, db2, db3]);
  472. useEffect(() => {
  473. if (selectedRegion) {
  474. mapRef.current;
  475. }
  476. }, [selectedRegion]);
  477. useEffect(() => {
  478. (async () => {
  479. let { status } = await Location.getForegroundPermissionsAsync();
  480. if (status !== 'granted') {
  481. setShowNomads(false);
  482. storage.set('showNomads', false);
  483. return;
  484. }
  485. let currentLocation = await Location.getCurrentPositionAsync({
  486. accuracy: Location.Accuracy.Balanced
  487. });
  488. setLocation(currentLocation.coords);
  489. updateLocation({
  490. token,
  491. lat: currentLocation.coords.latitude,
  492. lng: currentLocation.coords.longitude
  493. });
  494. })();
  495. }, []);
  496. useEffect(() => {
  497. const currentYear = moment().year();
  498. let yearSelector: { label: string; value: number }[] = [{ label: 'visited', value: 1 }];
  499. for (let i = currentYear; i >= 1951; i--) {
  500. yearSelector.push({ label: i.toString(), value: i });
  501. }
  502. handleModalStateChange({ years: yearSelector });
  503. }, []);
  504. useFocusEffect(
  505. useCallback(() => {
  506. navigation.getParent()?.setOptions({
  507. tabBarStyle: {
  508. display: regionPopupVisible ? 'none' : 'flex',
  509. position: 'absolute',
  510. ...Platform.select({
  511. android: {
  512. height: 58
  513. }
  514. })
  515. }
  516. });
  517. }, [regionPopupVisible, navigation])
  518. );
  519. const mapRef = useRef<MapViewRef>(null);
  520. const cameraRef = useRef<CameraRef>(null);
  521. useEffect(() => {
  522. if (userInfo) {
  523. setUserInfoData(JSON.parse(userInfo));
  524. }
  525. }, [userInfo]);
  526. const handlePress = () => {
  527. if (isExpanded) {
  528. setIndex(0);
  529. setSearchInput('');
  530. }
  531. setIsExpanded((prev) => !prev);
  532. width.value = withTiming(isExpanded ? 48 : usableWidth, {
  533. duration: 300,
  534. easing: Easing.inOut(Easing.ease)
  535. });
  536. };
  537. const animatedStyle = useAnimatedStyle(() => {
  538. return {
  539. width: width.value
  540. };
  541. });
  542. const loadInitialRegion = () => {
  543. try {
  544. const savedInitialRegion = storage.get('initialRegion', StoreType.STRING) as string;
  545. if (savedInitialRegion) {
  546. const region = JSON.parse(savedInitialRegion);
  547. setInitialRegion(region);
  548. }
  549. } catch (e) {
  550. console.error('Failed to load saved initial region:', e);
  551. }
  552. };
  553. useEffect(() => {
  554. loadInitialRegion();
  555. }, []);
  556. useEffect(() => {
  557. if (initialRegion && !route.params?.id) {
  558. setTimeout(() => {
  559. cameraRef.current?.setCamera({
  560. centerCoordinate: [initialRegion.longitude, initialRegion.latitude],
  561. zoomLevel: Math.log2(360 / initialRegion.latitudeDelta),
  562. animationDuration: 500
  563. });
  564. }, 500);
  565. }
  566. }, [initialRegion]);
  567. const onMapPress = async (event: any) => {
  568. if (!mapRef.current) return;
  569. if (selectedMarker) {
  570. closeCallout();
  571. return;
  572. }
  573. try {
  574. const { screenPointX, screenPointY } = event.properties;
  575. const { features } = await mapRef.current.queryRenderedFeaturesAtPoint(
  576. [screenPointX, screenPointY],
  577. undefined,
  578. ['regions', 'countries', 'dare']
  579. );
  580. if (features?.length) {
  581. const region = features[0];
  582. if (selectedRegion === region.properties?.id) return;
  583. let db = type === 'regions' ? db1 : type === 'countries' ? db3 : db2;
  584. let tableName = type === 'dare' ? 'places' : type;
  585. let foundRegion = region.properties?.id;
  586. setSelectedRegion(region.properties?.id);
  587. await getData(db, foundRegion, tableName, handleRegionData)
  588. .then(() => {
  589. setRegionPopupVisible(true);
  590. })
  591. .catch((error) => {
  592. console.error('Error fetching data', error);
  593. refreshDatabases();
  594. });
  595. if (tableName === 'regions') {
  596. token
  597. ? await mutateUserData(
  598. { region_id: +foundRegion, token: String(token) },
  599. {
  600. onSuccess: (data) => {
  601. setUserData({ type: 'nm', id: +foundRegion, ...data });
  602. }
  603. }
  604. )
  605. : setUserData({ type: 'nm', id: +foundRegion });
  606. if (regionsList) {
  607. const region = regionsList.data.find((region) => region.id === +foundRegion);
  608. if (region) {
  609. const bounds = turf.bbox(region.bbox);
  610. cameraRef.current?.fitBounds(
  611. [bounds[2], bounds[3]],
  612. [bounds[0], bounds[1]],
  613. [10, 10, 50, 10],
  614. 1000
  615. );
  616. }
  617. }
  618. } else if (tableName === 'countries') {
  619. token
  620. ? await mutateCountriesData(
  621. { id: +foundRegion, token },
  622. {
  623. onSuccess: (data) => {
  624. setUserData({ type: 'countries', id: +foundRegion, ...data.data });
  625. }
  626. }
  627. )
  628. : setUserData({ type: 'countries', id: +foundRegion });
  629. if (countriesList) {
  630. const region = countriesList.data.find((region) => region.id === +foundRegion);
  631. if (region) {
  632. const bounds = turf.bbox(region.bbox);
  633. cameraRef.current?.fitBounds(
  634. [bounds[2], bounds[3]],
  635. [bounds[0], bounds[1]],
  636. [10, 10, 50, 10],
  637. 1000
  638. );
  639. }
  640. }
  641. } else {
  642. token
  643. ? await mutateUserDataDare(
  644. { dare_id: +foundRegion, token: String(token) },
  645. {
  646. onSuccess: (data) => {
  647. setUserData({ type: 'dare', id: +foundRegion, ...data });
  648. }
  649. }
  650. )
  651. : setUserData({ type: 'dare', id: +foundRegion });
  652. if (dareList) {
  653. const region = dareList.data.find((region) => region.id === +foundRegion);
  654. if (region) {
  655. const bounds = turf.bbox(region.bbox);
  656. cameraRef.current?.fitBounds(
  657. [bounds[2], bounds[3]],
  658. [bounds[0], bounds[1]],
  659. [10, 10, 50, 10],
  660. 1000
  661. );
  662. }
  663. }
  664. }
  665. } else {
  666. handleClosePopup();
  667. }
  668. } catch (error) {
  669. console.error('Error onMapPress features:', error);
  670. }
  671. };
  672. const handleRegionDidChange = async (feature: GeoJSON.Feature<GeoJSON.Point, RegionPayload>) => {
  673. if (!feature) return;
  674. const { zoomLevel } = feature.properties;
  675. const { coordinates } = feature.geometry;
  676. if (!zoomLevel || !coordinates) return;
  677. const latitudeDelta = 360 / 2 ** zoomLevel;
  678. const longitudeDelta = latitudeDelta;
  679. const region = {
  680. latitude: coordinates[1],
  681. longitude: coordinates[0],
  682. latitudeDelta,
  683. longitudeDelta
  684. };
  685. storage.set('initialRegion', JSON.stringify(region));
  686. };
  687. const handleClosePopup = async () => {
  688. setSelectedRegion(null);
  689. setRegionPopupVisible(false);
  690. setRegionData(null);
  691. };
  692. const handleGetLocation = async () => {
  693. let { status, canAskAgain } = await Location.getForegroundPermissionsAsync();
  694. if (status === 'granted') {
  695. getLocation();
  696. } else if (!canAskAgain) {
  697. setOpenSettingsVisible(true);
  698. } else {
  699. setAskLocationVisible(true);
  700. }
  701. };
  702. const getLocation = async () => {
  703. let currentLocation = await Location.getCurrentPositionAsync({
  704. accuracy: Location.Accuracy.Balanced
  705. });
  706. setLocation(currentLocation.coords);
  707. updateLocation({
  708. token,
  709. lat: currentLocation.coords.latitude,
  710. lng: currentLocation.coords.longitude
  711. });
  712. if (currentLocation.coords) {
  713. cameraRef.current?.flyTo(
  714. [currentLocation.coords.longitude, currentLocation.coords.latitude],
  715. 1000
  716. );
  717. }
  718. handleClosePopup();
  719. };
  720. const handleAcceptPermission = async () => {
  721. setAskLocationVisible(false);
  722. let { status, canAskAgain } = await Location.requestForegroundPermissionsAsync();
  723. if (status === 'granted') {
  724. getLocation();
  725. } else if (!canAskAgain) {
  726. setOpenSettingsVisible(true);
  727. }
  728. };
  729. const handleOpenEditModal = () => {
  730. handleModalStateChange({
  731. selectedFirstYear: userData?.first_visit_year,
  732. selectedLastYear: userData?.last_visit_year,
  733. selectedQuality:
  734. qualityOptions.find((quality) => quality.id === userData?.best_visit_quality) ||
  735. qualityOptions[2],
  736. selectedNoOfVisits: userData?.no_of_visits || 1,
  737. id: regionData?.id
  738. });
  739. setIsEditModalVisible(true);
  740. };
  741. const handleOpenEditSlowModal = () => {
  742. setIsEditSlowModalVisible(true);
  743. };
  744. const handleSearch = async () => {
  745. setSearch(searchInput);
  746. setSearchVisible(true);
  747. };
  748. const handleCloseModal = () => {
  749. setSearchInput('');
  750. setSearchVisible(false);
  751. handlePress();
  752. };
  753. const handleRegionData = (regionData: any, avatars: string[]) => {
  754. setRegionData(regionData);
  755. setUserAvatars(avatars);
  756. };
  757. const handleFindRegion = async (id: number, type: 'regions' | 'countries' | 'places') => {
  758. setType(type === 'places' ? 'dare' : type);
  759. if (!db1 || !db2 || !db3) {
  760. return;
  761. }
  762. const db = type === 'regions' ? db1 : type === 'countries' ? db3 : db2;
  763. if (id) {
  764. setSelectedRegion(id);
  765. await getData(db, id, type, handleRegionData)
  766. .then(() => {
  767. setRegionPopupVisible(true);
  768. })
  769. .catch((error) => {
  770. console.error('Error fetching data', error);
  771. refreshDatabases();
  772. });
  773. if (type === 'regions') {
  774. token
  775. ? await mutateUserData(
  776. { region_id: id, token: String(token) },
  777. {
  778. onSuccess: (data) => {
  779. setUserData({ type: 'nm', id, ...data });
  780. }
  781. }
  782. )
  783. : setUserData({ type: 'nm', id });
  784. if (regionsList) {
  785. const region = regionsList.data.find((region) => region.id === +id);
  786. if (region) {
  787. const bounds = turf.bbox(region.bbox);
  788. cameraRef.current?.fitBounds(
  789. [bounds[2], bounds[3]],
  790. [bounds[0], bounds[1]],
  791. [10, 10, 50, 10],
  792. 1000
  793. );
  794. }
  795. }
  796. } else if (type === 'countries') {
  797. token
  798. ? await mutateCountriesData(
  799. { id, token },
  800. {
  801. onSuccess: (data) => {
  802. setUserData({ type: 'countries', id, ...data.data });
  803. }
  804. }
  805. )
  806. : setUserData({ type: 'countries', id });
  807. if (countriesList) {
  808. const region = countriesList.data.find((region) => region.id === +id);
  809. if (region) {
  810. const bounds = turf.bbox(region.bbox);
  811. cameraRef.current?.fitBounds(
  812. [bounds[2], bounds[3]],
  813. [bounds[0], bounds[1]],
  814. [10, 10, 50, 10],
  815. 1000
  816. );
  817. }
  818. }
  819. } else {
  820. token
  821. ? await mutateUserDataDare(
  822. { dare_id: +id, token: String(token) },
  823. {
  824. onSuccess: (data) => {
  825. setUserData({ type: 'dare', id: +id, ...data });
  826. }
  827. }
  828. )
  829. : setUserData({ type: 'dare', id: +id });
  830. if (dareList) {
  831. const region = dareList.data.find((region) => region.id === +id);
  832. if (region) {
  833. const bounds = turf.bbox(region.bbox);
  834. cameraRef.current?.fitBounds(
  835. [bounds[2], bounds[3]],
  836. [bounds[0], bounds[1]],
  837. [10, 10, 50, 10],
  838. 1000
  839. );
  840. }
  841. }
  842. }
  843. } else {
  844. handleClosePopup();
  845. }
  846. };
  847. const handleMarkerPress = async (event: any) => {
  848. const { features } = event;
  849. if (features?.length) {
  850. const selectedFeature = features[0];
  851. const { coordinates } = selectedFeature.geometry;
  852. const visited = seriesVisited.includes(selectedFeature.properties.id) ? 1 : 0;
  853. const icon = images[selectedFeature.properties.series_id];
  854. const { name, description, series_name, series_id, id } = selectedFeature.properties;
  855. setSelectedMarker({
  856. coordinates,
  857. name,
  858. icon,
  859. description,
  860. series_name,
  861. visited,
  862. series_id,
  863. id
  864. });
  865. }
  866. };
  867. const closeCallout = () => {
  868. setSelectedMarker(null);
  869. };
  870. const toggleSeries = useCallback(
  871. async (item: any) => {
  872. if (!token) {
  873. setIsWarningModalVisible(true);
  874. return;
  875. }
  876. const itemData = {
  877. token,
  878. series_id: item.series_id,
  879. item_id: item.id,
  880. checked: (item.visited === 0 ? 1 : 0) as 0 | 1,
  881. double: 0 as 0 | 1
  882. };
  883. try {
  884. updateSeriesItem(itemData);
  885. if (item.visited === 1) {
  886. setSeriesVisited((current) => current.filter((id) => id !== item.id));
  887. setSelectedMarker((current: any) => ({ ...current, visited: 0 }));
  888. } else {
  889. setSeriesVisited((current) => [...current, item.id]);
  890. setSelectedMarker((current: any) => ({ ...current, visited: 1 }));
  891. }
  892. } catch (error) {
  893. console.error('Failed to update series state', error);
  894. }
  895. },
  896. [token, updateSeriesItem]
  897. );
  898. const handleModalStateChange = (updates: { [key: string]: any }) => {
  899. setModalState((prevState) => ({ ...prevState, ...updates }));
  900. };
  901. return (
  902. <SafeAreaView style={{ height: '100%' }}>
  903. <StatusBar translucent backgroundColor="transparent" />
  904. <MapLibreGL.MapView
  905. ref={mapRef}
  906. style={styles.map}
  907. styleJSON={VECTOR_MAP_HOST + '/nomadmania-maps.json'}
  908. rotateEnabled={false}
  909. attributionEnabled={false}
  910. onPress={onMapPress}
  911. onRegionDidChange={handleRegionDidChange}
  912. >
  913. <MapLibreGL.Images images={images}>
  914. <View />
  915. </MapLibreGL.Images>
  916. {type === 'regions' && (
  917. <>
  918. <MapLibreGL.FillLayer
  919. id={regions.id}
  920. sourceID={regions.source}
  921. sourceLayerID={regions['source-layer']}
  922. filter={regions.filter as any}
  923. style={regions.style}
  924. maxZoomLevel={regions.maxzoom}
  925. belowLayerID={regions_visited.id}
  926. />
  927. <MapLibreGL.FillLayer
  928. id={regions_visited.id}
  929. sourceID={regions_visited.source}
  930. sourceLayerID={regions_visited['source-layer']}
  931. filter={regionsVisitedFilter as any}
  932. style={regions_visited.style}
  933. maxZoomLevel={regions_visited.maxzoom}
  934. belowLayerID="waterway-name"
  935. />
  936. </>
  937. )}
  938. {type === 'countries' && (
  939. <>
  940. <MapLibreGL.FillLayer
  941. id={countries.id}
  942. sourceID={countries.source}
  943. sourceLayerID={countries['source-layer']}
  944. filter={countries.filter as any}
  945. style={countries.style}
  946. maxZoomLevel={countries.maxzoom}
  947. belowLayerID={countries_visited.id}
  948. />
  949. <MapLibreGL.FillLayer
  950. id={countries_visited.id}
  951. sourceID={countries_visited.source}
  952. sourceLayerID={countries_visited['source-layer']}
  953. filter={countriesVisitedFilter as any}
  954. style={countries_visited.style}
  955. maxZoomLevel={countries_visited.maxzoom}
  956. belowLayerID="waterway-name"
  957. />
  958. </>
  959. )}
  960. {type === 'dare' && (
  961. <>
  962. <MapLibreGL.FillLayer
  963. id={dare.id}
  964. sourceID={dare.source}
  965. sourceLayerID={dare['source-layer']}
  966. filter={dare.filter as any}
  967. style={dare.style}
  968. maxZoomLevel={dare.maxzoom}
  969. belowLayerID={dare_visited.id}
  970. />
  971. <MapLibreGL.FillLayer
  972. id={dare_visited.id}
  973. sourceID={dare_visited.source}
  974. sourceLayerID={dare_visited['source-layer']}
  975. filter={dareVisitedFilter as any}
  976. style={dare_visited.style}
  977. maxZoomLevel={dare_visited.maxzoom}
  978. belowLayerID="waterway-name"
  979. />
  980. </>
  981. )}
  982. {selectedRegion && type && (
  983. <MapLibreGL.FillLayer
  984. id={selected_region.id}
  985. sourceID={type}
  986. sourceLayerID={type}
  987. filter={['==', 'id', selectedRegion]}
  988. style={selected_region.style}
  989. maxZoomLevel={selected_region.maxzoom}
  990. belowLayerID="waterway-name"
  991. />
  992. )}
  993. <MapLibreGL.VectorSource
  994. id="nomadmania_series"
  995. tileUrlTemplates={[MAP_HOST + '/tileserver/series/{z}/{x}/{y}.pbf']}
  996. onPress={handleMarkerPress}
  997. >
  998. {seriesFilter.status !== 1 ? (
  999. <MapLibreGL.SymbolLayer
  1000. id={series_layer.id}
  1001. sourceID={series_layer.source}
  1002. sourceLayerID={series_layer['source-layer']}
  1003. belowLayerID={Platform.OS === 'android' ? 'waterway-name' : undefined}
  1004. filter={seriesNotVisitedFilter as any}
  1005. style={{
  1006. iconImage: ['get', 'series_id'],
  1007. iconSize: 0.18,
  1008. visibility: 'visible',
  1009. iconColor: '#666',
  1010. iconOpacity: 1,
  1011. iconHaloColor: '#ffffff',
  1012. iconHaloWidth: 1,
  1013. iconHaloBlur: 0.5
  1014. }}
  1015. />
  1016. ) : (
  1017. <></>
  1018. )}
  1019. {seriesFilter.status !== 0 ? (
  1020. <MapLibreGL.SymbolLayer
  1021. id={series_visited.id}
  1022. sourceID={series_visited.source}
  1023. sourceLayerID={series_visited['source-layer']}
  1024. belowLayerID={Platform.OS === 'android' ? 'waterway-name' : undefined}
  1025. filter={seriesVisitedFilter as any}
  1026. style={{
  1027. iconImage: '{series_id}v',
  1028. iconSize: 0.18,
  1029. visibility: 'visible',
  1030. iconColor: '#666',
  1031. iconOpacity: 1,
  1032. iconHaloColor: '#ffffff',
  1033. iconHaloWidth: 1,
  1034. iconHaloBlur: 0.5
  1035. }}
  1036. />
  1037. ) : (
  1038. <></>
  1039. )}
  1040. </MapLibreGL.VectorSource>
  1041. {nomads && (
  1042. <MapLibreGL.ShapeSource
  1043. id="nomads"
  1044. shape={nomads}
  1045. onPress={(event) => console.log(event.features)}
  1046. >
  1047. <MapLibreGL.SymbolLayer
  1048. id="nomads_symbol"
  1049. style={{
  1050. iconImage: ['get', 'icon_key'],
  1051. iconSize: 0.15,
  1052. iconAllowOverlap: true
  1053. }}
  1054. ></MapLibreGL.SymbolLayer>
  1055. </MapLibreGL.ShapeSource>
  1056. )}
  1057. {selectedMarker && (
  1058. <MarkerItem marker={selectedMarker} toggleSeries={toggleSeries} token={token} />
  1059. )}
  1060. <MapLibreGL.Camera ref={cameraRef} />
  1061. {location && (
  1062. <MapLibreGL.UserLocation animated={true} showsUserHeadingIndicator={true}>
  1063. {/* to do custom user location */}
  1064. </MapLibreGL.UserLocation>
  1065. )}
  1066. </MapLibreGL.MapView>
  1067. {regionPopupVisible && regionData ? (
  1068. <>
  1069. <TouchableOpacity
  1070. style={[styles.cornerButton, styles.topLeftButton, styles.closeLeftButton]}
  1071. onPress={handleClosePopup}
  1072. >
  1073. <CloseSvg fill="white" width={13} height={13} />
  1074. <Text style={styles.textClose}>Close</Text>
  1075. </TouchableOpacity>
  1076. <TouchableOpacity
  1077. onPress={handleGetLocation}
  1078. style={[styles.cornerButton, styles.topRightButton, styles.bottomButton]}
  1079. >
  1080. <LocationIcon />
  1081. </TouchableOpacity>
  1082. <RegionPopup
  1083. region={regionData}
  1084. userAvatars={userAvatars}
  1085. userData={userData}
  1086. openEditModal={handleOpenEditModal}
  1087. updateNM={(id, first, last, visits, quality) => {
  1088. if (!token) {
  1089. setIsWarningModalVisible(true);
  1090. return;
  1091. }
  1092. handleUpdateNM(id, first, last, visits, quality);
  1093. const updatedIds = regionsVisited.includes(id)
  1094. ? regionsVisited.filter((visitedId) => visitedId !== id)
  1095. : [...regionsVisited, id];
  1096. setRegionsVisited(updatedIds);
  1097. refetchVisitedCountries();
  1098. }}
  1099. updateDare={(id, visits) => {
  1100. if (!token) {
  1101. setIsWarningModalVisible(true);
  1102. return;
  1103. }
  1104. handleUpdateDare(id, visits);
  1105. const updatedIds = dareVisited.includes(id)
  1106. ? dareVisited.filter((visitedId) => visitedId !== id)
  1107. : [...dareVisited, id];
  1108. setDareVisited(updatedIds);
  1109. }}
  1110. disabled={!token || !isConnected}
  1111. updateSlow={(id, v, s11, s31, s101) => {
  1112. if (!token) {
  1113. setIsWarningModalVisible(true);
  1114. return;
  1115. }
  1116. handleUpdateSlow(id, v, s11, s31, s101);
  1117. const updatedIds = countriesVisited.includes(id)
  1118. ? countriesVisited.filter((visitedId) => visitedId !== id)
  1119. : [...countriesVisited, id];
  1120. setCountriesVisited(updatedIds);
  1121. }}
  1122. openEditSlowModal={handleOpenEditSlowModal}
  1123. />
  1124. </>
  1125. ) : (
  1126. <>
  1127. {!isExpanded ? (
  1128. <TouchableOpacity
  1129. style={[styles.cornerButton, styles.topRightButton]}
  1130. onPress={() => navigation.navigate(NAVIGATION_PAGES.PROFILE_TAB)}
  1131. >
  1132. {token ? (
  1133. userInfoData?.avatar ? (
  1134. <Image
  1135. style={styles.avatar}
  1136. source={{ uri: API_HOST + '/img/avatars/' + userInfoData?.avatar }}
  1137. />
  1138. ) : (
  1139. <AvatarWithInitials
  1140. text={`${userInfoData?.first_name ? userInfoData?.first_name[0] : ''}${userInfoData?.last_name ? userInfoData?.last_name[0] : ''}`}
  1141. flag={API_HOST + '/img/flags_new/' + userInfoData?.homebase_flag}
  1142. size={48}
  1143. borderColor={Colors.WHITE}
  1144. />
  1145. )
  1146. ) : (
  1147. <ProfileIcon fill={Colors.DARK_BLUE} />
  1148. )}
  1149. </TouchableOpacity>
  1150. ) : null}
  1151. <Animated.View
  1152. style={[
  1153. styles.searchContainer,
  1154. styles.cornerButton,
  1155. styles.topLeftButton,
  1156. animatedStyle,
  1157. { padding: 5 }
  1158. ]}
  1159. >
  1160. {isExpanded ? (
  1161. <>
  1162. <TouchableOpacity onPress={handlePress} style={styles.iconButton}>
  1163. <CloseSvg fill={'#0F3F4F'} />
  1164. </TouchableOpacity>
  1165. <TextInput
  1166. style={styles.input}
  1167. placeholder="Search regions, places, nomads"
  1168. placeholderTextColor={Colors.LIGHT_GRAY}
  1169. value={searchInput}
  1170. onChangeText={(text) => setSearchInput(text)}
  1171. onSubmitEditing={handleSearch}
  1172. />
  1173. <TouchableOpacity onPress={handleSearch} style={styles.iconButton}>
  1174. <SearchIcon fill={'#0F3F4F'} />
  1175. </TouchableOpacity>
  1176. </>
  1177. ) : (
  1178. <TouchableOpacity onPress={handlePress} style={[styles.iconButton]}>
  1179. <SearchIcon fill={'#0F3F4F'} />
  1180. </TouchableOpacity>
  1181. )}
  1182. </Animated.View>
  1183. <TouchableOpacity
  1184. style={[styles.cornerButton, styles.bottomButton, styles.bottomLeftButton]}
  1185. onPress={() => {
  1186. setIsFilterVisible(true);
  1187. closeCallout();
  1188. }}
  1189. >
  1190. <FilterIcon />
  1191. </TouchableOpacity>
  1192. <TouchableOpacity
  1193. onPress={handleGetLocation}
  1194. style={[styles.cornerButton, styles.bottomButton, styles.bottomRightButton]}
  1195. >
  1196. <LocationIcon />
  1197. </TouchableOpacity>
  1198. </>
  1199. )}
  1200. <SearchModal
  1201. searchVisible={searchVisible}
  1202. handleCloseModal={handleCloseModal}
  1203. handleFindRegion={handleFindRegion}
  1204. index={index}
  1205. searchData={searchData}
  1206. setIndex={setIndex}
  1207. token={token}
  1208. />
  1209. <WarningModal
  1210. type={'unauthorized'}
  1211. isVisible={isWarningModalVisible}
  1212. onClose={() => setIsWarningModalVisible(false)}
  1213. />
  1214. <EditNmModal
  1215. isVisible={isEditModalVisible}
  1216. onClose={() => setIsEditModalVisible(false)}
  1217. modalState={modalState}
  1218. updateModalState={handleModalStateChange}
  1219. updateNM={handleUpdateNM}
  1220. />
  1221. <FilterModal
  1222. isFilterVisible={isFilterVisible}
  1223. setIsFilterVisible={setIsFilterVisible}
  1224. tilesTypes={tilesTypes}
  1225. tilesType={tilesType}
  1226. setTilesType={setTilesType}
  1227. setType={setType}
  1228. userId={userId ? +userId : 0}
  1229. setRegionsFilter={setRegionsFilter}
  1230. setSeriesFilter={setSeriesFilter}
  1231. setShowNomads={setShowNomads}
  1232. showNomads={showNomads}
  1233. isPublicView={false}
  1234. isLogged={token ? true : false}
  1235. />
  1236. <EditModal
  1237. isVisible={isEditSlowModalVisible}
  1238. onClose={() => setIsEditSlowModalVisible(false)}
  1239. item={{ ...userData, country_id: regionData?.id }}
  1240. updateSlow={(id, v, s11, s31, s101) => handleUpdateSlow(id, v, s11, s31, s101)}
  1241. />
  1242. <WarningModal
  1243. type={'success'}
  1244. isVisible={askLocationVisible}
  1245. onClose={() => setAskLocationVisible(false)}
  1246. action={handleAcceptPermission}
  1247. message="To use this feature we need your permission to access your location. If you press OK your system will ask you to approve location sharing with NomadMania app."
  1248. />
  1249. <WarningModal
  1250. type={'success'}
  1251. isVisible={openSettingsVisible}
  1252. onClose={() => setOpenSettingsVisible(false)}
  1253. action={() =>
  1254. Platform.OS === 'ios' ? Linking.openURL('app-settings:') : Linking.openSettings()
  1255. }
  1256. message="NomadMania app needs location permissions to function properly. Open settings?"
  1257. />
  1258. </SafeAreaView>
  1259. );
  1260. };
  1261. export default MapScreen;