Browse Source

Merge branch 'search' of SashaGoncharov19/nomadmania-app into dev

Viktoriia 1 year ago
parent
commit
fcd39814ea

+ 18 - 1
Route.tsx

@@ -177,7 +177,24 @@ const Route = () => {
       <ScreenStack.Screen name={NAVIGATION_PAGES.IN_APP}>
         {() => (
           <BottomTab.Navigator screenOptions={screenOptions}>
-            <BottomTab.Screen name={NAVIGATION_PAGES.MAP_TAB} component={MapDrawerNavigator} />
+            <BottomTab.Screen name={NAVIGATION_PAGES.IN_APP_MAP_TAB}>
+              {() => (
+                <ScreenStack.Navigator screenOptions={screenOptions}>
+                  <ScreenStack.Screen
+                    name={NAVIGATION_PAGES.MAP_TAB}
+                    component={MapDrawerNavigator}
+                  />
+                  <ScreenStack.Screen
+                    name={NAVIGATION_PAGES.PUBLIC_PROFILE_VIEW}
+                    component={ProfileScreen}
+                  />
+                  <ScreenStack.Screen
+                    name={NAVIGATION_PAGES.USERS_MAP}
+                    component={UsersMapScreen}
+                  />
+                </ScreenStack.Navigator>
+              )}
+            </BottomTab.Screen>
             <BottomTab.Screen name={NAVIGATION_PAGES.IN_APP_TRAVELLERS_TAB}>
               {() => (
                 <ScreenStack.Navigator screenOptions={screenOptions}>

+ 1 - 1
src/components/TabBarButton/index.tsx

@@ -13,7 +13,7 @@ import { styles } from './style';
 
 const getTabIcon = (routeName: string) => {
   switch (routeName) {
-    case NAVIGATION_PAGES.MAP_TAB:
+    case NAVIGATION_PAGES.IN_APP_MAP_TAB:
       return MapIcon;
     case NAVIGATION_PAGES.IN_APP_TRAVELLERS_TAB:
       return TravellersIcon;

+ 1 - 1
src/components/TabBarButton/style.tsx

@@ -6,7 +6,7 @@ export const styles = StyleSheet.create({
     alignItems: 'center',
     justifyContent: 'center',
     overflow: 'hidden',
-    marginTop: Platform.OS === 'ios' ? 8 : 0,
+    marginTop: Platform.OS === 'ios' ? 4 : 0,
   },
   labelStyle: {
     marginTop: 4,

+ 3 - 0
src/modules/api/search/index.ts

@@ -0,0 +1,3 @@
+export * from './queries';
+export * from './search-api';
+export * from './search-query-keys';

+ 1 - 0
src/modules/api/search/queries/index.ts

@@ -0,0 +1 @@
+export * from './use-post-universal';

+ 16 - 0
src/modules/api/search/queries/use-post-universal.tsx

@@ -0,0 +1,16 @@
+import { useQuery } from '@tanstack/react-query';
+
+import { searchQueryKeys } from '../search-query-keys';
+import { searchApi, type PostGetUniversalReturn } from '../search-api';
+
+import type { BaseAxiosError } from '../../../../types';
+
+export const useGetUniversalSearch = (search: string) => {
+  return useQuery<PostGetUniversalReturn, BaseAxiosError>({
+    queryKey: searchQueryKeys.getUniversal(search),
+    queryFn: async () => {
+      const response = await searchApi.getUniversal(search);
+      return response.data;
+    }
+  });
+};

+ 30 - 0
src/modules/api/search/search-api.ts

@@ -0,0 +1,30 @@
+import { request } from '../../../utils';
+import { API } from '../../../types';
+import { ResponseType } from '../response-type';
+
+export interface PostGetUniversalReturn extends ResponseType {
+  users: {
+    id: number;
+    avatar: string | null;
+    flag1: string;
+    flag2: string | null;
+    name: string;
+  }[];
+  regions: {
+    id: number;
+    name: string;
+    flag1: string;
+    flag2: string | null;
+  }[];
+  dare: {
+    id: number;
+    name: string;
+    flag1: string;
+    flag2: string | null;
+  }[];
+}
+
+export const searchApi = {
+  getUniversal: (search: string) =>
+    request.postForm<PostGetUniversalReturn>(API.GET_UNIVERSAL, { search })
+};

+ 3 - 0
src/modules/api/search/search-query-keys.tsx

@@ -0,0 +1,3 @@
+export const searchQueryKeys = {
+  getUniversal: (search: string) => ['getLastRegionsUpdate', search] as const,
+};

+ 172 - 0
src/screens/InAppScreens/MapScreen/UniversalSearch/index.tsx

@@ -0,0 +1,172 @@
+import React, { useState } from 'react';
+import { View, Text, TouchableOpacity, Image } from 'react-native';
+import { TabView, TabBar } from 'react-native-tab-view';
+import ReactModal from 'react-native-modal';
+import { FlashList } from '@shopify/flash-list';
+import { useNavigation } from '@react-navigation/native';
+
+import { Colors } from 'src/theme';
+import { styles } from './styles';
+import { NAVIGATION_PAGES } from 'src/types';
+import { API_HOST } from 'src/constants';
+import { Loading } from 'src/components';
+
+const SearchModal = ({
+  searchVisible,
+  handleCloseModal,
+  handleFindRegion,
+  index,
+  searchData,
+  setIndex
+}: {
+  searchVisible: boolean;
+  handleCloseModal: () => void;
+  handleFindRegion: (id: number, type: string) => void;
+  index: number;
+  searchData: any;
+  setIndex: (index: number) => void;
+}) => {
+  const navigation = useNavigation();
+  const [routes] = useState([
+    { key: 'users', title: 'Nomads' },
+    { key: 'regions', title: 'Regions' },
+    { key: 'dare', title: 'Places' }
+  ]);
+  const [shouldOpenModal, setShouldOpenModal] = useState<{ id: number; type: string } | null>(null);
+
+  const renderItem = ({ item }: { item: any }) => {
+    const name = item.name?.split(/ – | - /);
+
+    return index === 0 ? (
+      <TouchableOpacity
+        style={{ paddingVertical: 12 }}
+        onPress={() => {
+          handleCloseModal();
+          navigation.navigate(
+            ...([NAVIGATION_PAGES.PUBLIC_PROFILE_VIEW, { userId: item.id }] as never)
+          );
+        }}
+      >
+        <View style={styles.container}>
+          {item.avatar && <Image source={{ uri: API_HOST + item.avatar }} style={styles.img} />}
+          <View style={styles.textContainer}>
+            <Text style={styles.title}>{item.name}</Text>
+            <View style={{ flexDirection: 'row', alignItems: 'center' }}>
+              <Image source={{ uri: API_HOST + item.flag1 }} style={styles.flagSmall} />
+              {item.flag2 && item.flag2 !== item.flag1 && (
+                <Image
+                  source={{ uri: API_HOST + item.flag2 }}
+                  style={[
+                    styles.flagSmall,
+                    {
+                      marginLeft: -6
+                    }
+                  ]}
+                />
+              )}
+            </View>
+          </View>
+        </View>
+      </TouchableOpacity>
+    ) : (
+      <TouchableOpacity
+        style={{ paddingVertical: 12 }}
+        onPress={() => {
+          handleCloseModal();
+          if (index === 1) {
+            setShouldOpenModal({ id: item.id, type: 'regions' });
+          } else {
+            setShouldOpenModal({ id: item.id, type: 'places' });
+          }
+        }}
+      >
+        <View style={styles.container}>
+          {item.flag1 && <Image source={{ uri: API_HOST + item.flag1 }} style={styles.img} />}
+          {item.flag2 && (
+            <Image
+              source={{ uri: API_HOST + item.flag2 }}
+              style={[
+                styles.img,
+                {
+                  marginLeft: -20
+                }
+              ]}
+            />
+          )}
+          <View style={styles.textContainer}>
+            <Text style={styles.title}>{name[0]}</Text>
+            <Text style={styles.subTitle}>{name[1]}</Text>
+          </View>
+        </View>
+      </TouchableOpacity>
+    );
+  };
+
+  const renderScene = ({ route }: { route: any }) => {
+    return (
+      <View style={{ flex: 1 }}>
+        {searchData?.[route.key] ? (
+          <FlashList
+            viewabilityConfig={{
+              waitForInteraction: true,
+              itemVisiblePercentThreshold: 50,
+              minimumViewTime: 1000
+            }}
+            estimatedItemSize={45}
+            data={searchData?.[route.key]}
+            renderItem={renderItem}
+            keyExtractor={(item) => item.id.toString()}
+            showsVerticalScrollIndicator={false}
+            contentContainerStyle={{ paddingVertical: 16, paddingHorizontal: 8 }}
+          />
+        ) : (
+          <Loading />
+        )}
+      </View>
+    );
+  };
+
+  return (
+    <ReactModal
+      isVisible={searchVisible}
+      onBackdropPress={handleCloseModal}
+      onBackButtonPress={handleCloseModal}
+      style={styles.modal}
+      statusBarTranslucent={true}
+      presentationStyle="overFullScreen"
+      onModalHide={() => {
+        if (shouldOpenModal) {
+          handleFindRegion(shouldOpenModal.id, shouldOpenModal.type);
+          setShouldOpenModal(null);
+        }
+      }}
+    >
+      <View style={styles.modalContainer}>
+        <TabView
+          navigationState={{ index, routes }}
+          renderScene={renderScene}
+          onIndexChange={setIndex}
+          lazy={true}
+          renderTabBar={(props) => (
+            <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 }]}
+                >
+                  {route.title}
+                </Text>
+              )}
+            />
+          )}
+        />
+      </View>
+    </ReactModal>
+  );
+};
+
+export default SearchModal;

+ 63 - 0
src/screens/InAppScreens/MapScreen/UniversalSearch/styles.tsx

@@ -0,0 +1,63 @@
+import { StyleSheet, Dimensions } from 'react-native';
+import { Colors } from '../../../../theme';
+import { getFontSize } from 'src/utils';
+
+export const styles = StyleSheet.create({
+  tabBar: {
+    backgroundColor: 'transparent',
+    elevation: 0,
+    shadowOpacity: 0,
+    marginTop: 0,
+    height: 42
+  },
+  tabLabel: {
+    color: 'grey',
+    fontSize: getFontSize(14),
+    fontWeight: '700',
+    padding: 8,
+    width: Dimensions.get('window').width / 3,
+    textAlign: 'center'
+  },
+  tabStyle: {
+    padding: 0,
+    marginHorizontal: 2
+  },
+  img: {
+    width: 28,
+    height: 28,
+    borderRadius: 14,
+    borderWidth: 1,
+    borderColor: Colors.FILL_LIGHT
+  },
+  flagSmall: {
+    width: 16,
+    height: 16,
+    borderRadius: 8,
+    borderWidth: 1,
+    borderColor: Colors.FILL_LIGHT
+  },
+  title: {
+    color: Colors.DARK_BLUE,
+    fontFamily: 'montserrat-700',
+    fontSize: 14
+  },
+  subTitle: {
+    color: Colors.DARK_BLUE,
+    fontWeight: '600',
+    fontSize: 11
+  },
+  textContainer: { gap: 2, justifyContent: 'center', flex: 1 },
+  container: { flexDirection: 'row', gap: 8, alignItems: 'center' },
+  modal: {
+    justifyContent: 'flex-end',
+    margin: 0
+  },
+  modalContainer: {
+    backgroundColor: 'white',
+    borderRadius: 15,
+    paddingHorizontal: 16,
+    gap: 12,
+    paddingVertical: 16,
+    height: '80%'
+  }
+});

+ 206 - 46
src/screens/InAppScreens/MapScreen/index.tsx

@@ -1,4 +1,13 @@
-import { Animated, Linking, Platform, Text, TouchableOpacity, View } from 'react-native';
+import {
+  Animated as Animation,
+  Dimensions,
+  Linking,
+  Platform,
+  Text,
+  TextInput,
+  TouchableOpacity,
+  View
+} from 'react-native';
 import React, { useEffect, useMemo, useRef, useState, useCallback } from 'react';
 import MapView, { Geojson, Marker, UrlTile } from 'react-native-maps';
 import * as turf from '@turf/turf';
@@ -41,13 +50,18 @@ import { usePostSetNmRegionMutation } from '@api/myRegions';
 import { usePostSetDareRegionMutation } from '@api/myDARE';
 import moment from 'moment';
 import { qualityOptions } from '../TravelsScreen/utils/constants';
+import Animated, { Easing } from 'react-native-reanimated';
+import { useSharedValue, useAnimatedStyle, withTiming } from 'react-native-reanimated';
+import { Colors } from 'src/theme';
+import { useGetUniversalSearch } from '@api/search';
+import SearchModal from './UniversalSearch';
 
 const localTileDir = `${FileSystem.cacheDirectory}tiles/background`;
 const localGridDir = `${FileSystem.cacheDirectory}tiles/grid`;
 const localVisitedDir = `${FileSystem.cacheDirectory}tiles/user_visited`;
 const localDareDir = `${FileSystem.cacheDirectory}tiles/regions_mqp`;
 
-const AnimatedMarker = Animated.createAnimatedComponent(Marker);
+const AnimatedMarker = Animation.createAnimatedComponent(Marker);
 
 const MapScreen: React.FC<MapScreenProps> = ({ navigation }) => {
   const [dareData, setDareData] = useState(jsonData);
@@ -92,7 +106,10 @@ const MapScreen: React.FC<MapScreenProps> = ({ navigation }) => {
     years: [],
     id: null
   });
-  
+  const [search, setSearch] = useState('');
+  const [searchInput, setSearchInput] = useState('');
+  const { data: searchData } = useGetUniversalSearch(search);
+
   useEffect(() => {
     if (!dareData) {
       const fetchData = async () => {
@@ -132,7 +149,13 @@ const MapScreen: React.FC<MapScreenProps> = ({ navigation }) => {
 
   const cancelTokenRef = useRef(false);
   const currentTokenRef = useRef(0);
-  const strokeWidthAnim = useRef(new Animated.Value(2)).current;
+  const strokeWidthAnim = useRef(new Animation.Value(2)).current;
+
+  const [isExpanded, setIsExpanded] = useState(false);
+  const [searchVisible, setSearchVisible] = useState(false);
+  const [index, setIndex] = useState<number>(0);
+  const width = useSharedValue(48);
+  const usableWidth = Dimensions.get('window').width - 32;
 
   useEffect(() => {
     if (netInfo?.isInternetReachable) {
@@ -143,14 +166,14 @@ const MapScreen: React.FC<MapScreenProps> = ({ navigation }) => {
   }, [netInfo?.isInternetReachable]);
 
   useEffect(() => {
-    Animated.loop(
-      Animated.sequence([
-        Animated.timing(strokeWidthAnim, {
+    Animation.loop(
+      Animation.sequence([
+        Animation.timing(strokeWidthAnim, {
           toValue: 3,
           duration: 700,
           useNativeDriver: false
         }),
-        Animated.timing(strokeWidthAnim, {
+        Animation.timing(strokeWidthAnim, {
           toValue: 2,
           duration: 700,
           useNativeDriver: false
@@ -161,13 +184,31 @@ const MapScreen: React.FC<MapScreenProps> = ({ navigation }) => {
 
   useEffect(() => {
     navigation.addListener('state', (state) => {
-      navigation.getParent()?.setOptions({
+      navigation
+        .getParent()
+        ?.getParent()
+        ?.setOptions({
+          tabBarStyle: {
+            display:
+              (state.data.state.history[1] as { status?: string })?.status === 'open' ||
+              regionPopupVisible
+                ? 'none'
+                : 'flex',
+            position: 'absolute',
+            ...Platform.select({
+              android: {
+                height: 58
+              }
+            })
+          }
+        });
+    });
+    navigation
+      .getParent()
+      ?.getParent()
+      ?.setOptions({
         tabBarStyle: {
-          display:
-            (state.data.state.history[1] as { status?: string })?.status === 'open' ||
-            regionPopupVisible
-              ? 'none'
-              : 'flex',
+          display: regionPopupVisible ? 'none' : 'flex',
           position: 'absolute',
           ...Platform.select({
             android: {
@@ -176,18 +217,6 @@ const MapScreen: React.FC<MapScreenProps> = ({ navigation }) => {
           })
         }
       });
-    });
-    navigation.getParent()?.setOptions({
-      tabBarStyle: {
-        display: regionPopupVisible ? 'none' : 'flex',
-        position: 'absolute',
-        ...Platform.select({
-          android: {
-            height: 58
-          }
-        })
-      }
-    });
   }, [regionPopupVisible, navigation]);
 
   useEffect(() => {
@@ -199,16 +228,6 @@ const MapScreen: React.FC<MapScreenProps> = ({ navigation }) => {
 
       let currentLocation = await Location.getCurrentPositionAsync({});
       setLocation(currentLocation.coords);
-
-      mapRef.current?.animateToRegion(
-        {
-          latitude: currentLocation.coords.latitude,
-          longitude: currentLocation.coords.longitude,
-          latitudeDelta: 5,
-          longitudeDelta: 5
-        },
-        1000
-      );
     })();
   }, []);
 
@@ -404,6 +423,81 @@ const MapScreen: React.FC<MapScreenProps> = ({ navigation }) => {
     }
   };
 
+  const handleFindRegion = async (id: number, type: string) => {
+    cancelTokenRef.current = true;
+    const db = type === 'regions' ? getFirstDatabase() : getSecondDatabase();
+    const dataset = type === 'regions' ? regions : dareData;
+
+    const foundRegion = dataset.features.find((region: any) => region.properties.id === id);
+
+    if (foundRegion) {
+      setSelectedRegion({
+        type: 'FeatureCollection',
+        features: [
+          {
+            geometry: foundRegion.geometry,
+            properties: {
+              ...foundRegion.properties,
+              fill: 'rgba(57, 115, 172, 0.2)',
+              stroke: '#3973AC'
+            },
+            type: 'Feature'
+          }
+        ]
+      });
+
+      await getData(db, id, type, handleRegionData)
+        .then(() => {
+          setRegionPopupVisible(true);
+        })
+        .catch((error) => {
+          console.error('Error fetching data', error);
+          refreshDatabases();
+        });
+
+      const bounds = turf.bbox(foundRegion);
+      const region = calculateMapRegion(bounds);
+
+      mapRef.current?.animateToRegion(region, 1000);
+
+      if (type === 'regions') {
+        await mutateUserData(
+          { region_id: +id, token: String(token) },
+          {
+            onSuccess: (data) => {
+              setUserData({ type: 'nm', ...data });
+            }
+          }
+        );
+        await mutateAsync(
+          { regions: JSON.stringify([id]), token: String(token) },
+          {
+            onSuccess: (data) => {
+              setSeries(data.series);
+
+              const allMarkers = data.items.map(processMarkerData);
+              setProcessedMarkers(allMarkers);
+              setMarkers(allMarkers);
+            }
+          }
+        );
+      } else {
+        await mutateUserDataDare(
+          { dare_id: +id, token: String(token) },
+          {
+            onSuccess: (data) => {
+              setUserData({ type: 'dare', ...data });
+            }
+          }
+        );
+        setProcessedMarkers([]);
+        setMarkers([]);
+      }
+    } else {
+      handleClosePopup();
+    }
+  };
+
   const renderMapTiles = (url: string, cacheDir: string, zIndex: number, opacity = 1) => (
     <UrlTile
       urlTemplate={`${url}/{z}/{x}/{y}`}
@@ -558,6 +652,35 @@ const MapScreen: React.FC<MapScreenProps> = ({ navigation }) => {
     [userData, token]
   );
 
+  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 handleCloseModal = () => {
+    setSearchInput('');
+    setSearchVisible(false);
+    handlePress();
+  };
+
   return (
     <View style={styles.container}>
       <ClusteredMapView
@@ -588,7 +711,7 @@ const MapScreen: React.FC<MapScreenProps> = ({ navigation }) => {
         {renderMapTiles(dareTiles, localDareDir, 2, 0.5)}
         {location && (
           <AnimatedMarker coordinate={location} anchor={{ x: 0.5, y: 0.5 }}>
-            <Animated.View style={[styles.location, { borderWidth: strokeWidthAnim }]} />
+            <Animation.View style={[styles.location, { borderWidth: strokeWidthAnim }]} />
           </AnimatedMarker>
         )}
         {markers && renderMarkers()}
@@ -638,16 +761,45 @@ const MapScreen: React.FC<MapScreenProps> = ({ navigation }) => {
         </>
       ) : (
         <>
-          <TouchableOpacity
-            style={[styles.cornerButton, styles.topRightButton]}
-            onPress={() => (navigation as any)?.openDrawer()}
+          {!isExpanded ? (
+            <TouchableOpacity
+              style={[styles.cornerButton, styles.topRightButton]}
+              onPress={() => (navigation as any)?.openDrawer()}
+            >
+              <MenuIcon />
+            </TouchableOpacity>
+          ) : null}
+
+          <Animated.View
+            style={[
+              styles.searchContainer,
+              styles.cornerButton,
+              styles.topLeftButton,
+              animatedStyle
+            ]}
           >
-            <MenuIcon />
-          </TouchableOpacity>
-
-          {/* <TouchableOpacity style={[styles.cornerButton, styles.topRightButton]}>
-            <SearchIcon fill={'#0F3F4F'} />
-          </TouchableOpacity> */}
+            {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)}
+                />
+                <TouchableOpacity onPress={handleSearch} style={styles.iconButton}>
+                  <SearchIcon fill={'#0F3F4F'} />
+                </TouchableOpacity>
+              </>
+            ) : (
+              <TouchableOpacity onPress={handlePress} style={[styles.iconButton]}>
+                <SearchIcon fill={'#0F3F4F'} />
+              </TouchableOpacity>
+            )}
+          </Animated.View>
 
           {/* <TouchableOpacity
             style={[styles.cornerButton, styles.bottomButton, styles.bottomLeftButton]}
@@ -675,6 +827,14 @@ const MapScreen: React.FC<MapScreenProps> = ({ navigation }) => {
         updateModalState={handleModalStateChange}
         updateNM={handleUpdateNM}
       />
+      <SearchModal
+        searchVisible={searchVisible}
+        handleCloseModal={handleCloseModal}
+        handleFindRegion={handleFindRegion}
+        index={index}
+        searchData={searchData}
+        setIndex={setIndex}
+      />
     </View>
   );
 };

+ 19 - 3
src/screens/InAppScreens/MapScreen/style.tsx

@@ -1,5 +1,6 @@
-import { StyleSheet, Platform } from 'react-native';
+import { StyleSheet, Platform, Dimensions } from 'react-native';
 import { Colors } from '../../../theme';
+import { getFontSize } from 'src/utils';
 
 export const styles = StyleSheet.create({
   container: {
@@ -25,7 +26,7 @@ export const styles = StyleSheet.create({
     elevation: 5,
   },
   topLeftButton: {
-    top: 44,
+    top: 52,
     left: 16,
   },
   closeLeftButton: {
@@ -39,7 +40,7 @@ export const styles = StyleSheet.create({
     gap: 6,
   },
   topRightButton: {
-    top: 44,
+    top: 52,
     right: 16,
   },
   bottomButton: {
@@ -78,4 +79,19 @@ export const styles = StyleSheet.create({
       elevation: 5,
     },
   },
+  searchContainer: {
+    flexDirection: 'row',
+    alignItems: 'center',
+    overflow: 'hidden',
+    padding: 5
+  },
+  iconButton: {
+    padding: 10
+  },
+  input: {
+    flex: 1,
+    height: 40,
+    color: Colors.DARK_BLUE,
+    fontWeight: '600'
+  },
 });

+ 2 - 0
src/screens/InAppScreens/ProfileScreen/UsersMap/index.tsx

@@ -32,6 +32,7 @@ const UsersMapScreen: FC<Props> = ({ navigation, route }) => {
     navigation.getParent()?.setOptions({
       tabBarStyle: {
         display: 'none',
+        position: 'absolute',
         ...Platform.select({
           android: {
             height: 58
@@ -57,6 +58,7 @@ const UsersMapScreen: FC<Props> = ({ navigation, route }) => {
     navigation.getParent()?.setOptions({
       tabBarStyle: {
         display: 'flex',
+        position: 'absolute',
         ...Platform.select({
           android: {
             height: 58

+ 2 - 2
src/screens/InAppScreens/ProfileScreen/UsersMap/styles.tsx

@@ -25,7 +25,7 @@ export const styles = StyleSheet.create({
     elevation: 5
   },
   topLeftButton: {
-    top: 50,
+    top: 58,
     left: 16
   },
   closeLeftButton: {
@@ -45,7 +45,7 @@ export const styles = StyleSheet.create({
     lineHeight: 14
   },
   topRightButton: {
-    top: 44,
+    top: 52,
     right: 16
   },
   bottomButton: {

+ 1 - 1
src/screens/InAppScreens/ProfileScreen/index.tsx

@@ -65,7 +65,7 @@ const ProfileScreen: FC<Props> = ({ navigation, route }) => {
       : navigation.dispatch(
           CommonActions.reset({
             index: 1,
-            routes: [{ name: NAVIGATION_PAGES.MAP_TAB }]
+            routes: [{ name: NAVIGATION_PAGES.IN_APP_MAP_TAB }]
           })
         );
   };

+ 8 - 6
src/screens/InAppScreens/TravellersScreen/Components/Profile.tsx

@@ -146,13 +146,13 @@ export const Profile: FC<Props> = ({
                   style={adaptiveStyle(ProfileStyles.profileAvatar, {})}
                   source={{ uri: avatarBaseUri + avatar }}
                 />
-              ) : (
+              ) : homebase_flag ? (
                 <AvatarWithInitials
                   text={`${first_name[0] ?? ''}${last_name[0] ?? ''}`}
                   flag={flagBaseUri + homebase_flag}
                   size={48}
                 />
-              )}
+              ) : null}
             </View>
             <View style={adaptiveStyle(ProfileStyles.profileDataRoot, {})}>
               <Text
@@ -168,10 +168,12 @@ export const Profile: FC<Props> = ({
                   <Text style={adaptiveStyle(ProfileStyles.profileAge, {})}>
                     Age: {date_of_birth ?? ''}
                   </Text>
-                  <Image
-                    source={{ uri: flagBaseUri + homebase_flag }}
-                    style={adaptiveStyle(ProfileStyles.countryFlag, {})}
-                  />
+                  {homebase_flag && (
+                    <Image
+                      source={{ uri: flagBaseUri + homebase_flag }}
+                      style={adaptiveStyle(ProfileStyles.countryFlag, {})}
+                    />
+                  )}
                   {homebase2_flag && homebase2_flag !== homebase_flag ? (
                     <Image
                       source={{ uri: flagBaseUri + homebase2_flag }}

+ 2 - 3
src/screens/InAppScreens/TravellersScreen/utils/sort.ts

@@ -49,9 +49,8 @@ export const applyModalSort = (
         );
         break;
       case 'DEEP':
-        //TODO: Crash
         filteredLocalData = filteredLocalData.sort(
-          (a: Ranking, b: Ranking) => b.score_deep - a.score_deep
+          (a: Ranking, b: Ranking) => b?.score_deep - a?.score_deep
         );
         break;
       case 'YES':
@@ -61,7 +60,7 @@ export const applyModalSort = (
         );
         break;
       case 'SLOW':
-        const SLOWFilteredUsers = allRanking.filter(
+        const SLOWFilteredUsers = filteredLocalData.filter(
           (user) => user.score_slow < 4500 && user.score_slow > 0
         );
         filteredLocalData = SLOWFilteredUsers.sort(

+ 6 - 3
src/types/api.ts

@@ -15,7 +15,8 @@ export enum API_ROUTE {
   QUICK_ENTER = 'quickEnter',
   TRIUMPHS = 'triumphs',
   SERIES_RANKING = 'series-ranking',
-  APP = 'app'
+  APP = 'app',
+  SEARCH = 'search'
 }
 
 export enum API_ENDPOINT {
@@ -82,7 +83,8 @@ export enum API_ENDPOINT {
   GET_LAST_REGIONS_UPDATE = 'last-regions-db-update',
   GET_LAST_DARE_UPDATE = 'last-dare-db-update',
   GET_SERVERS = 'get-servers',
-  GET_PROFILE_REGIONS = 'get-profile'
+  GET_PROFILE_REGIONS = 'get-profile',
+  GET_UNIVERSAL = 'universal'
 }
 
 export enum API {
@@ -148,7 +150,8 @@ export enum API {
   GET_LAST_REGIONS_DB_UPDATE = `${API_ROUTE.APP}/${API_ENDPOINT.GET_LAST_REGIONS_UPDATE}`,
   GET_LAST_DARE_DB_UPDATE = `${API_ROUTE.APP}/${API_ENDPOINT.GET_LAST_DARE_UPDATE}`,
   GET_SERVERS = `${API_ROUTE.APP}/${API_ENDPOINT.GET_SERVERS}`,
-  GET_PROFILE_REGIONS = `${API_ROUTE.REGIONS}/${API_ENDPOINT.GET_PROFILE_REGIONS}`
+  GET_PROFILE_REGIONS = `${API_ROUTE.REGIONS}/${API_ENDPOINT.GET_PROFILE_REGIONS}`,
+  GET_UNIVERSAL = `${API_ROUTE.SEARCH}/${API_ENDPOINT.GET_UNIVERSAL}`
 }
 
 export type BaseAxiosError = AxiosError;

+ 2 - 1
src/types/navigation.ts

@@ -6,7 +6,8 @@ export enum NAVIGATION_PAGES {
   RESET_PASSWORD = 'resetPassword',
   RESET_PASSWORD_DEEP = 'resetPasswordDeep',
   IN_APP = 'inAppStack',
-  MAP_TAB = 'Map',
+  IN_APP_MAP_TAB = 'Map',
+  MAP_TAB = 'inAppMapTab',
   IN_APP_TRAVELLERS_TAB = 'Travellers',
   TRAVELLERS_TAB = 'inAppTravellers',
   MASTER_RANKING = 'inAppMasterRanking',