import { Platform, TouchableOpacity, Image, Linking, TextInput, Dimensions, StatusBar, ActivityIndicator, ScrollView, View } from 'react-native'; import React, { FC, useEffect, useRef, useState } from 'react'; import * as Location from 'expo-location'; import Animated, { Easing, useSharedValue, useAnimatedStyle, withTiming } from 'react-native-reanimated'; import { styles } from './styles'; import { API_HOST, VECTOR_MAP_HOST } from 'src/constants'; import { CommonActions, NavigationProp } from '@react-navigation/native'; import { AvatarWithInitials, LocationPopup } from 'src/components'; import { Colors } from 'src/theme'; import CloseSvg from 'assets/icons/close.svg'; import LocationIcon from 'assets/icons/location.svg'; import SearchIcon from 'assets/icons/search.svg'; import FilterModal from '../../MapScreen/FilterModal'; import SearchModal from '../../MapScreen/UniversalSearch'; import { useGetUniversalSearch } from '@api/search'; import { storage, StoreType } from 'src/storage'; import { NAVIGATION_PAGES } from 'src/types'; import { SafeAreaView } from 'react-native-safe-area-context'; import * as MapLibreRN from '@maplibre/maplibre-react-native'; import { usePostGetVisitedCountriesIdsQuery, usePostGetVisitedDareIdsQuery, usePostGetVisitedRegionsIdsQuery } from '@api/maps'; import moment from 'moment'; import TravelsIcon from 'assets/icons/bottom-navigation/globe-solid.svg'; import MapButton from 'src/components/MapButton'; import ScaleBar from 'src/components/ScaleBar'; import _ from 'lodash'; const defaultUserAvatar = require('assets/icon-user-share-location-solid.png'); const generateFilter = (ids: number[]) => { return ids.length ? ['any', ...ids.map((id) => ['==', 'id', id])] : ['==', 'id', -1]; }; let regions_visited = { id: 'regions_visited', type: 'fill', source: 'regions', 'source-layer': 'regions', style: { fillColor: 'rgba(255, 126, 0, 1)', fillOpacity: 0.5, fillOutlineColor: 'rgba(14, 80, 109, 0)' }, 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.5, fillOutlineColor: 'rgba(14, 80, 109, 0)' }, filter: generateFilter([]), maxzoom: 12 }; let dare_visited = { id: 'dare_visited', type: 'fill', source: 'dare', 'source-layer': 'dare', style: { fillColor: 'rgba(255, 126, 0, 1)', fillOpacity: 0.5, 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, 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, 1)' }, filter: ['all'], maxzoom: 16 }; type Props = { navigation: NavigationProp; route: any; }; const UsersMapScreen: FC = ({ navigation, route }) => { const token = storage.get('token', StoreType.STRING) as string; const userId = route.params?.userId; const data = route.params?.data; const [regionsVisitedFilter, setRegionsVisitedFilter] = useState(generateFilter([])); const [countriesVisitedFilter, setCountriesVisitedFilter] = useState(generateFilter([])); const [dareVisitedFilter, setDareVisitedFilter] = useState(generateFilter([])); const [regionsFilter, setRegionsFilter] = useState({ visitedLabel: 'by', year: moment().year() }); const mapRef = useRef(null); const cameraRef = useRef(null); const [isFilterVisible, setIsFilterVisible] = useState(null); 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'); const [location, setLocation] = useState(null); const [askLocationVisible, setAskLocationVisible] = useState(false); const [openSettingsVisible, setOpenSettingsVisible] = useState(false); const [isExpanded, setIsExpanded] = useState(false); const [searchVisible, setSearchVisible] = useState(false); const [index, setIndex] = useState(0); const width = useSharedValue(48); const usableWidth = Dimensions.get('window').width - 32; const [search, setSearch] = useState(''); const [searchInput, setSearchInput] = useState(''); const { data: searchData } = useGetUniversalSearch(search, search.length > 0); const [isLocationLoading, setIsLocationLoading] = useState(false); const [zoom, setZoom] = useState(0); const [center, setCenter] = useState(null); const [isZooming, setIsZooming] = useState(true); const hideTimer = useRef | null>(null); const { data: visitedRegionIds } = usePostGetVisitedRegionsIdsQuery( token, regionsFilter.visitedLabel, regionsFilter.year, +userId, type === 'regions' && !!userId ); const { data: visitedCountryIds } = usePostGetVisitedCountriesIdsQuery( token, regionsFilter.visitedLabel, regionsFilter.year, +userId, type === 'countries' && !!userId ); const { data: visitedDareIds } = usePostGetVisitedDareIdsQuery( token, +userId, type === 'dare' && !!userId ); useEffect(() => { if (visitedRegionIds) { setRegionsVisitedFilter(generateFilter(visitedRegionIds.ids)); } else { setRegionsVisitedFilter(['==', 'id', -1]); } }, [visitedRegionIds]); useEffect(() => { if (visitedCountryIds) { setCountriesVisitedFilter(generateFilter(visitedCountryIds.ids)); } else { setCountriesVisitedFilter(['==', 'id', -1]); } }, [visitedCountryIds]); useEffect(() => { if (visitedDareIds) { setDareVisitedFilter(generateFilter(visitedDareIds.ids)); } else { setDareVisitedFilter(['==', 'id', -1]); } }, [visitedDareIds]); useEffect(() => { if ( data.location_sharing && data.location_last_seen_location?.lng && data.location_last_seen_location?.lat && cameraRef.current ) { cameraRef.current.flyTo( [data.location_last_seen_location.lng, data.location_last_seen_location.lat], 1000 ); } }, [data, cameraRef.current]); const handleMapChange = async () => { if (!mapRef.current) return; if (hideTimer.current) clearTimeout(hideTimer.current); setIsZooming(true); const currentZoom = await mapRef.current.getZoom(); const currentCenter = await mapRef.current.getCenter(); setZoom(currentZoom); setCenter(currentCenter); }; const handleGetLocation = async () => { setIsLocationLoading(true); try { let { status, canAskAgain } = await Location.getForegroundPermissionsAsync(); const isServicesEnabled = await Location.hasServicesEnabledAsync(); if (status === 'granted' && isServicesEnabled) { await getLocation(); } else if (!canAskAgain || !isServicesEnabled) { setOpenSettingsVisible(true); } else { setAskLocationVisible(true); } } finally { setIsLocationLoading(false); } }; const getLocation = async () => { let currentLocation = await Location.getCurrentPositionAsync({ accuracy: Location.Accuracy.Balanced }); setLocation(currentLocation.coords); if (currentLocation.coords) { cameraRef.current?.flyTo( [currentLocation.coords.longitude, currentLocation.coords.latitude], 1000 ); } }; const handleAcceptPermission = async () => { setAskLocationVisible(false); let { status, canAskAgain } = await Location.requestForegroundPermissionsAsync(); const isServicesEnabled = await Location.hasServicesEnabledAsync(); if (status === 'granted' && isServicesEnabled) { getLocation(); } else if (!canAskAgain || !isServicesEnabled) { setOpenSettingsVisible(true); } }; const 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 handleGoBack = () => { navigation.goBack(); }; const handleCloseModal = () => { setSearchInput(''); setSearchVisible(false); handlePress(); }; const handleFindRegion = (id: number, type: string) => { navigation.dispatch( CommonActions.reset({ index: 1, routes: [ { name: NAVIGATION_PAGES.IN_APP_MAP_TAB, state: { routes: [ { name: NAVIGATION_PAGES.MAP_TAB, params: { id, type } } ] } } ] }) ); }; const locationFeature: GeoJSON.Feature = { type: 'Feature', geometry: { type: 'Point', coordinates: [data.location_last_seen_location?.lng, data.location_last_seen_location?.lat] }, properties: {} }; return ( { hideTimer.current = setTimeout(() => { setIsZooming(false); }, 2000); }} onRegionIsChanging={handleMapChange} onRegionWillChange={_.debounce(handleMapChange, 200)} > {type === 'regions' && ( <> )} {type === 'countries' && ( <> )} {type === 'dare' && ( <> )} {data.location_sharing && data.location_last_seen_location && data.own_profile !== 1 && ( )} {location && ( )} {center ? ( ) : null} {!isExpanded ? ( {data.user_data.avatar ? ( ) : ( )} ) : null} { setIsFilterVisible('regions'); }} icon={TravelsIcon} text="Travels" /> {isLocationLoading ? ( ) : ( )} {isExpanded ? ( <> setSearchInput(text)} onSubmitEditing={handleSearch} /> ) : ( )} 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." /> setOpenSettingsVisible(false)} onAccept={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(); } }} modalText="NomadMania app needs location permissions to function properly. Open settings?" /> ); }; export default UsersMapScreen;