|
|
@@ -8,7 +8,8 @@ import {
|
|
|
StatusBar,
|
|
|
ActivityIndicator,
|
|
|
ScrollView,
|
|
|
- View
|
|
|
+ View,
|
|
|
+ Text
|
|
|
} from 'react-native';
|
|
|
import React, { FC, useCallback, useEffect, useRef, useState } from 'react';
|
|
|
import * as Location from 'expo-location';
|
|
|
@@ -18,11 +19,12 @@ import Animated, {
|
|
|
useAnimatedStyle,
|
|
|
withTiming
|
|
|
} from 'react-native-reanimated';
|
|
|
+import * as turf from '@turf/turf';
|
|
|
|
|
|
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 { CommonActions, NavigationProp, useFocusEffect } from '@react-navigation/native';
|
|
|
+import { AvatarWithInitials, LocationPopup, WarningModal } from 'src/components';
|
|
|
import { Colors } from 'src/theme';
|
|
|
|
|
|
import CloseSvg from 'assets/icons/close.svg';
|
|
|
@@ -47,6 +49,21 @@ 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';
|
|
|
+import { SQLiteDatabase } from 'expo-sqlite';
|
|
|
+import { getData } from 'src/modules/map/regionData';
|
|
|
+import { useBottomTabBarHeight } from '@react-navigation/bottom-tabs';
|
|
|
+import {
|
|
|
+ getCountriesDatabase,
|
|
|
+ getFirstDatabase,
|
|
|
+ getSecondDatabase,
|
|
|
+ refreshDatabases
|
|
|
+} from 'src/db';
|
|
|
+import { fetchUserData, fetchUserDataDare, useGetListRegionsQuery } from '@api/regions';
|
|
|
+import { fetchCountryUserData, useGetListCountriesQuery } from '@api/countries';
|
|
|
+import { useRegion } from 'src/contexts/RegionContext';
|
|
|
+import { useGetListDareQuery } from '@api/myDARE';
|
|
|
+import { useConnection } from 'src/contexts/ConnectionContext';
|
|
|
+import RegionPopup from 'src/components/RegionPopup';
|
|
|
const defaultUserAvatar = require('assets/icon-user-share-location-solid.png');
|
|
|
|
|
|
const generateFilter = (ids: number[]) => {
|
|
|
@@ -134,6 +151,31 @@ let dare = {
|
|
|
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
|
|
|
+};
|
|
|
+
|
|
|
type Props = {
|
|
|
navigation: NavigationProp<any>;
|
|
|
route: any;
|
|
|
@@ -143,6 +185,7 @@ const UsersMapScreen: FC<Props> = ({ navigation, route }) => {
|
|
|
const token = storage.get('token', StoreType.STRING) as string;
|
|
|
const userId = route.params?.userId;
|
|
|
const data = route.params?.data;
|
|
|
+ const tabBarHeight = useBottomTabBarHeight();
|
|
|
|
|
|
const [regionsVisitedFilter, setRegionsVisitedFilter] = useState(generateFilter([]));
|
|
|
const [countriesVisitedFilter, setCountriesVisitedFilter] = useState(generateFilter([]));
|
|
|
@@ -205,6 +248,28 @@ const UsersMapScreen: FC<Props> = ({ navigation, route }) => {
|
|
|
type === 'dare' && !!userId
|
|
|
);
|
|
|
|
|
|
+ const [isConnected, setIsConnected] = useState<boolean>(true);
|
|
|
+ const netInfo = useConnection();
|
|
|
+
|
|
|
+ const { mutateAsync: mutateUserData } = fetchUserData();
|
|
|
+ const { mutateAsync: mutateUserDataDare } = fetchUserDataDare();
|
|
|
+ const { mutateAsync: mutateCountriesData } = fetchCountryUserData();
|
|
|
+ const { data: regionsList } = useGetListRegionsQuery(isConnected);
|
|
|
+ const { data: countriesList } = useGetListCountriesQuery(isConnected);
|
|
|
+ const { data: dareList } = useGetListDareQuery(isConnected);
|
|
|
+
|
|
|
+ const { handleUpdateNM, handleUpdateDare, handleUpdateSlow, userData, setUserData } = useRegion();
|
|
|
+
|
|
|
+ const [selectedRegion, setSelectedRegion] = useState<number | null>(null);
|
|
|
+ const [db1, setDb1] = useState<SQLiteDatabase | null>(null);
|
|
|
+ const [db2, setDb2] = useState<SQLiteDatabase | null>(null);
|
|
|
+ const [db3, setDb3] = useState<SQLiteDatabase | null>(null);
|
|
|
+ const [regionData, setRegionData] = useState<any | null>(null);
|
|
|
+ const [userAvatars, setUserAvatars] = useState<string[]>([]);
|
|
|
+ const [isWarningModalVisible, setIsWarningModalVisible] = useState<boolean>(false);
|
|
|
+
|
|
|
+ const [regionPopupVisible, setRegionPopupVisible] = useState<boolean | null>(false);
|
|
|
+
|
|
|
const cameraController = {
|
|
|
flyTo: useCallback((coordinates: number[], duration: number = 1000) => {
|
|
|
isAnimatingRef.current = true;
|
|
|
@@ -253,9 +318,69 @@ const UsersMapScreen: FC<Props> = ({ navigation, route }) => {
|
|
|
(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);
|
|
|
+ }
|
|
|
}, [])
|
|
|
};
|
|
|
|
|
|
+ useFocusEffect(
|
|
|
+ useCallback(() => {
|
|
|
+ navigation.getParent()?.setOptions({
|
|
|
+ tabBarStyle: {
|
|
|
+ display: regionPopupVisible ? 'none' : 'flex',
|
|
|
+ position: 'absolute'
|
|
|
+ }
|
|
|
+ });
|
|
|
+ }, [regionPopupVisible, navigation])
|
|
|
+ );
|
|
|
+
|
|
|
+ useEffect(() => {
|
|
|
+ if (netInfo && netInfo.isConnected !== null) {
|
|
|
+ setIsConnected(netInfo.isConnected);
|
|
|
+ }
|
|
|
+ }, [netInfo]);
|
|
|
+
|
|
|
+ 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(() => {
|
|
|
if (visitedRegionIds) {
|
|
|
setRegionsVisitedFilter(generateFilter(visitedRegionIds.ids));
|
|
|
@@ -394,25 +519,230 @@ const UsersMapScreen: FC<Props> = ({ navigation, route }) => {
|
|
|
handlePress();
|
|
|
};
|
|
|
|
|
|
- const handleFindRegion = (id: number, type: string) => {
|
|
|
- navigation.dispatch(
|
|
|
- CommonActions.reset({
|
|
|
- index: 1,
|
|
|
- routes: [
|
|
|
- {
|
|
|
- name: NAVIGATION_PAGES.IN_APP_MAP_TAB,
|
|
|
- state: {
|
|
|
- routes: [
|
|
|
+ const handleRegionData = async (regionData: any, avatars: string[]) => {
|
|
|
+ if (!regionData) {
|
|
|
+ await refreshDatabases();
|
|
|
+ }
|
|
|
+ setRegionData(regionData);
|
|
|
+ setUserAvatars(avatars);
|
|
|
+ };
|
|
|
+
|
|
|
+ const handleClosePopup = async () => {
|
|
|
+ setSelectedRegion(null);
|
|
|
+ setRegionPopupVisible(false);
|
|
|
+ setRegionData(null);
|
|
|
+ };
|
|
|
+
|
|
|
+ const handleOpenEditModal = () => {
|
|
|
+ navigation.navigate(NAVIGATION_PAGES.EDIT_NM_DATA, { regionId: regionData?.id });
|
|
|
+ };
|
|
|
+
|
|
|
+ const handleOpenEditSlowModal = () => {};
|
|
|
+
|
|
|
+ const handleFindRegion = async (id: number, type: string) => {
|
|
|
+ setType(type === 'places' ? 'dare' : type);
|
|
|
+ if (!db1 || !db2 || !db3) {
|
|
|
+ return;
|
|
|
+ }
|
|
|
+ const db = type === 'regions' ? db1 : type === 'countries' ? db3 : db2;
|
|
|
+
|
|
|
+ if (id) {
|
|
|
+ setSelectedRegion(id);
|
|
|
+
|
|
|
+ await getData(db, id, type, handleRegionData)
|
|
|
+ .then(() => {
|
|
|
+ setRegionPopupVisible(true);
|
|
|
+ })
|
|
|
+ .catch((error) => {
|
|
|
+ console.error('Error fetching data', error);
|
|
|
+ refreshDatabases();
|
|
|
+ });
|
|
|
+
|
|
|
+ if (type === 'regions') {
|
|
|
+ token
|
|
|
+ ? await mutateUserData(
|
|
|
+ { region_id: id, token: String(token) },
|
|
|
+ {
|
|
|
+ onSuccess: (data) => {
|
|
|
+ setUserData({ type: 'nm', id, ...data });
|
|
|
+ }
|
|
|
+ }
|
|
|
+ )
|
|
|
+ : setUserData({ type: 'nm', id });
|
|
|
+
|
|
|
+ if (regionsList && regionsList.data) {
|
|
|
+ const region = regionsList.data.find((region) => region.id === +id);
|
|
|
+ if (region) {
|
|
|
+ const bounds = turf.bbox(region.bbox);
|
|
|
+ cameraController.fitBounds(
|
|
|
+ [bounds[2], bounds[3]],
|
|
|
+ [bounds[0], bounds[1]],
|
|
|
+ [10, 10, 50, 10],
|
|
|
+ 1000
|
|
|
+ );
|
|
|
+ }
|
|
|
+ }
|
|
|
+ } else if (type === 'countries') {
|
|
|
+ token
|
|
|
+ ? await mutateCountriesData(
|
|
|
+ { id, token },
|
|
|
+ {
|
|
|
+ onSuccess: (data) => {
|
|
|
+ setUserData({ type: 'countries', id, ...data.data });
|
|
|
+ }
|
|
|
+ }
|
|
|
+ )
|
|
|
+ : setUserData({ type: 'countries', id });
|
|
|
+
|
|
|
+ if (countriesList && countriesList.data) {
|
|
|
+ const region = countriesList.data.find((region) => region.id === +id);
|
|
|
+ if (region) {
|
|
|
+ const bounds = turf.bbox(region.bbox);
|
|
|
+ cameraController.fitBounds(
|
|
|
+ [bounds[2], bounds[3]],
|
|
|
+ [bounds[0], bounds[1]],
|
|
|
+ [10, 10, 50, 10],
|
|
|
+ 1000
|
|
|
+ );
|
|
|
+ }
|
|
|
+ }
|
|
|
+ } else {
|
|
|
+ token
|
|
|
+ ? await mutateUserDataDare(
|
|
|
+ { dare_id: +id, token: String(token) },
|
|
|
+ {
|
|
|
+ onSuccess: (data) => {
|
|
|
+ setUserData({ type: 'dare', id: +id, ...data });
|
|
|
+ }
|
|
|
+ }
|
|
|
+ )
|
|
|
+ : setUserData({ type: 'dare', id: +id });
|
|
|
+
|
|
|
+ if (dareList && dareList.data) {
|
|
|
+ const region = dareList.data.find((region) => region.id === +id);
|
|
|
+ if (region) {
|
|
|
+ const bounds = turf.bbox(region.bbox);
|
|
|
+ cameraController.fitBounds(
|
|
|
+ [bounds[2], bounds[3]],
|
|
|
+ [bounds[0], bounds[1]],
|
|
|
+ [10, 10, 50, 10],
|
|
|
+ 1000
|
|
|
+ );
|
|
|
+ }
|
|
|
+ }
|
|
|
+ }
|
|
|
+ } else {
|
|
|
+ handleClosePopup();
|
|
|
+ }
|
|
|
+ };
|
|
|
+
|
|
|
+ const onMapPress = async (event: any) => {
|
|
|
+ if (!mapRef.current) return;
|
|
|
+ if (type === 'blank') return;
|
|
|
+ try {
|
|
|
+ const { screenPointX, screenPointY } = event.properties;
|
|
|
+
|
|
|
+ const { features } = await mapRef.current.queryRenderedFeaturesAtPoint(
|
|
|
+ [screenPointX, screenPointY],
|
|
|
+ undefined,
|
|
|
+ ['regions', 'countries', 'dare']
|
|
|
+ );
|
|
|
+
|
|
|
+ if (features?.length) {
|
|
|
+ const region = features[0];
|
|
|
+ if (selectedRegion === region.properties?.id) return;
|
|
|
+
|
|
|
+ let db = type === 'regions' ? db1 : type === 'countries' ? db3 : db2;
|
|
|
+ let tableName = type === 'dare' ? 'places' : type;
|
|
|
+ let foundRegion = region.properties?.id;
|
|
|
+
|
|
|
+ setSelectedRegion(region.properties?.id);
|
|
|
+
|
|
|
+ await getData(db, foundRegion, tableName, handleRegionData)
|
|
|
+ .then(() => {
|
|
|
+ setRegionPopupVisible(true);
|
|
|
+ })
|
|
|
+ .catch((error) => {
|
|
|
+ console.error('Error fetching data', error);
|
|
|
+ refreshDatabases();
|
|
|
+ });
|
|
|
+
|
|
|
+ if (tableName === 'regions') {
|
|
|
+ token
|
|
|
+ ? await mutateUserData(
|
|
|
+ { region_id: +foundRegion, token: String(token) },
|
|
|
+ {
|
|
|
+ onSuccess: (data) => {
|
|
|
+ setUserData({ type: 'nm', id: +foundRegion, ...data });
|
|
|
+ }
|
|
|
+ }
|
|
|
+ )
|
|
|
+ : setUserData({ type: 'nm', id: +foundRegion });
|
|
|
+ if (regionsList && regionsList.data) {
|
|
|
+ const region = regionsList.data.find((region) => region.id === +foundRegion);
|
|
|
+ if (region) {
|
|
|
+ const bounds = turf.bbox(region.bbox);
|
|
|
+ cameraController.fitBounds(
|
|
|
+ [bounds[2], bounds[3]],
|
|
|
+ [bounds[0], bounds[1]],
|
|
|
+ [10, 10, 50, 10],
|
|
|
+ 1000
|
|
|
+ );
|
|
|
+ }
|
|
|
+ }
|
|
|
+ } else if (tableName === 'countries') {
|
|
|
+ token
|
|
|
+ ? await mutateCountriesData(
|
|
|
+ { id: +foundRegion, token },
|
|
|
+ {
|
|
|
+ onSuccess: (data) => {
|
|
|
+ setUserData({ type: 'countries', id: +foundRegion, ...data.data });
|
|
|
+ }
|
|
|
+ }
|
|
|
+ )
|
|
|
+ : setUserData({ type: 'countries', id: +foundRegion });
|
|
|
+ if (countriesList && countriesList.data) {
|
|
|
+ const region = countriesList.data.find((region) => region.id === +foundRegion);
|
|
|
+ if (region) {
|
|
|
+ const bounds = turf.bbox(region.bbox);
|
|
|
+ cameraController.fitBounds(
|
|
|
+ [bounds[2], bounds[3]],
|
|
|
+ [bounds[0], bounds[1]],
|
|
|
+ [10, 10, 50, 10],
|
|
|
+ 1000
|
|
|
+ );
|
|
|
+ }
|
|
|
+ }
|
|
|
+ } else {
|
|
|
+ token
|
|
|
+ ? await mutateUserDataDare(
|
|
|
+ { dare_id: +foundRegion, token: String(token) },
|
|
|
{
|
|
|
- name: NAVIGATION_PAGES.MAP_TAB,
|
|
|
- params: { id, type }
|
|
|
+ onSuccess: (data) => {
|
|
|
+ setUserData({ type: 'dare', id: +foundRegion, ...data });
|
|
|
+ }
|
|
|
}
|
|
|
- ]
|
|
|
+ )
|
|
|
+ : setUserData({ type: 'dare', id: +foundRegion });
|
|
|
+ if (dareList && dareList.data) {
|
|
|
+ const region = dareList.data.find((region) => region.id === +foundRegion);
|
|
|
+ if (region) {
|
|
|
+ const bounds = turf.bbox(region.bbox);
|
|
|
+ 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 locationFeature: GeoJSON.Feature<GeoJSON.Point> = {
|
|
|
@@ -434,102 +764,141 @@ const UsersMapScreen: FC<Props> = ({ navigation, route }) => {
|
|
|
mapStyle={VECTOR_MAP_HOST + '/nomadmania-maps2025.json'}
|
|
|
rotateEnabled={false}
|
|
|
attributionEnabled={false}
|
|
|
+ onPress={onMapPress}
|
|
|
onRegionDidChange={() => {
|
|
|
hideTimer.current = setTimeout(() => {
|
|
|
setIsZooming(false);
|
|
|
}, 2000);
|
|
|
}}
|
|
|
- // onRegionIsChanging={handleMapChange}
|
|
|
onRegionWillChange={_.debounce(handleMapChange, 200)}
|
|
|
>
|
|
|
- {type === 'regions' && (
|
|
|
+ <>
|
|
|
+ <MapLibreRN.LineLayer
|
|
|
+ id="nm-regions-line-layer"
|
|
|
+ sourceID={regions.source}
|
|
|
+ sourceLayerID={regions['source-layer']}
|
|
|
+ filter={regions.filter as any}
|
|
|
+ maxZoomLevel={regions.maxzoom}
|
|
|
+ style={{
|
|
|
+ lineColor: 'rgba(14, 80, 109, 1)',
|
|
|
+ lineWidth: ['interpolate', ['linear'], ['zoom'], 0, 0.2, 4, 1, 5, 1.5, 12, 3],
|
|
|
+ lineWidthTransition: { duration: 300, delay: 0 },
|
|
|
+ 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.LineLayer
|
|
|
- id="nm-regions-line-layer"
|
|
|
- sourceID={regions.source}
|
|
|
- sourceLayerID={regions['source-layer']}
|
|
|
- filter={regions.filter as any}
|
|
|
- maxZoomLevel={regions.maxzoom}
|
|
|
- style={{
|
|
|
- lineColor: 'rgba(14, 80, 109, 1)',
|
|
|
- lineWidth: ['interpolate', ['linear'], ['zoom'], 0, 0.2, 4, 1, 5, 1.5, 12, 3],
|
|
|
- lineWidthTransition: { duration: 300, delay: 0 }
|
|
|
- }}
|
|
|
- belowLayerID="waterway-name"
|
|
|
- />
|
|
|
- <MapLibreRN.FillLayer
|
|
|
- id={regions.id}
|
|
|
- sourceID={regions.source}
|
|
|
- sourceLayerID={regions['source-layer']}
|
|
|
- filter={regions.filter as any}
|
|
|
- style={regions.style}
|
|
|
- maxZoomLevel={regions.maxzoom}
|
|
|
- belowLayerID={regions_visited.id}
|
|
|
- />
|
|
|
<MapLibreRN.FillLayer
|
|
|
- id={regions_visited.id}
|
|
|
- sourceID={regions_visited.source}
|
|
|
- sourceLayerID={regions_visited['source-layer']}
|
|
|
- filter={regionsVisitedFilter as any}
|
|
|
- style={regions_visited.style}
|
|
|
- maxZoomLevel={regions_visited.maxzoom}
|
|
|
+ id={selected_region.id}
|
|
|
+ sourceID={type}
|
|
|
+ sourceLayerID={type}
|
|
|
+ filter={['==', 'id', selectedRegion]}
|
|
|
+ style={selected_region.style}
|
|
|
+ maxZoomLevel={selected_region.maxzoom}
|
|
|
belowLayerID="waterway-name"
|
|
|
/>
|
|
|
- </>
|
|
|
- )}
|
|
|
- {type === 'countries' && (
|
|
|
- <>
|
|
|
<MapLibreRN.LineLayer
|
|
|
- id="countries-line-layer"
|
|
|
- sourceID={countries.source}
|
|
|
- sourceLayerID={countries['source-layer']}
|
|
|
- filter={countries.filter as any}
|
|
|
- maxZoomLevel={countries.maxzoom}
|
|
|
- style={{
|
|
|
- lineColor: 'rgba(14, 80, 109, 1)',
|
|
|
- lineWidth: ['interpolate', ['linear'], ['zoom'], 0, 0.2, 4, 1, 5, 1.5, 12, 3],
|
|
|
- lineWidthTransition: { duration: 300, delay: 0 }
|
|
|
- }}
|
|
|
- belowLayerID="waterway-name"
|
|
|
- />
|
|
|
- <MapLibreRN.FillLayer
|
|
|
- id={countries.id}
|
|
|
- sourceID={countries.source}
|
|
|
- sourceLayerID={countries['source-layer']}
|
|
|
- filter={countries.filter as any}
|
|
|
- style={countries.style}
|
|
|
- maxZoomLevel={countries.maxzoom}
|
|
|
- belowLayerID={countries_visited.id}
|
|
|
- />
|
|
|
- <MapLibreRN.FillLayer
|
|
|
- id={countries_visited.id}
|
|
|
- sourceID={countries_visited.source}
|
|
|
- sourceLayerID={countries_visited['source-layer']}
|
|
|
- filter={countriesVisitedFilter as any}
|
|
|
- style={countries_visited.style}
|
|
|
- maxZoomLevel={countries_visited.maxzoom}
|
|
|
- belowLayerID="waterway-name"
|
|
|
- />
|
|
|
- </>
|
|
|
- )}
|
|
|
- {type === 'dare' && (
|
|
|
- <>
|
|
|
- <MapLibreRN.FillLayer
|
|
|
- id={dare.id}
|
|
|
- sourceID={dare.source}
|
|
|
- sourceLayerID={dare['source-layer']}
|
|
|
- filter={dare.filter as any}
|
|
|
- style={dare.style}
|
|
|
- maxZoomLevel={dare.maxzoom}
|
|
|
- belowLayerID={dare_visited.id}
|
|
|
- />
|
|
|
- <MapLibreRN.FillLayer
|
|
|
- id={dare_visited.id}
|
|
|
- sourceID={dare_visited.source}
|
|
|
- sourceLayerID={dare_visited['source-layer']}
|
|
|
- filter={dareVisitedFilter as any}
|
|
|
- style={dare_visited.style}
|
|
|
- maxZoomLevel={dare_visited.maxzoom}
|
|
|
+ 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"
|
|
|
/>
|
|
|
</>
|
|
|
@@ -577,94 +946,161 @@ const UsersMapScreen: FC<Props> = ({ navigation, route }) => {
|
|
|
<ScaleBar zoom={zoom} latitude={center[1]} isVisible={isZooming} bottom={80} />
|
|
|
) : null}
|
|
|
|
|
|
- {!isExpanded ? (
|
|
|
- <TouchableOpacity
|
|
|
- style={[styles.cornerButton, styles.topRightButton]}
|
|
|
- onPress={handleGoBack}
|
|
|
- >
|
|
|
- {data.user_data.avatar ? (
|
|
|
- <Image
|
|
|
- style={styles.avatar}
|
|
|
- source={{ uri: API_HOST + '/img/avatars/' + data.user_data.avatar }}
|
|
|
- />
|
|
|
- ) : (
|
|
|
- <AvatarWithInitials
|
|
|
- text={`${data.user_data.first_name[0] ?? ''}${data.user_data.last_name[0] ?? ''}`}
|
|
|
- flag={API_HOST + '/img/flags_new/' + data.user_data.flag1}
|
|
|
- size={48}
|
|
|
- borderColor={Colors.WHITE}
|
|
|
- />
|
|
|
- )}
|
|
|
- </TouchableOpacity>
|
|
|
- ) : 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>
|
|
|
|
|
|
- <View style={styles.tabs}>
|
|
|
- <ScrollView
|
|
|
- horizontal
|
|
|
- showsHorizontalScrollIndicator={false}
|
|
|
- contentContainerStyle={{ marginLeft: 12, gap: 12, flexDirection: 'row' }}
|
|
|
- >
|
|
|
- <MapButton
|
|
|
- onPress={() => {
|
|
|
- setIsFilterVisible('regions');
|
|
|
+ <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);
|
|
|
}}
|
|
|
- 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={true}
|
|
|
+ updateDare={(id, visits) => {
|
|
|
+ if (!token) {
|
|
|
+ setIsWarningModalVisible(true);
|
|
|
+ return;
|
|
|
+ }
|
|
|
+ handleUpdateDare(id, visits);
|
|
|
+ }}
|
|
|
+ disabled={!token || !isConnected}
|
|
|
+ updateSlow={(id, v, s11, s31, s101) => {
|
|
|
+ if (!token) {
|
|
|
+ setIsWarningModalVisible(true);
|
|
|
+ return;
|
|
|
+ }
|
|
|
+ handleUpdateSlow(id, v, s11, s31, s101);
|
|
|
+ }}
|
|
|
+ openEditSlowModal={handleOpenEditSlowModal}
|
|
|
/>
|
|
|
- </ScrollView>
|
|
|
- </View>
|
|
|
-
|
|
|
- <TouchableOpacity
|
|
|
- onPress={handleGetLocation}
|
|
|
- style={[styles.cornerButton, styles.bottomButton, styles.bottomRightButton]}
|
|
|
- >
|
|
|
- {isLocationLoading ? (
|
|
|
- <ActivityIndicator size="small" color={Colors.DARK_BLUE} />
|
|
|
- ) : (
|
|
|
- <LocationIcon />
|
|
|
- )}
|
|
|
- </TouchableOpacity>
|
|
|
- <Animated.View
|
|
|
- style={[
|
|
|
- styles.searchContainer,
|
|
|
- styles.cornerButton,
|
|
|
- styles.topLeftButton,
|
|
|
- animatedStyle,
|
|
|
- { padding: 5 }
|
|
|
- ]}
|
|
|
- >
|
|
|
- {isExpanded ? (
|
|
|
- <>
|
|
|
- <TouchableOpacity onPress={handlePress} style={styles.iconButton}>
|
|
|
- <CloseSvg fill={'#0F3F4F'} />
|
|
|
+ </>
|
|
|
+ ) : (
|
|
|
+ <>
|
|
|
+ {!isExpanded ? (
|
|
|
+ <TouchableOpacity
|
|
|
+ style={[styles.cornerButton, styles.topRightButton]}
|
|
|
+ onPress={handleGoBack}
|
|
|
+ >
|
|
|
+ {data.user_data.avatar ? (
|
|
|
+ <Image
|
|
|
+ style={styles.avatar}
|
|
|
+ source={{ uri: API_HOST + '/img/avatars/' + data.user_data.avatar }}
|
|
|
+ />
|
|
|
+ ) : (
|
|
|
+ <AvatarWithInitials
|
|
|
+ text={`${data.user_data.first_name[0] ?? ''}${data.user_data.last_name[0] ?? ''}`}
|
|
|
+ flag={API_HOST + '/img/flags_new/' + data.user_data.flag1}
|
|
|
+ size={48}
|
|
|
+ borderColor={Colors.WHITE}
|
|
|
+ />
|
|
|
+ )}
|
|
|
</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'} />
|
|
|
+ ) : 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={{ marginLeft: 12, gap: 12, flexDirection: 'row' }}
|
|
|
+ >
|
|
|
+ <MapButton
|
|
|
+ onPress={() => {
|
|
|
+ setIsFilterVisible('regions');
|
|
|
+ }}
|
|
|
+ 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={true}
|
|
|
+ />
|
|
|
+ </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>
|
|
|
- )}
|
|
|
- </Animated.View>
|
|
|
+ </>
|
|
|
+ )}
|
|
|
+
|
|
|
<FilterModal
|
|
|
isFilterVisible={isFilterVisible}
|
|
|
setIsFilterVisible={setIsFilterVisible}
|
|
|
@@ -708,6 +1144,12 @@ const UsersMapScreen: FC<Props> = ({ navigation, route }) => {
|
|
|
setIndex={setIndex}
|
|
|
token={token}
|
|
|
/>
|
|
|
+
|
|
|
+ <WarningModal
|
|
|
+ type={'unauthorized'}
|
|
|
+ isVisible={isWarningModalVisible}
|
|
|
+ onClose={() => setIsWarningModalVisible(false)}
|
|
|
+ />
|
|
|
</SafeAreaView>
|
|
|
);
|
|
|
};
|