1234567891011121314151617181920212223242526272829303132333435363738394041424344454647484950515253545556575859606162636465666768697071727374757677787980818283848586878889909192939495969798991001011021031041051061071081091101111121131141151161171181191201211221231241251261271281291301311321331341351361371381391401411421431441451461471481491501511521531541551561571581591601611621631641651661671681691701711721731741751761771781791801811821831841851861871881891901911921931941951961971981992002012022032042052062072082092102112122132142152162172182192202212222232242252262272282292302312322332342352362372382392402412422432442452462472482492502512522532542552562572582592602612622632642652662672682692702712722732742752762772782792802812822832842852862872882892902912922932942952962972982993003013023033043053063073083093103113123133143153163173183193203213223233243253263273283293303313323333343353363373383393403413423433443453463473483493503513523533543553563573583593603613623633643653663673683693703713723733743753763773783793803813823833843853863873883893903913923933943953963973983994004014024034044054064074084094104114124134144154164174184194204214224234244254264274284294304314324334344354364374384394404414424434444454464474484494504514524534544554564574584594604614624634644654664674684694704714724734744754764774784794804814824834844854864874884894904914924934944954964974984995005015025035045055065075085095105115125135145155165175185195205215225235245255265275285295305315325335345355365375385395405415425435445455465475485495505515525535545555565575585595605615625635645655665675685695705715725735745755765775785795805815825835845855865875885895905915925935945955965975985996006016026036046056066076086096106116126136146156166176186196206216226236246256266276286296306316326336346356366376386396406416426436446456466476486496506516526536546556566576586596606616626636646656666676686696706716726736746756766776786796806816826836846856866876886896906916926936946956966976986997007017027037047057067077087097107117127137147157167177187197207217227237247257267277287297307317327337347357367377387397407417427437447457467477487497507517527537547557567577587597607617627637647657667677687697707717727737747757767777787797807817827837847857867877887897907917927937947957967977987998008018028038048058068078088098108118128138148158168178188198208218228238248258268278288298308318328338348358368378388398408418428438448458468478488498508518528538548558568578588598608618628638648658668678688698708718728738748758768778788798808818828838848858868878888898908918928938948958968978988999009019029039049059069079089099109119129139149159169179189199209219229239249259269279289299309319329339349359369379389399409419429439449459469479489499509519529539549559569579589599609619629639649659669679689699709719729739749759769779789799809819829839849859869879889899909919929939949959969979989991000100110021003100410051006 |
- import {
- Animated as Animation,
- Dimensions,
- Linking,
- Platform,
- Text,
- TextInput,
- TouchableOpacity,
- View,
- Image
- } from 'react-native';
- import React, { useEffect, useMemo, useRef, useState, useCallback } from 'react';
- import MapView, { Geojson, Marker, UrlTile } from 'react-native-maps';
- import * as turf from '@turf/turf';
- import * as FileSystem from 'expo-file-system';
- import * as Location from 'expo-location';
- import { storage, StoreType } from '../../../storage';
- 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 regions from '../../../../assets/geojson/nm2022.json';
- import jsonData, { fetchJsonData } from '../../../database/geojsonService';
- import {
- getCountriesDatabase,
- getFirstDatabase,
- getSecondDatabase,
- refreshDatabases
- } from '../../../db';
- import { LocationPopup, WarningModal, EditNmModal, AvatarWithInitials } from '../../../components';
- import { styles } from './style';
- import {
- calculateMapCountry,
- calculateMapRegion,
- filterCandidates,
- filterCandidatesMarkers,
- findRegionInDataset,
- processMarkerData
- } from '../../../utils/mapHelpers';
- import { getData } from '../../../modules/map/regionData';
- import { fetchSeriesData, usePostSetToggleItem } from '@api/series';
- import MarkerItem from './MarkerItem';
- import ClusterItem from './ClusterItem';
- import { FeatureCollection, ItemSeries, MapScreenProps, Region, Series } from '../../../types/map';
- import { API_HOST, FASTEST_MAP_HOST } from 'src/constants';
- import { useConnection } from 'src/contexts/ConnectionContext';
- import ClusteredMapView from 'react-native-map-clustering';
- import { fetchUserData, fetchUserDataDare } from '@api/regions';
- import RegionPopup from 'src/components/RegionPopup';
- import moment from 'moment';
- import { qualityOptions } from '../TravelsScreen/utils/constants';
- import Animated, { Easing } from 'react-native-reanimated';
- import { useSharedValue, useAnimatedStyle, withTiming } from 'react-native-reanimated';
- import { Colors } from 'src/theme';
- import { useGetUniversalSearch } from '@api/search';
- import SearchModal from './UniversalSearch';
- import FilterModal from './FilterModal';
- import { NAVIGATION_PAGES } from 'src/types';
- import { useRegion } from 'src/contexts/RegionContext';
- import { useFocusEffect } from '@react-navigation/native';
- import { openstreetmapUrl } from 'src/constants/constants';
- import { fetchCountryUserData } from '@api/countries';
- import EditModal from '../TravelsScreen/Components/EditSlowModal';
- const localTileDir = `${FileSystem.cacheDirectory}tiles/background`;
- const localGridDir = `${FileSystem.cacheDirectory}tiles/grid`;
- const localVisitedDir = `${FileSystem.cacheDirectory}tiles/user_visited`;
- const localDareDir = `${FileSystem.cacheDirectory}tiles/regions_mqp`;
- const AnimatedMarker = Animation.createAnimatedComponent(Marker);
- const MapScreen: React.FC<MapScreenProps> = ({ navigation, route }) => {
- const [dareData, setDareData] = useState(jsonData);
- const tilesBaseURL = `${FASTEST_MAP_HOST}/tiles_osm`;
- const gridUrl = `${FASTEST_MAP_HOST}/tiles_nm/grid`;
- const userId = storage.get('uid', StoreType.STRING);
- const dareTiles = `${FASTEST_MAP_HOST}/tiles_nm/dare`;
- const token = storage.get('token', StoreType.STRING) as string;
- const netInfo = useConnection();
- const { mutateAsync } = fetchSeriesData();
- const { mutateAsync: mutateUserData } = fetchUserData();
- const { mutateAsync: mutateUserDataDare } = fetchUserDataDare();
- const { mutateAsync: mutateCountriesData } = fetchCountryUserData();
- const { mutate: updateSeriesItem } = usePostSetToggleItem();
- const visitedDefaultTiles = `${FASTEST_MAP_HOST}/tiles_nm/user_visited/${userId}`;
- const mapRef = useRef<MapView>(null);
- const [isConnected, setIsConnected] = useState<boolean | null>(true);
- const [selectedRegion, setSelectedRegion] = useState<FeatureCollection | null>(null);
- const [regionPopupVisible, setRegionPopupVisible] = useState<boolean | null>(false);
- const [regionData, setRegionData] = useState<Region | null>(null);
- const [userAvatars, setUserAvatars] = useState<string[]>([]);
- const [location, setLocation] = useState<Location.LocationObjectCoords | null>(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 [markers, setMarkers] = useState<ItemSeries[]>([]);
- const [series, setSeries] = useState<Series[] | null>(null);
- const [processedMarkers, setProcessedMarkers] = useState<ItemSeries[]>([]);
- const [zoomLevel, setZoomLevel] = useState<number>(0);
- const [isEditModalVisible, setIsEditModalVisible] = useState(false);
- const [modalState, setModalState] = useState({
- selectedFirstYear: 2021,
- selectedLastYear: 2021,
- selectedQuality: qualityOptions[2],
- selectedNoOfVisits: 1,
- years: [],
- id: null
- });
- const [search, setSearch] = useState('');
- const [searchInput, setSearchInput] = useState('');
- const { data: searchData } = useGetUniversalSearch(search, search.length > 0);
- const [isFilterVisible, setIsFilterVisible] = useState(false);
- 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(0);
- const [visitedTiles, setVisitedTiles] = useState(visitedDefaultTiles);
- const [seriesFilter, setSeriesFilter] = useState<any>({
- visible: true,
- groups: [],
- applied: false,
- status: -1
- });
- const { handleUpdateNM, handleUpdateDare, handleUpdateSlow, userData, setUserData } = useRegion();
- const userInfo = storage.get('currentUserData', StoreType.STRING) as string;
- const savedFilterSettings = storage.get('filterSettings', StoreType.STRING) as string;
- const savedVisitedTilesUrl = storage.get('visitedTilesUrl', StoreType.STRING) as string;
- const [userInfoData, setUserInfoData] = useState<any>(null);
- useFocusEffect(
- useCallback(() => {
- const updateMarkers = async () => {
- await mutateAsync(
- { regions: JSON.stringify([regionData?.id]), token: String(token) },
- {
- onSuccess: (data) => {
- setSeries(data.series);
- const allMarkers = data.items.map(processMarkerData);
- setProcessedMarkers(allMarkers);
- setMarkers(allMarkers);
- }
- }
- );
- };
- if (userData && userData?.type === 'nm') {
- updateMarkers();
- }
- }, [userData])
- );
- useEffect(() => {
- if (route.params?.id && route.params?.type && dareData) {
- handleFindRegion(route.params?.id, route.params?.type);
- }
- }, [route, dareData]);
- useEffect(() => {
- if (userInfo) {
- setUserInfoData(JSON.parse(userInfo));
- }
- }, [userInfo]);
- useEffect(() => {
- if (savedFilterSettings) {
- const filterSettings = JSON.parse(savedFilterSettings);
- setTilesType(filterSettings.tilesType);
- setType(filterSettings.type);
- setSeriesFilter(filterSettings.seriesFilter);
- }
- if (savedVisitedTilesUrl) {
- setVisitedTiles(savedVisitedTilesUrl);
- }
- }, [savedFilterSettings, savedVisitedTilesUrl]);
- useEffect(() => {
- if (!dareData) {
- const fetchData = async () => {
- const fetchedData = await fetchJsonData();
- setDareData(fetchedData);
- };
- fetchData();
- }
- }, [dareData]);
- const handleModalStateChange = (updates: { [key: string]: any }) => {
- setModalState((prevState) => ({ ...prevState, ...updates }));
- };
- 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);
- };
- 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 });
- }, []);
- const cancelTokenRef = useRef(false);
- const currentTokenRef = useRef(0);
- const strokeWidthAnim = useRef(new Animation.Value(2)).current;
- const [isExpanded, setIsExpanded] = useState(false);
- const [searchVisible, setSearchVisible] = useState(false);
- const [index, setIndex] = useState<number>(0);
- const width = useSharedValue(48);
- const usableWidth = Dimensions.get('window').width - 32;
- useEffect(() => {
- if (netInfo?.isInternetReachable) {
- setIsConnected(true);
- } else {
- setIsConnected(false);
- }
- }, [netInfo?.isInternetReachable]);
- useEffect(() => {
- Animation.loop(
- Animation.sequence([
- Animation.timing(strokeWidthAnim, {
- toValue: 3,
- duration: 700,
- useNativeDriver: false
- }),
- Animation.timing(strokeWidthAnim, {
- toValue: 2,
- duration: 700,
- useNativeDriver: false
- })
- ])
- ).start();
- }, [strokeWidthAnim]);
- useFocusEffect(
- useCallback(() => {
- navigation.getParent()?.setOptions({
- tabBarStyle: {
- display: regionPopupVisible ? 'none' : 'flex',
- position: 'absolute',
- ...Platform.select({
- android: {
- height: 58
- }
- })
- }
- });
- }, [regionPopupVisible, navigation])
- );
- useEffect(() => {
- (async () => {
- let { status } = await Location.getForegroundPermissionsAsync();
- if (status !== 'granted') {
- return;
- }
- let currentLocation = await Location.getCurrentPositionAsync({});
- setLocation(currentLocation.coords);
- })();
- }, []);
- const findFeaturesInVisibleMapArea = async (visibleMapArea: {
- latitude?: any;
- longitude?: any;
- latitudeDelta: any;
- longitudeDelta?: any;
- }) => {
- if (!isConnected) return;
- const currentZoom = Math.log2(360 / visibleMapArea.latitudeDelta);
- setZoomLevel(currentZoom);
- if (cancelTokenRef.current) {
- setMarkers(processedMarkers);
- return;
- }
- const thisToken = ++currentTokenRef.current;
- if (!regions || !dareData) return;
- if (currentZoom < 7) {
- setMarkers([]);
- return;
- }
- const { latitude, longitude, latitudeDelta, longitudeDelta } = visibleMapArea;
- const bbox: turf.BBox = [
- longitude - longitudeDelta / 2,
- latitude - latitudeDelta / 2,
- longitude + longitudeDelta / 2,
- latitude + latitudeDelta / 2
- ];
- const visibleAreaPolygon = turf.bboxPolygon(bbox);
- const regionsFound = filterCandidates(regions, bbox);
- // const daresFound = filterCandidates(dareRegions, bbox);
- const regionIds = regionsFound.map(
- (region: { properties: { id: any } }) => region.properties.id
- );
- isConnected &&
- (await mutateAsync(
- { regions: JSON.stringify(regionIds), token: String(token) },
- {
- onSuccess: (data) => {
- if (thisToken !== currentTokenRef.current) return;
- setSeries(data.series);
- const markersVisible = filterCandidatesMarkers(data.items, visibleAreaPolygon);
- const allMarkers = markersVisible.map(processMarkerData);
- setMarkers(allMarkers);
- }
- }
- ));
- };
- 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);
- mapRef.current?.animateToRegion(
- {
- latitude: currentLocation.coords.latitude,
- longitude: currentLocation.coords.longitude,
- latitudeDelta: 5,
- longitudeDelta: 5
- },
- 800
- );
- handleClosePopup();
- };
- const handleAcceptPermission = async () => {
- setAskLocationVisible(false);
- let { status, canAskAgain } = await Location.requestForegroundPermissionsAsync();
- if (status === 'granted') {
- getLocation();
- } else if (!canAskAgain) {
- setOpenSettingsVisible(true);
- }
- };
- const handleRegionData = (regionData: Region, avatars: string[]) => {
- setRegionData(regionData);
- setUserAvatars(avatars);
- };
- const handleMapPress = async (event: {
- nativeEvent: { coordinate: { latitude: any; longitude: any }; action?: string };
- }) => {
- if (event.nativeEvent?.action === 'marker-press') return;
- cancelTokenRef.current = true;
- const { latitude, longitude } = event.nativeEvent.coordinate;
- const point = turf.point([longitude, latitude]);
- let db = getSecondDatabase();
- let tableName = 'places';
- let foundRegion: any;
- type !== 1 && (foundRegion = findRegionInDataset(dareData, point));
- if (type === 1) {
- foundRegion = findRegionInDataset(regions, point);
- db = getFirstDatabase();
- tableName = 'regions';
- if (foundRegion) {
- const countryId = foundRegion.properties?.country_id;
- if (countryId) {
- if (countryId === regionData?.id) return;
- setSelectedRegion(null);
- setMarkers([]);
- setProcessedMarkers([]);
- db = getCountriesDatabase();
- tableName = 'countries';
- await getData(db, countryId, tableName, handleRegionData)
- .then(() => {
- setRegionPopupVisible(true);
- })
- .catch((error) => {
- console.error('Error fetching data', error);
- refreshDatabases();
- });
- await mutateCountriesData(
- { id: +countryId, token },
- {
- onSuccess: (data) => {
- setUserData({ type: 'countries', ...data.data });
- const bounds = turf.bbox(data.data.bbox);
- const center = data.data.center;
- const region = calculateMapCountry(bounds, center);
- mapRef.current?.animateToRegion(region, 1000);
- }
- }
- );
- return;
- }
- }
- }
- if (!foundRegion) {
- foundRegion = findRegionInDataset(regions, point);
- db = getFirstDatabase();
- tableName = 'regions';
- }
- if (foundRegion) {
- if (foundRegion.properties?.id === regionData?.id) return;
- const id = foundRegion.properties?.id;
- setSelectedRegion({
- type: 'FeatureCollection',
- features: [
- {
- geometry: foundRegion.geometry,
- properties: {
- ...foundRegion.properties,
- fill: 'rgba(57, 115, 172, 0.2)',
- stroke: '#3973AC'
- },
- type: 'Feature'
- }
- ]
- });
- await getData(db, id, tableName, handleRegionData)
- .then(() => {
- setRegionPopupVisible(true);
- })
- .catch((error) => {
- console.error('Error fetching data', error);
- refreshDatabases();
- });
- const bounds = turf.bbox(foundRegion);
- const region = calculateMapRegion(bounds);
- zoomLevel < 7 && mapRef.current?.animateToRegion(region, 1000);
- if (tableName === 'regions') {
- await mutateUserData(
- { region_id: +id, token: String(token) },
- {
- onSuccess: (data) => {
- setUserData({ type: 'nm', ...data });
- }
- }
- );
- await mutateAsync(
- { regions: JSON.stringify([id]), token: String(token) },
- {
- onSuccess: (data) => {
- setSeries(data.series);
- const allMarkers = data.items.map(processMarkerData);
- setProcessedMarkers(allMarkers);
- }
- }
- );
- } else {
- await mutateUserDataDare(
- { dare_id: +id, token: String(token) },
- {
- onSuccess: (data) => {
- setUserData({ type: 'dare', ...data });
- }
- }
- );
- setProcessedMarkers([]);
- }
- } else {
- handleClosePopup();
- }
- };
- const handleFindRegion = async (id: number, type: string) => {
- cancelTokenRef.current = true;
- const db =
- type === 'regions'
- ? getFirstDatabase()
- : type === 'countries'
- ? getCountriesDatabase()
- : getSecondDatabase();
- if (type === 'countries') {
- setSelectedRegion(null);
- setMarkers([]);
- setProcessedMarkers([]);
- await getData(db, id, type, handleRegionData)
- .then(() => {
- setRegionPopupVisible(true);
- })
- .catch((error) => {
- console.error('Error fetching data', error);
- refreshDatabases();
- });
- await mutateCountriesData(
- { id: +id, token },
- {
- onSuccess: (data) => {
- setUserData({ type: 'countries', ...data.data });
- const bounds = turf.bbox(data.data.bbox);
- const center = data.data.center;
- const region = calculateMapCountry(bounds, center);
- mapRef.current?.animateToRegion(region, 1000);
- }
- }
- );
- return;
- }
- const dataset = type === 'regions' ? regions : dareData;
- const foundRegion = dataset.features.find((region: any) => region.properties.id === id);
- if (foundRegion) {
- setSelectedRegion({
- type: 'FeatureCollection',
- features: [
- {
- geometry: foundRegion.geometry,
- properties: {
- ...foundRegion.properties,
- fill: 'rgba(57, 115, 172, 0.2)',
- stroke: '#3973AC'
- },
- type: 'Feature'
- }
- ]
- });
- await getData(db, id, type, handleRegionData)
- .then(() => {
- setRegionPopupVisible(true);
- })
- .catch((error) => {
- console.error('Error fetching data', error);
- refreshDatabases();
- });
- const bounds = turf.bbox(foundRegion);
- const region = calculateMapRegion(bounds);
- mapRef.current?.animateToRegion(region, 1000);
- if (type === 'regions') {
- await mutateUserData(
- { region_id: +id, token: String(token) },
- {
- onSuccess: (data) => {
- setUserData({ type: 'nm', ...data });
- }
- }
- );
- await mutateAsync(
- { regions: JSON.stringify([id]), token: String(token) },
- {
- onSuccess: (data) => {
- setSeries(data.series);
- const allMarkers = data.items.map(processMarkerData);
- setProcessedMarkers(allMarkers);
- setMarkers(allMarkers);
- }
- }
- );
- } else {
- await mutateUserDataDare(
- { dare_id: +id, token: String(token) },
- {
- onSuccess: (data) => {
- setUserData({ type: 'dare', ...data });
- }
- }
- );
- setProcessedMarkers([]);
- setMarkers([]);
- }
- } else {
- handleClosePopup();
- }
- };
- const renderMapTiles = (url: string, cacheDir: string, zIndex: number, opacity = 1) => (
- <UrlTile
- key={`${url}-${cacheDir}-${zoomLevel > 6 ? 0 : 1}`}
- urlTemplate={url === tilesBaseURL && zoomLevel > 13 ? openstreetmapUrl : `${url}/{z}/{x}/{y}`}
- maximumZ={18}
- maximumNativeZ={url === tilesBaseURL ? 18 : 13}
- tileCachePath={cacheDir !== localVisitedDir && zoomLevel < 8 ? `${cacheDir}` : undefined}
- shouldReplaceMapContent
- minimumZ={0}
- offlineMode={!isConnected}
- opacity={opacity}
- zIndex={zIndex}
- />
- );
- function renderGeoJSON() {
- if (!selectedRegion) return null;
- return (
- <Geojson
- geojson={selectedRegion as any}
- fillColor="rgba(57, 115, 172, 0.2)"
- strokeColor="#3973ac"
- strokeWidth={Platform.OS == 'android' ? 3 : 2}
- zIndex={3}
- />
- );
- }
- const handleClosePopup = async () => {
- cancelTokenRef.current = false;
- setRegionPopupVisible(false);
- setMarkers([]);
- setSelectedRegion(null);
- setRegionData(null);
- const boundaries = await mapRef.current?.getMapBoundaries();
- const { northEast, southWest } = boundaries || {};
- const latitudeDelta = (northEast?.latitude ?? 0) - (southWest?.latitude ?? 0);
- const longitudeDelta = (northEast?.longitude ?? 0) - (southWest?.longitude ?? 0);
- const latitude = (southWest?.latitude ?? 0) + latitudeDelta / 2;
- const longitude = (southWest?.longitude ?? 0) + longitudeDelta / 2;
- findFeaturesInVisibleMapArea({ latitude, longitude, latitudeDelta, longitudeDelta });
- };
- const renderedGeoJSON = useMemo(() => renderGeoJSON(), [selectedRegion]);
- const toggleSeries = useCallback(
- async (item: any) => {
- if (!token) {
- setIsWarningModalVisible(true);
- return;
- }
- setMarkers((currentMarkers) =>
- currentMarkers.map((marker) =>
- marker.id === item.id ? { ...marker, visited: Number(!marker.visited) as 0 | 1 } : marker
- )
- );
- setProcessedMarkers((currentMarkers) =>
- currentMarkers.map((marker) =>
- marker.id === item.id ? { ...marker, visited: Number(!marker.visited) as 0 | 1 } : marker
- )
- );
- const itemData = {
- token: token,
- series_id: item.series_id,
- item_id: item.id,
- checked: (!item.visited ? 1 : 0) as 0 | 1,
- double: 0 as 0 | 1
- };
- try {
- updateSeriesItem(itemData);
- } catch (error) {
- console.error('Failed to update series state', error);
- }
- },
- [token, updateSeriesItem]
- );
- const renderMarkers = () => {
- let filteredMarkers = markers;
- if (seriesFilter.applied) {
- filteredMarkers = filteredMarkers.filter((marker) =>
- seriesFilter.groups.includes(marker.series_id)
- );
- if (seriesFilter.status !== -1) {
- filteredMarkers = filteredMarkers.filter(
- (marker) => marker.visited === seriesFilter.status
- );
- }
- }
- return filteredMarkers.map((marker, idx) => {
- const coordinate = { latitude: marker.pointJSON[0], longitude: marker.pointJSON[1] };
- const markerSeries = series?.find((s) => s.id === marker.series_id);
- const iconUrl = markerSeries ? API_HOST + markerSeries.icon : 'default_icon_url';
- const seriesName = markerSeries ? markerSeries.name : 'Unknown';
- return (
- <MarkerItem
- marker={marker}
- iconUrl={iconUrl}
- key={`${idx} - ${marker.id}`}
- coordinate={coordinate}
- seriesName={seriesName}
- toggleSeries={toggleSeries}
- token={token}
- />
- );
- });
- };
- 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 handleSearch = async () => {
- setSearch(searchInput);
- setSearchVisible(true);
- };
- const handleCloseModal = () => {
- setSearchInput('');
- setSearchVisible(false);
- handlePress();
- };
- const handleOpenEditSlowModal = () => {
- setIsEditSlowModalVisible(true);
- };
- return (
- <View style={styles.container}>
- <ClusteredMapView
- initialRegion={{
- latitude: 0,
- longitude: 0,
- latitudeDelta: 180,
- longitudeDelta: 180
- }}
- ref={mapRef}
- showsMyLocationButton={false}
- showsCompass={false}
- zoomControlEnabled={false}
- onPress={handleMapPress}
- style={styles.map}
- mapType={Platform.OS == 'android' ? 'none' : 'standard'}
- maxZoomLevel={17}
- minZoomLevel={0}
- onRegionChangeComplete={findFeaturesInVisibleMapArea}
- minPoints={zoomLevel < 7 ? 0 : 12}
- tracksViewChanges={false}
- renderCluster={(cluster) => <ClusterItem key={cluster.id} cluster={cluster} />}
- >
- {renderedGeoJSON}
- {renderMapTiles(tilesBaseURL, localTileDir, 1)}
- {type === 0 && renderMapTiles(gridUrl, localGridDir, 2)}
- {userId && renderMapTiles(visitedTiles, localVisitedDir, 3, 0.5)}
- {type === 2 && !token && renderMapTiles(dareTiles, localDareDir, 2, 0.5)}
- {location && (
- <AnimatedMarker coordinate={location} anchor={{ x: 0.5, y: 0.5 }}>
- <Animation.View style={[styles.location, { borderWidth: strokeWidthAnim }]} />
- </AnimatedMarker>
- )}
- {markers && seriesFilter.visible && renderMarkers()}
- </ClusteredMapView>
- <LocationPopup
- visible={askLocationVisible}
- onClose={() => setAskLocationVisible(false)}
- onAccept={handleAcceptPermission}
- modalText="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."
- />
- <LocationPopup
- visible={openSettingsVisible}
- onClose={() => setOpenSettingsVisible(false)}
- onAccept={() =>
- Platform.OS === 'ios' ? Linking.openURL('app-settings:') : Linking.openSettings()
- }
- modalText="NomadMania app needs location permissions to function properly. Open settings?"
- />
- {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);
- }}
- updateDare={(id, visits) => {
- if (!token) {
- setIsWarningModalVisible(true);
- return;
- }
- handleUpdateDare(id, visits);
- }}
- disabled={!token || !isConnected}
- updateSlow={handleUpdateSlow}
- 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);
- }}
- >
- <FilterIcon />
- </TouchableOpacity>
- <TouchableOpacity
- onPress={handleGetLocation}
- style={[styles.cornerButton, styles.bottomButton, styles.bottomRightButton]}
- >
- <LocationIcon />
- </TouchableOpacity>
- </>
- )}
- <WarningModal
- type={'unauthorized'}
- isVisible={isWarningModalVisible}
- onClose={() => setIsWarningModalVisible(false)}
- />
- <EditNmModal
- isVisible={isEditModalVisible}
- onClose={() => setIsEditModalVisible(false)}
- modalState={modalState}
- updateModalState={handleModalStateChange}
- updateNM={handleUpdateNM}
- />
- <SearchModal
- searchVisible={searchVisible}
- handleCloseModal={handleCloseModal}
- handleFindRegion={handleFindRegion}
- index={index}
- searchData={searchData}
- setIndex={setIndex}
- token={token}
- />
- <FilterModal
- isFilterVisible={isFilterVisible}
- setIsFilterVisible={setIsFilterVisible}
- tilesTypes={tilesTypes}
- tilesType={tilesType}
- setTilesType={setTilesType}
- type={type}
- setType={setType}
- userId={userId ? +userId : 0}
- setVisitedTiles={setVisitedTiles}
- setSeriesFilter={setSeriesFilter}
- isPublicView={false}
- isLogged={!!token}
- />
- <EditModal
- isVisible={isEditSlowModalVisible}
- onClose={() => setIsEditSlowModalVisible(false)}
- item={{ ...userData, country_id: regionData?.id }}
- updateSlow={(id, v, s11, s31, s101) => handleUpdateSlow(id, v, s11, s31, s101)}
- />
- </View>
- );
- };
- export default MapScreen;
|