소스 검색

map && location fixes

Viktoriia 7 달 전
부모
커밋
16057dd889

+ 20 - 8
src/screens/InAppScreens/MapScreen/FilterModal/index.tsx

@@ -110,7 +110,9 @@ const FilterModal = ({
     const syncSettings = async () => {
       if (locationSettings) {
         let { status } = await Location.getForegroundPermissionsAsync();
-        setIsSharing(locationSettings.sharing !== 0 && status === 'granted');
+        const isServicesEnabled = await Location.hasServicesEnabledAsync();
+
+        setIsSharing(locationSettings.sharing !== 0 && status === 'granted' && isServicesEnabled);
       }
     };
 
@@ -495,10 +497,11 @@ const FilterModal = ({
 
   const handleGetLocation = async () => {
     let { status, canAskAgain } = await Location.getForegroundPermissionsAsync();
+    const isServicesEnabled = await Location.hasServicesEnabledAsync();
 
-    if (status === 'granted') {
+    if (status === 'granted' && isServicesEnabled) {
       getLocation();
-    } else if (!canAskAgain) {
+    } else if (!canAskAgain || !isServicesEnabled) {
       setOpenSettingsVisible(true);
     } else {
       setAskLocationVisible(true);
@@ -528,10 +531,11 @@ const FilterModal = ({
   const handleAcceptPermission = async () => {
     setAskLocationVisible(false);
     let { status, canAskAgain } = await Location.requestForegroundPermissionsAsync();
+    const isServicesEnabled = await Location.hasServicesEnabledAsync();
 
-    if (status === 'granted') {
+    if (status === 'granted' && isServicesEnabled) {
       getLocation();
-    } else if (!canAskAgain) {
+    } else if (!canAskAgain || !isServicesEnabled) {
       setOpenSettingsVisible(true);
     }
   };
@@ -623,9 +627,17 @@ const FilterModal = ({
           type={'success'}
           isVisible={openSettingsVisible}
           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?"
         />
       </View>

+ 13 - 13
src/screens/InAppScreens/MapScreen/UserItem/index.tsx

@@ -38,22 +38,22 @@ const UserItem = ({ marker }: { marker: any }) => {
     <>
       {Platform.OS === 'ios' ? (
         <MapLibreGL.PointAnnotation
-          id="selected_user_callout"
+          id={marker.id.toString()}
           coordinate={marker.coordinates}
           anchor={{ x: 0.5, y: 1 }}
         >
           <View style={styles.customView}>
             <View style={styles.calloutContainer}>
               <View style={[styles.calloutImageContainer, { borderColor: Colors.WHITE }]}>
-                <Image
-                  source={{ uri: marker.avatar.uri }}
-                  style={styles.userImage}
-                  resizeMode="contain"
-                />
+                <Image source={marker.avatar} style={styles.userImage} resizeMode="contain" />
               </View>
               <View style={styles.calloutTextContainer}>
                 <View style={{ flexDirection: 'row', alignItems: 'center', gap: 8 }}>
-                  <Image source={{ uri: marker.flag.uri }} style={styles.flag} resizeMode="cover" />
+                  <Image
+                    source={{ uri: marker.flag.uri, cache: 'force-cache' }}
+                    style={styles.flag}
+                    resizeMode="cover"
+                  />
                   <Text style={styles.seriesName}>
                     {marker.first_name + ' ' + marker.last_name}
                   </Text>
@@ -91,15 +91,15 @@ const UserItem = ({ marker }: { marker: any }) => {
           <View style={styles.customView}>
             <View style={styles.calloutContainer}>
               <View style={styles.calloutImageContainer}>
-                <Image
-                  source={{ uri: marker.avatar.uri }}
-                  style={styles.userImage}
-                  resizeMode="contain"
-                />
+                <Image source={marker.avatar} style={styles.userImage} resizeMode="contain" />
               </View>
               <View style={styles.calloutTextContainer}>
                 <View style={{ flexDirection: 'row', alignItems: 'center', gap: 8 }}>
-                  <Image source={{ uri: marker.flag.uri }} style={styles.flag} resizeMode="cover" />
+                  <Image
+                    source={{ uri: marker.flag.uri, cache: 'force-cache' }}
+                    style={styles.flag}
+                    resizeMode="cover"
+                  />
                   <Text style={styles.seriesName}>
                     {marker.first_name + ' ' + marker.last_name}
                   </Text>

+ 82 - 31
src/screens/InAppScreens/MapScreen/index.tsx

@@ -12,7 +12,7 @@ import {
 } from 'react-native';
 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 { SafeAreaView } from 'react-native-safe-area-context';
 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 * as turf from '@turf/turf';
 import * as Location from 'expo-location';
+import { Image as ExpoImage } from 'expo-image';
 
 import SearchIcon from 'assets/icons/search.svg';
 import LocationIcon from 'assets/icons/location.svg';
@@ -375,14 +376,14 @@ const MapScreen: any = ({ navigation, route }: { navigation: any; route: any })
 
   useEffect(() => {
     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]);
 
@@ -390,13 +391,19 @@ const MapScreen: any = ({ navigation, route }: { navigation: any; route: any })
     if (seriesIcons) {
       let loadedSeriesImages: any = {};
 
-      seriesIcons.data.forEach((icon) => {
+      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) {
           loadedSeriesImages[id] = { uri: img };
           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(() => {
     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) {
             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]);
 
@@ -560,7 +586,14 @@ const MapScreen: any = ({ navigation, route }: { navigation: any; route: any })
   useEffect(() => {
     (async () => {
       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);
         storage.set('showNomads', false);
         setNomads(null);
@@ -615,6 +648,7 @@ const MapScreen: any = ({ navigation, route }: { navigation: any; route: any })
 
   const mapRef = useRef<MapViewRef>(null);
   const cameraRef = useRef<CameraRef>(null);
+  const shapeSourceRef = useRef<ShapeSourceRef>(null);
 
   useEffect(() => {
     if (userInfo) {
@@ -809,10 +843,11 @@ const MapScreen: any = ({ navigation, route }: { navigation: any; route: any })
     setIsLocationLoading(true);
     try {
       let { status, canAskAgain } = await Location.getForegroundPermissionsAsync();
+      const isServicesEnabled = await Location.hasServicesEnabledAsync();
 
-      if (status === 'granted') {
+      if (status === 'granted' && isServicesEnabled) {
         await getLocation();
-      } else if (!canAskAgain) {
+      } else if (!canAskAgain || !isServicesEnabled) {
         setOpenSettingsVisible(true);
       } else {
         setAskLocationVisible(true);
@@ -855,10 +890,11 @@ const MapScreen: any = ({ navigation, route }: { navigation: any; route: any })
   const handleAcceptPermission = async () => {
     setAskLocationVisible(false);
     let { status, canAskAgain } = await Location.requestForegroundPermissionsAsync();
+    const isServicesEnabled = await Location.hasServicesEnabledAsync();
 
-    if (status === 'granted') {
+    if (status === 'granted' && isServicesEnabled) {
       getLocation();
-    } else if (!canAskAgain) {
+    } else if (!canAskAgain || !isServicesEnabled) {
       setOpenSettingsVisible(true);
     }
   };
@@ -1064,7 +1100,7 @@ const MapScreen: any = ({ navigation, route }: { navigation: any; route: any })
     if (selectedFeature) {
       setSelectedUser({
         coordinates,
-        avatar: { uri: API_HOST + avatar },
+        avatar: avatar ? { uri: API_HOST + avatar } : require('assets/logo-ua.png'),
         first_name,
         last_name,
         flag: { uri: API_HOST + flag },
@@ -1258,6 +1294,8 @@ const MapScreen: any = ({ navigation, route }: { navigation: any; route: any })
 
         {nomads && showNomads && (
           <MapLibreGL.ShapeSource
+            ref={shapeSourceRef}
+            tolerance={20}
             id="nomads"
             shape={nomads}
             onPress={async (event) => {
@@ -1267,8 +1305,10 @@ const MapScreen: any = ({ navigation, route }: { navigation: any; route: any })
               if (isCluster) {
                 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({
                   centerCoordinate: clusterCoordinates,
@@ -1282,11 +1322,12 @@ const MapScreen: any = ({ navigation, route }: { navigation: any; route: any })
               }
             }}
             cluster={true}
-            clusterRadius={20}
+            clusterRadius={50}
           >
             <MapLibreGL.CircleLayer
               id="nomads_circle"
               filter={['has', 'point_count']}
+              aboveLayerID={Platform.OS === 'android' ? 'place-continent' : undefined}
               style={{
                 circleRadius: ['step', ['get', 'point_count'], 15, 10, 20, 30, 25],
                 circleColor: 'rgba(255, 126, 0, 1)',
@@ -1298,6 +1339,7 @@ const MapScreen: any = ({ navigation, route }: { navigation: any; route: any })
             <MapLibreGL.SymbolLayer
               id="nomads_count"
               filter={['has', 'point_count']}
+              aboveLayerID={Platform.OS === 'android' ? 'nomads_circle' : undefined}
               style={{
                 textField: ['get', 'point_count'],
                 textFont: ['Noto Sans Bold'],
@@ -1312,6 +1354,8 @@ const MapScreen: any = ({ navigation, route }: { navigation: any; route: any })
             />
             <MapLibreGL.SymbolLayer
               id="nomads_symbol"
+              filter={['!', ['has', 'point_count']]}
+              aboveLayerID={Platform.OS === 'android' ? 'place-continent' : undefined}
               style={{
                 iconImage: ['get', 'icon_key'],
                 iconSize: [
@@ -1325,7 +1369,6 @@ const MapScreen: any = ({ navigation, route }: { navigation: any; route: any })
                 ],
                 iconAllowOverlap: true
               }}
-              filter={['!=', 'id', +userId]}
             ></MapLibreGL.SymbolLayer>
           </MapLibreGL.ShapeSource>
         )}
@@ -1560,9 +1603,17 @@ const MapScreen: any = ({ navigation, route }: { navigation: any; route: any })
         type={'success'}
         isVisible={openSettingsVisible}
         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?"
       />
     </SafeAreaView>

+ 17 - 7
src/screens/InAppScreens/ProfileScreen/UsersMap/index.tsx

@@ -213,10 +213,11 @@ const UsersMapScreen: FC<Props> = ({ navigation, route }) => {
     setIsLocationLoading(true);
     try {
       let { status, canAskAgain } = await Location.getForegroundPermissionsAsync();
+      const isServicesEnabled = await Location.hasServicesEnabledAsync();
 
-      if (status === 'granted') {
+      if (status === 'granted' && isServicesEnabled) {
         await getLocation();
-      } else if (!canAskAgain) {
+      } else if (!canAskAgain || !isServicesEnabled) {
         setOpenSettingsVisible(true);
       } else {
         setAskLocationVisible(true);
@@ -243,10 +244,11 @@ const UsersMapScreen: FC<Props> = ({ navigation, route }) => {
   const handleAcceptPermission = async () => {
     setAskLocationVisible(false);
     let { status, canAskAgain } = await Location.requestForegroundPermissionsAsync();
+    const isServicesEnabled = await Location.hasServicesEnabledAsync();
 
-    if (status === 'granted') {
+    if (status === 'granted' && isServicesEnabled) {
       getLocation();
-    } else if (!canAskAgain) {
+    } else if (!canAskAgain || !isServicesEnabled) {
       setOpenSettingsVisible(true);
     }
   };
@@ -507,9 +509,17 @@ const UsersMapScreen: FC<Props> = ({ navigation, route }) => {
       <LocationPopup
         visible={openSettingsVisible}
         onClose={() => setOpenSettingsVisible(false)}
-        onAccept={() =>
-          Platform.OS === 'ios' ? Linking.openURL('app-settings:') : Linking.openSettings()
-        }
+        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?"
       />
       <SearchModal

+ 25 - 9
src/screens/LocationSharingScreen/index.tsx

@@ -42,7 +42,11 @@ const LocationSharingScreen = ({ navigation }: { navigation: any }) => {
     const syncSettings = async () => {
       if (locationSettings) {
         let { status } = await Location.getForegroundPermissionsAsync();
-        setIsSharingWithEveryone(locationSettings.sharing !== 0 && status === 'granted');
+        const isServicesEnabled = await Location.hasServicesEnabledAsync();
+
+        setIsSharingWithEveryone(
+          locationSettings.sharing !== 0 && status === 'granted' && isServicesEnabled
+        );
       }
     };
 
@@ -75,7 +79,9 @@ const LocationSharingScreen = ({ navigation }: { navigation: any }) => {
   useEffect(() => {
     const getInitialPermissionsStatus = async () => {
       const status = await checkLocationPermissions();
-      if (status !== 'granted' && isSharingWithEveryone) {
+      const isServicesEnabled = await Location.hasServicesEnabledAsync();
+
+      if ((status !== 'granted' && isSharingWithEveryone) || !isServicesEnabled) {
         setSettings({ token, sharing: 0 });
         storage.set('showNomads', false);
         setIsSharingWithEveryone(false);
@@ -111,10 +117,11 @@ const LocationSharingScreen = ({ navigation }: { navigation: any }) => {
 
   const handleGetLocation = async () => {
     let { status, canAskAgain } = await Location.getForegroundPermissionsAsync();
+    const isServicesEnabled = await Location.hasServicesEnabledAsync();
 
-    if (status === 'granted') {
+    if (status === 'granted' && isServicesEnabled) {
       getLocation();
-    } else if (!canAskAgain) {
+    } else if (!canAskAgain || !isServicesEnabled) {
       setOpenSettingsVisible(true);
     } else {
       setAskLocationVisible(true);
@@ -137,10 +144,11 @@ const LocationSharingScreen = ({ navigation }: { navigation: any }) => {
   const handleAcceptPermission = async () => {
     setAskLocationVisible(false);
     let { status, canAskAgain } = await Location.requestForegroundPermissionsAsync();
+    const isServicesEnabled = await Location.hasServicesEnabledAsync();
 
-    if (status === 'granted') {
+    if (status === 'granted' && isServicesEnabled) {
       getLocation();
-    } else if (!canAskAgain) {
+    } else if (!canAskAgain || !isServicesEnabled) {
       setOpenSettingsVisible(true);
     }
   };
@@ -195,9 +203,17 @@ const LocationSharingScreen = ({ navigation }: { navigation: any }) => {
         type={'success'}
         isVisible={openSettingsVisible}
         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?"
       />
     </PageWrapper>