|
@@ -12,7 +12,7 @@ import {
|
|
} from 'react-native';
|
|
} from 'react-native';
|
|
import React, { useEffect, useRef, useState, useCallback } from 'react';
|
|
import React, { useEffect, useRef, useState, useCallback } from 'react';
|
|
|
|
|
|
-import MapLibreGL, { CameraRef, MapViewRef } from '@maplibre/maplibre-react-native';
|
|
|
|
|
|
+import MapLibreGL, { CameraRef, MapViewRef, ShapeSourceRef } from '@maplibre/maplibre-react-native';
|
|
import { styles } from './style';
|
|
import { styles } from './style';
|
|
import { SafeAreaView } from 'react-native-safe-area-context';
|
|
import { SafeAreaView } from 'react-native-safe-area-context';
|
|
import { Colors } from 'src/theme';
|
|
import { Colors } from 'src/theme';
|
|
@@ -20,6 +20,7 @@ import { storage, StoreType } from 'src/storage';
|
|
import { RegionPayload } from '@maplibre/maplibre-react-native/javascript/components/MapView';
|
|
import { RegionPayload } from '@maplibre/maplibre-react-native/javascript/components/MapView';
|
|
import * as turf from '@turf/turf';
|
|
import * as turf from '@turf/turf';
|
|
import * as Location from 'expo-location';
|
|
import * as Location from 'expo-location';
|
|
|
|
+import { Image as ExpoImage } from 'expo-image';
|
|
|
|
|
|
import SearchIcon from 'assets/icons/search.svg';
|
|
import SearchIcon from 'assets/icons/search.svg';
|
|
import LocationIcon from 'assets/icons/location.svg';
|
|
import LocationIcon from 'assets/icons/location.svg';
|
|
@@ -375,14 +376,14 @@ const MapScreen: any = ({ navigation, route }: { navigation: any; route: any })
|
|
|
|
|
|
useEffect(() => {
|
|
useEffect(() => {
|
|
if (usersLocation) {
|
|
if (usersLocation) {
|
|
- const filteredNomads: GeoJSON.FeatureCollection = {
|
|
|
|
- type: 'FeatureCollection',
|
|
|
|
- features: usersLocation.geojson.features.filter(
|
|
|
|
- (feature: GeoJSON.Feature) => feature.properties?.id !== +userId
|
|
|
|
- )
|
|
|
|
- };
|
|
|
|
|
|
+ const filteredNomads: GeoJSON.FeatureCollection = {
|
|
|
|
+ type: 'FeatureCollection',
|
|
|
|
+ features: usersLocation.geojson.features.filter(
|
|
|
|
+ (feature: GeoJSON.Feature) => feature.properties?.id !== +userId
|
|
|
|
+ )
|
|
|
|
+ };
|
|
|
|
|
|
- setNomads(filteredNomads);
|
|
|
|
|
|
+ setNomads(filteredNomads);
|
|
}
|
|
}
|
|
}, [usersLocation]);
|
|
}, [usersLocation]);
|
|
|
|
|
|
@@ -390,13 +391,19 @@ const MapScreen: any = ({ navigation, route }: { navigation: any; route: any })
|
|
if (seriesIcons) {
|
|
if (seriesIcons) {
|
|
let loadedSeriesImages: any = {};
|
|
let loadedSeriesImages: any = {};
|
|
|
|
|
|
- seriesIcons.data.forEach((icon) => {
|
|
|
|
|
|
+ seriesIcons.data.forEach(async (icon) => {
|
|
const id = icon.id;
|
|
const id = icon.id;
|
|
const img = API_HOST + '/static/img/series_new2/' + icon.new_icon_png;
|
|
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;
|
|
const imgVisited = API_HOST + '/static/img/series_new2/' + icon.new_icon_visited_png;
|
|
if (img && imgVisited) {
|
|
if (img && imgVisited) {
|
|
loadedSeriesImages[id] = { uri: img };
|
|
loadedSeriesImages[id] = { uri: img };
|
|
loadedSeriesImages[`${id}v`] = { uri: imgVisited };
|
|
loadedSeriesImages[`${id}v`] = { uri: imgVisited };
|
|
|
|
+
|
|
|
|
+ const cachedUrl = await ExpoImage.getCachePathAsync(img);
|
|
|
|
+ const cachedUrlVisited = await ExpoImage.getCachePathAsync(imgVisited);
|
|
|
|
+ if (!cachedUrl || !cachedUrlVisited) {
|
|
|
|
+ ExpoImage.prefetch([img, imgVisited]);
|
|
|
|
+ }
|
|
}
|
|
}
|
|
});
|
|
});
|
|
|
|
|
|
@@ -406,20 +413,39 @@ const MapScreen: any = ({ navigation, route }: { navigation: any; route: any })
|
|
|
|
|
|
useEffect(() => {
|
|
useEffect(() => {
|
|
if (nomads && nomads.features) {
|
|
if (nomads && nomads.features) {
|
|
- let loadedNomadsImages: any = {};
|
|
|
|
|
|
+ const loadImages = async () => {
|
|
|
|
+ let loadedNomadsImages: any = {};
|
|
|
|
+ loadedNomadsImages['default_icon'] = require('assets/logo-ua.png');
|
|
|
|
+
|
|
|
|
+ const promises = nomads.features.map(async (feature) => {
|
|
|
|
+ const user_id = feature.properties?.avatar
|
|
|
|
+ ? `user_${feature.properties?.id}`
|
|
|
|
+ : 'default_icon';
|
|
|
|
+ const avatarPath = feature.properties?.avatar;
|
|
|
|
+ const avatarUrl = { uri: `${API_HOST}${avatarPath}`, cache: 'force-cache' };
|
|
|
|
|
|
- nomads.features.forEach((feature) => {
|
|
|
|
- const user_id = `user_${feature.properties?.id}`;
|
|
|
|
- const avatarUrl = `${API_HOST}${feature.properties?.avatar}`;
|
|
|
|
- if (avatarUrl) {
|
|
|
|
- loadedNomadsImages[user_id] = { uri: avatarUrl };
|
|
|
|
if (feature.properties) {
|
|
if (feature.properties) {
|
|
feature.properties.icon_key = user_id;
|
|
feature.properties.icon_key = user_id;
|
|
}
|
|
}
|
|
- }
|
|
|
|
- });
|
|
|
|
|
|
|
|
- setImages((prevImages: any) => ({ ...prevImages, ...loadedNomadsImages }));
|
|
|
|
|
|
+ if (avatarPath) {
|
|
|
|
+ const cachedUrls = await ExpoImage.getCachePathAsync(avatarUrl.uri);
|
|
|
|
+ if (!cachedUrls) {
|
|
|
|
+ ExpoImage.prefetch([`${API_HOST}${avatarPath}`]);
|
|
|
|
+ }
|
|
|
|
+
|
|
|
|
+ return (loadedNomadsImages[user_id] = avatarUrl);
|
|
|
|
+ } else {
|
|
|
|
+ return Promise.resolve();
|
|
|
|
+ }
|
|
|
|
+ });
|
|
|
|
+
|
|
|
|
+ await Promise.all(promises);
|
|
|
|
+
|
|
|
|
+ setImages((prevImages: any) => ({ ...prevImages, ...loadedNomadsImages }));
|
|
|
|
+ };
|
|
|
|
+
|
|
|
|
+ loadImages();
|
|
}
|
|
}
|
|
}, [nomads]);
|
|
}, [nomads]);
|
|
|
|
|
|
@@ -560,7 +586,14 @@ const MapScreen: any = ({ navigation, route }: { navigation: any; route: any })
|
|
useEffect(() => {
|
|
useEffect(() => {
|
|
(async () => {
|
|
(async () => {
|
|
let { status } = await Location.getForegroundPermissionsAsync();
|
|
let { status } = await Location.getForegroundPermissionsAsync();
|
|
- if (status !== 'granted' || !token || (locationSettings && locationSettings.sharing === 0)) {
|
|
|
|
|
|
+ const isServicesEnabled = await Location.hasServicesEnabledAsync();
|
|
|
|
+
|
|
|
|
+ if (
|
|
|
|
+ status !== 'granted' ||
|
|
|
|
+ !token ||
|
|
|
|
+ (locationSettings && locationSettings.sharing === 0) ||
|
|
|
|
+ !isServicesEnabled
|
|
|
|
+ ) {
|
|
setShowNomads(false);
|
|
setShowNomads(false);
|
|
storage.set('showNomads', false);
|
|
storage.set('showNomads', false);
|
|
setNomads(null);
|
|
setNomads(null);
|
|
@@ -615,6 +648,7 @@ const MapScreen: any = ({ navigation, route }: { navigation: any; route: any })
|
|
|
|
|
|
const mapRef = useRef<MapViewRef>(null);
|
|
const mapRef = useRef<MapViewRef>(null);
|
|
const cameraRef = useRef<CameraRef>(null);
|
|
const cameraRef = useRef<CameraRef>(null);
|
|
|
|
+ const shapeSourceRef = useRef<ShapeSourceRef>(null);
|
|
|
|
|
|
useEffect(() => {
|
|
useEffect(() => {
|
|
if (userInfo) {
|
|
if (userInfo) {
|
|
@@ -809,10 +843,11 @@ const MapScreen: any = ({ navigation, route }: { navigation: any; route: any })
|
|
setIsLocationLoading(true);
|
|
setIsLocationLoading(true);
|
|
try {
|
|
try {
|
|
let { status, canAskAgain } = await Location.getForegroundPermissionsAsync();
|
|
let { status, canAskAgain } = await Location.getForegroundPermissionsAsync();
|
|
|
|
+ const isServicesEnabled = await Location.hasServicesEnabledAsync();
|
|
|
|
|
|
- if (status === 'granted') {
|
|
|
|
|
|
+ if (status === 'granted' && isServicesEnabled) {
|
|
await getLocation();
|
|
await getLocation();
|
|
- } else if (!canAskAgain) {
|
|
|
|
|
|
+ } else if (!canAskAgain || !isServicesEnabled) {
|
|
setOpenSettingsVisible(true);
|
|
setOpenSettingsVisible(true);
|
|
} else {
|
|
} else {
|
|
setAskLocationVisible(true);
|
|
setAskLocationVisible(true);
|
|
@@ -855,10 +890,11 @@ const MapScreen: any = ({ navigation, route }: { navigation: any; route: any })
|
|
const handleAcceptPermission = async () => {
|
|
const handleAcceptPermission = async () => {
|
|
setAskLocationVisible(false);
|
|
setAskLocationVisible(false);
|
|
let { status, canAskAgain } = await Location.requestForegroundPermissionsAsync();
|
|
let { status, canAskAgain } = await Location.requestForegroundPermissionsAsync();
|
|
|
|
+ const isServicesEnabled = await Location.hasServicesEnabledAsync();
|
|
|
|
|
|
- if (status === 'granted') {
|
|
|
|
|
|
+ if (status === 'granted' && isServicesEnabled) {
|
|
getLocation();
|
|
getLocation();
|
|
- } else if (!canAskAgain) {
|
|
|
|
|
|
+ } else if (!canAskAgain || !isServicesEnabled) {
|
|
setOpenSettingsVisible(true);
|
|
setOpenSettingsVisible(true);
|
|
}
|
|
}
|
|
};
|
|
};
|
|
@@ -1064,7 +1100,7 @@ const MapScreen: any = ({ navigation, route }: { navigation: any; route: any })
|
|
if (selectedFeature) {
|
|
if (selectedFeature) {
|
|
setSelectedUser({
|
|
setSelectedUser({
|
|
coordinates,
|
|
coordinates,
|
|
- avatar: { uri: API_HOST + avatar },
|
|
|
|
|
|
+ avatar: avatar ? { uri: API_HOST + avatar } : require('assets/logo-ua.png'),
|
|
first_name,
|
|
first_name,
|
|
last_name,
|
|
last_name,
|
|
flag: { uri: API_HOST + flag },
|
|
flag: { uri: API_HOST + flag },
|
|
@@ -1258,6 +1294,8 @@ const MapScreen: any = ({ navigation, route }: { navigation: any; route: any })
|
|
|
|
|
|
{nomads && showNomads && (
|
|
{nomads && showNomads && (
|
|
<MapLibreGL.ShapeSource
|
|
<MapLibreGL.ShapeSource
|
|
|
|
+ ref={shapeSourceRef}
|
|
|
|
+ tolerance={20}
|
|
id="nomads"
|
|
id="nomads"
|
|
shape={nomads}
|
|
shape={nomads}
|
|
onPress={async (event) => {
|
|
onPress={async (event) => {
|
|
@@ -1267,8 +1305,10 @@ const MapScreen: any = ({ navigation, route }: { navigation: any; route: any })
|
|
if (isCluster) {
|
|
if (isCluster) {
|
|
const clusterCoordinates = (feature.geometry as GeoJSON.Point).coordinates;
|
|
const clusterCoordinates = (feature.geometry as GeoJSON.Point).coordinates;
|
|
|
|
|
|
- const currentZoom = await mapRef.current?.getZoom();
|
|
|
|
- const newZoom = (currentZoom || 0) + 3;
|
|
|
|
|
|
+ const zoom = await shapeSourceRef.current?.getClusterExpansionZoom(
|
|
|
|
+ feature as turf.Feature
|
|
|
|
+ );
|
|
|
|
+ const newZoom = zoom ?? 2;
|
|
|
|
|
|
cameraRef.current?.setCamera({
|
|
cameraRef.current?.setCamera({
|
|
centerCoordinate: clusterCoordinates,
|
|
centerCoordinate: clusterCoordinates,
|
|
@@ -1282,11 +1322,12 @@ const MapScreen: any = ({ navigation, route }: { navigation: any; route: any })
|
|
}
|
|
}
|
|
}}
|
|
}}
|
|
cluster={true}
|
|
cluster={true}
|
|
- clusterRadius={20}
|
|
|
|
|
|
+ clusterRadius={50}
|
|
>
|
|
>
|
|
<MapLibreGL.CircleLayer
|
|
<MapLibreGL.CircleLayer
|
|
id="nomads_circle"
|
|
id="nomads_circle"
|
|
filter={['has', 'point_count']}
|
|
filter={['has', 'point_count']}
|
|
|
|
+ aboveLayerID={Platform.OS === 'android' ? 'place-continent' : undefined}
|
|
style={{
|
|
style={{
|
|
circleRadius: ['step', ['get', 'point_count'], 15, 10, 20, 30, 25],
|
|
circleRadius: ['step', ['get', 'point_count'], 15, 10, 20, 30, 25],
|
|
circleColor: 'rgba(255, 126, 0, 1)',
|
|
circleColor: 'rgba(255, 126, 0, 1)',
|
|
@@ -1298,6 +1339,7 @@ const MapScreen: any = ({ navigation, route }: { navigation: any; route: any })
|
|
<MapLibreGL.SymbolLayer
|
|
<MapLibreGL.SymbolLayer
|
|
id="nomads_count"
|
|
id="nomads_count"
|
|
filter={['has', 'point_count']}
|
|
filter={['has', 'point_count']}
|
|
|
|
+ aboveLayerID={Platform.OS === 'android' ? 'nomads_circle' : undefined}
|
|
style={{
|
|
style={{
|
|
textField: ['get', 'point_count'],
|
|
textField: ['get', 'point_count'],
|
|
textFont: ['Noto Sans Bold'],
|
|
textFont: ['Noto Sans Bold'],
|
|
@@ -1312,6 +1354,8 @@ const MapScreen: any = ({ navigation, route }: { navigation: any; route: any })
|
|
/>
|
|
/>
|
|
<MapLibreGL.SymbolLayer
|
|
<MapLibreGL.SymbolLayer
|
|
id="nomads_symbol"
|
|
id="nomads_symbol"
|
|
|
|
+ filter={['!', ['has', 'point_count']]}
|
|
|
|
+ aboveLayerID={Platform.OS === 'android' ? 'place-continent' : undefined}
|
|
style={{
|
|
style={{
|
|
iconImage: ['get', 'icon_key'],
|
|
iconImage: ['get', 'icon_key'],
|
|
iconSize: [
|
|
iconSize: [
|
|
@@ -1325,7 +1369,6 @@ const MapScreen: any = ({ navigation, route }: { navigation: any; route: any })
|
|
],
|
|
],
|
|
iconAllowOverlap: true
|
|
iconAllowOverlap: true
|
|
}}
|
|
}}
|
|
- filter={['!=', 'id', +userId]}
|
|
|
|
></MapLibreGL.SymbolLayer>
|
|
></MapLibreGL.SymbolLayer>
|
|
</MapLibreGL.ShapeSource>
|
|
</MapLibreGL.ShapeSource>
|
|
)}
|
|
)}
|
|
@@ -1560,9 +1603,17 @@ const MapScreen: any = ({ navigation, route }: { navigation: any; route: any })
|
|
type={'success'}
|
|
type={'success'}
|
|
isVisible={openSettingsVisible}
|
|
isVisible={openSettingsVisible}
|
|
onClose={() => setOpenSettingsVisible(false)}
|
|
onClose={() => setOpenSettingsVisible(false)}
|
|
- action={() =>
|
|
|
|
- Platform.OS === 'ios' ? Linking.openURL('app-settings:') : Linking.openSettings()
|
|
|
|
- }
|
|
|
|
|
|
+ action={async () => {
|
|
|
|
+ const isServicesEnabled = await Location.hasServicesEnabledAsync();
|
|
|
|
+
|
|
|
|
+ if (!isServicesEnabled) {
|
|
|
|
+ Platform.OS === 'ios'
|
|
|
|
+ ? Linking.openURL('app-settings:')
|
|
|
|
+ : Linking.sendIntent('android.settings.LOCATION_SOURCE_SETTINGS');
|
|
|
|
+ } else {
|
|
|
|
+ Platform.OS === 'ios' ? Linking.openURL('app-settings:') : Linking.openSettings();
|
|
|
|
+ }
|
|
|
|
+ }}
|
|
message="NomadMania app needs location permissions to function properly. Open settings?"
|
|
message="NomadMania app needs location permissions to function properly. Open settings?"
|
|
/>
|
|
/>
|
|
</SafeAreaView>
|
|
</SafeAreaView>
|