소스 검색

filter buttons map screen

Viktoriia 7 달 전
부모
커밋
2c933ef959

+ 49 - 0
src/components/MapButton/index.tsx

@@ -0,0 +1,49 @@
+import React from 'react';
+import { TouchableOpacity, Text } from 'react-native';
+import { SvgProps } from 'react-native-svg';
+import { Colors } from 'src/theme';
+
+interface MapButtonProps {
+  onPress: () => void;
+  icon: React.ComponentType<SvgProps> | null;
+  text: string;
+}
+
+const MapButton: React.FC<MapButtonProps> = ({ onPress, icon: Icon, text }) => {
+  return (
+    <TouchableOpacity
+      style={{
+        flexDirection: 'row',
+        alignItems: 'center',
+        backgroundColor: Colors.WHITE,
+        borderRadius: 17,
+        paddingHorizontal: 10,
+        paddingVertical: 6,
+        gap: 4,
+        shadowColor: '#000',
+        shadowOffset: {
+          width: 0,
+          height: 1
+        },
+        shadowOpacity: 0.25,
+        shadowRadius: 1.5,
+        elevation: 2,
+        marginVertical: 4
+      }}
+      onPress={onPress}
+    >
+      {Icon && <Icon fill={Colors.DARK_BLUE} width={16} height={16} />}
+      <Text
+        style={{
+          color: Colors.DARK_BLUE,
+          fontSize: 12,
+          fontWeight: '600'
+        }}
+      >
+        {text}
+      </Text>
+    </TouchableOpacity>
+  );
+};
+
+export default MapButton;

+ 1 - 0
src/components/index.ts

@@ -20,3 +20,4 @@ export * from './MenuDrawer';
 export * from './ErrorModal';
 export * from './BlinkingDot';
 export * from './MessagesDot';
+export * from './MapButton';

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

@@ -16,7 +16,6 @@ import { Dropdown, MultiSelect } from 'react-native-searchable-dropdown-kj';
 import { Button, WarningModal } from 'src/components';
 import { ButtonVariants } from 'src/types/components';
 import { styles } from './styles';
-import { TabBar, TabView } from 'react-native-tab-view';
 import { usePostGetMapYearsQuery } from '@api/user';
 import { API_HOST } from 'src/constants';
 import CheckSvg from 'assets/icons/mark.svg';
@@ -26,7 +25,6 @@ import { storage, StoreType } from 'src/storage';
 import moment from 'moment';
 import {
   usePostGetSettingsQuery,
-  usePostIsFeatureActiveQuery,
   usePostSetSettingsMutation,
   usePostUpdateLocationMutation
 } from '@api/location';
@@ -51,8 +49,8 @@ const FilterModal = ({
   showNomads,
   setShowNomads
 }: {
-  isFilterVisible: boolean;
-  setIsFilterVisible: (isVisible: boolean) => void;
+  isFilterVisible: string | null;
+  setIsFilterVisible: (filterType: string | null) => void;
   tilesTypes: any[];
   tilesType: any;
   setTilesType: (item: any) => void;
@@ -69,7 +67,6 @@ const FilterModal = ({
   const { data: locationSettings } = usePostGetSettingsQuery(token, isLogged && !isPublicView);
   const { mutateAsync: setSettings } = usePostSetSettingsMutation();
   const { mutateAsync: updateLocation } = usePostUpdateLocationMutation();
-  const [index, setIndex] = useState(0);
   const [selectedYear, setSelectedYear] = useState<{ label: string; value: number } | null>(null);
   const [allYears, setAllYears] = useState<{ label: string; value: number }[]>([]);
   const [selectedVisible, setSelectedVisible] = useState({ label: 'visited by', value: 0 });
@@ -77,11 +74,6 @@ const FilterModal = ({
     { label: 'visited by', value: 0 },
     { label: 'visited in', value: 1 }
   ];
-  const { data: isFeatureActive } = usePostIsFeatureActiveQuery(token, !!token);
-  const [routes, setRoutes] = useState([
-    { key: 'regions', title: 'Travels' },
-    { key: 'series', title: 'Series' }
-  ]);
   const { data } = usePostGetMapYearsQuery(token as string, userId, isLogged ? true : false);
   const { data: seriesList } = useGetListQuery(true);
   const [series, setSeries] = useState<{ label: string; value: number }[]>([]);
@@ -96,16 +88,6 @@ const FilterModal = ({
   const [askLocationVisible, setAskLocationVisible] = useState<boolean>(false);
   const [openSettingsVisible, setOpenSettingsVisible] = useState<boolean>(false);
 
-  useEffect(() => {
-    if (isFeatureActive && isFeatureActive.active) {
-      setRoutes([
-        { key: 'regions', title: 'Travels' },
-        { key: 'series', title: 'Series' },
-        { key: 'nomads', title: 'Nomads' }
-      ]);
-    }
-  }, [isFeatureActive]);
-
   useEffect(() => {
     const syncSettings = async () => {
       if (locationSettings) {
@@ -222,8 +204,7 @@ const FilterModal = ({
   };
 
   const handleCloseFilter = () => {
-    setIndex(0);
-    setIsFilterVisible(false);
+    setIsFilterVisible(null);
   };
 
   const renderRegions = () => {
@@ -644,8 +625,8 @@ const FilterModal = ({
     );
   };
 
-  const renderScene = ({ route }: { route: any }) => {
-    switch (route.key) {
+  const renderScene = (filterKey: string | null) => {
+    switch (filterKey) {
       case 'regions':
         return renderRegions();
       case 'series':
@@ -661,46 +642,15 @@ const FilterModal = ({
 
   return (
     <ReactModal
-      isVisible={isFilterVisible}
+      isVisible={!!isFilterVisible}
       onBackdropPress={handleCloseFilter}
       onBackButtonPress={handleCloseFilter}
       style={styles.modal}
       statusBarTranslucent={true}
       presentationStyle="overFullScreen"
     >
-      <View style={[styles.modalContainer, { height: 400 }]}>
-        <TabView
-          navigationState={{ index, routes }}
-          renderScene={renderScene}
-          onIndexChange={setIndex}
-          lazy={true}
-          swipeEnabled={!isPublicView}
-          renderTabBar={(props) =>
-            !isPublicView ? (
-              <TabBar
-                {...props}
-                indicatorStyle={{ backgroundColor: Colors.DARK_BLUE }}
-                style={styles.tabBar}
-                tabStyle={styles.tabStyle}
-                pressColor={'transparent'}
-                renderLabel={({ route, focused }) => (
-                  <Text
-                    style={[
-                      styles.tabLabel,
-                      {
-                        color: Colors.DARK_BLUE,
-                        opacity: focused ? 1 : 0.4,
-                        fontSize: isSmallScreen ? 13 : 14
-                      }
-                    ]}
-                  >
-                    {route.title}
-                  </Text>
-                )}
-              />
-            ) : null
-          }
-        />
+      <View style={[styles.modalContainer, { height: 360 }]}>
+        {renderScene(isFilterVisible)}
       </View>
     </ReactModal>
   );

+ 1 - 1
src/screens/InAppScreens/MapScreen/FilterModal/styles.tsx

@@ -31,7 +31,7 @@ export const styles = StyleSheet.create({
     borderRadius: 15,
     paddingHorizontal: 16,
     gap: 12,
-    paddingTop: 16
+    paddingTop: 12
   },
   dropdownWrapper: { gap: 4, flex: 1 },
   textSmall: {

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

@@ -8,7 +8,8 @@ import {
   View,
   Image,
   StatusBar,
-  ActivityIndicator
+  ActivityIndicator,
+  ScrollView
 } from 'react-native';
 import React, { useEffect, useRef, useState, useCallback } from 'react';
 
@@ -69,11 +70,18 @@ import MarkerItem from './MarkerItem';
 import {
   usePostGetSettingsQuery,
   usePostGetUsersLocationQuery,
+  usePostIsFeatureActiveQuery,
   usePostUpdateLocationMutation
 } from '@api/location';
 import UserItem from './UserItem';
 import { useConnection } from 'src/contexts/ConnectionContext';
 
+import TravelsIcon from 'assets/icons/bottom-navigation/globe-solid.svg';
+import SeriesIcon from 'assets/icons/travels-section/series.svg';
+import NomadsIcon from 'assets/icons/bottom-navigation/travellers.svg';
+import { useBottomTabBarHeight } from '@react-navigation/bottom-tabs';
+import MapButton from 'src/components/MapButton';
+
 const defaultUserAvatar = require('assets/icon-user-share-location-solid.png');
 const logo = require('assets/logo-ua.png');
 const defaultSeriesIcon = require('assets/series-default.png');
@@ -254,6 +262,7 @@ const INITIAL_REGION = {
 };
 
 const MapScreen: any = ({ navigation, route }: { navigation: any; route: any }) => {
+  const tabBarHeight = useBottomTabBarHeight();
   const userId = storage.get('uid', StoreType.STRING) as string;
   const token = storage.get('token', StoreType.STRING) as string;
 
@@ -284,7 +293,10 @@ const MapScreen: any = ({ navigation, route }: { navigation: any; route: any })
   const [showNomads, setShowNomads] = useState(
     (storage.get('showNomads', StoreType.BOOLEAN) as boolean) ?? false
   );
-  const { data: locationSettings, refetch } = usePostGetSettingsQuery(token, !!token && isConnected);
+  const { data: locationSettings, refetch } = usePostGetSettingsQuery(
+    token,
+    !!token && isConnected
+  );
   const { mutateAsync: updateLocation } = usePostUpdateLocationMutation();
   const { data: visitedRegionIds, refetch: refetchVisitedRegions } =
     usePostGetVisitedRegionsIdsQuery(
@@ -307,8 +319,12 @@ const MapScreen: any = ({ navigation, route }: { navigation: any; route: any })
     +userId,
     type === 'dare' && !!userId && isConnected
   );
-  const { data: visitedSeriesIds } = usePostGetVisitedSeriesIdsQuery(token, !!userId && isConnected);
+  const { data: visitedSeriesIds } = usePostGetVisitedSeriesIdsQuery(
+    token,
+    !!userId && isConnected
+  );
   const { data: seriesIcons } = useGetIconsQuery(isConnected);
+  const { data: isFeatureActive } = usePostIsFeatureActiveQuery(token, !!token && isConnected);
   const userInfo = storage.get('currentUserData', StoreType.STRING) as string;
   const { mutateAsync: mutateUserData } = fetchUserData();
   const { mutateAsync: mutateUserDataDare } = fetchUserDataDare();
@@ -328,7 +344,7 @@ const MapScreen: any = ({ navigation, route }: { navigation: any; route: any })
   const [isWarningModalVisible, setIsWarningModalVisible] = useState<boolean>(false);
   const [isEditSlowModalVisible, setIsEditSlowModalVisible] = useState<boolean>(false);
   const [isEditModalVisible, setIsEditModalVisible] = useState(false);
-  const [isFilterVisible, setIsFilterVisible] = useState(false);
+  const [isFilterVisible, setIsFilterVisible] = useState<string | null>(null);
   const [isLocationLoading, setIsLocationLoading] = useState(false);
 
   const [modalState, setModalState] = useState({
@@ -390,7 +406,7 @@ const MapScreen: any = ({ navigation, route }: { navigation: any; route: any })
   }, [showNomads]);
 
   useEffect(() => {
-    if (usersLocation) {
+    if (usersLocation && showNomads) {
       const filteredNomads: GeoJSON.FeatureCollection = {
         type: 'FeatureCollection',
         features: usersLocation.geojson.features.filter(
@@ -400,14 +416,14 @@ const MapScreen: any = ({ navigation, route }: { navigation: any; route: any })
 
       setNomads(filteredNomads);
     }
-  }, [usersLocation]);
+  }, [usersLocation, showNomads]);
 
   useEffect(() => {
     if (seriesIcons) {
       const loadImages = async () => {
         const loadedSeriesImages: Record<string, { uri: string }> = {};
         const prefetchUrls: string[] = [];
-  
+
         const promises = seriesIcons.data.map(async (icon) => {
           const id = icon.id?.toString();
           const img = `${API_HOST}/static/img/series_new2_small/${icon.new_icon_png}`;
@@ -419,7 +435,6 @@ const MapScreen: any = ({ navigation, route }: { navigation: any; route: any })
             !processedImages.current.has(id) &&
             !processedImages.current.has(`${id}v`)
           ) {
-
             processedImages.current.add(id);
             processedImages.current.add(`${id}v`);
 
@@ -442,7 +457,7 @@ const MapScreen: any = ({ navigation, route }: { navigation: any; route: any })
           ExpoImage.prefetch(prefetchUrls);
         }
       };
-  
+
       loadImages();
     }
   }, [seriesIcons]);
@@ -1125,17 +1140,20 @@ const MapScreen: any = ({ navigation, route }: { navigation: any; route: any })
         onPress={onMapPress}
         onRegionDidChange={handleRegionDidChange}
       >
-        <MapLibreGL.Images images={images} onImageMissing={(image) => {
-          if (processedImages.current.has(image)) {
-            return;
-          }
-      
-          processedImages.current.add(image);
-          setImages((prevImages: any) => ({
-            ...prevImages,
-            [image]: defaultSeriesIcon,
-          }));
-        }}>
+        <MapLibreGL.Images
+          images={images}
+          onImageMissing={(image) => {
+            if (processedImages.current.has(image)) {
+              return;
+            }
+
+            processedImages.current.add(image);
+            setImages((prevImages: any) => ({
+              ...prevImages,
+              [image]: defaultSeriesIcon
+            }));
+          }}
+        >
           <View />
         </MapLibreGL.Images>
 
@@ -1441,7 +1459,12 @@ const MapScreen: any = ({ navigation, route }: { navigation: any; route: any })
 
           <TouchableOpacity
             onPress={handleGetLocation}
-            style={[styles.cornerButton, styles.topRightButton, styles.bottomButton]}
+            style={[
+              styles.cornerButton,
+              styles.topRightButton,
+              styles.bottomButton,
+              { bottom: tabBarHeight + 20 }
+            ]}
           >
             {isLocationLoading ? (
               <ActivityIndicator size="small" color={Colors.DARK_BLUE} />
@@ -1556,19 +1579,49 @@ const MapScreen: any = ({ navigation, route }: { navigation: any; route: any })
             )}
           </Animated.View>
 
-          <TouchableOpacity
-            style={[styles.cornerButton, styles.bottomButton, styles.bottomLeftButton]}
-            onPress={() => {
-              setIsFilterVisible(true);
-              closeCallout();
-            }}
-          >
-            <FilterIcon />
-          </TouchableOpacity>
+          <View style={[styles.tabs, { bottom: tabBarHeight + 20 }]}>
+            <ScrollView
+              horizontal
+              showsHorizontalScrollIndicator={false}
+              contentContainerStyle={{ paddingHorizontal: 12, gap: 12, flexDirection: 'row' }}
+            >
+              <MapButton
+                onPress={() => {
+                  setIsFilterVisible('regions');
+                  closeCallout();
+                }}
+                icon={TravelsIcon}
+                text="Travels"
+              />
+              <MapButton
+                onPress={() => {
+                  setIsFilterVisible('series');
+                  closeCallout();
+                }}
+                icon={SeriesIcon}
+                text="Series"
+              />
+              {isFeatureActive && isFeatureActive.active ? (
+                <MapButton
+                  onPress={() => {
+                    setIsFilterVisible('nomads');
+                    closeCallout();
+                  }}
+                  icon={NomadsIcon}
+                  text="Nomads"
+                />
+              ) : null}
+            </ScrollView>
+          </View>
 
           <TouchableOpacity
             onPress={handleGetLocation}
-            style={[styles.cornerButton, styles.bottomButton, styles.bottomRightButton]}
+            style={[
+              styles.cornerButton,
+              styles.bottomButton,
+              styles.bottomRightButton,
+              { bottom: tabBarHeight + 20 }
+            ]}
           >
             {isLocationLoading ? (
               <ActivityIndicator size="small" color={Colors.DARK_BLUE} />

+ 18 - 6
src/screens/InAppScreens/MapScreen/style.tsx

@@ -44,10 +44,14 @@ export const styles = StyleSheet.create({
     borderRadius: 24,
     alignItems: 'center',
     justifyContent: 'center',
-    shadowColor: 'rgba(33, 37, 41, 0.12)',
-    shadowOffset: { width: 0, height: 4 },
-    shadowRadius: 8,
-    elevation: 5
+    shadowColor: '#000',
+    shadowOffset: {
+      width: 0,
+      height: 1,
+    },
+    shadowOpacity: 0.25,
+    shadowRadius: 1.5,
+    elevation: 2,
   },
   topLeftButton: {
     top: 52,
@@ -72,8 +76,7 @@ export const styles = StyleSheet.create({
   },
   searchContainer: {
     flexDirection: 'row',
-    alignItems: 'center',
-    overflow: 'hidden'
+    alignItems: 'center'
   },
   iconButton: {
     padding: 10
@@ -150,5 +153,14 @@ export const styles = StyleSheet.create({
     shadowOpacity: 0.12,
     shadowRadius: 8,
     elevation: 5
+  },
+  tabs: {
+    position: 'absolute',
+    padding: 3,
+    display: 'flex',
+    alignItems: 'flex-end',
+    justifyContent: 'flex-start',
+    flexDirection: 'row',
+    paddingRight: 55
   }
 });

+ 22 - 9
src/screens/InAppScreens/ProfileScreen/UsersMap/index.tsx

@@ -6,7 +6,9 @@ import {
   TextInput,
   Dimensions,
   StatusBar,
-  ActivityIndicator
+  ActivityIndicator,
+  ScrollView,
+  View
 } from 'react-native';
 import React, { FC, useEffect, useRef, useState } from 'react';
 import * as Location from 'expo-location';
@@ -24,7 +26,6 @@ import { AvatarWithInitials, LocationPopup } from 'src/components';
 import { Colors } from 'src/theme';
 
 import CloseSvg from 'assets/icons/close.svg';
-import FilterIcon from 'assets/icons/filter.svg';
 import LocationIcon from 'assets/icons/location.svg';
 import SearchIcon from 'assets/icons/search.svg';
 
@@ -42,6 +43,9 @@ import {
 } from '@api/maps';
 import moment from 'moment';
 
+import TravelsIcon from 'assets/icons/bottom-navigation/globe-solid.svg';
+import MapButton from 'src/components/MapButton';
+
 MapLibreGL.setAccessToken(null);
 
 const generateFilter = (ids: number[]) => {
@@ -150,7 +154,7 @@ const UsersMapScreen: FC<Props> = ({ navigation, route }) => {
 
   const mapRef = useRef<MapViewRef>(null);
   const cameraRef = useRef<CameraRef>(null);
-  const [isFilterVisible, setIsFilterVisible] = useState(false);
+  const [isFilterVisible, setIsFilterVisible] = useState<string | null>(null);
   const [tilesType, setTilesType] = useState({ label: 'NM regions', value: 0 });
   const tilesTypes = [
     { label: 'NM regions', value: 0 },
@@ -439,12 +443,21 @@ const UsersMapScreen: FC<Props> = ({ navigation, route }) => {
         </TouchableOpacity>
       ) : null}
 
-      <TouchableOpacity
-        style={[styles.cornerButton, styles.bottomButton, styles.bottomLeftButton]}
-        onPress={() => setIsFilterVisible(true)}
-      >
-        <FilterIcon />
-      </TouchableOpacity>
+      <View style={styles.tabs}>
+        <ScrollView
+          horizontal
+          showsHorizontalScrollIndicator={false}
+          contentContainerStyle={{ marginLeft: 12, gap: 12, flexDirection: 'row' }}
+        >
+          <MapButton
+            onPress={() => {
+              setIsFilterVisible('regions');
+            }}
+            icon={TravelsIcon}
+            text="Travels"
+          />
+        </ScrollView>
+      </View>
 
       <TouchableOpacity
         onPress={handleGetLocation}

+ 21 - 20
src/screens/InAppScreens/ProfileScreen/UsersMap/styles.tsx

@@ -19,10 +19,14 @@ export const styles = StyleSheet.create({
     borderRadius: 24,
     alignItems: 'center',
     justifyContent: 'center',
-    shadowColor: 'rgba(33, 37, 41, 0.12)',
-    shadowOffset: { width: 0, height: 4 },
-    shadowRadius: 8,
-    elevation: 5
+    shadowColor: '#000',
+    shadowOffset: {
+      width: 0,
+      height: 1
+    },
+    shadowOpacity: 0.25,
+    shadowRadius: 1.5,
+    elevation: 2
   },
   topLeftButton: {
     top: 52,
@@ -49,7 +53,7 @@ export const styles = StyleSheet.create({
     right: 16
   },
   bottomButton: {
-    bottom: 22,
+    bottom: 20,
     width: 42,
     height: 42,
     borderRadius: 21
@@ -72,23 +76,11 @@ export const styles = StyleSheet.create({
     height: 18,
     borderRadius: 9,
     borderColor: 'white',
-    backgroundColor: '#ED9334',
-    scale: 1,
-    shadow: {
-      shadowColor: '#212529',
-      shadowOffset: {
-        width: 0,
-        height: 4
-      },
-      shadowOpacity: 0.12,
-      shadowRadius: 8,
-      elevation: 5
-    }
+    backgroundColor: '#ED9334'
   },
   searchContainer: {
     flexDirection: 'row',
-    alignItems: 'center',
-    overflow: 'hidden'
+    alignItems: 'center'
   },
   iconButton: {
     padding: 10
@@ -98,5 +90,14 @@ export const styles = StyleSheet.create({
     height: 40,
     color: Colors.DARK_BLUE,
     fontWeight: '600'
-  }
+  },
+  tabs: {
+    position: 'absolute',
+    padding: 3,
+    bottom: 20,
+    display: 'flex',
+    alignItems: 'flex-end',
+    justifyContent: 'flex-start',
+    flexDirection: 'row'
+  },
 });