12345678910111213141516171819202122232425262728293031323334353637383940414243444546474849505152535455565758596061626364656667686970717273747576777879808182838485868788899091929394959697989910010110210310410510610710810911011111211311411511611711811912012112212312412512612712812913013113213313413513613713813914014114214314414514614714814915015115215315415515615715815916016116216316416516616716816917017117217317417517617717817918018118218318418518618718818919019119219319419519619719819920020120220320420520620720820921021121221321421521621721821922022122222322422522622722822923023123223323423523623723823924024124224324424524624724824925025125225325425525625725825926026126226326426526626726826927027127227327427527627727827928028128228328428528628728828929029129229329429529629729829930030130230330430530630730830931031131231331431531631731831932032132232332432532632732832933033133233333433533633733833934034134234334434534634734834935035135235335435535635735835936036136236336436536636736836937037137237337437537637737837938038138238338438538638738838939039139239339439539639739839940040140240340440540640740840941041141241341441541641741841942042142242342442542642742842943043143243343443543643743843944044144244344444544644744844945045145245345445545645745845946046146246346446546646746846947047147247347447547647747847948048148248348448548648748848949049149249349449549649749849950050150250350450550650750850951051151251351451551651751851952052152252352452552652752852953053153253353453553653753853954054154254354454554654754854955055155255355455555655755855956056156256356456556656756856957057157257357457557657757857958058158258358458558658758858959059159259359459559659759859960060160260360460560660760860961061161261361461561661761861962062162262362462562662762862963063163263363463563663763863964064164264364464564664764864965065165265365465565665765865966066166266366466566666766866967067167267367467567667767867968068168268368468568668768868969069169269369469569669769869970070170270370470570670770870971071171271371471571671771871972072172272372472572672772872973073173273373473573673773873974074174274374474574674774874975075175275375475575675775875976076176276376476576676776876977077177277377477577677777877978078178278378478578678778878979079179279379479579679779879980080180280380480580680780880981081181281381481581681781881982082182282382482582682782882983083183283383483583683783883984084184284384484584684784884985085185285385485585685785885986086186286386486586686786886987087187287387487587687787887988088188288388488588688788888989089189289389489589689789889990090190290390490590690790890991091191291391491591691791891992092192292392492592692792892993093193293393493593693793893994094194294394494594694794894995095195295395495595695795895996096196296396496596696796896997097197297397497597697797897998098198298398498598698798898999099199299399499599699799899910001001100210031004100510061007100810091010101110121013101410151016101710181019102010211022102310241025102610271028102910301031103210331034103510361037103810391040104110421043104410451046104710481049105010511052105310541055105610571058105910601061106210631064106510661067106810691070107110721073107410751076107710781079108010811082108310841085108610871088108910901091109210931094109510961097109810991100110111021103110411051106110711081109111011111112111311141115111611171118111911201121112211231124112511261127112811291130113111321133113411351136113711381139114011411142114311441145114611471148114911501151115211531154115511561157115811591160116111621163116411651166116711681169117011711172117311741175117611771178117911801181118211831184118511861187118811891190119111921193119411951196119711981199120012011202120312041205120612071208120912101211121212131214121512161217121812191220122112221223122412251226122712281229123012311232123312341235123612371238123912401241124212431244124512461247124812491250125112521253125412551256125712581259126012611262126312641265126612671268126912701271127212731274127512761277127812791280128112821283128412851286128712881289129012911292129312941295129612971298129913001301130213031304130513061307130813091310131113121313131413151316131713181319132013211322132313241325132613271328132913301331133213331334133513361337133813391340134113421343134413451346134713481349135013511352135313541355135613571358135913601361136213631364136513661367136813691370137113721373137413751376 |
- import {
- Dimensions,
- Linking,
- Platform,
- Text,
- TextInput,
- TouchableOpacity,
- View,
- Image,
- StatusBar
- } from 'react-native';
- import React, { useEffect, useRef, useState, useCallback } from 'react';
- import MapLibreGL, { CameraRef, MapViewRef } 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 { RegionPayload } from '@maplibre/maplibre-react-native/javascript/components/MapView';
- 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 FilterIcon from 'assets/icons/filter.svg';
- import ProfileIcon from 'assets/icons/bottom-navigation/profile.svg';
- import RegionPopup from 'src/components/RegionPopup';
- import { useRegion } from 'src/contexts/RegionContext';
- import { qualityOptions } from '../TravelsScreen/utils/constants';
- import { AvatarWithInitials, EditNmModal, WarningModal } from 'src/components';
- import { API_HOST, MAP_HOST, VECTOR_MAP_HOST } from 'src/constants';
- import { NAVIGATION_PAGES } from 'src/types';
- import Animated, {
- Easing,
- useAnimatedStyle,
- useSharedValue,
- withTiming
- } from 'react-native-reanimated';
- import { getData } from 'src/modules/map/regionData';
- import {
- getCountriesDatabase,
- getFirstDatabase,
- getSecondDatabase,
- refreshDatabases
- } from 'src/db';
- import { fetchUserData, fetchUserDataDare, useGetListRegionsQuery } from '@api/regions';
- import { SQLiteDatabase } from 'expo-sqlite/legacy';
- import { useFocusEffect } from '@react-navigation/native';
- import { useGetUniversalSearch } from '@api/search';
- import { fetchCountryUserData, useGetListCountriesQuery } from '@api/countries';
- import SearchModal from './UniversalSearch';
- import EditModal from '../TravelsScreen/Components/EditSlowModal';
- import CheckSvg from 'assets/icons/mark.svg';
- import moment from 'moment';
- import {
- usePostGetVisitedCountriesIdsQuery,
- usePostGetVisitedDareIdsQuery,
- usePostGetVisitedRegionsIdsQuery,
- usePostGetVisitedSeriesIdsQuery
- } from '@api/maps';
- import FilterModal from './FilterModal';
- import { useGetListDareQuery } from '@api/myDARE';
- import { useGetIconsQuery, usePostSetToggleItem } from '@api/series';
- import MarkerItem from './MarkerItem';
- import { usePostGetUsersLocationQuery, usePostUpdateLocationMutation } from '@api/location';
- MapLibreGL.setAccessToken(null);
- MapLibreGL.Logger.setLogLevel('error');
- const generateFilter = (ids: number[]) => {
- return ids?.length ? ['any', ...ids.map((id) => ['==', 'id', id])] : ['==', 'id', -1];
- };
- // to do refactor
- let regions_visited = {
- id: 'regions_visited',
- type: 'fill',
- source: 'regions',
- 'source-layer': 'regions',
- style: {
- fillColor: 'rgba(255, 126, 0, 1)',
- fillOpacity: 0.6,
- fillOutlineColor: 'rgba(14, 80, 109, 1)'
- },
- filter: generateFilter([]),
- maxzoom: 12
- };
- 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, 1)'
- },
- filter: generateFilter([]),
- maxzoom: 10
- };
- let dare_visited = {
- id: 'dare_visited',
- type: 'fill',
- source: 'dare',
- 'source-layer': 'dare',
- style: {
- fillColor: 'rgba(255, 126, 0, 1)',
- fillOpacity: 0.6,
- fillOutlineColor: 'rgba(255, 126, 0, 1)'
- },
- 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, 1)'
- },
- 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, 1)'
- },
- 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, 1)'
- },
- 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)',
- fillOutlineColor: '#3973ac'
- },
- maxzoom: 12
- };
- let series_layer = {
- id: 'series_layer',
- type: 'symbol',
- source: 'nomadmania_series',
- 'source-layer': 'series',
- layout: {
- 'icon-image': '{series_id}',
- 'icon-size': 0.1,
- 'text-anchor': 'top',
- 'text-field': '{series_name} - {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': true,
- 'text-allow-overlap': true
- },
- 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',
- layout: {
- 'icon-image': '{series_id}v',
- 'icon-size': 0.15,
- 'text-anchor': 'top',
- 'text-field': '{series_name} - {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': true,
- 'text-allow-overlap': true
- },
- 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 MapScreen: any = ({ navigation, route }: { navigation: any; route: any }) => {
- const userId = storage.get('uid', StoreType.STRING) as string;
- const token = storage.get('token', StoreType.STRING) as string;
- const { data: regionsList } = useGetListRegionsQuery(true);
- const { data: countriesList } = useGetListCountriesQuery(true);
- const { data: dareList } = useGetListDareQuery(true);
- const [tilesType, setTilesType] = useState({ label: 'NM regions', value: 0 });
- const tilesTypes = [
- { label: 'NM regions', value: 0 },
- { label: 'UN countries', value: 1 },
- { label: 'DARE places', value: 2 }
- ];
- const [type, setType] = useState<'regions' | 'countries' | 'dare'>('regions');
- const [seriesFilter, setSeriesFilter] = useState<any>({
- visible: true,
- groups: [],
- applied: false,
- status: -1
- });
- const [regionsFilter, setRegionsFilter] = useState<any>({
- visitedLabel: 'by',
- year: moment().year()
- });
- const [showNomads, setShowNomads] = useState(
- (storage.get('showNomads', StoreType.BOOLEAN) as boolean) ?? false
- );
- const { mutateAsync: updateLocation } = usePostUpdateLocationMutation();
- const { data: visitedRegionIds, refetch: refetchVisitedRegions } =
- usePostGetVisitedRegionsIdsQuery(
- token,
- regionsFilter.visitedLabel,
- regionsFilter.year,
- +userId,
- type === 'regions' && !!userId
- );
- const { data: visitedCountryIds, refetch: refetchVisitedCountries } =
- usePostGetVisitedCountriesIdsQuery(
- token,
- regionsFilter.visitedLabel,
- regionsFilter.year,
- +userId,
- type === 'countries' && !!userId
- );
- const { data: visitedDareIds, refetch: refetchVisitedDare } = usePostGetVisitedDareIdsQuery(
- token,
- +userId,
- type === 'dare' && !!userId
- );
- const { data: visitedSeriesIds } = usePostGetVisitedSeriesIdsQuery(token, !!userId);
- const { data: seriesIcons } = useGetIconsQuery(true);
- const userInfo = storage.get('currentUserData', StoreType.STRING) as string;
- const { mutateAsync: mutateUserData } = fetchUserData();
- const { mutateAsync: mutateUserDataDare } = fetchUserDataDare();
- const { mutateAsync: mutateCountriesData } = fetchCountryUserData();
- const [selectedRegion, setSelectedRegion] = useState<number | null>(null);
- const [initialRegion, setInitialRegion] = useState(INITIAL_REGION);
- const [regionPopupVisible, setRegionPopupVisible] = useState<boolean | null>(false);
- const [regionData, setRegionData] = useState<any | null>(null);
- const [location, setLocation] = useState<any | null>(null);
- const [userAvatars, setUserAvatars] = useState<string[]>([]);
- const [userInfoData, setUserInfoData] = useState<any>(null);
- const [selectedMarker, setSelectedMarker] = useState<any>(null);
- const [askLocationVisible, setAskLocationVisible] = useState<boolean>(false);
- const [openSettingsVisible, setOpenSettingsVisible] = useState<boolean>(false);
- const [isWarningModalVisible, setIsWarningModalVisible] = useState<boolean>(false);
- const [isEditSlowModalVisible, setIsEditSlowModalVisible] = useState<boolean>(false);
- const [isEditModalVisible, setIsEditModalVisible] = useState(false);
- const [isFilterVisible, setIsFilterVisible] = useState(false);
- const [modalState, setModalState] = useState({
- selectedFirstYear: 2021,
- selectedLastYear: 2021,
- selectedQuality: qualityOptions[2],
- selectedNoOfVisits: 1,
- years: [],
- id: null
- });
- const [isConnected, setIsConnected] = useState<boolean | null>(true);
- 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 } = usePostGetUsersLocationQuery(
- token,
- !!token && showNomads && Boolean(location)
- );
- useEffect(() => {
- if (!showNomads) {
- setNomads(null);
- }
- }, [showNomads]);
- useEffect(() => {
- if (usersLocation) {
- console.log('usersLocation', usersLocation);
- setNomads(usersLocation.geojson);
- }
- }, [usersLocation]);
- useEffect(() => {
- let loadedImages: any = {};
- if (seriesIcons) {
- seriesIcons.data.forEach(async (icon) => {
- const id = icon.id;
- const img = API_HOST + '/static/img/series_new2/' + icon.new_icon_png;
- const imgVisited = API_HOST + '/static/img/series_new2/' + icon.new_icon_visited_png;
- if (!img || !imgVisited) return;
- try {
- const iconImage = { uri: img };
- const visitedIconImage = { uri: imgVisited };
- loadedImages[id] = iconImage;
- loadedImages[`${id}v`] = visitedIconImage;
- } catch (error) {
- console.error(`Error loading icon for series_id ${id}:`, error);
- }
- });
- }
- if (nomads && nomads.features) {
- nomads.features.forEach((feature) => {
- const user_id = `user_${feature.properties?.id}`;
- const avatarUrl = `${API_HOST}${feature.properties?.avatar}`;
- if (avatarUrl) {
- loadedImages[user_id] = { uri: avatarUrl };
- if (feature.properties) {
- feature.properties.icon_key = user_id;
- }
- }
- });
- }
- setImages(loadedImages);
- }, [nomads, seriesIcons]);
- useEffect(() => {
- const loadDatabases = async () => {
- const firstDb = await getFirstDatabase();
- const secondDb = await getSecondDatabase();
- const countriesDb = await getCountriesDatabase();
- setDb1(firstDb);
- setDb2(secondDb);
- setDb3(countriesDb);
- };
- if (!db1 || !db2 || !db3) {
- loadDatabases();
- }
- }, [db1, db2, db3]);
- useEffect(() => {
- const savedFilterSettings = storage.get('filterSettings', StoreType.STRING) as string;
- if (savedFilterSettings) {
- const filterSettings = JSON.parse(savedFilterSettings);
- setTilesType(filterSettings.tilesType);
- setType(filterSettings.type);
- setRegionsFilter({
- visitedLabel: filterSettings.selectedVisible.value === 1 ? 'in' : 'by',
- year: filterSettings.selectedYear.value
- });
- setSeriesFilter(filterSettings.seriesFilter);
- }
- }, []);
- useFocusEffect(
- useCallback(() => {
- if (token) {
- refetchVisitedRegions();
- refetchVisitedCountries();
- refetchVisitedDare();
- }
- }, [navigation])
- );
- // to do refactor
- useEffect(() => {
- if (visitedRegionIds) {
- setRegionsVisited(visitedRegionIds.ids);
- }
- }, [visitedRegionIds]);
- useEffect(() => {
- if (visitedCountryIds) {
- setCountriesVisited(visitedCountryIds.ids);
- }
- }, [visitedCountryIds]);
- useEffect(() => {
- if (visitedDareIds) {
- setDareVisited(visitedDareIds.ids);
- }
- }, [visitedDareIds]);
- useEffect(() => {
- if (visitedSeriesIds && token) {
- setSeriesVisited(visitedSeriesIds.ids);
- }
- }, [visitedSeriesIds]);
- useEffect(() => {
- if (regionsVisited && regionsVisited.length) {
- setRegionsVisitedFilter(generateFilter(regionsVisited));
- }
- }, [regionsVisited]);
- useEffect(() => {
- if (countriesVisited && countriesVisited.length) {
- setCountriesVisitedFilter(generateFilter(countriesVisited));
- }
- }, [countriesVisited]);
- useEffect(() => {
- if (dareVisited && dareVisited.length) {
- setDareVisitedFilter(generateFilter(dareVisited));
- }
- }, [dareVisited]);
- useEffect(() => {
- if (!seriesFilter.visible) {
- setSeriesVisitedFilter(generateFilter([]));
- setSeriesNotVisitedFilter(generateFilter([]));
- return;
- }
- if (seriesFilter.applied) {
- if (seriesVisited?.length) {
- setSeriesVisitedFilter([
- 'all',
- ['any', ...seriesVisited.map((id) => ['==', 'id', id])],
- ['any', ...seriesFilter.groups.map((groupId: number) => ['==', 'series_id', groupId])]
- ]);
- setSeriesNotVisitedFilter([
- 'all',
- ['all', ...seriesVisited.map((id) => ['!=', 'id', id])],
- ['any', ...seriesFilter.groups.map((groupId: number) => ['==', 'series_id', groupId])]
- ]);
- } else {
- setSeriesNotVisitedFilter([
- 'any',
- ...seriesFilter.groups.map((groupId: number) => ['==', 'series_id', groupId])
- ]);
- }
- } else {
- setSeriesVisitedFilter(['any', ...seriesVisited.map((id) => ['==', 'id', id])]);
- setSeriesNotVisitedFilter(['all', ...seriesVisited.map((id) => ['!=', 'id', id])]);
- }
- }, [seriesVisited, seriesFilter]);
- useEffect(() => {
- if (route.params?.id && route.params?.type && db1 && db2 && db3) {
- handleFindRegion(route.params?.id, route.params?.type);
- }
- }, [route, db1, db2, db3]);
- useEffect(() => {
- if (selectedRegion) {
- mapRef.current;
- }
- }, [selectedRegion]);
- useEffect(() => {
- (async () => {
- let { status } = await Location.getForegroundPermissionsAsync();
- if (status !== 'granted') {
- setShowNomads(false);
- storage.set('showNomads', false);
- return;
- }
- let currentLocation = await Location.getCurrentPositionAsync({
- accuracy: Location.Accuracy.Balanced
- });
- setLocation(currentLocation.coords);
- updateLocation({
- token,
- lat: currentLocation.coords.latitude,
- lng: currentLocation.coords.longitude
- });
- })();
- }, []);
- 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<MapViewRef>(null);
- const cameraRef = useRef<CameraRef>(null);
- useEffect(() => {
- if (userInfo) {
- setUserInfoData(JSON.parse(userInfo));
- }
- }, [userInfo]);
- const handlePress = () => {
- if (isExpanded) {
- setIndex(0);
- 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) {
- setTimeout(() => {
- cameraRef.current?.setCamera({
- centerCoordinate: [initialRegion.longitude, initialRegion.latitude],
- zoomLevel: Math.log2(360 / initialRegion.latitudeDelta),
- animationDuration: 500
- });
- }, 500);
- }
- }, [initialRegion]);
- const onMapPress = async (event: any) => {
- if (!mapRef.current) return;
- if (selectedMarker) {
- closeCallout();
- return;
- }
- try {
- const { screenPointX, screenPointY } = event.properties;
- const { features } = await mapRef.current.queryRenderedFeaturesAtPoint(
- [screenPointX, screenPointY],
- undefined,
- ['regions', 'countries', 'dare']
- );
- if (features?.length) {
- const region = features[0];
- if (selectedRegion === region.properties?.id) return;
- let db = type === 'regions' ? db1 : type === 'countries' ? db3 : db2;
- let tableName = type === 'dare' ? 'places' : type;
- let foundRegion = region.properties?.id;
- setSelectedRegion(region.properties?.id);
- await getData(db, foundRegion, tableName, handleRegionData)
- .then(() => {
- setRegionPopupVisible(true);
- })
- .catch((error) => {
- console.error('Error fetching data', error);
- refreshDatabases();
- });
- if (tableName === 'regions') {
- token
- ? await mutateUserData(
- { region_id: +foundRegion, token: String(token) },
- {
- onSuccess: (data) => {
- setUserData({ type: 'nm', id: +foundRegion, ...data });
- }
- }
- )
- : setUserData({ type: 'nm', id: +foundRegion });
- if (regionsList) {
- const region = regionsList.data.find((region) => region.id === +foundRegion);
- if (region) {
- const bounds = turf.bbox(region.bbox);
- cameraRef.current?.fitBounds(
- [bounds[2], bounds[3]],
- [bounds[0], bounds[1]],
- [10, 10, 50, 10],
- 1000
- );
- }
- }
- } else if (tableName === 'countries') {
- token
- ? await mutateCountriesData(
- { id: +foundRegion, token },
- {
- onSuccess: (data) => {
- setUserData({ type: 'countries', id: +foundRegion, ...data.data });
- }
- }
- )
- : setUserData({ type: 'countries', id: +foundRegion });
- if (countriesList) {
- const region = countriesList.data.find((region) => region.id === +foundRegion);
- if (region) {
- const bounds = turf.bbox(region.bbox);
- cameraRef.current?.fitBounds(
- [bounds[2], bounds[3]],
- [bounds[0], bounds[1]],
- [10, 10, 50, 10],
- 1000
- );
- }
- }
- } else {
- token
- ? await mutateUserDataDare(
- { dare_id: +foundRegion, token: String(token) },
- {
- onSuccess: (data) => {
- setUserData({ type: 'dare', id: +foundRegion, ...data });
- }
- }
- )
- : setUserData({ type: 'dare', id: +foundRegion });
- if (dareList) {
- const region = dareList.data.find((region) => region.id === +foundRegion);
- if (region) {
- const bounds = turf.bbox(region.bbox);
- cameraRef.current?.fitBounds(
- [bounds[2], bounds[3]],
- [bounds[0], bounds[1]],
- [10, 10, 50, 10],
- 1000
- );
- }
- }
- }
- } else {
- handleClosePopup();
- }
- } catch (error) {
- console.error('Error onMapPress features:', error);
- }
- };
- const handleRegionDidChange = async (feature: GeoJSON.Feature<GeoJSON.Point, RegionPayload>) => {
- 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 () => {
- let { status, canAskAgain } = await Location.getForegroundPermissionsAsync();
- if (status === 'granted') {
- getLocation();
- } else if (!canAskAgain) {
- setOpenSettingsVisible(true);
- } else {
- setAskLocationVisible(true);
- }
- };
- const getLocation = async () => {
- let currentLocation = await Location.getCurrentPositionAsync({
- accuracy: Location.Accuracy.Balanced
- });
- setLocation(currentLocation.coords);
- updateLocation({
- token,
- lat: currentLocation.coords.latitude,
- lng: currentLocation.coords.longitude
- });
- if (currentLocation.coords) {
- cameraRef.current?.flyTo(
- [currentLocation.coords.longitude, currentLocation.coords.latitude],
- 1000
- );
- }
- handleClosePopup();
- };
- const handleAcceptPermission = async () => {
- setAskLocationVisible(false);
- let { status, canAskAgain } = await Location.requestForegroundPermissionsAsync();
- if (status === 'granted') {
- getLocation();
- } else if (!canAskAgain) {
- setOpenSettingsVisible(true);
- }
- };
- const handleOpenEditModal = () => {
- handleModalStateChange({
- selectedFirstYear: userData?.first_visit_year,
- selectedLastYear: userData?.last_visit_year,
- selectedQuality:
- qualityOptions.find((quality) => quality.id === userData?.best_visit_quality) ||
- qualityOptions[2],
- selectedNoOfVisits: userData?.no_of_visits || 1,
- id: regionData?.id
- });
- setIsEditModalVisible(true);
- };
- const handleOpenEditSlowModal = () => {
- setIsEditSlowModalVisible(true);
- };
- const handleSearch = async () => {
- setSearch(searchInput);
- setSearchVisible(true);
- };
- const handleCloseModal = () => {
- setSearchInput('');
- setSearchVisible(false);
- handlePress();
- };
- const handleRegionData = (regionData: any, avatars: string[]) => {
- setRegionData(regionData);
- setUserAvatars(avatars);
- };
- const handleFindRegion = async (id: number, type: 'regions' | 'countries' | 'places') => {
- setType(type === 'places' ? 'dare' : type);
- if (!db1 || !db2 || !db3) {
- return;
- }
- const db = type === 'regions' ? db1 : type === 'countries' ? db3 : db2;
- if (id) {
- setSelectedRegion(id);
- await getData(db, id, type, handleRegionData)
- .then(() => {
- setRegionPopupVisible(true);
- })
- .catch((error) => {
- console.error('Error fetching data', error);
- refreshDatabases();
- });
- if (type === 'regions') {
- token
- ? await mutateUserData(
- { region_id: id, token: String(token) },
- {
- onSuccess: (data) => {
- setUserData({ type: 'nm', id, ...data });
- }
- }
- )
- : setUserData({ type: 'nm', id });
- if (regionsList) {
- const region = regionsList.data.find((region) => region.id === +id);
- if (region) {
- const bounds = turf.bbox(region.bbox);
- cameraRef.current?.fitBounds(
- [bounds[2], bounds[3]],
- [bounds[0], bounds[1]],
- [10, 10, 50, 10],
- 1000
- );
- }
- }
- } else if (type === 'countries') {
- token
- ? await mutateCountriesData(
- { id, token },
- {
- onSuccess: (data) => {
- setUserData({ type: 'countries', id, ...data.data });
- }
- }
- )
- : setUserData({ type: 'countries', id });
- if (countriesList) {
- const region = countriesList.data.find((region) => region.id === +id);
- if (region) {
- const bounds = turf.bbox(region.bbox);
- cameraRef.current?.fitBounds(
- [bounds[2], bounds[3]],
- [bounds[0], bounds[1]],
- [10, 10, 50, 10],
- 1000
- );
- }
- }
- } else {
- token
- ? await mutateUserDataDare(
- { dare_id: +id, token: String(token) },
- {
- onSuccess: (data) => {
- setUserData({ type: 'dare', id: +id, ...data });
- }
- }
- )
- : setUserData({ type: 'dare', id: +id });
- if (dareList) {
- const region = dareList.data.find((region) => region.id === +id);
- if (region) {
- const bounds = turf.bbox(region.bbox);
- cameraRef.current?.fitBounds(
- [bounds[2], bounds[3]],
- [bounds[0], bounds[1]],
- [10, 10, 50, 10],
- 1000
- );
- }
- }
- }
- } else {
- handleClosePopup();
- }
- };
- const handleMarkerPress = async (event: any) => {
- const { features } = event;
- if (features?.length) {
- const selectedFeature = features[0];
- const { coordinates } = selectedFeature.geometry;
- const visited = seriesVisited.includes(selectedFeature.properties.id) ? 1 : 0;
- const icon = images[selectedFeature.properties.series_id];
- const { name, description, series_name, series_id, id } = selectedFeature.properties;
- setSelectedMarker({
- coordinates,
- name,
- icon,
- description,
- series_name,
- visited,
- series_id,
- id
- });
- }
- };
- const closeCallout = () => {
- setSelectedMarker(null);
- };
- const toggleSeries = useCallback(
- async (item: any) => {
- if (!token) {
- setIsWarningModalVisible(true);
- return;
- }
- const itemData = {
- token,
- series_id: item.series_id,
- item_id: item.id,
- checked: (item.visited === 0 ? 1 : 0) as 0 | 1,
- double: 0 as 0 | 1
- };
- try {
- updateSeriesItem(itemData);
- if (item.visited === 1) {
- setSeriesVisited((current) => current.filter((id) => id !== item.id));
- setSelectedMarker((current: any) => ({ ...current, visited: 0 }));
- } else {
- setSeriesVisited((current) => [...current, item.id]);
- setSelectedMarker((current: any) => ({ ...current, visited: 1 }));
- }
- } catch (error) {
- console.error('Failed to update series state', error);
- }
- },
- [token, updateSeriesItem]
- );
- const handleModalStateChange = (updates: { [key: string]: any }) => {
- setModalState((prevState) => ({ ...prevState, ...updates }));
- };
- return (
- <SafeAreaView style={{ height: '100%' }}>
- <StatusBar translucent backgroundColor="transparent" />
- <MapLibreGL.MapView
- ref={mapRef}
- style={styles.map}
- styleJSON={VECTOR_MAP_HOST + '/nomadmania-maps.json'}
- rotateEnabled={false}
- attributionEnabled={false}
- onPress={onMapPress}
- onRegionDidChange={handleRegionDidChange}
- >
- <MapLibreGL.Images images={images}>
- <View />
- </MapLibreGL.Images>
- {type === 'regions' && (
- <>
- <MapLibreGL.FillLayer
- id={regions.id}
- sourceID={regions.source}
- sourceLayerID={regions['source-layer']}
- filter={regions.filter as any}
- style={regions.style}
- maxZoomLevel={regions.maxzoom}
- belowLayerID={regions_visited.id}
- />
- <MapLibreGL.FillLayer
- id={regions_visited.id}
- sourceID={regions_visited.source}
- sourceLayerID={regions_visited['source-layer']}
- filter={regionsVisitedFilter as any}
- style={regions_visited.style}
- maxZoomLevel={regions_visited.maxzoom}
- belowLayerID="waterway-name"
- />
- </>
- )}
- {type === 'countries' && (
- <>
- <MapLibreGL.FillLayer
- id={countries.id}
- sourceID={countries.source}
- sourceLayerID={countries['source-layer']}
- filter={countries.filter as any}
- style={countries.style}
- maxZoomLevel={countries.maxzoom}
- belowLayerID={countries_visited.id}
- />
- <MapLibreGL.FillLayer
- id={countries_visited.id}
- sourceID={countries_visited.source}
- sourceLayerID={countries_visited['source-layer']}
- filter={countriesVisitedFilter as any}
- style={countries_visited.style}
- maxZoomLevel={countries_visited.maxzoom}
- belowLayerID="waterway-name"
- />
- </>
- )}
- {type === 'dare' && (
- <>
- <MapLibreGL.FillLayer
- id={dare.id}
- sourceID={dare.source}
- sourceLayerID={dare['source-layer']}
- filter={dare.filter as any}
- style={dare.style}
- maxZoomLevel={dare.maxzoom}
- belowLayerID={dare_visited.id}
- />
- <MapLibreGL.FillLayer
- id={dare_visited.id}
- sourceID={dare_visited.source}
- sourceLayerID={dare_visited['source-layer']}
- filter={dareVisitedFilter as any}
- style={dare_visited.style}
- maxZoomLevel={dare_visited.maxzoom}
- belowLayerID="waterway-name"
- />
- </>
- )}
- {selectedRegion && type && (
- <MapLibreGL.FillLayer
- id={selected_region.id}
- sourceID={type}
- sourceLayerID={type}
- filter={['==', 'id', selectedRegion]}
- style={selected_region.style}
- maxZoomLevel={selected_region.maxzoom}
- belowLayerID="waterway-name"
- />
- )}
- <MapLibreGL.VectorSource
- id="nomadmania_series"
- tileUrlTemplates={[MAP_HOST + '/tileserver/series/{z}/{x}/{y}.pbf']}
- onPress={handleMarkerPress}
- >
- {seriesFilter.status !== 1 ? (
- <MapLibreGL.SymbolLayer
- id={series_layer.id}
- sourceID={series_layer.source}
- sourceLayerID={series_layer['source-layer']}
- belowLayerID={Platform.OS === 'android' ? 'waterway-name' : undefined}
- filter={seriesNotVisitedFilter as any}
- style={{
- iconImage: ['get', 'series_id'],
- iconSize: 0.18,
- visibility: 'visible',
- iconColor: '#666',
- iconOpacity: 1,
- iconHaloColor: '#ffffff',
- iconHaloWidth: 1,
- iconHaloBlur: 0.5
- }}
- />
- ) : (
- <></>
- )}
- {seriesFilter.status !== 0 ? (
- <MapLibreGL.SymbolLayer
- id={series_visited.id}
- sourceID={series_visited.source}
- sourceLayerID={series_visited['source-layer']}
- belowLayerID={Platform.OS === 'android' ? 'waterway-name' : undefined}
- filter={seriesVisitedFilter as any}
- style={{
- iconImage: '{series_id}v',
- iconSize: 0.18,
- visibility: 'visible',
- iconColor: '#666',
- iconOpacity: 1,
- iconHaloColor: '#ffffff',
- iconHaloWidth: 1,
- iconHaloBlur: 0.5
- }}
- />
- ) : (
- <></>
- )}
- </MapLibreGL.VectorSource>
- {nomads && (
- <MapLibreGL.ShapeSource
- id="nomads"
- shape={nomads}
- onPress={(event) => console.log(event.features)}
- >
- <MapLibreGL.SymbolLayer
- id="nomads_symbol"
- style={{
- iconImage: ['get', 'icon_key'],
- iconSize: 0.15,
- iconAllowOverlap: true
- }}
- ></MapLibreGL.SymbolLayer>
- </MapLibreGL.ShapeSource>
- )}
- {selectedMarker && (
- <MarkerItem marker={selectedMarker} toggleSeries={toggleSeries} token={token} />
- )}
- <MapLibreGL.Camera ref={cameraRef} />
- {location && (
- <MapLibreGL.UserLocation animated={true} showsUserHeadingIndicator={true}>
- {/* to do custom user location */}
- </MapLibreGL.UserLocation>
- )}
- </MapLibreGL.MapView>
- {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]}
- >
- <LocationIcon />
- </TouchableOpacity>
- <RegionPopup
- region={regionData}
- userAvatars={userAvatars}
- userData={userData}
- openEditModal={handleOpenEditModal}
- updateNM={(id, first, last, visits, quality) => {
- if (!token) {
- setIsWarningModalVisible(true);
- return;
- }
- handleUpdateNM(id, first, last, visits, quality);
- const updatedIds = regionsVisited.includes(id)
- ? regionsVisited.filter((visitedId) => visitedId !== id)
- : [...regionsVisited, id];
- setRegionsVisited(updatedIds);
- refetchVisitedCountries();
- }}
- updateDare={(id, visits) => {
- if (!token) {
- setIsWarningModalVisible(true);
- return;
- }
- handleUpdateDare(id, visits);
- const updatedIds = dareVisited.includes(id)
- ? dareVisited.filter((visitedId) => visitedId !== id)
- : [...dareVisited, id];
- setDareVisited(updatedIds);
- }}
- disabled={!token || !isConnected}
- updateSlow={(id, v, s11, s31, s101) => {
- if (!token) {
- setIsWarningModalVisible(true);
- return;
- }
- handleUpdateSlow(id, v, s11, s31, s101);
- const updatedIds = countriesVisited.includes(id)
- ? countriesVisited.filter((visitedId) => visitedId !== id)
- : [...countriesVisited, id];
- setCountriesVisited(updatedIds);
- }}
- openEditSlowModal={handleOpenEditSlowModal}
- />
- </>
- ) : (
- <>
- {!isExpanded ? (
- <TouchableOpacity
- style={[styles.cornerButton, styles.topRightButton]}
- onPress={() => navigation.navigate(NAVIGATION_PAGES.PROFILE_TAB)}
- >
- {token ? (
- userInfoData?.avatar ? (
- <Image
- style={styles.avatar}
- source={{ uri: API_HOST + '/img/avatars/' + userInfoData?.avatar }}
- />
- ) : (
- <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>
- <TouchableOpacity
- style={[styles.cornerButton, styles.bottomButton, styles.bottomLeftButton]}
- onPress={() => {
- setIsFilterVisible(true);
- closeCallout();
- }}
- >
- <FilterIcon />
- </TouchableOpacity>
- <TouchableOpacity
- onPress={handleGetLocation}
- style={[styles.cornerButton, styles.bottomButton, styles.bottomRightButton]}
- >
- <LocationIcon />
- </TouchableOpacity>
- </>
- )}
- <SearchModal
- searchVisible={searchVisible}
- handleCloseModal={handleCloseModal}
- handleFindRegion={handleFindRegion}
- index={index}
- searchData={searchData}
- setIndex={setIndex}
- token={token}
- />
- <WarningModal
- type={'unauthorized'}
- isVisible={isWarningModalVisible}
- onClose={() => setIsWarningModalVisible(false)}
- />
- <EditNmModal
- isVisible={isEditModalVisible}
- onClose={() => setIsEditModalVisible(false)}
- modalState={modalState}
- updateModalState={handleModalStateChange}
- updateNM={handleUpdateNM}
- />
- <FilterModal
- isFilterVisible={isFilterVisible}
- setIsFilterVisible={setIsFilterVisible}
- tilesTypes={tilesTypes}
- tilesType={tilesType}
- setTilesType={setTilesType}
- setType={setType}
- userId={userId ? +userId : 0}
- setRegionsFilter={setRegionsFilter}
- setSeriesFilter={setSeriesFilter}
- setShowNomads={setShowNomads}
- showNomads={showNomads}
- isPublicView={false}
- isLogged={token ? true : false}
- />
- <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={() =>
- Platform.OS === 'ios' ? Linking.openURL('app-settings:') : Linking.openSettings()
- }
- message="NomadMania app needs location permissions to function properly. Open settings?"
- />
- </SafeAreaView>
- );
- };
- export default MapScreen;
|