| 12345678910111213141516171819202122232425262728293031323334353637383940414243444546474849505152535455565758596061626364656667686970717273747576777879808182838485868788899091929394959697989910010110210310410510610710810911011111211311411511611711811912012112212312412512612712812913013113213313413513613713813914014114214314414514614714814915015115215315415515615715815916016116216316416516616716816917017117217317417517617717817918018118218318418518618718818919019119219319419519619719819920020120220320420520620720820921021121221321421521621721821922022122222322422522622722822923023123223323423523623723823924024124224324424524624724824925025125225325425525625725825926026126226326426526626726826927027127227327427527627727827928028128228328428528628728828929029129229329429529629729829930030130230330430530630730830931031131231331431531631731831932032132232332432532632732832933033133233333433533633733833934034134234334434534634734834935035135235335435535635735835936036136236336436536636736836937037137237337437537637737837938038138238338438538638738838939039139239339439539639739839940040140240340440540640740840941041141241341441541641741841942042142242342442542642742842943043143243343443543643743843944044144244344444544644744844945045145245345445545645745845946046146246346446546646746846947047147247347447547647747847948048148248348448548648748848949049149249349449549649749849950050150250350450550650750850951051151251351451551651751851952052152252352452552652752852953053153253353453553653753853954054154254354454554654754854955055155255355455555655755855956056156256356456556656756856957057157257357457557657757857958058158258358458558658758858959059159259359459559659759859960060160260360460560660760860961061161261361461561661761861962062162262362462562662762862963063163263363463563663763863964064164264364464564664764864965065165265365465565665765865966066166266366466566666766866967067167267367467567667767867968068168268368468568668768868969069169269369469569669769869970070170270370470570670770870971071171271371471571671771871972072172272372472572672772872973073173273373473573673773873974074174274374474574674774874975075175275375475575675775875976076176276376476576676776876977077177277377477577677777877978078178278378478578678778878979079179279379479579679779879980080180280380480580680780880981081181281381481581681781881982082182282382482582682782882983083183283383483583683783883984084184284384484584684784884985085185285385485585685785885986086186286386486586686786886987087187287387487587687787887988088188288388488588688788888989089189289389489589689789889990090190290390490590690790890991091191291391491591691791891992092192292392492592692792892993093193293393493593693793893994094194294394494594694794894995095195295395495595695795895996096196296396496596696796896997097197297397497597697797897998098198298398498598698798898999099199299399499599699799899910001001100210031004100510061007100810091010101110121013101410151016101710181019102010211022102310241025102610271028102910301031103210331034103510361037103810391040104110421043104410451046104710481049105010511052105310541055105610571058105910601061106210631064106510661067106810691070107110721073107410751076107710781079108010811082108310841085108610871088108910901091109210931094109510961097109810991100110111021103110411051106110711081109111011111112111311141115111611171118111911201121112211231124112511261127112811291130113111321133113411351136113711381139114011411142114311441145114611471148114911501151115211531154115511561157115811591160116111621163116411651166116711681169117011711172117311741175117611771178117911801181118211831184118511861187118811891190119111921193119411951196119711981199120012011202120312041205120612071208120912101211121212131214121512161217121812191220122112221223122412251226122712281229123012311232123312341235123612371238123912401241124212431244124512461247124812491250125112521253125412551256125712581259126012611262126312641265126612671268126912701271127212731274127512761277127812791280128112821283128412851286128712881289129012911292129312941295129612971298129913001301130213031304130513061307130813091310131113121313131413151316131713181319132013211322132313241325132613271328132913301331133213331334133513361337133813391340134113421343134413451346134713481349135013511352135313541355135613571358135913601361136213631364136513661367136813691370137113721373137413751376137713781379138013811382138313841385138613871388138913901391139213931394139513961397139813991400140114021403140414051406140714081409141014111412141314141415141614171418141914201421142214231424142514261427142814291430143114321433143414351436143714381439144014411442144314441445144614471448144914501451145214531454145514561457145814591460146114621463146414651466146714681469147014711472147314741475147614771478147914801481148214831484148514861487148814891490149114921493149414951496149714981499150015011502150315041505150615071508150915101511151215131514151515161517151815191520152115221523152415251526152715281529153015311532153315341535153615371538153915401541154215431544154515461547154815491550155115521553155415551556155715581559156015611562156315641565156615671568156915701571157215731574157515761577157815791580158115821583158415851586158715881589159015911592159315941595159615971598159916001601160216031604160516061607160816091610161116121613161416151616161716181619162016211622162316241625162616271628162916301631163216331634163516361637163816391640164116421643164416451646164716481649165016511652165316541655165616571658165916601661166216631664166516661667166816691670167116721673167416751676167716781679168016811682168316841685168616871688168916901691169216931694169516961697169816991700170117021703170417051706170717081709171017111712171317141715171617171718171917201721172217231724172517261727172817291730173117321733173417351736173717381739174017411742174317441745174617471748174917501751175217531754175517561757175817591760176117621763176417651766176717681769177017711772177317741775177617771778177917801781178217831784178517861787178817891790179117921793179417951796179717981799180018011802180318041805180618071808180918101811181218131814181518161817181818191820182118221823182418251826182718281829183018311832183318341835183618371838183918401841184218431844184518461847184818491850185118521853185418551856185718581859186018611862186318641865186618671868186918701871187218731874187518761877187818791880188118821883188418851886188718881889189018911892189318941895189618971898189919001901190219031904190519061907190819091910191119121913191419151916191719181919192019211922192319241925192619271928192919301931193219331934193519361937193819391940194119421943194419451946194719481949195019511952195319541955195619571958195919601961196219631964196519661967196819691970197119721973197419751976197719781979198019811982198319841985198619871988198919901991199219931994199519961997199819992000200120022003200420052006200720082009201020112012201320142015201620172018201920202021202220232024202520262027202820292030203120322033203420352036203720382039204020412042204320442045204620472048204920502051205220532054205520562057205820592060206120622063206420652066206720682069207020712072207320742075207620772078207920802081208220832084208520862087208820892090209120922093209420952096209720982099210021012102210321042105210621072108210921102111211221132114211521162117211821192120212121222123212421252126212721282129213021312132213321342135213621372138213921402141214221432144214521462147214821492150215121522153215421552156215721582159216021612162216321642165216621672168216921702171217221732174217521762177217821792180218121822183218421852186218721882189219021912192219321942195219621972198219922002201220222032204220522062207220822092210221122122213221422152216221722182219222022212222222322242225222622272228222922302231223222332234223522362237223822392240224122422243224422452246224722482249225022512252225322542255225622572258225922602261226222632264226522662267226822692270227122722273227422752276227722782279228022812282228322842285228622872288228922902291229222932294229522962297229822992300230123022303230423052306230723082309231023112312231323142315231623172318231923202321232223232324232523262327232823292330233123322333233423352336233723382339234023412342234323442345234623472348234923502351235223532354235523562357235823592360236123622363236423652366236723682369237023712372237323742375237623772378237923802381238223832384238523862387238823892390239123922393239423952396239723982399240024012402240324042405240624072408240924102411241224132414241524162417241824192420242124222423242424252426242724282429243024312432243324342435243624372438243924402441244224432444244524462447244824492450245124522453245424552456245724582459246024612462246324642465246624672468246924702471247224732474247524762477247824792480248124822483248424852486248724882489249024912492249324942495249624972498249925002501250225032504250525062507250825092510251125122513251425152516251725182519252025212522252325242525252625272528252925302531253225332534253525362537253825392540254125422543254425452546254725482549255025512552255325542555255625572558255925602561256225632564256525662567256825692570257125722573257425752576257725782579258025812582258325842585258625872588258925902591259225932594259525962597259825992600260126022603260426052606260726082609261026112612261326142615261626172618261926202621262226232624262526262627262826292630263126322633263426352636263726382639264026412642264326442645264626472648264926502651265226532654265526562657265826592660266126622663266426652666266726682669267026712672267326742675267626772678267926802681268226832684268526862687268826892690269126922693269426952696269726982699270027012702270327042705270627072708270927102711271227132714271527162717271827192720272127222723272427252726272727282729273027312732273327342735273627372738273927402741274227432744274527462747274827492750275127522753275427552756275727582759276027612762276327642765276627672768276927702771277227732774277527762777277827792780278127822783278427852786278727882789279027912792279327942795279627972798279928002801280228032804280528062807280828092810281128122813281428152816281728182819282028212822282328242825282628272828282928302831283228332834283528362837283828392840284128422843284428452846284728482849285028512852285328542855285628572858285928602861286228632864286528662867286828692870287128722873287428752876287728782879288028812882288328842885288628872888288928902891289228932894289528962897289828992900290129022903290429052906290729082909291029112912291329142915291629172918291929202921292229232924292529262927292829292930293129322933293429352936293729382939294029412942294329442945294629472948294929502951295229532954295529562957295829592960296129622963296429652966296729682969297029712972297329742975297629772978297929802981298229832984298529862987298829892990299129922993299429952996299729982999300030013002300330043005300630073008300930103011301230133014301530163017301830193020302130223023302430253026302730283029303030313032303330343035303630373038303930403041304230433044304530463047304830493050305130523053305430553056305730583059306030613062306330643065306630673068306930703071307230733074307530763077307830793080308130823083308430853086308730883089309030913092309330943095309630973098309931003101310231033104310531063107310831093110311131123113311431153116311731183119312031213122312331243125312631273128312931303131313231333134313531363137313831393140314131423143314431453146314731483149315031513152 |
- import {
- Dimensions,
- Linking,
- Platform,
- Text,
- TextInput,
- TouchableOpacity,
- View,
- Image,
- StatusBar,
- ActivityIndicator,
- ScrollView
- } from 'react-native';
- import React, { useEffect, useRef, useState, useCallback, useMemo } 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 SearchIcon from 'assets/icons/search.svg';
- import LocationIcon from 'assets/icons/location.svg';
- import CloseSvg from 'assets/icons/close.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, {
- configureReanimatedLogger,
- 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';
- 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/legacy';
- import moment from 'moment';
- import {
- usePostGetVisitedCountriesIdsQuery,
- usePostGetVisitedDareIdsQuery,
- usePostGetVisitedRegionsIdsQuery,
- usePostGetVisitedSeriesIdsQuery
- } from '@api/maps';
- import FilterModal, { FilterModalRef } from './FilterModal';
- import { useGetListDareQuery } from '@api/myDARE';
- import { useGetIconsQuery, usePostSetToggleItem } from '@api/series';
- import MarkerItem from './MarkerItem';
- import {
- usePostGetSettingsQuery,
- usePostGetUsersCountQuery,
- usePostGetUsersLocationFilteredMutation,
- 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 { stopBackgroundLocationUpdates } from 'src/utils/backgroundLocation';
- import { SheetManager } from 'react-native-actions-sheet';
- import MultipleSeriesModal from './MultipleSeriesModal';
- import { useSubscription } from 'src/screens/OfflineMapsScreen/useSubscription';
- import { point } from '@turf/turf';
- import { booleanPointInPolygon } from '@turf/turf';
- import { area } from '@turf/turf';
- configureReanimatedLogger({
- strict: false
- });
- const clusteredUsersIcon = require('assets/icons/icon-clustered-users.png');
- const logo = require('assets/logo-world.png');
- MapLibreRN.Logger.setLogLevel('error');
- const isValidNumber = (val: any) => typeof val === 'number' && !isNaN(val) && isFinite(val);
- const sanitizeNomadsGeoJSON = (
- collection: GeoJSON.FeatureCollection
- ): GeoJSON.FeatureCollection => {
- const validFeatures = collection.features.filter((feature) => {
- if (!feature || !feature.geometry || !feature.properties) return false;
- const geom = feature.geometry;
- if (geom.type !== 'Point') return false;
- const coords = (geom as GeoJSON.Point).coordinates;
- if (!Array.isArray(coords) || coords.length < 2) return false;
- const [lng, lat] = coords;
- if (lng == null || lat == null) return false;
- if (!isValidNumber(lng) || !isValidNumber(lat)) return false;
- if (lng < -180 || lng > 180 || lat < -90 || lat > 90) return false;
- return true;
- });
- return { type: 'FeatureCollection', features: validFeatures };
- };
- const generateFilter = (ids: number[]) => {
- const validIds = ids?.filter(isValidNumber) || [];
- return validIds.length ? ['any', ...validIds.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: 5,
- 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: 5,
- 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 SERIES_ICON_SIZE = [
- 'interpolate',
- ['linear'],
- ['zoom'],
- 3,
- 0.3,
- 4,
- 0.36,
- 5,
- 0.43,
- 6,
- 0.5,
- 7,
- 0.57,
- 8,
- 0.63,
- 9,
- 0.7,
- 10,
- 0.77,
- 11,
- 0.84,
- 12,
- 0.91,
- 13,
- 0.98,
- 14,
- 1.05,
- 15,
- 1.12,
- 16,
- 1.18,
- 17,
- 1.25
- ] as unknown as number;
- const SERIES_CIRCLE_RADIUS = [
- 'interpolate',
- ['linear'],
- ['zoom'],
- 3,
- 5,
- 4,
- 6,
- 5,
- 7,
- 6,
- 8,
- 7,
- 9,
- 8,
- 10,
- 9,
- 11,
- 10,
- 12,
- 11,
- 13,
- 12,
- 14,
- 13,
- 16,
- 14,
- 17,
- 15,
- 18,
- 16,
- 19,
- 17,
- 20
- ] as unknown as number;
- const SERIES_CIRCLE_STROKE = [
- 'interpolate',
- ['linear'],
- ['zoom'],
- 3,
- 1,
- 7,
- 1.5,
- 13,
- 2,
- 17,
- 2.5
- ] as unknown as number;
- const SERIES_TEXT_SIZE = [
- 'interpolate',
- ['linear'],
- ['zoom'],
- 3,
- 6,
- 4,
- 7,
- 5,
- 8,
- 6,
- 9,
- 7,
- 10,
- 8,
- 10,
- 9,
- 11,
- 10,
- 11,
- 11,
- 12,
- 12,
- 12,
- 13,
- 13,
- 14,
- 13,
- 15,
- 14,
- 16,
- 14,
- 17,
- 15
- ] as unknown as number;
- const processedSeriesShares = new Set<number>();
- 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 [usersCount, setUsersCount] = useState(0);
- 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,
- labelIds: []
- });
- const [regionsFilter, setRegionsFilter] = useState<any>({
- visitedLabel: 'by',
- year: moment().year()
- });
- const [nomadsFilter, setNomadsFilter] = useState<any>({
- friends: 0,
- trusted: 0,
- countries: []
- });
- 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 [forceRefetch, setForceRefetch] = useState(0);
- const { data: visitedRegionIds, refetch: refetchVisitedRegions } =
- usePostGetVisitedRegionsIdsQuery(
- token,
- regionsFilter.visitedLabel,
- regionsFilter.year,
- +userId,
- type === 'regions' && !!userId && isConnected,
- forceRefetch
- );
- const { data: visitedCountryIds, refetch: refetchVisitedCountries } =
- usePostGetVisitedCountriesIdsQuery(
- token,
- regionsFilter.visitedLabel,
- regionsFilter.year,
- +userId,
- type === 'countries' && !!userId && isConnected,
- forceRefetch
- );
- 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 filterModalRef = useRef<FilterModalRef>(null);
- const [isLocationLoading, setIsLocationLoading] = useState(false);
- const [premiumModalVisible, setPremiumModalVisible] = 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 { mutateAsync: getLocationFiltered } = usePostGetUsersLocationFilteredMutation();
- 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 [renderCamera, setRenderCamera] = useState(Platform.OS === 'ios');
- const isAnimatingRef = useRef(false);
- const animationTimeoutRef = useRef<NodeJS.Timeout | null>(null);
- const handledSeriesShareRef = useRef<number | null>(null);
- const [markerCoords, setMarkerCoords] = useState<any>(null);
- const [refreshInterval, setRefreshInterval] = useState(0);
- const isSmallScreen = Dimensions.get('window').width < 383;
- const processedImages = useRef(new Set<string>());
- const [didFinishLoadingStyle, setDidFinishLoadingStyle] = useState(false);
- const { isPremium, loading } = useSubscription();
- useEffect(() => {
- if (netInfo && netInfo.isConnected !== null) {
- setIsConnected(netInfo.isConnected);
- }
- }, [netInfo]);
- useEffect(() => {
- if (Platform.OS === 'android') {
- MapLibreRN.setConnected(isConnected);
- }
- }, [isConnected])
- useEffect(() => {
- if (showNomads) {
- refetchUsersLocation();
- }
- }, [showNomads]);
- useEffect(() => {
- if (usersLocation && usersLocation.geojson && showNomads) {
- const rawFiltered: GeoJSON.FeatureCollection = {
- type: 'FeatureCollection',
- features: usersLocation.geojson.features.filter(
- (feature: GeoJSON.Feature) => feature.properties?.id !== +userId
- )
- };
- const filteredNomads = sanitizeNomadsGeoJSON(rawFiltered);
- if (!nomads || filteredNomads.features.length !== nomads.features.length) {
- setNomads(filteredNomads);
- }
- if (usersOnMapCount && usersOnMapCount.count) {
- setUsersCount(usersOnMapCount.count);
- }
- }
- }, [usersLocation, showNomads, refreshInterval]);
- useEffect(() => {
- if (
- usersOnMapCount &&
- usersOnMapCount.count &&
- !nomadsFilter.friends &&
- !nomadsFilter.countries?.length
- ) {
- setUsersCount(usersOnMapCount.count);
- }
- }, [usersOnMapCount]);
- 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);
- }
- };
- didFinishLoadingStyle && loadCachedIcons();
- }, [didFinishLoadingStyle]);
- useEffect(() => {
- if (!seriesIcons || !didFinishLoadingStyle) 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, didFinishLoadingStyle]);
- 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;
- const storageShowNomads = storage.get('showNomads', StoreType.BOOLEAN) as boolean;
- 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()
- });
- setShowNomads(storageShowNomads ?? false);
- setNomadsFilter({
- friends: filterSettings.nomadsFilter ? filterSettings.nomadsFilter.friends : 0,
- trusted: filterSettings.nomadsFilter ? filterSettings.nomadsFilter.trusted : 0,
- countries: filterSettings.nomadsFilter?.countries ?? []
- });
- setSeriesFilter(filterSettings.seriesFilter);
- }
- }, []);
- useFocusEffect(
- useCallback(() => {
- if (token) {
- setForceRefetch((prev) => prev + 1);
- refetchVisitedCountries();
- refetchVisitedDare();
- }
- if (userData && type === 'regions') {
- handleClosePopup();
- }
- }, [navigation, token])
- );
- 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 (loading) return;
- if (
- !isPremium ||
- !token ||
- showNomads ||
- (!nomadsFilter.friends && !nomadsFilter.countries?.length)
- )
- return;
- const countriesFilter = nomadsFilter.countries
- ? nomadsFilter.countries.map((country: any) => country.country)
- : [];
- getLocationFiltered(
- {
- token,
- friends: nomadsFilter.friends,
- trusted: nomadsFilter.trusted,
- countries: nomadsFilter.countries ? JSON.stringify(countriesFilter) : undefined
- },
- {
- onSuccess: (data: any) => {
- if (data && data?.geojson) {
- setUsersCount(data.geojson.features?.length);
- const rawFiltered: GeoJSON.FeatureCollection = {
- type: 'FeatureCollection',
- features: data.geojson.features.filter(
- (feature: GeoJSON.Feature) => feature.properties?.id !== +userId
- )
- };
- const filteredNomads = sanitizeNomadsGeoJSON(rawFiltered);
- if (!nomads || JSON.stringify(filteredNomads) !== JSON.stringify(nomads)) {
- setNomads(filteredNomads);
- }
- } else {
- setNomads(null);
- }
- },
- onError: (error) => {
- console.error('Error fetching filtered users location:', error);
- }
- }
- );
- }, [nomadsFilter, loading]);
- // const isMustModeEnabled = useMemo(
- // () => seriesFilter.visible && seriesFilter.groups?.includes(-1),
- // [seriesFilter]
- // );
- // const isGroupEnabled = useCallback(
- // (groupId: number) => {
- // if (!seriesFilter.visible) return false;
- // if (!seriesFilter.groups?.length) return true;
- // return seriesFilter.groups.includes(groupId);
- // },
- // [seriesFilter]
- // );
- // const seriesGroups = useMemo(() => {
- // if (!seriesIcons?.data) return [];
- // return seriesIcons.data.map((icon) => ({
- // id: icon.id
- // }));
- // }, [seriesIcons]);
- const seriesTextField = useMemo(() => {
- const ids = seriesFilter.labelIds;
- if (!ids) return ['step', ['zoom'], '', 9, ['get', 'name']] as unknown as string;
- if (ids.length === 0) return '';
- return [
- 'step',
- ['zoom'],
- '',
- 9,
- ['case', ['in', ['get', 'series_id'], ['literal', ids]], ['get', 'name'], '']
- ] as unknown as string;
- }, [seriesFilter.labelIds]);
- useEffect(() => {
- if (!seriesFilter.visible) {
- setSeriesVisitedFilter(generateFilter([]));
- setSeriesNotVisitedFilter(generateFilter([]));
- return;
- }
- if (seriesFilter.applied) {
- if (seriesVisited?.length) {
- setSeriesVisitedFilter([
- 'all',
- ['any', ...seriesVisited.filter(isValidNumber).map((id) => ['==', 'id', id])],
- [
- 'any',
- ...seriesFilter.groups.filter(isValidNumber).map((groupId: number) => {
- if (groupId === -1) {
- return ['==', 'must', 1];
- }
- return ['==', 'series_id', groupId];
- })
- ]
- ]);
- setSeriesNotVisitedFilter([
- 'all',
- ['all', ...seriesVisited.filter(isValidNumber).map((id) => ['!=', 'id', id])],
- [
- 'any',
- ...seriesFilter.groups.filter(isValidNumber).map((groupId: number) => {
- if (groupId === -1) {
- return ['==', 'must', 1];
- }
- return ['==', 'series_id', groupId];
- })
- ]
- ]);
- } else {
- setSeriesNotVisitedFilter([
- 'any',
- ...seriesFilter.groups.filter(isValidNumber).map((groupId: number) => {
- if (groupId === -1) {
- return ['==', 'must', 1];
- }
- return ['==', 'series_id', groupId];
- })
- ]);
- }
- } else {
- setSeriesVisitedFilter(
- seriesVisited.length
- ? ['any', ...seriesVisited.filter(isValidNumber).map((id) => ['==', 'id', id])]
- : generateFilter([])
- );
- setSeriesNotVisitedFilter(
- seriesVisited.length
- ? ['all', ...seriesVisited.filter(isValidNumber).map((id) => ['!=', 'id', id])]
- : ['all']
- );
- }
- }, [seriesVisited, seriesFilter]);
- useEffect(() => {
- if (isValidNumber(route.params?.lon) && isValidNumber(route.params?.lat)) {
- setMarkerCoords([route.params.lon, route.params.lat]);
- const timeoutId = setTimeout(() => {
- cameraController.setCamera({
- centerCoordinate: [route.params?.lon, route.params?.lat],
- zoomLevel: 15,
- animationDuration: 800,
- animationMode: 'flyTo'
- });
- }, 800);
- return () => clearTimeout(timeoutId);
- }
- if (route.params?.seriesItemShare) {
- const itemToFind = route.params.seriesItemShare;
- const shareId = itemToFind._timestamp || itemToFind.item_id;
- if (!processedSeriesShares.has(shareId)) {
- processedSeriesShares.add(shareId);
- handledSeriesShareRef.current = shareId;
- navigation.setParams({ seriesItemShare: undefined });
- handleFindSeries(itemToFind);
- }
- } else 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();
- }
- return () => {
- if (handledSeriesShareRef.current !== null) {
- setSelectedMarker(null);
- }
- };
- }, [])
- );
- useEffect(() => {
- if (refreshInterval > 0 && showNomads) {
- const intervalId = setInterval(() => {
- refetchUsersLocation();
- }, refreshInterval);
- return () => clearInterval(intervalId);
- }
- }, [refreshInterval, showNomads]);
- useEffect(() => {
- (async () => {
- let { status } = await Location.getForegroundPermissionsAsync();
- const isServicesEnabled = await Location.hasServicesEnabledAsync();
- if (locationSettings && locationSettings.sharing_refresh_interval) {
- setRefreshInterval(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 requestBackgroundPermissionSafe();
- // 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);
- const androidCancelRef = useRef<null | (() => void)>(null);
- const setCameraAndroid = (config: any) => {
- androidCancelRef.current?.();
- androidCancelRef.current = null;
- setRenderCamera(true);
- let frame = 0;
- let cancelled = false;
- const startedAt = Date.now();
- const MAX_WAIT_MS = 1500;
- const cleanup = () => {
- cancelled = true;
- if (frame) cancelAnimationFrame(frame);
- isAnimatingRef.current = false;
- setRenderCamera(false);
- };
- androidCancelRef.current = cleanup;
- const trySetCamera = () => {
- if (cancelled) return;
- if (cameraRef.current) {
- cameraRef.current.setCamera(config);
- const duration = (config.animationDuration || 1000) + 200;
- animationTimeoutRef.current && clearTimeout(animationTimeoutRef.current);
- animationTimeoutRef.current = setTimeout(() => {
- if (!cancelled) cleanup();
- }, duration);
- return;
- }
- if (Date.now() - startedAt > MAX_WAIT_MS) {
- cleanup();
- return;
- }
- frame = requestAnimationFrame(trySetCamera);
- };
- frame = requestAnimationFrame(trySetCamera);
- };
- const cameraController = {
- setCamera: useCallback((config: any) => {
- isAnimatingRef.current = true;
- if (animationTimeoutRef.current) {
- clearTimeout(animationTimeoutRef.current);
- }
- if (Platform.OS === 'android') {
- setRenderCamera(true);
- setCameraAndroid(config);
- return;
- } else {
- cameraRef.current?.setCamera(config);
- animationTimeoutRef.current = setTimeout(
- () => {
- isAnimatingRef.current = false;
- },
- (config.animationDuration || 1000) + 100
- );
- }
- }, []),
- fitBounds: useCallback((ne: number[], sw: number[], padding: number[], duration: number) => {
- isAnimatingRef.current = true;
- if (animationTimeoutRef.current) {
- clearTimeout(animationTimeoutRef.current);
- }
- if (Platform.OS === 'android') {
- setRenderCamera(true);
- requestAnimationFrame(() => {
- cameraRef.current?.fitBounds(ne, sw, padding, duration);
- });
- animationTimeoutRef.current = setTimeout(() => {
- isAnimatingRef.current = false;
- setRenderCamera(false);
- }, duration + 200);
- } else {
- cameraRef.current?.fitBounds(ne, sw, padding, duration);
- animationTimeoutRef.current = setTimeout(() => {
- isAnimatingRef.current = false;
- }, duration + 100);
- }
- }, []),
- flyTo: useCallback((coordinates: number[], duration: number = 1000) => {
- isAnimatingRef.current = true;
- if (animationTimeoutRef.current) {
- clearTimeout(animationTimeoutRef.current);
- }
- if (Platform.OS === 'android') {
- setRenderCamera(true);
- requestAnimationFrame(() => {
- cameraRef.current?.flyTo(coordinates, duration);
- });
- animationTimeoutRef.current = setTimeout(() => {
- isAnimatingRef.current = false;
- setRenderCamera(false);
- }, duration + 200);
- } else {
- cameraRef.current?.flyTo(coordinates, duration);
- animationTimeoutRef.current = setTimeout(() => {
- isAnimatingRef.current = false;
- }, duration + 100);
- }
- }, []),
- moveTo: useCallback((coordinates: number[], duration: number = 0) => {
- isAnimatingRef.current = true;
- if (animationTimeoutRef.current) {
- clearTimeout(animationTimeoutRef.current);
- }
- if (Platform.OS === 'android') {
- setRenderCamera(true);
- requestAnimationFrame(() => {
- cameraRef.current?.moveTo(coordinates, duration);
- });
- animationTimeoutRef.current = setTimeout(() => {
- isAnimatingRef.current = false;
- setRenderCamera(false);
- }, duration + 200);
- } else {
- cameraRef.current?.moveTo(coordinates, duration);
- animationTimeoutRef.current = setTimeout(() => {
- isAnimatingRef.current = false;
- }, duration + 100);
- }
- }, []),
- zoomTo: useCallback((zoomLevel: number, duration: number = 1000) => {
- isAnimatingRef.current = true;
- if (animationTimeoutRef.current) {
- clearTimeout(animationTimeoutRef.current);
- }
- if (Platform.OS === 'android') {
- setRenderCamera(true);
- requestAnimationFrame(() => {
- cameraRef.current?.zoomTo(zoomLevel, duration);
- });
- animationTimeoutRef.current = setTimeout(() => {
- isAnimatingRef.current = false;
- setRenderCamera(false);
- }, duration + 200);
- } else {
- cameraRef.current?.zoomTo(zoomLevel, duration);
- animationTimeoutRef.current = setTimeout(() => {
- isAnimatingRef.current = false;
- }, duration + 100);
- }
- }, [])
- };
- useEffect(() => {
- return () => {
- androidCancelRef.current?.();
- if (animationTimeoutRef.current) clearTimeout(animationTimeoutRef.current);
- };
- }, []);
- useEffect(() => {
- if (userInfo) {
- setUserInfoData(JSON.parse(userInfo));
- }
- }, [userInfo]);
- const requestBackgroundPermissionSafe = async () => {
- await new Promise((resolve) => setTimeout(resolve, 300));
- return await Location.requestBackgroundPermissionsAsync();
- };
- 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 &&
- isValidNumber(initialRegion.longitude) &&
- isValidNumber(initialRegion.latitude)
- ) {
- const timeoutId = setTimeout(() => {
- cameraController.setCamera({
- centerCoordinate: [initialRegion.longitude, initialRegion.latitude],
- zoomLevel:
- isValidNumber(initialRegion.latitudeDelta) && initialRegion.latitudeDelta > 0
- ? Math.log2(360 / initialRegion.latitudeDelta)
- : 0,
- animationDuration: 500,
- animationMode: 'flyTo'
- });
- }, 500);
- return () => clearTimeout(timeoutId);
- }
- }, [initialRegion]);
- const handleMapChange = async () => {
- if (!mapRef.current || isAnimatingRef.current) return;
- if (hideTimer.current) clearTimeout(hideTimer.current);
- setIsZooming(true);
- const newZoom = await mapRef.current.getZoom();
- const newCenter = await mapRef.current.getCenter();
- setZoom((prevZoom) => {
- if (prevZoom !== newZoom) return newZoom;
- return prevZoom;
- });
- setCenter((prevCenter) => {
- if (!prevCenter || prevCenter[0] !== newCenter[0] || prevCenter[0] !== newCenter[0]) {
- return newCenter;
- }
- return prevCenter;
- });
- };
- useEffect(() => {
- return () => {
- if (animationTimeoutRef.current) {
- clearTimeout(animationTimeoutRef.current);
- }
- };
- }, []);
- const onMapPress = async (event: any) => {
- if (!mapRef.current) return;
- // const { screenPointX, screenPointY } = event.properties;
- // const seriesLayerIds = seriesGroups.flatMap((g) => [
- // `series-${g.id}-unvisited`,
- // `series-${g.id}-must-icon`,
- // `series-${g.id}-visited-normal`,
- // `series-${g.id}-visited-must`
- // ]);
- // const { features: seriesFeatures } = await mapRef.current.queryRenderedFeaturesAtPoint(
- // [screenPointX, screenPointY],
- // undefined,
- // seriesLayerIds
- // );
- // if (seriesFeatures?.length) {
- // handleMarkerPress({ features: seriesFeatures });
- // return;
- // }
- if (selectedMarker || selectedUser) {
- closeCallout();
- return;
- }
- if (type === 'blank') return;
- try {
- const { screenPointX, screenPointY } = event.properties;
- const [lng, lat] = event.geometry?.coordinates;
- const { features } = await mapRef.current.queryRenderedFeaturesAtPoint(
- [screenPointX, screenPointY],
- undefined,
- ['regions', 'countries', 'dare']
- );
- if (features?.length) {
- let region = features[0];
- try {
- if (typeof lng === 'number' && typeof lat === 'number') {
- const p = point([lng, lat]);
- const inside = features
- .filter(
- (f) => f?.geometry?.type === 'Polygon' || f?.geometry?.type === 'MultiPolygon'
- )
- .filter((f) => booleanPointInPolygon(p, f as any));
- const best = inside.slice().sort((a, b) => area(a as any) - area(b as any))[0];
- if (best) {
- region = best;
- }
- }
- } catch (e) {
- console.warn('region detection failed', e);
- }
- 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);
- if (region.properties?.alt_id) {
- setSelectedRegion(region.properties?.alt_id);
- db = db1;
- tableName = 'regions';
- }
- await getData(db, foundRegion, tableName, handleRegionData)
- .then(() => {
- setRegionPopupVisible(true);
- })
- .catch((error) => {
- console.error('Error fetching data', error);
- refreshDatabases();
- });
- if (tableName === 'regions') {
- const isVisitedLocal = regionsVisited.includes(+foundRegion);
- setUserData({ type: 'nm', id: +foundRegion, visited: isVisitedLocal });
- if (token) {
- await mutateUserData(
- { region_id: +foundRegion, token: String(token) },
- {
- onSuccess: (data) => {
- setUserData({ type: 'nm', id: +foundRegion, ...data });
- }
- }
- );
- } else {
- setUserData({ type: 'nm', id: +foundRegion });
- }
- if (regionsList && regionsList.data) {
- const region = regionsList.data.find((region) => region.id === +foundRegion);
- if (region && region.bbox) {
- const bounds = turf.bbox(region.bbox);
- if (bounds.every(isValidNumber)) {
- cameraController.fitBounds(
- [bounds[2], bounds[3]],
- [bounds[0], bounds[1]],
- [10, 10, 50, 10],
- 1000
- );
- }
- }
- }
- } else if (tableName === 'countries') {
- const isVisitedLocal = countriesVisited.includes(+foundRegion);
- setUserData({ type: 'countries', id: +foundRegion, visited: isVisitedLocal });
- if (token) {
- await mutateCountriesData(
- { id: +foundRegion, token },
- {
- onSuccess: (data) => {
- setUserData({ type: 'countries', id: +foundRegion, ...data.data });
- }
- }
- );
- } else {
- setUserData({ type: 'countries', id: +foundRegion });
- }
- if (countriesList && countriesList.data) {
- const region = countriesList.data.find((region) => region.id === +foundRegion);
- if (region && region.bbox) {
- const bounds = turf.bbox(region.bbox);
- if (bounds.every(isValidNumber)) {
- cameraController.fitBounds(
- [bounds[2], bounds[3]],
- [bounds[0], bounds[1]],
- [10, 10, 50, 10],
- 1000
- );
- }
- }
- }
- } else {
- const isVisitedLocal = dareVisited.includes(+foundRegion);
- setUserData({ type: 'dare', id: +foundRegion, visited: isVisitedLocal });
- if (token) {
- await mutateUserDataDare(
- { dare_id: +foundRegion, token: String(token) },
- {
- onSuccess: (data) => {
- setUserData({ type: 'dare', id: +foundRegion, ...data });
- }
- }
- );
- } else {
- setUserData({ type: 'dare', id: +foundRegion });
- }
- if (dareList && dareList.data) {
- const region = dareList.data.find((region) => region.id === +foundRegion);
- if (region && region.bbox) {
- const bounds = turf.bbox(region.bbox);
- if (bounds.every(isValidNumber)) {
- cameraController.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 requestBackgroundPermissionSafe();
- // 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: Platform.OS === 'ios' ? Location.Accuracy.Balanced : Location.Accuracy.Low
- });
- setLocation(currentLocation.coords);
- if (currentLocation.coords) {
- cameraController.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) {
- await getLocation();
- } else if (!canAskAgain || !isServicesEnabled) {
- setOpenSettingsVisible(true);
- }
- };
- const handleOpenEditModal = () => {
- navigation.navigate(NAVIGATION_PAGES.TRIPS_2025, {
- region: { id: regionData.id, name: regionData.name }
- });
- };
- 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') {
- const isVisitedLocal = regionsVisited.includes(+id);
- setUserData({ type: 'nm', id, visited: isVisitedLocal });
- if (token) {
- await mutateUserData(
- { region_id: id, token: String(token) },
- {
- onSuccess: (data) => {
- setUserData({ type: 'nm', id, ...data });
- }
- }
- );
- } else {
- setUserData({ type: 'nm', id });
- }
- if (regionsList && regionsList.data) {
- const region = regionsList.data.find((region) => region.id === +id);
- if (region && region.bbox) {
- const bounds = turf.bbox(region.bbox);
- if (bounds.every(isValidNumber)) {
- cameraController.fitBounds(
- [bounds[2], bounds[3]],
- [bounds[0], bounds[1]],
- [10, 10, 50, 10],
- 1000
- );
- }
- }
- }
- } else if (type === 'countries') {
- const isVisitedLocal = countriesVisited.includes(+id);
- setUserData({ type: 'countries', id, visited: isVisitedLocal });
- if (token) {
- await mutateCountriesData(
- { id, token },
- {
- onSuccess: (data) => {
- setUserData({ type: 'countries', id, ...data.data });
- }
- }
- );
- } else {
- setUserData({ type: 'countries', id });
- }
- if (countriesList && countriesList.data) {
- const region = countriesList.data.find((region) => region.id === +id);
- if (region && region.bbox) {
- const bounds = turf.bbox(region.bbox);
- if (bounds.every(isValidNumber)) {
- cameraController.fitBounds(
- [bounds[2], bounds[3]],
- [bounds[0], bounds[1]],
- [10, 10, 50, 10],
- 1000
- );
- }
- }
- }
- } else {
- const isVisitedLocal = dareVisited.includes(+id);
- setUserData({ type: 'dare', id: +id, visited: isVisitedLocal });
- if (token) {
- await mutateUserDataDare(
- { dare_id: +id, token: String(token) },
- {
- onSuccess: (data) => {
- setUserData({ type: 'dare', id: +id, ...data });
- }
- }
- );
- } else {
- setUserData({ type: 'dare', id: +id });
- }
- if (dareList && dareList.data) {
- const region = dareList.data.find((region) => region.id === +id);
- if (region && region.bbox) {
- const bounds = turf.bbox(region.bbox);
- if (bounds.every(isValidNumber)) {
- cameraController.fitBounds(
- [bounds[2], bounds[3]],
- [bounds[0], bounds[1]],
- [10, 10, 50, 10],
- 1000
- );
- }
- }
- }
- }
- } else {
- handleClosePopup();
- }
- };
- const handleFindSeries = async (seriesItem: any) => {
- let coordinates = null;
- try {
- if (seriesItem.location_geojson) {
- const geojsonObj = JSON.parse(seriesItem.location_geojson);
- if (geojsonObj.type === 'Point') {
- const parsedCoords = geojsonObj.coordinates;
- if (parsedCoords && parsedCoords.length === 2) {
- coordinates = [parsedCoords[1], parsedCoords[0]];
- }
- } else if (
- geojsonObj.type === 'Polygon' ||
- geojsonObj.type === 'MultiPolygon' ||
- geojsonObj.type === 'GeometryCollection'
- ) {
- try {
- const center = turf.centroid(geojsonObj);
- if (center && center.geometry && center.geometry.coordinates) {
- const parsedCoords = center.geometry.coordinates;
- coordinates = [parsedCoords[1], parsedCoords[0]];
- }
- } catch (err) {
- console.error('Error finding centroid for series', err);
- let firstPoint;
- if (geojsonObj.type === 'Polygon' && geojsonObj.coordinates?.[0]?.[0]) {
- firstPoint = geojsonObj.coordinates[0][0];
- } else if (geojsonObj.type === 'MultiPolygon' && geojsonObj.coordinates?.[0]?.[0]?.[0]) {
- firstPoint = geojsonObj.coordinates[0][0][0];
- } else if (geojsonObj.type === 'GeometryCollection' && geojsonObj.geometries?.[0]) {
- const firstGeom = geojsonObj.geometries[0];
- if (firstGeom.type === 'Point' && firstGeom.coordinates) {
- firstPoint = firstGeom.coordinates;
- } else if (firstGeom.type === 'Polygon' && firstGeom.coordinates?.[0]?.[0]) {
- firstPoint = firstGeom.coordinates[0][0];
- }
- }
- if (firstPoint && firstPoint.length >= 2) {
- coordinates = [firstPoint[1], firstPoint[0]];
- }
- }
- }
- }
- } catch (e) {
- console.error('Error parsing coordinates for series', e);
- }
- if (coordinates) {
- setTimeout(() => {
- cameraController.setCamera({
- centerCoordinate: coordinates,
- zoomLevel: 10,
- animationDuration: 1000,
- animationMode: 'flyTo'
- });
- }, 800);
- handledSeriesShareRef.current = seriesItem.item_id;
- setSelectedMarker({
- coordinates,
- name: seriesItem.item_name,
- icon: seriesItem.series_icon ? { uri: API_HOST + seriesItem.series_icon } : null,
- description: seriesItem.series_name,
- series_name: seriesItem.series_name,
- visited: seriesItem.visited,
- series_id: seriesItem.series_id,
- id: seriesItem.item_id,
- avatars: null,
- no_visited: 0
- });
- setSelectedUser(null);
- setTimeout(async () => {
- try {
- if (!mapRef.current) return;
- const pointInView = await mapRef.current.getPointInView(coordinates);
- if (pointInView) {
- const { features } = await mapRef.current.queryRenderedFeaturesAtPoint(
- pointInView,
- undefined,
- []
- );
- if (features && features.length > 0) {
- const feature = features.find(
- (f: any) => f.properties && f.properties.id === seriesItem.item_id
- );
- if (feature) {
- feature.properties.override_visited = seriesItem.visited;
- handleMarkerPress({ features: [feature] });
- }
- }
- }
- } catch (e) {
- console.log('Failed to query map features for series', e);
- }
- }, 1500);
- }
- };
- const handleMarkerPress = async (event: any) => {
- const { features } = event;
- if (features?.length) {
- const uniqueFeatures = features
- ? Array.from(new Map(features.map((f: any) => [f.properties.id, f])).values())
- : [];
- if (uniqueFeatures.length > 1) {
- const markers = uniqueFeatures
- .map((f: any) => {
- const markerCoordinates = f.geometry.coordinates;
- if (!markerCoordinates) return null;
- return {
- coordinates: markerCoordinates,
- name: f.properties.name,
- icon: images[f.properties.series_id],
- description: f.properties.description,
- series_name: f.properties.series_name,
- visited: f.properties.override_visited !== undefined ? f.properties.override_visited : (seriesVisited.includes(f.properties.id) ? 1 : 0),
- series_id: f.properties.series_id,
- id: f.properties.id,
- avatars: f.properties.avatars,
- no_visited: f.properties.no_visited
- };
- })
- .filter(Boolean)
- .sort((a: any, b: any) => a.visited - b.visited);
- SheetManager.show('multiple-series-modal', {
- payload: {
- markers,
- token,
- toggleSeries,
- setSelectedMarker,
- setIsWarningModalVisible,
- setPremiumModalVisible
- } as any
- });
- setSelectedUser(null);
- return;
- }
- const selectedFeature = features[0];
- const { coordinates } = selectedFeature.geometry;
- const { name, description, series_name, series_id, id, avatars, no_visited, override_visited } = selectedFeature.properties;
- const visited = override_visited !== undefined ? override_visited : (seriesVisited.includes(id) ? 1 : 0);
- const icon = images[series_id];
- if (coordinates) {
- setSelectedMarker((prev: any) => {
- const isSameMarker = prev && prev.id === id;
- const currentVisited = isSameMarker ? prev.visited : visited;
- return {
- coordinates,
- name,
- icon: icon || (isSameMarker ? prev.icon : null),
- description,
- series_name,
- visited: currentVisited,
- series_id,
- id,
- avatars,
- no_visited
- };
- });
- 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 && current.id === item.id ? { ...current, visited: 0 } : current
- );
- } else {
- setSeriesVisited((current) => [...current, item.id]);
- setSelectedMarker((current: any) =>
- current && current.id === item.id ? { ...current, visited: 1 } : current
- );
- }
- } 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-maps2025.json'}
- // mapStyle={VECTOR_MAP_HOST + '/nomadmania-maps2026.json'}
- rotateEnabled={false}
- attributionEnabled={false}
- onPress={onMapPress}
- onRegionDidChange={handleRegionDidChange}
- onRegionWillChange={_.debounce(handleMapChange, 200)}
- onDidFinishLoadingStyle={() => setDidFinishLoadingStyle(true)}
- >
- {/* <MapLibreRN.Images
- images={{
- ...images,
- 'default-series-icon': defaultSeriesIcon
- }}
- onImageMissing={(image) => {
- try {
- if (processedImages.current.has(image)) {
- return;
- }
- processedImages.current.add(image);
- setImages((prevImages: any) => ({
- ...prevImages,
- [image]: defaultSeriesIcon
- }));
- } catch (error) {
- console.error('Error in onImageMissing:', error);
- }
- }}
- >
- <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>
- )}
- <>
- <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 },
- visibility: type === 'regions' ? 'visible' : 'none'
- }}
- belowLayerID="waterway-name"
- />
- <MapLibreRN.FillLayer
- id={regions.id}
- sourceID={regions.source}
- sourceLayerID={regions['source-layer']}
- filter={regions.filter as any}
- style={{
- ...regions.style,
- visibility: type === 'regions' ? 'visible' : 'none'
- }}
- 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,
- visibility: type === 'regions' ? 'visible' : 'none'
- }}
- maxZoomLevel={regions_visited.maxzoom}
- belowLayerID="waterway-name"
- />
- </>
- <>
- <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 },
- visibility: type === 'countries' ? 'visible' : 'none'
- }}
- belowLayerID="waterway-name"
- />
- <MapLibreRN.FillLayer
- id={countries.id}
- sourceID={countries.source}
- sourceLayerID={countries['source-layer']}
- filter={countries.filter as any}
- style={{
- ...countries.style,
- visibility: type === 'countries' ? 'visible' : 'none'
- }}
- 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,
- visibility: type === 'countries' ? 'visible' : 'none'
- }}
- maxZoomLevel={countries_visited.maxzoom}
- belowLayerID="waterway-name"
- />
- </>
- <>
- <MapLibreRN.FillLayer
- id={dare.id}
- sourceID={dare.source}
- sourceLayerID={dare['source-layer']}
- filter={dare.filter as any}
- style={{
- ...dare.style,
- visibility: type === 'dare' ? 'visible' : 'none'
- }}
- 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,
- visibility: type === 'dare' ? 'visible' : 'none'
- }}
- 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"
- />
- </>
- )}
- {/* {didFinishLoadingStyle &&
- seriesGroups?.length > 0 &&
- seriesGroups.map((group) => {
- const visible = isGroupEnabled(group.id);
- const mustVisible = seriesFilter.visible && (visible || isMustModeEnabled);
- return (
- <React.Fragment key={`series-group-${group.id}`}>
- {seriesFilter.status !== 1
- ? (() => {
- try {
- return [
- <MapLibreRN.SymbolLayer
- key={`symbol_unvisited_normal_${group.id}`}
- id={`series-${group.id}-unvisited`}
- sourceID={`series-${group.id}`}
- sourceLayerID={`series-${group.id}`}
- aboveLayerID={'waterway-name'}
- filter={['all', seriesNotVisitedFilter, ['!=', 'must', 1]] as any}
- style={{
- symbolSpacing: 1,
- iconImage: '{series_id}',
- iconAllowOverlap: true,
- iconIgnorePlacement: true,
- iconSize: SERIES_ICON_SIZE,
- visibility: visible ? 'visible' : 'none',
- iconColor: '#666',
- iconOpacity: 1,
- iconHaloColor: '#ffffff',
- iconHaloWidth: 1,
- iconHaloBlur: 0.5,
- textAnchor: 'top',
- textField: '{name}',
- textFont: ['Noto Sans Regular'],
- textMaxWidth: 9,
- textOffset: [0, 1.3],
- textPadding: 2,
- textSize: 12,
- textOptional: true,
- textIgnorePlacement: false,
- textAllowOverlap: false,
- textColor: '#666',
- textHaloColor: '#ffffff',
- textHaloWidth: 1,
- textHaloBlur: 0.5
- }}
- />,
- <MapLibreRN.CircleLayer
- key={`circle_unvisited_must_${group.id}`}
- id={`series-${group.id}-must-wrapper`}
- sourceID={`series-${group.id}`}
- sourceLayerID={`series-${group.id}`}
- aboveLayerID={`series-${group.id}-unvisited`}
- filter={['all', seriesNotVisitedFilter, ['==', 'must', 1]] as any}
- style={{
- circleRadius: SERIES_CIRCLE_RADIUS,
- circleColor: Colors.ORANGE,
- circleOpacity: 1,
- circleStrokeWidth: SERIES_CIRCLE_STROKE,
- circleStrokeColor: Colors.ORANGE,
- visibility: mustVisible ? 'visible' : 'none'
- }}
- />,
- <MapLibreRN.SymbolLayer
- key={`symbol_unvisited_must_${group.id}`}
- id={`series-${group.id}-must-icon`}
- sourceID={`series-${group.id}`}
- sourceLayerID={`series-${group.id}`}
- aboveLayerID={`series-${group.id}-must-wrapper`}
- filter={['all', seriesNotVisitedFilter, ['==', 'must', 1]] as any}
- style={{
- symbolSpacing: 1,
- iconImage: '{series_id}',
- iconAllowOverlap: true,
- iconIgnorePlacement: true,
- iconSize: SERIES_ICON_SIZE,
- visibility: mustVisible ? 'visible' : 'none',
- iconColor: '#666',
- iconOpacity: 1,
- iconHaloColor: '#ffffff',
- iconHaloWidth: 1,
- iconHaloBlur: 0.5,
- textAnchor: 'top',
- textField: '{name}',
- textFont: ['Noto Sans Regular'],
- textMaxWidth: 9,
- textOffset: [0, 1.3],
- textPadding: 2,
- textSize: 12,
- textOptional: true,
- textIgnorePlacement: false,
- textAllowOverlap: false,
- textColor: '#666',
- textHaloColor: '#ffffff',
- textHaloWidth: 1,
- textHaloBlur: 0.5
- }}
- />
- ];
- } catch (error) {
- console.warn('SymbolLayer render error:', error);
- return null;
- }
- })()
- : null}
- {seriesFilter.status !== 0
- ? (() => {
- try {
- return [
- <MapLibreRN.SymbolLayer
- key={`symbol_visited_normal_${group.id}`}
- id={`series-${group.id}-visited-normal`}
- sourceID={`series-${group.id}`}
- sourceLayerID={`series-${group.id}`}
- aboveLayerID={'waterway-name'}
- filter={['all', seriesVisitedFilter, ['!=', 'must', 1]] as any}
- style={{
- symbolSpacing: 1,
- iconImage: '{series_id}v',
- iconAllowOverlap: true,
- iconIgnorePlacement: true,
- iconSize: SERIES_ICON_SIZE,
- visibility: visible ? 'visible' : 'none',
- iconColor: '#666',
- iconOpacity: 1,
- iconHaloColor: '#ffffff',
- iconHaloWidth: 1,
- iconHaloBlur: 0.5,
- textAnchor: 'top',
- textField: '{name}',
- textFont: ['Noto Sans Regular'],
- textMaxWidth: 9,
- textOffset: [0, 1.3],
- textPadding: 2,
- textSize: 12,
- textOptional: true,
- textIgnorePlacement: false,
- textAllowOverlap: false,
- textColor: '#666',
- textHaloColor: '#ffffff',
- textHaloWidth: 1,
- textHaloBlur: 0.5
- }}
- />,
- <MapLibreRN.CircleLayer
- key={`circle_visited_must_${group.id}`}
- id={`series-${group.id}-visited-must-wrapper`}
- sourceID={`series-${group.id}`}
- sourceLayerID={`series-${group.id}`}
- aboveLayerID={`series-${group.id}-visited-normal`}
- filter={['all', seriesVisitedFilter, ['==', 'must', 1]] as any}
- style={{
- circleRadius: SERIES_CIRCLE_RADIUS,
- circleColor: Colors.ORANGE,
- circleOpacity: 1,
- circleStrokeWidth: SERIES_CIRCLE_STROKE,
- circleStrokeColor: Colors.ORANGE,
- visibility: mustVisible ? 'visible' : 'none'
- }}
- />,
- <MapLibreRN.SymbolLayer
- key={`symbol_visited_must_${group.id}`}
- id={`series-${group.id}-visited-must`}
- sourceID={`series-${group.id}`}
- sourceLayerID={`series-${group.id}`}
- aboveLayerID={`series-${group.id}-visited-must-wrapper`}
- filter={['all', seriesVisitedFilter, ['==', 'must', 1]] as any}
- style={{
- symbolSpacing: 1,
- iconImage: '{series_id}v',
- iconAllowOverlap: true,
- iconIgnorePlacement: true,
- iconSize: SERIES_ICON_SIZE,
- visibility: mustVisible ? 'visible' : 'none',
- iconColor: '#666',
- iconOpacity: 1,
- iconHaloColor: '#ffffff',
- iconHaloWidth: 1,
- iconHaloBlur: 0.5,
- textAnchor: 'top',
- textField: '{name}',
- textFont: ['Noto Sans Regular'],
- textMaxWidth: 9,
- textOffset: [0, 1.3],
- textPadding: 2,
- textSize: 12,
- textOptional: true,
- textIgnorePlacement: false,
- textAllowOverlap: false,
- textColor: '#666',
- textHaloColor: '#ffffff',
- textHaloWidth: 1,
- textHaloBlur: 0.5
- }}
- />
- ];
- } catch (error) {
- console.warn('SymbolLayer render error:', error);
- return null;
- }
- })()
- : null}
- </React.Fragment>
- );
- })} */}
- <MapLibreRN.VectorSource
- id="nomadmania_series"
- tileUrlTemplates={[VECTOR_MAP_HOST + '/tiles/series/{z}/{x}/{y}.pbf']}
- onPress={handleMarkerPress}
- >
- {seriesFilter.status !== 1
- ? (() => {
- try {
- return [
- <MapLibreRN.SymbolLayer
- key="symbol_unvisited_normal"
- id={`${series_layer.id}_normal`}
- sourceID={series_layer.source}
- sourceLayerID={series_layer['source-layer']}
- aboveLayerID={'waterway-name'}
- filter={['all', seriesNotVisitedFilter, ['!=', 'must', 1]] as any}
- minZoomLevel={series_layer.minzoom}
- maxZoomLevel={series_layer.maxzoom}
- style={{
- symbolSpacing: 1,
- iconImage: '{series_id}',
- iconAllowOverlap: true,
- iconIgnorePlacement: true,
- iconSize: SERIES_ICON_SIZE,
- visibility: 'visible',
- iconColor: '#666',
- iconOpacity: 1,
- iconHaloColor: '#ffffff',
- iconHaloWidth: 1,
- iconHaloBlur: 0.5,
- textAnchor: 'top',
- textField: seriesTextField,
- textFont: ['Noto Sans Regular'],
- textMaxWidth: 9,
- textOffset: [0, 1.3],
- textPadding: 2,
- textSize: SERIES_TEXT_SIZE,
- textOptional: true,
- textIgnorePlacement: false,
- textAllowOverlap: false,
- textColor: '#666',
- textHaloColor: '#ffffff',
- textHaloWidth: 1,
- textHaloBlur: 0.5
- }}
- />,
- <MapLibreRN.CircleLayer
- key="circle_unvisited_must"
- id={`${series_layer.id}_must_wrapper`}
- sourceID={series_layer.source}
- sourceLayerID={series_layer['source-layer']}
- aboveLayerID={`${series_layer.id}_normal`}
- filter={['all', seriesNotVisitedFilter, ['==', 'must', 1]] as any}
- minZoomLevel={series_layer.minzoom}
- maxZoomLevel={series_layer.maxzoom}
- style={{
- circleRadius: SERIES_CIRCLE_RADIUS,
- circleColor: Colors.ORANGE,
- circleOpacity: 1,
- circleStrokeWidth: SERIES_CIRCLE_STROKE,
- circleStrokeColor: Colors.ORANGE,
- visibility: 'visible'
- }}
- />,
- <MapLibreRN.SymbolLayer
- key="symbol_unvisited_must"
- id={series_layer.id}
- sourceID={series_layer.source}
- sourceLayerID={series_layer['source-layer']}
- aboveLayerID={`${series_layer.id}_must_wrapper`}
- filter={['all', seriesNotVisitedFilter, ['==', 'must', 1]] as any}
- minZoomLevel={series_layer.minzoom}
- maxZoomLevel={series_layer.maxzoom}
- style={{
- symbolSpacing: 1,
- iconImage: '{series_id}',
- iconAllowOverlap: true,
- iconIgnorePlacement: true,
- iconSize: SERIES_ICON_SIZE,
- visibility: 'visible',
- iconColor: '#666',
- iconOpacity: 1,
- iconHaloColor: '#ffffff',
- iconHaloWidth: 1,
- iconHaloBlur: 0.5,
- textAnchor: 'top',
- textField: seriesTextField,
- textFont: ['Noto Sans Regular'],
- textMaxWidth: 9,
- textOffset: [0, 1.3],
- textPadding: 2,
- textSize: SERIES_TEXT_SIZE,
- textOptional: true,
- textIgnorePlacement: false,
- textAllowOverlap: false,
- textColor: '#666',
- textHaloColor: '#ffffff',
- textHaloWidth: 1,
- textHaloBlur: 0.5
- }}
- />
- ];
- } catch (error) {
- console.warn('SymbolLayer render error:', error);
- return null;
- }
- })()
- : null}
- {seriesFilter.status !== 0
- ? (() => {
- try {
- return [
- <MapLibreRN.SymbolLayer
- key="symbol_visited_normal"
- id={`${series_visited.id}_normal`}
- sourceID={series_visited.source}
- sourceLayerID={series_visited['source-layer']}
- aboveLayerID={'waterway-name'}
- filter={['all', seriesVisitedFilter, ['!=', 'must', 1]] as any}
- minZoomLevel={series_visited.minzoom}
- maxZoomLevel={series_visited.maxzoom}
- style={{
- symbolSpacing: 1,
- iconImage: '{series_id}v',
- iconAllowOverlap: true,
- iconIgnorePlacement: true,
- iconSize: SERIES_ICON_SIZE,
- visibility: 'visible',
- iconColor: '#666',
- iconOpacity: 1,
- iconHaloColor: '#ffffff',
- iconHaloWidth: 1,
- iconHaloBlur: 0.5,
- textAnchor: 'top',
- textField: seriesTextField,
- textFont: ['Noto Sans Regular'],
- textMaxWidth: 9,
- textOffset: [0, 1.3],
- textPadding: 2,
- textSize: SERIES_TEXT_SIZE,
- textOptional: true,
- textIgnorePlacement: false,
- textAllowOverlap: false,
- textColor: '#666',
- textHaloColor: '#ffffff',
- textHaloWidth: 1,
- textHaloBlur: 0.5
- }}
- />,
- <MapLibreRN.CircleLayer
- key="circle_visited_must"
- id={`${series_visited.id}_must_wrapper`}
- sourceID={series_visited.source}
- sourceLayerID={series_visited['source-layer']}
- aboveLayerID={`${series_visited.id}_normal`}
- filter={['all', seriesVisitedFilter, ['==', 'must', 1]] as any}
- minZoomLevel={series_visited.minzoom}
- maxZoomLevel={series_visited.maxzoom}
- style={{
- circleRadius: SERIES_CIRCLE_RADIUS,
- circleColor: Colors.ORANGE,
- circleOpacity: 1,
- circleStrokeWidth: SERIES_CIRCLE_STROKE,
- circleStrokeColor: Colors.ORANGE,
- visibility: 'visible'
- }}
- />,
- <MapLibreRN.SymbolLayer
- key="symbol_visited_must"
- id={series_visited.id}
- sourceID={series_visited.source}
- sourceLayerID={series_visited['source-layer']}
- aboveLayerID={`${series_visited.id}_must_wrapper`}
- filter={['all', seriesVisitedFilter, ['==', 'must', 1]] as any}
- minZoomLevel={series_visited.minzoom}
- maxZoomLevel={series_visited.maxzoom}
- style={{
- symbolSpacing: 1,
- iconImage: '{series_id}v',
- iconAllowOverlap: true,
- iconIgnorePlacement: true,
- iconSize: SERIES_ICON_SIZE,
- visibility: 'visible',
- iconColor: '#666',
- iconOpacity: 1,
- iconHaloColor: '#ffffff',
- iconHaloWidth: 1,
- iconHaloBlur: 0.5,
- textAnchor: 'top',
- textField: seriesTextField,
- textFont: ['Noto Sans Regular'],
- textMaxWidth: 9,
- textOffset: [0, 1.3],
- textPadding: 2,
- textSize: SERIES_TEXT_SIZE,
- textOptional: true,
- textIgnorePlacement: false,
- textAllowOverlap: false,
- textColor: '#666',
- textHaloColor: '#ffffff',
- textHaloWidth: 1,
- textHaloBlur: 0.5
- }}
- />
- ];
- } catch (error) {
- console.warn('SymbolLayer render error:', error);
- return null;
- }
- })()
- : null}
- </MapLibreRN.VectorSource>
- <MapLibreRN.ShapeSource
- ref={shapeSourceRef}
- tolerance={20}
- id="nomads"
- shape={
- nomads && (showNomads || nomadsFilter.friends || nomadsFilter.countries?.length)
- ? nomads
- : { type: 'FeatureCollection', features: [] }
- }
- 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;
- cameraController.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: ['case', ['==', ['get', 'friend'], 1], '02', '00'],
- iconAllowOverlap: true
- }}
- ></MapLibreRN.SymbolLayer>
- </MapLibreRN.ShapeSource>
- {selectedUser && <UserItem marker={selectedUser} />}
- {selectedMarker && (
- <MarkerItem
- marker={selectedMarker}
- toggleSeries={toggleSeries}
- token={token}
- isPremium={isPremium}
- setPremiumModalVisible={setPremiumModalVisible}
- />
- )}
- {(renderCamera || Platform.OS === 'ios') && (
- <MapLibreRN.Camera ref={cameraRef} followUserLocation={undefined} animationMode="flyTo" />
- )}
- {location && (
- <MapLibreRN.UserLocation
- animated={true}
- showsUserHeadingIndicator={true}
- onPress={async () => {
- const currentZoom = await mapRef.current?.getZoom();
- const newZoom = (currentZoom || 0) + 2;
- cameraController.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}
- onAddVisitSuccess={(id, isFuture) => {
- if (!isFuture && !regionsVisited.includes(id)) {
- setRegionsVisited([...regionsVisited, id]);
- }
- refetchVisitedCountries();
- }}
- />
- </>
- ) : (
- <>
- {!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: isSmallScreen ? 8 : 12,
- paddingTop: 6,
- gap: isSmallScreen ? 6 : 10,
- flexDirection: 'row'
- }}
- >
- <MapButton
- onPress={() => {
- try {
- filterModalRef.current?.openFilter('regions');
- closeCallout();
- } catch (error) {
- console.error('Error opening filter:', error);
- }
- }}
- icon={TravelsIcon}
- text={
- tilesType.value === 0 || tilesType.value === 1
- ? token
- ? `${tilesType.value === 0 ? 'NM' : 'UN'} ${regionsFilter.visitedLabel} ${regionsFilter.year}`
- : `${tilesType.value === 0 ? 'NM' : 'UN'}`
- : tilesType.value === 2
- ? 'DARE'
- : 'Travels'
- }
- active={type !== 'blank'}
- />
- <MapButton
- onPress={() => {
- try {
- filterModalRef.current?.openFilter('series');
- closeCallout();
- } catch (error) {
- console.error('Error opening filter:', error);
- }
- }}
- icon={SeriesIcon}
- text="Series"
- active={seriesFilter.visible}
- />
- {token ? (
- <MapButton
- onPress={() => {
- try {
- filterModalRef.current?.openFilter('nomads');
- closeCallout();
- } catch (error) {
- console.error('Error opening filter:', error);
- }
- }}
- icon={NomadsIcon}
- text="Nomads"
- active={showNomads || nomadsFilter.friends || nomadsFilter.countries?.length}
- >
- {usersCount && usersCount > 0 ? (
- <MessagesDot
- messagesCount={usersCount}
- 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}
- handleFindSeries={handleFindSeries}
- 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
- ref={filterModalRef}
- tilesTypes={tilesTypes}
- tilesType={tilesType}
- setTilesType={setTilesType}
- setType={setType}
- userId={userId ? +userId : 0}
- setRegionsFilter={setRegionsFilter}
- setSeriesFilter={setSeriesFilter}
- setShowNomads={setShowNomads}
- setNomadsFilter={setNomadsFilter}
- showNomads={showNomads}
- isPublicView={false}
- isLogged={token ? true : false}
- usersOnMapCount={token && usersOnMapCount?.count ? usersOnMapCount.count : null}
- friendsOnTheMapCount={
- token && usersOnMapCount?.friends_count ? usersOnMapCount.friends_count : null
- }
- isConnected={isConnected}
- isPremium={isPremium}
- />
- <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?"
- />
- <WarningModal
- type={'success'}
- isVisible={premiumModalVisible}
- message={
- 'This feature is available to Premium users. Premium account settings can be managed on our website.'
- }
- action={() => {}}
- onClose={() => setPremiumModalVisible(false)}
- title={'Premium Feature'}
- />
- <MultipleSeriesModal />
- </SafeAreaView>
- );
- };
- export default MapScreen;
|