123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713714715716717718719720721722723724725726727728729730731732733734735736737738739740741742743744745746747748749750751752753754755756757758759760761762763764765766767768769770771772773774775776777778779780781782783784785786787788789790791792793794795796797798799800801802803804805806807808809810811812813814815816817818819820821822823824825826827828829830831832833834835836837838839840841842843844845846847848849850851852853854855856857858859860861862863864865866867868869870871872873874875876877878879880881882883884885886887888889890891892893894895896897898899900901902903904905906907908909910911912913914915916917918919920921922923924925926927928929930931932933934935936937938939940941942943944945946947948949950951952953954955956957958959960961962963964965966967968969970971972973974975976977978979980981982983984985986987988989990991992993994995996997998999100010011002100310041005100610071008100910101011101210131014101510161017101810191020102110221023102410251026102710281029103010311032103310341035103610371038103910401041104210431044104510461047104810491050105110521053105410551056105710581059106010611062106310641065106610671068106910701071107210731074107510761077107810791080108110821083108410851086108710881089109010911092109310941095109610971098109911001101110211031104110511061107110811091110111111121113111411151116111711181119112011211122112311241125112611271128112911301131113211331134113511361137113811391140114111421143114411451146114711481149115011511152115311541155115611571158115911601161116211631164116511661167116811691170117111721173117411751176117711781179118011811182118311841185118611871188118911901191119211931194119511961197119811991200120112021203120412051206120712081209121012111212121312141215121612171218121912201221122212231224122512261227122812291230123112321233123412351236123712381239124012411242124312441245124612471248124912501251125212531254125512561257125812591260126112621263126412651266126712681269127012711272127312741275127612771278127912801281128212831284128512861287128812891290129112921293129412951296129712981299130013011302130313041305130613071308130913101311131213131314131513161317131813191320132113221323132413251326132713281329133013311332133313341335133613371338133913401341134213431344134513461347134813491350135113521353135413551356135713581359136013611362136313641365136613671368136913701371137213731374137513761377137813791380138113821383138413851386138713881389139013911392139313941395139613971398139914001401140214031404140514061407140814091410141114121413141414151416141714181419142014211422142314241425142614271428142914301431143214331434143514361437143814391440144114421443144414451446144714481449145014511452145314541455145614571458145914601461146214631464146514661467146814691470147114721473147414751476147714781479148014811482148314841485148614871488148914901491149214931494149514961497149814991500150115021503150415051506150715081509151015111512151315141515151615171518151915201521152215231524152515261527152815291530153115321533153415351536153715381539154015411542154315441545154615471548154915501551155215531554155515561557155815591560156115621563156415651566156715681569157015711572157315741575157615771578157915801581158215831584158515861587158815891590159115921593159415951596159715981599160016011602160316041605160616071608160916101611161216131614161516161617161816191620162116221623162416251626162716281629163016311632163316341635163616371638163916401641164216431644164516461647164816491650165116521653165416551656165716581659166016611662166316641665166616671668166916701671167216731674167516761677167816791680168116821683168416851686168716881689169016911692169316941695169616971698169917001701170217031704170517061707170817091710171117121713171417151716171717181719172017211722172317241725172617271728172917301731173217331734173517361737173817391740174117421743174417451746174717481749175017511752175317541755175617571758175917601761176217631764176517661767176817691770177117721773177417751776177717781779178017811782178317841785178617871788178917901791179217931794179517961797179817991800180118021803180418051806180718081809181018111812181318141815181618171818181918201821182218231824182518261827182818291830183118321833183418351836183718381839184018411842184318441845184618471848184918501851185218531854185518561857185818591860186118621863186418651866186718681869187018711872187318741875187618771878187918801881188218831884188518861887188818891890189118921893189418951896189718981899190019011902190319041905190619071908190919101911191219131914191519161917191819191920192119221923192419251926192719281929193019311932193319341935193619371938193919401941194219431944194519461947194819491950195119521953195419551956195719581959196019611962196319641965196619671968196919701971 |
- import {
- Dimensions,
- Linking,
- Platform,
- Text,
- TextInput,
- TouchableOpacity,
- View,
- Image,
- StatusBar,
- ActivityIndicator,
- ScrollView
- } from 'react-native';
- import React, { useEffect, useRef, useState, useCallback } from 'react';
- import * as MapLibreRN from '@maplibre/maplibre-react-native';
- import { styles } from './style';
- import { SafeAreaView } from 'react-native-safe-area-context';
- import { Colors } from 'src/theme';
- import { storage, StoreType } from 'src/storage';
- import * as turf from '@turf/turf';
- import * as Location from 'expo-location';
- import { Image as ExpoImage } from 'expo-image';
- import SearchIcon from 'assets/icons/search.svg';
- import LocationIcon from 'assets/icons/location.svg';
- import CloseSvg from 'assets/icons/close.svg';
- import FilterIcon from 'assets/icons/filter.svg';
- import ProfileIcon from 'assets/icons/bottom-navigation/profile.svg';
- import RegionPopup from 'src/components/RegionPopup';
- import { useRegion } from 'src/contexts/RegionContext';
- import { qualityOptions } from '../TravelsScreen/utils/constants';
- import { AvatarWithInitials, EditNmModal, WarningModal } from 'src/components';
- import { API_HOST, VECTOR_MAP_HOST } from 'src/constants';
- import { NAVIGATION_PAGES } from 'src/types';
- import Animated, {
- Easing,
- useAnimatedStyle,
- useSharedValue,
- withTiming
- } from 'react-native-reanimated';
- import { getData } from 'src/modules/map/regionData';
- import {
- getCountriesDatabase,
- getFirstDatabase,
- getSecondDatabase,
- refreshDatabases
- } from 'src/db';
- import { fetchUserData, fetchUserDataDare, useGetListRegionsQuery } from '@api/regions';
- import { SQLiteDatabase } from 'expo-sqlite/legacy';
- import { useFocusEffect } from '@react-navigation/native';
- import { useGetUniversalSearch } from '@api/search';
- import { fetchCountryUserData, useGetListCountriesQuery } from '@api/countries';
- import SearchModal from './UniversalSearch';
- import EditModal from '../TravelsScreen/Components/EditSlowModal';
- import * as FileSystem from 'expo-file-system';
- import CheckSvg from 'assets/icons/mark.svg';
- import moment from 'moment';
- import {
- usePostGetVisitedCountriesIdsQuery,
- usePostGetVisitedDareIdsQuery,
- usePostGetVisitedRegionsIdsQuery,
- usePostGetVisitedSeriesIdsQuery
- } from '@api/maps';
- import FilterModal from './FilterModal';
- import { useGetListDareQuery } from '@api/myDARE';
- import { useGetIconsQuery, usePostSetToggleItem } from '@api/series';
- import MarkerItem from './MarkerItem';
- import {
- usePostGetSettingsQuery,
- usePostGetUsersCountQuery,
- usePostGetUsersLocationQuery,
- usePostUpdateLocationMutation
- } from '@api/location';
- import UserItem from './UserItem';
- import { useConnection } from 'src/contexts/ConnectionContext';
- import TravelsIcon from 'assets/icons/bottom-navigation/globe-solid.svg';
- import SeriesIcon from 'assets/icons/travels-section/series.svg';
- import NomadsIcon from 'assets/icons/bottom-navigation/travellers.svg';
- import { useBottomTabBarHeight } from '@react-navigation/bottom-tabs';
- import MapButton from 'src/components/MapButton';
- import { useAvatarStore } from 'src/stores/avatarVersionStore';
- import _ from 'lodash';
- import ScaleBar from 'src/components/ScaleBar';
- import MessagesDot from 'src/components/MessagesDot';
- import {
- restartBackgroundLocationUpdates,
- startBackgroundLocationUpdates,
- stopBackgroundLocationUpdates
- } from 'src/utils/backgroundLocation';
- const clusteredUsersIcon = require('assets/icons/icon-clustered-users.png');
- const defaultUserAvatar = require('assets/icon-user-share-location-solid.png');
- const logo = require('assets/logo-ua.png');
- const defaultSeriesIcon = require('assets/series-default.png');
- MapLibreRN.Logger.setLogLevel('error');
- const generateFilter = (ids: number[]) => {
- return ids?.length ? ['any', ...ids.map((id) => ['==', 'id', id])] : ['==', 'id', -1];
- };
- // to do refactor
- let regions_visited = {
- id: 'regions_visited',
- type: 'fill',
- source: 'regions',
- 'source-layer': 'regions',
- style: {
- fillColor: 'rgba(255, 126, 0, 1)',
- fillOpacity: 0.6,
- fillOutlineColor: 'rgba(14, 80, 109, 0)'
- },
- filter: generateFilter([]),
- maxzoom: 10
- };
- let countries_visited = {
- id: 'countries_visited',
- type: 'fill',
- source: 'countries',
- 'source-layer': 'countries',
- style: {
- fillColor: 'rgba(255, 126, 0, 1)',
- fillOpacity: 0.6,
- fillOutlineColor: 'rgba(14, 80, 109, 0)'
- },
- filter: generateFilter([]),
- maxzoom: 10
- };
- let dare_visited = {
- id: 'dare_visited',
- type: 'fill',
- source: 'dare',
- 'source-layer': 'dare',
- style: {
- fillColor: 'rgba(255, 126, 0, 0.6)',
- fillOutlineColor: 'rgba(255, 126, 0, 0)'
- },
- filter: generateFilter([]),
- maxzoom: 12
- };
- let regions = {
- id: 'regions',
- type: 'fill',
- source: 'regions',
- 'source-layer': 'regions',
- style: {
- fillColor: 'rgba(15, 63, 79, 0)',
- fillOutlineColor: 'rgba(14, 80, 109, 0)'
- },
- filter: ['all'],
- maxzoom: 16
- };
- let countries = {
- id: 'countries',
- type: 'fill',
- source: 'countries',
- 'source-layer': 'countries',
- style: {
- fillColor: 'rgba(15, 63, 79, 0)',
- fillOutlineColor: 'rgba(14, 80, 109, 0)'
- },
- filter: ['all'],
- maxzoom: 16
- };
- let dare = {
- id: 'dare',
- type: 'fill',
- source: 'dare',
- 'source-layer': 'dare',
- style: {
- fillColor: 'rgba(14, 80, 109, 0.6)',
- fillOutlineColor: 'rgba(14, 80, 109, 0)'
- },
- filter: ['all'],
- maxzoom: 16
- };
- let selected_region = {
- id: 'selected_region',
- type: 'fill',
- source: 'regions',
- 'source-layer': 'regions',
- style: {
- fillColor: 'rgba(57, 115, 172, 0.3)'
- },
- maxzoom: 12
- };
- let selected_region_outline = {
- id: 'selected_region_outline',
- type: 'line',
- source: 'regions',
- 'source-layer': 'regions',
- style: {
- lineColor: '#ED9334',
- lineTranslate: [0, 0],
- lineTranslateAnchor: 'map',
- lineWidth: ['interpolate', ['linear'], ['zoom'], 0, 2, 4, 3, 5, 4, 12, 5]
- },
- maxzoom: 12
- };
- let series_layer = {
- id: 'series_layer',
- type: 'symbol',
- source: 'nomadmania_series',
- 'source-layer': 'series',
- minzoom: 6,
- maxzoom: 60,
- layout: {
- 'symbol-spacing': 1,
- 'icon-image': '{series_id}',
- 'icon-size': 0.15,
- 'icon-allow-overlap': true,
- 'icon-ignore-placement': true,
- 'text-anchor': 'top',
- 'text-field': '{name}',
- 'text-font': ['Noto Sans Regular'],
- 'text-max-width': 9,
- 'text-offset': [0, 0.6],
- 'text-padding': 2,
- 'text-size': 12,
- visibility: 'visible',
- 'text-optional': true,
- 'text-ignore-placement': false,
- 'text-allow-overlap': false
- },
- paint: {
- 'text-color': '#666',
- 'text-halo-blur': 0.5,
- 'text-halo-color': '#ffffff',
- 'text-halo-width': 1
- },
- filter: generateFilter([])
- };
- let series_visited = {
- id: 'series_visited',
- type: 'symbol',
- source: 'nomadmania_series',
- 'source-layer': 'series',
- minzoom: 6,
- maxzoom: 60,
- layout: {
- 'symbol-spacing': 1,
- 'icon-image': '{series_id}v',
- 'icon-size': 0.15,
- 'icon-allow-overlap': true,
- 'icon-ignore-placement': true,
- 'text-anchor': 'top',
- 'text-field': '{name}',
- 'text-font': ['Noto Sans Regular'],
- 'text-max-width': 9,
- 'text-offset': [0, 0.6],
- 'text-padding': 2,
- 'text-size': 12,
- visibility: 'visible',
- 'text-optional': true,
- 'text-ignore-placement': false,
- 'text-allow-overlap': false
- },
- paint: {
- 'text-color': '#666',
- 'text-halo-blur': 0.5,
- 'text-halo-color': '#ffffff',
- 'text-halo-width': 1
- },
- filter: generateFilter([])
- };
- const INITIAL_REGION = {
- latitude: 0,
- longitude: 0,
- latitudeDelta: 180,
- longitudeDelta: 180
- };
- const ICONS_DIR = FileSystem.documentDirectory + 'series_icons/';
- const MapScreen: any = ({ navigation, route }: { navigation: any; route: any }) => {
- const tabBarHeight = useBottomTabBarHeight();
- const userId = storage.get('uid', StoreType.STRING) as string;
- const token = storage.get('token', StoreType.STRING) as string;
- const [isConnected, setIsConnected] = useState<boolean>(true);
- const netInfo = useConnection();
- const { avatarVersion } = useAvatarStore();
- const { data: usersOnMapCount } = usePostGetUsersCountQuery(token, !!token && isConnected);
- const { data: regionsList } = useGetListRegionsQuery(isConnected);
- const { data: countriesList } = useGetListCountriesQuery(isConnected);
- const { data: dareList } = useGetListDareQuery(isConnected);
- const [tilesType, setTilesType] = useState({ label: 'NM regions', value: 0 });
- const tilesTypes = [
- { label: 'Blank', value: -1 },
- { label: 'NM regions', value: 0 },
- { label: 'UN countries', value: 1 },
- { label: 'DARE places', value: 2 }
- ];
- const [type, setType] = useState<'regions' | 'countries' | 'dare' | 'blank'>('regions');
- const [seriesFilter, setSeriesFilter] = useState<any>({
- visible: true,
- groups: [],
- applied: false,
- status: -1
- });
- const [regionsFilter, setRegionsFilter] = useState<any>({
- visitedLabel: 'by',
- year: moment().year()
- });
- const [showNomads, setShowNomads] = useState(
- (storage.get('showNomads', StoreType.BOOLEAN) as boolean) ?? false
- );
- const { data: locationSettings, refetch } = usePostGetSettingsQuery(
- token,
- !!token && isConnected
- );
- const { mutateAsync: updateLocation } = usePostUpdateLocationMutation();
- const { data: visitedRegionIds, refetch: refetchVisitedRegions } =
- usePostGetVisitedRegionsIdsQuery(
- token,
- regionsFilter.visitedLabel,
- regionsFilter.year,
- +userId,
- type === 'regions' && !!userId && isConnected
- );
- const { data: visitedCountryIds, refetch: refetchVisitedCountries } =
- usePostGetVisitedCountriesIdsQuery(
- token,
- regionsFilter.visitedLabel,
- regionsFilter.year,
- +userId,
- type === 'countries' && !!userId && isConnected
- );
- const { data: visitedDareIds, refetch: refetchVisitedDare } = usePostGetVisitedDareIdsQuery(
- token,
- +userId,
- type === 'dare' && !!userId && isConnected
- );
- const { data: visitedSeriesIds } = usePostGetVisitedSeriesIdsQuery(
- token,
- !!userId && isConnected
- );
- const { data: seriesIcons } = useGetIconsQuery(isConnected);
- const userInfo = storage.get('currentUserData', StoreType.STRING) as string;
- const { mutateAsync: mutateUserData } = fetchUserData();
- const { mutateAsync: mutateUserDataDare } = fetchUserDataDare();
- const { mutateAsync: mutateCountriesData } = fetchCountryUserData();
- const [selectedRegion, setSelectedRegion] = useState<number | null>(null);
- const [initialRegion, setInitialRegion] = useState(INITIAL_REGION);
- const [regionPopupVisible, setRegionPopupVisible] = useState<boolean | null>(false);
- const [regionData, setRegionData] = useState<any | null>(null);
- const [location, setLocation] = useState<any | null>(null);
- const [userAvatars, setUserAvatars] = useState<string[]>([]);
- const [userInfoData, setUserInfoData] = useState<any>(null);
- const [selectedMarker, setSelectedMarker] = useState<any>(null);
- const [askLocationVisible, setAskLocationVisible] = useState<boolean>(false);
- const [openSettingsVisible, setOpenSettingsVisible] = useState<boolean>(false);
- const [isWarningModalVisible, setIsWarningModalVisible] = useState<boolean>(false);
- const [isEditSlowModalVisible, setIsEditSlowModalVisible] = useState<boolean>(false);
- const [isEditModalVisible, setIsEditModalVisible] = useState(false);
- const [isFilterVisible, setIsFilterVisible] = useState<string | null>(null);
- const [isLocationLoading, setIsLocationLoading] = useState(false);
- const [modalState, setModalState] = useState({
- selectedFirstYear: 2021,
- selectedLastYear: 2021,
- selectedQuality: qualityOptions[2],
- selectedNoOfVisits: 1,
- years: [],
- id: null
- });
- const [isExpanded, setIsExpanded] = useState(false);
- const [search, setSearch] = useState('');
- const { data: searchData } = useGetUniversalSearch(search, search.length > 0);
- const [searchInput, setSearchInput] = useState('');
- const [searchVisible, setSearchVisible] = useState<boolean>(false);
- const [index, setIndex] = useState<number>(0);
- const width = useSharedValue(48);
- const usableWidth = Dimensions.get('window').width - 32;
- const { handleUpdateNM, handleUpdateDare, handleUpdateSlow, userData, setUserData } = useRegion();
- const [db1, setDb1] = useState<SQLiteDatabase | null>(null);
- const [db2, setDb2] = useState<SQLiteDatabase | null>(null);
- const [db3, setDb3] = useState<SQLiteDatabase | null>(null);
- const [regionsVisitedFilter, setRegionsVisitedFilter] = useState(generateFilter([]));
- const [countriesVisitedFilter, setCountriesVisitedFilter] = useState(generateFilter([]));
- const [dareVisitedFilter, setDareVisitedFilter] = useState(generateFilter([]));
- const [seriesVisitedFilter, setSeriesVisitedFilter] = useState(generateFilter([]));
- const [seriesNotVisitedFilter, setSeriesNotVisitedFilter] = useState(generateFilter([]));
- const [regionsVisited, setRegionsVisited] = useState<any[]>([]);
- const [countriesVisited, setCountriesVisited] = useState<any[]>([]);
- const [dareVisited, setDareVisited] = useState<any[]>([]);
- const [seriesVisited, setSeriesVisited] = useState<any[]>([]);
- const [images, setImages] = useState<any>({});
- const { mutateAsync: updateSeriesItem } = usePostSetToggleItem();
- const [nomads, setNomads] = useState<GeoJSON.FeatureCollection | null>(null);
- const { data: usersLocation, refetch: refetchUsersLocation } = usePostGetUsersLocationQuery(
- token,
- !!token && showNomads && Boolean(location) && isConnected
- );
- const [selectedUser, setSelectedUser] = useState<any>(null);
- const [zoom, setZoom] = useState(0);
- const [center, setCenter] = useState<number[] | null>(null);
- const [isZooming, setIsZooming] = useState(true);
- const hideTimer = useRef<ReturnType<typeof setTimeout> | null>(null);
- const [markerCoords, setMarkerCoords] = useState<any>(null);
- const [interval, setInterval] = useState(0);
- const isSmallScreen = Dimensions.get('window').width < 383;
- const processedImages = useRef(new Set<string>());
- useEffect(() => {
- if (netInfo && netInfo.isConnected !== null) {
- setIsConnected(netInfo.isConnected);
- }
- }, [netInfo]);
- useEffect(() => {
- if (showNomads) {
- refetchUsersLocation();
- }
- }, [showNomads]);
- useEffect(() => {
- if (usersLocation && usersLocation.geojson && showNomads) {
- const filteredNomads: GeoJSON.FeatureCollection = {
- type: 'FeatureCollection',
- features: usersLocation.geojson.features.filter(
- (feature: GeoJSON.Feature) => feature.properties?.id !== +userId
- )
- };
- if (!nomads || JSON.stringify(filteredNomads) !== JSON.stringify(nomads)) {
- setNomads(filteredNomads);
- }
- }
- }, [usersLocation, showNomads]);
- useEffect(() => {
- const loadCachedIcons = async () => {
- try {
- const dirInfo = await FileSystem.getInfoAsync(ICONS_DIR);
- if (!dirInfo.exists) return;
- const files = await FileSystem.readDirectoryAsync(ICONS_DIR);
- const cachedImages: Record<string, { uri: string }> = {};
- files.forEach((fileName) => {
- if (!fileName.endsWith('.png')) return;
- const key = fileName.replace('.png', '');
- cachedImages[key] = {
- uri: ICONS_DIR + fileName
- };
- processedImages.current.add(key);
- });
- setImages((prev: any) => ({ ...prev, ...cachedImages }));
- } catch (e) {
- console.warn('Error loading cached icons:', e);
- }
- };
- loadCachedIcons();
- }, []);
- useEffect(() => {
- if (!seriesIcons) return;
- const updateCacheFromAPI = async () => {
- const loadedImages: Record<string, { uri: string }> = {};
- const dirInfo = await FileSystem.getInfoAsync(ICONS_DIR);
- if (!dirInfo.exists) {
- await FileSystem.makeDirectoryAsync(ICONS_DIR, { intermediates: true });
- }
- const promises = seriesIcons.data.map(async (icon) => {
- const id = icon.id?.toString();
- if (!id || processedImages.current.has(id)) return;
- const imgUrl = `${API_HOST}/static/img/series_new2_small/${icon.new_icon_png}`;
- const imgVisitedUrl = `${API_HOST}/static/img/series_new2_small/${icon.new_icon_visited_png}`;
- const localPath = `${ICONS_DIR}${id}.png`;
- const localPathVisited = `${ICONS_DIR}${id}v.png`;
- const [imgInfo, visitedInfo] = await Promise.all([
- FileSystem.getInfoAsync(localPath),
- FileSystem.getInfoAsync(localPathVisited)
- ]);
- try {
- if (!imgInfo.exists) {
- await FileSystem.downloadAsync(imgUrl, localPath);
- }
- if (!visitedInfo.exists) {
- await FileSystem.downloadAsync(imgVisitedUrl, localPathVisited);
- }
- } catch (e) {
- console.warn(`Download failed for ${id}:`, e);
- return;
- }
- processedImages.current.add(id);
- processedImages.current.add(`${id}v`);
- loadedImages[id] = { uri: localPath };
- loadedImages[`${id}v`] = { uri: localPathVisited };
- });
- await Promise.all(promises);
- setImages((prev: any) => ({ ...prev, ...loadedImages }));
- };
- updateCacheFromAPI();
- }, [seriesIcons]);
- useEffect(() => {
- const loadDatabases = async () => {
- const firstDb = await getFirstDatabase();
- const secondDb = await getSecondDatabase();
- const countriesDb = await getCountriesDatabase();
- setDb1(firstDb);
- setDb2(secondDb);
- setDb3(countriesDb);
- };
- if (!db1 || !db2 || !db3) {
- loadDatabases();
- }
- }, [db1, db2, db3]);
- useEffect(() => {
- const savedFilterSettings = storage.get('filterSettings', StoreType.STRING) as string;
- if (savedFilterSettings) {
- const filterSettings = JSON.parse(savedFilterSettings);
- setTilesType(filterSettings.tilesType);
- setType(filterSettings.type);
- setRegionsFilter({
- visitedLabel:
- filterSettings.selectedVisible?.value && filterSettings.selectedVisible.value === 1
- ? 'in'
- : 'by',
- year: filterSettings.selectedYear?.value ?? moment().year()
- });
- setSeriesFilter(filterSettings.seriesFilter);
- }
- }, []);
- useFocusEffect(
- useCallback(() => {
- if (token) {
- refetchVisitedRegions();
- refetchVisitedCountries();
- refetchVisitedDare();
- }
- }, [navigation])
- );
- // to do refactor
- useEffect(() => {
- if (visitedRegionIds) {
- setRegionsVisited(visitedRegionIds.ids);
- storage.set('visitedRegions', JSON.stringify(visitedRegionIds.ids));
- } else {
- const storedVisited = storage.get('visitedRegions', StoreType.STRING) as string;
- setRegionsVisited(storedVisited ? JSON.parse(storedVisited) : []);
- }
- }, [visitedRegionIds]);
- useEffect(() => {
- if (visitedCountryIds) {
- setCountriesVisited(visitedCountryIds.ids);
- storage.set('visitedCountries', JSON.stringify(visitedCountryIds.ids));
- } else {
- const storedVisited = storage.get('visitedCountries', StoreType.STRING) as string;
- setCountriesVisited(storedVisited ? JSON.parse(storedVisited) : []);
- }
- }, [visitedCountryIds]);
- useEffect(() => {
- if (visitedDareIds) {
- setDareVisited(visitedDareIds.ids);
- storage.set('visitedDares', JSON.stringify(visitedDareIds.ids));
- } else {
- const storedVisited = storage.get('visitedDares', StoreType.STRING) as string;
- setDareVisited(storedVisited ? JSON.parse(storedVisited) : []);
- }
- }, [visitedDareIds]);
- useEffect(() => {
- if (visitedSeriesIds && token) {
- setSeriesVisited(visitedSeriesIds.ids);
- storage.set('visitedSeries', JSON.stringify(visitedSeriesIds.ids));
- } else {
- const storedVisited = storage.get('visitedSeries', StoreType.STRING) as string;
- setSeriesVisited(storedVisited ? JSON.parse(storedVisited) : []);
- }
- }, [visitedSeriesIds]);
- useEffect(() => {
- if (regionsVisited && regionsVisited.length) {
- setRegionsVisitedFilter(generateFilter(regionsVisited));
- } else {
- setRegionsVisitedFilter(['==', 'id', -1]);
- }
- }, [regionsVisited]);
- useEffect(() => {
- if (countriesVisited && countriesVisited.length) {
- setCountriesVisitedFilter(generateFilter(countriesVisited));
- } else {
- setCountriesVisitedFilter(['==', 'id', -1]);
- }
- }, [countriesVisited]);
- useEffect(() => {
- if (dareVisited && dareVisited.length) {
- setDareVisitedFilter(generateFilter(dareVisited));
- } else {
- setDareVisitedFilter(['==', 'id', -1]);
- }
- }, [dareVisited]);
- useEffect(() => {
- if (!seriesFilter.visible) {
- setSeriesVisitedFilter(generateFilter([]));
- setSeriesNotVisitedFilter(generateFilter([]));
- return;
- }
- if (seriesFilter.applied) {
- if (seriesVisited?.length) {
- setSeriesVisitedFilter([
- 'all',
- ['any', ...seriesVisited.map((id) => ['==', 'id', id])],
- ['any', ...seriesFilter.groups.map((groupId: number) => ['==', 'series_id', groupId])]
- ]);
- setSeriesNotVisitedFilter([
- 'all',
- ['all', ...seriesVisited.map((id) => ['!=', 'id', id])],
- ['any', ...seriesFilter.groups.map((groupId: number) => ['==', 'series_id', groupId])]
- ]);
- } else {
- setSeriesNotVisitedFilter([
- 'any',
- ...seriesFilter.groups.map((groupId: number) => ['==', 'series_id', groupId])
- ]);
- }
- } else {
- setSeriesVisitedFilter(['any', ...seriesVisited.map((id) => ['==', 'id', id])]);
- setSeriesNotVisitedFilter(['all', ...seriesVisited.map((id) => ['!=', 'id', id])]);
- }
- }, [seriesVisited, seriesFilter]);
- useEffect(() => {
- if (route.params?.lon && route.params?.lat) {
- setMarkerCoords([route.params.lon, route.params.lat]);
- const timeoutId = setTimeout(() => {
- if (cameraRef.current) {
- cameraRef.current.setCamera({
- centerCoordinate: [route.params?.lon, route.params?.lat],
- zoomLevel: 15,
- animationDuration: 800
- });
- } else {
- console.warn('Camera ref is not available.');
- }
- }, 800);
- return () => clearTimeout(timeoutId);
- }
- if (route.params?.id && route.params?.type && db1 && db2 && db3) {
- handleFindRegion(route.params?.id, route.params?.type);
- }
- }, [route, db1, db2, db3]);
- useFocusEffect(
- useCallback(() => {
- if (token) {
- refetch();
- }
- }, [])
- );
- useEffect(() => {
- if (interval > 0 && showNomads) {
- const intervalId = setInterval(() => {
- if (location && token && showNomads) {
- refetchUsersLocation();
- }
- }, interval);
- return () => clearInterval(intervalId);
- }
- }, [interval, showNomads]);
- useEffect(() => {
- (async () => {
- let { status } = await Location.getForegroundPermissionsAsync();
- const isServicesEnabled = await Location.hasServicesEnabledAsync();
- if (locationSettings && locationSettings.sharing_refresh_interval) {
- setInterval(locationSettings.sharing_refresh_interval * 1000);
- }
- if (
- status !== 'granted' ||
- !token ||
- (locationSettings && locationSettings.sharing === 0) ||
- !isServicesEnabled
- ) {
- setShowNomads(false);
- storage.set('showNomads', false);
- await stopBackgroundLocationUpdates();
- return;
- }
- const bgStatus = await Location.getBackgroundPermissionsAsync();
- if (bgStatus.status !== 'granted') {
- const { status } = await Location.requestBackgroundPermissionsAsync();
- if (status === Location.PermissionStatus.GRANTED) {
- await startBackgroundLocationUpdates();
- } else {
- await stopBackgroundLocationUpdates();
- }
- } else {
- // await startBackgroundLocationUpdates();
- await restartBackgroundLocationUpdates();
- }
- try {
- let currentLocation = await Location.getCurrentPositionAsync({
- accuracy: Location.Accuracy.Balanced
- });
- setLocation(currentLocation.coords);
- if (locationSettings && locationSettings.sharing === 1 && token) {
- updateLocation({
- token,
- lat: currentLocation.coords.latitude,
- lng: currentLocation.coords.longitude
- });
- showNomads && refetchUsersLocation();
- }
- } catch (error) {
- console.error('Error fetching user location:', error);
- }
- })();
- }, [locationSettings]);
- useEffect(() => {
- const currentYear = moment().year();
- let yearSelector: { label: string; value: number }[] = [{ label: 'visited', value: 1 }];
- for (let i = currentYear; i >= 1951; i--) {
- yearSelector.push({ label: i.toString(), value: i });
- }
- handleModalStateChange({ years: yearSelector });
- }, []);
- useFocusEffect(
- useCallback(() => {
- navigation.getParent()?.setOptions({
- tabBarStyle: {
- display: regionPopupVisible ? 'none' : 'flex',
- position: 'absolute',
- ...Platform.select({
- android: {
- height: 58
- }
- })
- }
- });
- }, [regionPopupVisible, navigation])
- );
- const mapRef = useRef<MapLibreRN.MapViewRef>(null);
- const cameraRef = useRef<MapLibreRN.CameraRef>(null);
- const shapeSourceRef = useRef<MapLibreRN.ShapeSourceRef>(null);
- useEffect(() => {
- if (userInfo) {
- setUserInfoData(JSON.parse(userInfo));
- }
- }, [userInfo]);
- const handlePress = () => {
- if (isExpanded) {
- setSearchInput('');
- }
- setIsExpanded((prev) => !prev);
- width.value = withTiming(isExpanded ? 48 : usableWidth, {
- duration: 300,
- easing: Easing.inOut(Easing.ease)
- });
- };
- const animatedStyle = useAnimatedStyle(() => {
- return {
- width: width.value
- };
- });
- const loadInitialRegion = () => {
- try {
- const savedInitialRegion = storage.get('initialRegion', StoreType.STRING) as string;
- if (savedInitialRegion) {
- const region = JSON.parse(savedInitialRegion);
- setInitialRegion(region);
- }
- } catch (e) {
- console.error('Failed to load saved initial region:', e);
- }
- };
- useEffect(() => {
- loadInitialRegion();
- }, []);
- useEffect(() => {
- if (initialRegion && !route.params?.id) {
- const timeoutId = setTimeout(() => {
- if (cameraRef.current) {
- cameraRef.current.setCamera({
- centerCoordinate: [initialRegion.longitude, initialRegion.latitude],
- zoomLevel: Math.log2(360 / initialRegion.latitudeDelta),
- animationDuration: 500
- });
- } else {
- console.warn('Camera ref is not available.');
- }
- }, 500);
- return () => clearTimeout(timeoutId);
- }
- }, [initialRegion]);
- const handleMapChange = async () => {
- if (!mapRef.current) return;
- if (hideTimer.current) clearTimeout(hideTimer.current);
- setIsZooming(true);
- const currentZoom = await mapRef.current.getZoom();
- const currentCenter = await mapRef.current.getCenter();
- setZoom(currentZoom);
- setCenter(currentCenter);
- };
- const onMapPress = async (event: any) => {
- if (!mapRef.current) return;
- if (selectedMarker || selectedUser) {
- closeCallout();
- return;
- }
- if (type === 'blank') return;
- try {
- const { screenPointX, screenPointY } = event.properties;
- const { features } = await mapRef.current.queryRenderedFeaturesAtPoint(
- [screenPointX, screenPointY],
- undefined,
- ['regions', 'countries', 'dare']
- );
- if (features?.length) {
- const region = features[0];
- if (selectedRegion === region.properties?.id) return;
- let db = type === 'regions' ? db1 : type === 'countries' ? db3 : db2;
- let tableName = type === 'dare' ? 'places' : type;
- let foundRegion = region.properties?.id;
- setSelectedRegion(region.properties?.id);
- await getData(db, foundRegion, tableName, handleRegionData)
- .then(() => {
- setRegionPopupVisible(true);
- })
- .catch((error) => {
- console.error('Error fetching data', error);
- refreshDatabases();
- });
- if (tableName === 'regions') {
- token
- ? await mutateUserData(
- { region_id: +foundRegion, token: String(token) },
- {
- onSuccess: (data) => {
- setUserData({ type: 'nm', id: +foundRegion, ...data });
- }
- }
- )
- : setUserData({ type: 'nm', id: +foundRegion });
- if (regionsList && regionsList.data) {
- const region = regionsList.data.find((region) => region.id === +foundRegion);
- if (region) {
- const bounds = turf.bbox(region.bbox);
- cameraRef.current?.fitBounds(
- [bounds[2], bounds[3]],
- [bounds[0], bounds[1]],
- [10, 10, 50, 10],
- 1000
- );
- }
- }
- } else if (tableName === 'countries') {
- token
- ? await mutateCountriesData(
- { id: +foundRegion, token },
- {
- onSuccess: (data) => {
- setUserData({ type: 'countries', id: +foundRegion, ...data.data });
- }
- }
- )
- : setUserData({ type: 'countries', id: +foundRegion });
- if (countriesList && countriesList.data) {
- const region = countriesList.data.find((region) => region.id === +foundRegion);
- if (region) {
- const bounds = turf.bbox(region.bbox);
- cameraRef.current?.fitBounds(
- [bounds[2], bounds[3]],
- [bounds[0], bounds[1]],
- [10, 10, 50, 10],
- 1000
- );
- }
- }
- } else {
- token
- ? await mutateUserDataDare(
- { dare_id: +foundRegion, token: String(token) },
- {
- onSuccess: (data) => {
- setUserData({ type: 'dare', id: +foundRegion, ...data });
- }
- }
- )
- : setUserData({ type: 'dare', id: +foundRegion });
- if (dareList && dareList.data) {
- const region = dareList.data.find((region) => region.id === +foundRegion);
- if (region) {
- const bounds = turf.bbox(region.bbox);
- cameraRef.current?.fitBounds(
- [bounds[2], bounds[3]],
- [bounds[0], bounds[1]],
- [10, 10, 50, 10],
- 1000
- );
- }
- }
- }
- } else {
- handleClosePopup();
- }
- } catch (error) {
- console.error('Error onMapPress features:', error);
- }
- };
- const handleRegionDidChange = async (feature: GeoJSON.Feature<GeoJSON.Point, any>) => {
- hideTimer.current = setTimeout(() => {
- setIsZooming(false);
- }, 2000);
- if (!feature) return;
- const { zoomLevel } = feature.properties;
- const { coordinates } = feature.geometry;
- if (!zoomLevel || !coordinates) return;
- const latitudeDelta = 360 / 2 ** zoomLevel;
- const longitudeDelta = latitudeDelta;
- const region = {
- latitude: coordinates[1],
- longitude: coordinates[0],
- latitudeDelta,
- longitudeDelta
- };
- storage.set('initialRegion', JSON.stringify(region));
- };
- const handleClosePopup = async () => {
- setSelectedRegion(null);
- setRegionPopupVisible(false);
- setRegionData(null);
- };
- const handleGetLocation = async () => {
- setIsLocationLoading(true);
- try {
- let { status, canAskAgain } = await Location.getForegroundPermissionsAsync();
- const isServicesEnabled = await Location.hasServicesEnabledAsync();
- if (status === 'granted' && isServicesEnabled) {
- const bgStatus = await Location.getBackgroundPermissionsAsync();
- if (bgStatus.status !== 'granted') {
- const { status } = await Location.requestBackgroundPermissionsAsync();
- if (status === Location.PermissionStatus.GRANTED) {
- await startBackgroundLocationUpdates();
- } else {
- await stopBackgroundLocationUpdates();
- }
- } else {
- await startBackgroundLocationUpdates();
- }
- await getLocation();
- } else if (!canAskAgain || !isServicesEnabled) {
- setOpenSettingsVisible(true);
- } else {
- setAskLocationVisible(true);
- }
- } finally {
- setIsLocationLoading(false);
- }
- };
- const getLocation = async () => {
- try {
- let currentLocation = await Location.getCurrentPositionAsync({
- accuracy: Location.Accuracy.Balanced
- });
- setLocation(currentLocation.coords);
- if (currentLocation.coords) {
- cameraRef.current?.flyTo(
- [currentLocation.coords.longitude, currentLocation.coords.latitude],
- 1000
- );
- }
- if (locationSettings && locationSettings.sharing === 1 && token) {
- updateLocation({
- token,
- lat: currentLocation.coords.latitude,
- lng: currentLocation.coords.longitude
- });
- showNomads && refetchUsersLocation();
- }
- handleClosePopup();
- } catch (error) {
- console.error('Error fetching user location:', error);
- }
- };
- const handleAcceptPermission = async () => {
- setAskLocationVisible(false);
- let { status, canAskAgain } = await Location.requestForegroundPermissionsAsync();
- const isServicesEnabled = await Location.hasServicesEnabledAsync();
- if (status === 'granted' && isServicesEnabled) {
- getLocation();
- } else if (!canAskAgain || !isServicesEnabled) {
- setOpenSettingsVisible(true);
- }
- };
- const handleOpenEditModal = () => {
- handleModalStateChange({
- selectedFirstYear: userData?.first_visit_year,
- selectedLastYear: userData?.last_visit_year,
- selectedQuality:
- qualityOptions.find((quality) => quality.id === userData?.best_visit_quality) ||
- qualityOptions[2],
- selectedNoOfVisits: userData?.no_of_visits || 1,
- id: regionData?.id
- });
- setIsEditModalVisible(true);
- };
- const handleOpenEditSlowModal = () => {
- setIsEditSlowModalVisible(true);
- };
- const handleSearch = async () => {
- setSearch(searchInput);
- setSearchVisible(true);
- };
- const handleCloseModal = () => {
- setSearchInput('');
- setSearchVisible(false);
- handlePress();
- };
- const handleRegionData = async (regionData: any, avatars: string[]) => {
- if (!regionData) {
- await refreshDatabases();
- }
- setRegionData(regionData);
- setUserAvatars(avatars);
- };
- const handleFindRegion = async (id: number, type: 'regions' | 'countries' | 'places') => {
- setType(type === 'places' ? 'dare' : type);
- if (!db1 || !db2 || !db3) {
- return;
- }
- const db = type === 'regions' ? db1 : type === 'countries' ? db3 : db2;
- if (id) {
- setSelectedRegion(id);
- await getData(db, id, type, handleRegionData)
- .then(() => {
- setRegionPopupVisible(true);
- })
- .catch((error) => {
- console.error('Error fetching data', error);
- refreshDatabases();
- });
- if (type === 'regions') {
- token
- ? await mutateUserData(
- { region_id: id, token: String(token) },
- {
- onSuccess: (data) => {
- setUserData({ type: 'nm', id, ...data });
- }
- }
- )
- : setUserData({ type: 'nm', id });
- if (regionsList && regionsList.data) {
- const region = regionsList.data.find((region) => region.id === +id);
- if (region) {
- const bounds = turf.bbox(region.bbox);
- cameraRef.current?.fitBounds(
- [bounds[2], bounds[3]],
- [bounds[0], bounds[1]],
- [10, 10, 50, 10],
- 1000
- );
- }
- }
- } else if (type === 'countries') {
- token
- ? await mutateCountriesData(
- { id, token },
- {
- onSuccess: (data) => {
- setUserData({ type: 'countries', id, ...data.data });
- }
- }
- )
- : setUserData({ type: 'countries', id });
- if (countriesList && countriesList.data) {
- const region = countriesList.data.find((region) => region.id === +id);
- if (region) {
- const bounds = turf.bbox(region.bbox);
- cameraRef.current?.fitBounds(
- [bounds[2], bounds[3]],
- [bounds[0], bounds[1]],
- [10, 10, 50, 10],
- 1000
- );
- }
- }
- } else {
- token
- ? await mutateUserDataDare(
- { dare_id: +id, token: String(token) },
- {
- onSuccess: (data) => {
- setUserData({ type: 'dare', id: +id, ...data });
- }
- }
- )
- : setUserData({ type: 'dare', id: +id });
- if (dareList && dareList.data) {
- const region = dareList.data.find((region) => region.id === +id);
- if (region) {
- const bounds = turf.bbox(region.bbox);
- cameraRef.current?.fitBounds(
- [bounds[2], bounds[3]],
- [bounds[0], bounds[1]],
- [10, 10, 50, 10],
- 1000
- );
- }
- }
- }
- } else {
- handleClosePopup();
- }
- };
- const handleMarkerPress = async (event: any) => {
- const { features } = event;
- if (features?.length) {
- const selectedFeature = features[0];
- const { coordinates } = selectedFeature.geometry;
- const visited = seriesVisited.includes(selectedFeature.properties.id) ? 1 : 0;
- const icon = images[selectedFeature.properties.series_id];
- const { name, description, series_name, series_id, id } = selectedFeature.properties;
- if (coordinates) {
- setSelectedMarker({
- coordinates,
- name,
- icon,
- description,
- series_name,
- visited,
- series_id,
- id
- });
- setSelectedUser(null);
- }
- }
- };
- const closeCallout = () => {
- setSelectedMarker(null);
- setSelectedUser(null);
- };
- const toggleSeries = useCallback(
- async (item: any) => {
- if (!token) {
- setIsWarningModalVisible(true);
- return;
- }
- const itemData = {
- token,
- series_id: item.series_id,
- item_id: item.id,
- checked: (item.visited === 0 ? 1 : 0) as 0 | 1,
- double: 0 as 0 | 1
- };
- try {
- updateSeriesItem(itemData);
- if (item.visited === 1) {
- setSeriesVisited((current) => current.filter((id) => id !== item.id));
- setSelectedMarker((current: any) => ({ ...current, visited: 0 }));
- } else {
- setSeriesVisited((current) => [...current, item.id]);
- setSelectedMarker((current: any) => ({ ...current, visited: 1 }));
- }
- } catch (error) {
- console.error('Failed to update series state', error);
- }
- },
- [token, updateSeriesItem]
- );
- const handleModalStateChange = (updates: { [key: string]: any }) => {
- setModalState((prevState) => ({ ...prevState, ...updates }));
- };
- const handleUserPress = (event: any) => {
- const selectedFeature = event.features[0];
- const { coordinates } = selectedFeature.geometry;
- const { avatar, first_name, last_name, flag, id, last_seen } = selectedFeature.properties;
- if (selectedFeature) {
- setSelectedUser({
- coordinates,
- avatar: avatar ? { uri: API_HOST + avatar } : logo,
- first_name,
- last_name,
- flag: { uri: API_HOST + flag },
- id,
- last_seen
- });
- setSelectedMarker(null);
- }
- };
- return (
- <SafeAreaView style={{ height: '100%' }}>
- <StatusBar translucent backgroundColor="transparent" />
- <MapLibreRN.MapView
- ref={mapRef}
- style={styles.map}
- mapStyle={VECTOR_MAP_HOST + '/nomadmania-maps.json'}
- rotateEnabled={false}
- attributionEnabled={false}
- onPress={onMapPress}
- onRegionDidChange={handleRegionDidChange}
- onRegionIsChanging={handleMapChange}
- onRegionWillChange={_.debounce(handleMapChange, 200)}
- >
- <MapLibreRN.Images
- images={images}
- onImageMissing={(image) => {
- if (processedImages.current.has(image)) {
- return;
- }
- processedImages.current.add(image);
- setImages((prevImages: any) => ({
- ...prevImages,
- [image]: defaultSeriesIcon
- }));
- }}
- >
- <View />
- </MapLibreRN.Images>
- {markerCoords && (
- <MapLibreRN.PointAnnotation id="marker" coordinate={markerCoords}>
- <View
- style={{
- height: 24,
- width: 24,
- backgroundColor: Colors.ORANGE,
- borderRadius: 12,
- borderColor: Colors.WHITE,
- borderWidth: 2
- }}
- />
- </MapLibreRN.PointAnnotation>
- )}
- {type === 'regions' && (
- <>
- <MapLibreRN.LineLayer
- id="nm-regions-line-layer"
- sourceID={regions.source}
- sourceLayerID={regions['source-layer']}
- filter={regions.filter as any}
- maxZoomLevel={regions.maxzoom}
- style={{
- lineColor: 'rgba(14, 80, 109, 1)',
- lineWidth: ['interpolate', ['linear'], ['zoom'], 0, 0.2, 4, 1, 5, 1.5, 12, 3],
- lineWidthTransition: { duration: 300, delay: 0 }
- }}
- belowLayerID="waterway-name"
- />
- <MapLibreRN.FillLayer
- id={regions.id}
- sourceID={regions.source}
- sourceLayerID={regions['source-layer']}
- filter={regions.filter as any}
- style={regions.style}
- maxZoomLevel={regions.maxzoom}
- belowLayerID={regions_visited.id}
- />
- <MapLibreRN.FillLayer
- id={regions_visited.id}
- sourceID={regions_visited.source}
- sourceLayerID={regions_visited['source-layer']}
- filter={regionsVisitedFilter as any}
- style={regions_visited.style}
- maxZoomLevel={regions_visited.maxzoom}
- belowLayerID="waterway-name"
- />
- </>
- )}
- {type === 'countries' && (
- <>
- <MapLibreRN.LineLayer
- id="countries-line-layer"
- sourceID={countries.source}
- sourceLayerID={countries['source-layer']}
- filter={countries.filter as any}
- maxZoomLevel={countries.maxzoom}
- style={{
- lineColor: 'rgba(14, 80, 109, 1)',
- lineWidth: ['interpolate', ['linear'], ['zoom'], 0, 0.2, 4, 1, 5, 1.5, 12, 3],
- lineWidthTransition: { duration: 300, delay: 0 }
- }}
- belowLayerID="waterway-name"
- />
- <MapLibreRN.FillLayer
- id={countries.id}
- sourceID={countries.source}
- sourceLayerID={countries['source-layer']}
- filter={countries.filter as any}
- style={countries.style}
- maxZoomLevel={countries.maxzoom}
- belowLayerID={countries_visited.id}
- />
- <MapLibreRN.FillLayer
- id={countries_visited.id}
- sourceID={countries_visited.source}
- sourceLayerID={countries_visited['source-layer']}
- filter={countriesVisitedFilter as any}
- style={countries_visited.style}
- maxZoomLevel={countries_visited.maxzoom}
- belowLayerID="waterway-name"
- />
- </>
- )}
- {type === 'dare' && (
- <>
- <MapLibreRN.FillLayer
- id={dare.id}
- sourceID={dare.source}
- sourceLayerID={dare['source-layer']}
- filter={dare.filter as any}
- style={dare.style}
- maxZoomLevel={dare.maxzoom}
- belowLayerID={dare_visited.id}
- />
- <MapLibreRN.FillLayer
- id={dare_visited.id}
- sourceID={dare_visited.source}
- sourceLayerID={dare_visited['source-layer']}
- filter={dareVisitedFilter as any}
- style={dare_visited.style}
- maxZoomLevel={dare_visited.maxzoom}
- belowLayerID="waterway-name"
- />
- </>
- )}
- {selectedRegion && type && (
- <>
- <MapLibreRN.FillLayer
- id={selected_region.id}
- sourceID={type}
- sourceLayerID={type}
- filter={['==', 'id', selectedRegion]}
- style={selected_region.style}
- maxZoomLevel={selected_region.maxzoom}
- belowLayerID="waterway-name"
- />
- <MapLibreRN.LineLayer
- id={selected_region_outline.id}
- sourceID={type}
- sourceLayerID={type}
- filter={['==', 'id', selectedRegion]}
- style={selected_region_outline.style as any}
- maxZoomLevel={selected_region_outline.maxzoom}
- belowLayerID="waterway-name"
- />
- </>
- )}
- <MapLibreRN.VectorSource
- id="nomadmania_series"
- tileUrlTemplates={[VECTOR_MAP_HOST + '/tiles/series/{z}/{x}/{y}.pbf']}
- onPress={handleMarkerPress}
- >
- {seriesFilter.status !== 1 && Object.keys(images).length > 0 ? (
- <MapLibreRN.SymbolLayer
- id={series_layer.id}
- sourceID={series_layer.source}
- sourceLayerID={series_layer['source-layer']}
- aboveLayerID={Platform.OS === 'android' ? 'waterway-name' : undefined}
- filter={seriesNotVisitedFilter as any}
- minZoomLevel={series_layer.minzoom}
- maxZoomLevel={series_layer.maxzoom}
- style={{
- symbolSpacing: 1,
- iconImage: ['get', 'series_id'],
- iconSize: 0.51,
- iconAllowOverlap: true,
- iconIgnorePlacement: true,
- visibility: 'visible',
- iconColor: '#666',
- iconOpacity: 1,
- iconHaloColor: '#ffffff',
- iconHaloWidth: 1,
- iconHaloBlur: 0.5
- }}
- />
- ) : (
- <></>
- )}
- {seriesFilter.status !== 0 && Object.keys(images).length > 0 ? (
- <MapLibreRN.SymbolLayer
- id={series_visited.id}
- sourceID={series_visited.source}
- sourceLayerID={series_visited['source-layer']}
- aboveLayerID={Platform.OS === 'android' ? 'waterway-name' : undefined}
- filter={seriesVisitedFilter as any}
- minZoomLevel={series_visited.minzoom}
- maxZoomLevel={series_visited.maxzoom}
- style={{
- symbolSpacing: 1,
- iconImage: '{series_id}v',
- iconSize: 0.51,
- iconAllowOverlap: true,
- iconIgnorePlacement: true,
- visibility: 'visible',
- iconColor: '#666',
- iconOpacity: 1,
- iconHaloColor: '#ffffff',
- iconHaloWidth: 1,
- iconHaloBlur: 0.5
- }}
- />
- ) : (
- <></>
- )}
- </MapLibreRN.VectorSource>
- {nomads && showNomads && (
- <MapLibreRN.ShapeSource
- ref={shapeSourceRef}
- tolerance={20}
- id="nomads"
- shape={nomads}
- onPress={async (event) => {
- const feature = event.features[0];
- const isCluster = feature.properties?.cluster;
- if (isCluster) {
- const clusterCoordinates = (feature.geometry as GeoJSON.Point).coordinates;
- const zoom = await shapeSourceRef.current?.getClusterExpansionZoom(
- feature as GeoJSON.Feature<GeoJSON.Geometry>
- );
- const newZoom = zoom ?? 2;
- cameraRef.current?.setCamera({
- centerCoordinate: clusterCoordinates,
- zoomLevel: newZoom,
- animationDuration: 500,
- animationMode: 'flyTo'
- });
- return;
- } else {
- handleUserPress(event);
- }
- }}
- cluster={true}
- clusterRadius={50}
- >
- <MapLibreRN.SymbolLayer
- id="nomads_circle"
- filter={['has', 'point_count']}
- aboveLayerID={Platform.OS === 'android' ? 'place-continent' : undefined}
- style={{
- iconImage: clusteredUsersIcon,
- iconSize: [
- 'interpolate',
- ['linear'],
- ['get', 'point_count'],
- 0,
- 0.33,
- 10,
- 0.35,
- 20,
- 0.37,
- 50,
- 0.39,
- 75,
- 0.41,
- 100,
- 0.43
- ],
- iconAllowOverlap: true
- }}
- ></MapLibreRN.SymbolLayer>
- <MapLibreRN.SymbolLayer
- id="nomads_count"
- filter={['has', 'point_count']}
- aboveLayerID={Platform.OS === 'android' ? 'nomads_circle' : undefined}
- style={{
- textField: [
- 'case',
- ['<', ['get', 'point_count'], 1000],
- ['get', 'point_count'],
- ['concat', ['/', ['round', ['/', ['get', 'point_count'], 100]], 10], 'k']
- ],
- textFont: ['Noto Sans Bold'],
- textSize: [
- 'interpolate',
- ['linear'],
- ['get', 'point_count'],
- 0,
- 13.5,
- 20,
- 14,
- 75,
- 15
- ],
- textColor: '#FFFFFF',
- textAnchor: 'center',
- textOffset: [
- 'interpolate',
- ['linear'],
- ['get', 'point_count'],
- 0,
- ['literal', [0, 0.85]],
- 20,
- ['literal', [0, 0.92]],
- 75,
- ['literal', [0, 1]]
- ],
- textAllowOverlap: true
- }}
- />
- <MapLibreRN.SymbolLayer
- id="nomads_symbol"
- filter={['!', ['has', 'point_count']]}
- aboveLayerID={Platform.OS === 'android' ? 'place-continent' : undefined}
- style={{
- iconImage: defaultUserAvatar,
- iconSize: [
- 'interpolate',
- ['linear'],
- ['zoom'],
- 0,
- 0.24,
- 5,
- 0.28,
- 10,
- 0.33,
- 15,
- 0.38,
- 20,
- 0.42
- ],
- iconAllowOverlap: true
- }}
- ></MapLibreRN.SymbolLayer>
- </MapLibreRN.ShapeSource>
- )}
- {selectedUser && <UserItem marker={selectedUser} />}
- {selectedMarker && (
- <MarkerItem marker={selectedMarker} toggleSeries={toggleSeries} token={token} />
- )}
- <MapLibreRN.Camera ref={cameraRef} />
- {location && (
- <MapLibreRN.UserLocation
- animated={true}
- showsUserHeadingIndicator={true}
- onPress={async () => {
- const currentZoom = await mapRef.current?.getZoom();
- const newZoom = (currentZoom || 0) + 2;
- cameraRef.current?.setCamera({
- centerCoordinate: [location.longitude, location.latitude],
- zoomLevel: newZoom,
- animationDuration: 500,
- animationMode: 'flyTo'
- });
- }}
- >
- {/* to do custom user location */}
- </MapLibreRN.UserLocation>
- )}
- </MapLibreRN.MapView>
- {center ? (
- <ScaleBar
- zoom={zoom}
- latitude={center[1]}
- isVisible={isZooming}
- bottom={tabBarHeight + 80}
- />
- ) : null}
- {regionPopupVisible && regionData ? (
- <>
- <TouchableOpacity
- style={[styles.cornerButton, styles.topLeftButton, styles.closeLeftButton]}
- onPress={handleClosePopup}
- >
- <CloseSvg fill="white" width={13} height={13} />
- <Text style={styles.textClose}>Close</Text>
- </TouchableOpacity>
- <TouchableOpacity
- onPress={handleGetLocation}
- style={[
- styles.cornerButton,
- styles.topRightButton,
- styles.bottomButton,
- { bottom: tabBarHeight + 20 }
- ]}
- >
- {isLocationLoading ? (
- <ActivityIndicator size="small" color={Colors.DARK_BLUE} />
- ) : (
- <LocationIcon />
- )}
- </TouchableOpacity>
- <RegionPopup
- region={regionData}
- userAvatars={userAvatars}
- userData={userData}
- openEditModal={handleOpenEditModal}
- updateNM={(id, first, last, visits, quality) => {
- if (!token) {
- setIsWarningModalVisible(true);
- return;
- }
- handleUpdateNM(id, first, last, visits, quality);
- const updatedIds = regionsVisited.includes(id)
- ? regionsVisited.filter((visitedId) => visitedId !== id)
- : [...regionsVisited, id];
- setRegionsVisited(updatedIds);
- refetchVisitedCountries();
- }}
- updateDare={(id, visits) => {
- if (!token) {
- setIsWarningModalVisible(true);
- return;
- }
- handleUpdateDare(id, visits);
- const updatedIds = dareVisited.includes(id)
- ? dareVisited.filter((visitedId) => visitedId !== id)
- : [...dareVisited, id];
- setDareVisited(updatedIds);
- }}
- disabled={!token || !isConnected}
- updateSlow={(id, v, s11, s31, s101) => {
- if (!token) {
- setIsWarningModalVisible(true);
- return;
- }
- handleUpdateSlow(id, v, s11, s31, s101);
- const updatedIds = countriesVisited.includes(id)
- ? countriesVisited.filter((visitedId) => visitedId !== id)
- : [...countriesVisited, id];
- setCountriesVisited(updatedIds);
- }}
- openEditSlowModal={handleOpenEditSlowModal}
- />
- </>
- ) : (
- <>
- {!isExpanded ? (
- <TouchableOpacity
- style={[styles.cornerButton, styles.topRightButton]}
- onPress={() => navigation.navigate(NAVIGATION_PAGES.PROFILE_TAB)}
- >
- {token ? (
- userInfoData?.avatar ? (
- <Image
- style={styles.avatar}
- source={{
- uri: API_HOST + '/img/avatars/' + userInfoData?.avatar + '?v=' + avatarVersion
- }}
- />
- ) : (
- <AvatarWithInitials
- text={`${userInfoData?.first_name ? userInfoData?.first_name[0] : ''}${userInfoData?.last_name ? userInfoData?.last_name[0] : ''}`}
- flag={API_HOST + '/img/flags_new/' + userInfoData?.homebase_flag}
- size={48}
- borderColor={Colors.WHITE}
- />
- )
- ) : (
- <ProfileIcon fill={Colors.DARK_BLUE} />
- )}
- </TouchableOpacity>
- ) : null}
- <Animated.View
- style={[
- styles.searchContainer,
- styles.cornerButton,
- styles.topLeftButton,
- animatedStyle,
- { padding: 5 }
- ]}
- >
- {isExpanded ? (
- <>
- <TouchableOpacity onPress={handlePress} style={styles.iconButton}>
- <CloseSvg fill={'#0F3F4F'} />
- </TouchableOpacity>
- <TextInput
- style={styles.input}
- placeholder="Search regions, places, nomads"
- placeholderTextColor={Colors.LIGHT_GRAY}
- value={searchInput}
- onChangeText={(text) => setSearchInput(text)}
- onSubmitEditing={handleSearch}
- />
- <TouchableOpacity onPress={handleSearch} style={styles.iconButton}>
- <SearchIcon fill={'#0F3F4F'} />
- </TouchableOpacity>
- </>
- ) : (
- <TouchableOpacity onPress={handlePress} style={[styles.iconButton]}>
- <SearchIcon fill={'#0F3F4F'} />
- </TouchableOpacity>
- )}
- </Animated.View>
- <View style={[styles.tabs, { bottom: tabBarHeight + 20 }]}>
- <ScrollView
- horizontal
- showsHorizontalScrollIndicator={false}
- contentContainerStyle={{
- paddingHorizontal: 12,
- paddingTop: 6,
- gap: isSmallScreen ? 8 : 12,
- flexDirection: 'row'
- }}
- >
- <MapButton
- onPress={() => {
- try {
- setIsFilterVisible('regions');
- closeCallout();
- } catch (error) {
- console.error('Error opening filter:', error);
- }
- }}
- icon={TravelsIcon}
- text="Travels"
- active={type !== 'blank'}
- />
- <MapButton
- onPress={() => {
- try {
- setIsFilterVisible('series');
- closeCallout();
- } catch (error) {
- console.error('Error opening filter:', error);
- }
- }}
- icon={SeriesIcon}
- text="Series"
- active={seriesFilter.visible}
- />
- {token ? (
- <MapButton
- onPress={() => {
- try {
- setIsFilterVisible('nomads');
- closeCallout();
- } catch (error) {
- console.error('Error opening filter:', error);
- }
- }}
- icon={NomadsIcon}
- text="Nomads"
- active={showNomads}
- >
- {usersOnMapCount && usersOnMapCount?.count > 0 ? (
- <MessagesDot
- messagesCount={usersOnMapCount.count}
- fullNumber={true}
- right={-10}
- top={-8}
- />
- ) : null}
- </MapButton>
- ) : null}
- </ScrollView>
- </View>
- <TouchableOpacity
- onPress={handleGetLocation}
- style={[
- styles.cornerButton,
- styles.bottomButton,
- styles.bottomRightButton,
- { bottom: tabBarHeight + 20 }
- ]}
- >
- {isLocationLoading ? (
- <ActivityIndicator size="small" color={Colors.DARK_BLUE} />
- ) : (
- <LocationIcon />
- )}
- </TouchableOpacity>
- </>
- )}
- <SearchModal
- searchVisible={searchVisible}
- handleCloseModal={handleCloseModal}
- handleFindRegion={handleFindRegion}
- index={index}
- searchData={searchData}
- setIndex={setIndex}
- token={token}
- />
- <WarningModal
- type={'unauthorized'}
- isVisible={isWarningModalVisible}
- onClose={() => setIsWarningModalVisible(false)}
- />
- <EditNmModal
- isVisible={isEditModalVisible}
- onClose={() => setIsEditModalVisible(false)}
- modalState={modalState}
- updateModalState={handleModalStateChange}
- updateNM={handleUpdateNM}
- />
- <FilterModal
- isFilterVisible={isFilterVisible}
- setIsFilterVisible={setIsFilterVisible}
- tilesTypes={tilesTypes}
- tilesType={tilesType}
- setTilesType={setTilesType}
- setType={setType}
- userId={userId ? +userId : 0}
- setRegionsFilter={setRegionsFilter}
- setSeriesFilter={setSeriesFilter}
- setShowNomads={setShowNomads}
- showNomads={showNomads}
- isPublicView={false}
- isLogged={token ? true : false}
- usersOnMapCount={token && usersOnMapCount?.count ? usersOnMapCount.count : null}
- isConnected={isConnected}
- />
- <EditModal
- isVisible={isEditSlowModalVisible}
- onClose={() => setIsEditSlowModalVisible(false)}
- item={{ ...userData, country_id: regionData?.id }}
- updateSlow={(id, v, s11, s31, s101) => handleUpdateSlow(id, v, s11, s31, s101)}
- />
- <WarningModal
- type={'success'}
- isVisible={askLocationVisible}
- onClose={() => setAskLocationVisible(false)}
- action={handleAcceptPermission}
- 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."
- />
- <WarningModal
- type={'success'}
- isVisible={openSettingsVisible}
- onClose={() => setOpenSettingsVisible(false)}
- action={async () => {
- const isServicesEnabled = await Location.hasServicesEnabledAsync();
- if (!isServicesEnabled) {
- Platform.OS === 'ios'
- ? Linking.openURL('app-settings:')
- : Linking.sendIntent('android.settings.LOCATION_SOURCE_SETTINGS');
- } else {
- Platform.OS === 'ios' ? Linking.openURL('app-settings:') : Linking.openSettings();
- }
- }}
- message="NomadMania app needs location permissions to function properly. Open settings?"
- />
- </SafeAreaView>
- );
- };
- export default MapScreen;
|