Viktoriia 1 week geleden
bovenliggende
commit
f805dde24a
45 gewijzigde bestanden met toevoegingen van 850 en 596 verwijderingen
  1. 4 0
      App.tsx
  2. 16 3
      Route.tsx
  3. 6 1
      app.config.ts
  4. 2 4
      babel.config.js
  5. 1 0
      declarations.d.ts
  6. 15 9
      metro.config.js
  7. 72 69
      package.json
  8. 0 13
      patches/react-native-tab-view+3.5.2.patch
  9. 18 2
      src/components/AvatarWithInitials/index.tsx
  10. 97 60
      src/components/HorizontalTabView/index.tsx
  11. 44 11
      src/components/HorizontalTabView/styles.tsx
  12. 8 2
      src/components/TabBarButton/index.tsx
  13. 147 0
      src/components/TabViewWrapper/index.tsx
  14. 1 0
      src/components/index.ts
  15. 1 1
      src/constants/constants.ts
  16. 1 1
      src/database/cacheService/index.ts
  17. 1 1
      src/database/flagsService/index.ts
  18. 1 1
      src/database/tilesService/index.ts
  19. 4 4
      src/db/index.ts
  20. 31 48
      src/modules/map/regionData.ts
  21. 21 19
      src/screens/InAppScreens/MapScreen/CountryViewScreen/index.tsx
  22. 2 1
      src/screens/InAppScreens/MapScreen/RegionViewScreen/TravelSeriesList/styles.tsx
  23. 23 18
      src/screens/InAppScreens/MapScreen/RegionViewScreen/index.tsx
  24. 4 0
      src/screens/InAppScreens/MapScreen/RegionViewScreen/styles.tsx
  25. 17 35
      src/screens/InAppScreens/MapScreen/UniversalSearch/index.tsx
  26. 0 1
      src/screens/InAppScreens/MapScreen/UniversalSearch/styles.tsx
  27. 7 2
      src/screens/InAppScreens/MapScreen/index.tsx
  28. 10 10
      src/screens/InAppScreens/MessagesScreen/ChatScreen/index.tsx
  29. 49 27
      src/screens/InAppScreens/MessagesScreen/Components/AttachmentsModal.tsx
  30. 1 1
      src/screens/InAppScreens/MessagesScreen/Components/RenderMessageImage.tsx
  31. 1 1
      src/screens/InAppScreens/MessagesScreen/Components/renderMessageVideo.tsx
  32. 10 10
      src/screens/InAppScreens/MessagesScreen/GroupChatScreen/index.tsx
  33. 3 3
      src/screens/InAppScreens/MessagesScreen/index.tsx
  34. 13 8
      src/screens/InAppScreens/ProfileScreen/MyFriendsScreen/index.tsx
  35. 34 26
      src/screens/InAppScreens/TravellersScreen/Components/Profile.tsx
  36. 1 1
      src/screens/InAppScreens/TravellersScreen/Components/SeriesRankingItem.tsx
  37. 1 1
      src/screens/InAppScreens/TravellersScreen/Components/TriumphItem.tsx
  38. 12 5
      src/screens/InAppScreens/TravellersScreen/SeriesRankingListScreen/index.tsx
  39. 8 3
      src/screens/InAppScreens/TravellersScreen/UNMasters/index.tsx
  40. 56 56
      src/screens/InAppScreens/TravelsScreen/EventScreen/index.tsx
  41. 57 81
      src/screens/InAppScreens/TravelsScreen/EventsScreen/index.tsx
  42. 24 18
      src/screens/InAppScreens/TravelsScreen/Series/index.tsx
  43. 23 37
      src/screens/InAppScreens/TravelsScreen/SeriesItemScreen/index.tsx
  44. 1 1
      src/screens/InAppScreens/TravelsScreen/utils/useRegionData.ts
  45. 2 1
      tsconfig.json

+ 4 - 0
App.tsx

@@ -18,6 +18,7 @@ import { Linking, Platform } from 'react-native';
 import { API_HOST, API_URL, APP_VERSION } from 'src/constants';
 import axios from 'axios';
 import { API } from 'src/types';
+import { LogBox } from 'react-native';
 import { storage, StoreType } from 'src/storage';
 import { setupGlobalErrorHandler } from 'src/utils/globalErrorHandler';
 
@@ -25,6 +26,8 @@ const IOS_STORE_URL = 'https://apps.apple.com/app/id6502843543';
 const ANDROID_STORE_URL =
   'https://play.google.com/store/apps/details?id=com.nomadmania.presentation';
 
+LogBox.ignoreLogs([/defaultProps will be removed/, /IMGElement/]);
+
 const userId = (storage.get('uid', StoreType.STRING) as string) ?? 'not_logged_in';
 
 const routingInstrumentation = Sentry.reactNavigationIntegration({
@@ -142,6 +145,7 @@ const InnerApp = () => {
             routingInstrumentation.registerNavigationContainer(navigation);
           }}
           linking={linking}
+          navigationInChildEnabled
         >
           <Route />
           <ConnectionBanner />

+ 16 - 3
Route.tsx

@@ -57,7 +57,7 @@ import { storage, StoreType } from './src/storage';
 import { openDatabases } from './src/db';
 
 import TabBarButton from './src/components/TabBarButton';
-import { ParamListBase, RouteProp } from '@react-navigation/native';
+import { ParamListBase, RouteProp, useIsFocused } from '@react-navigation/native';
 import setupDatabaseAndSync from 'src/database';
 import { MenuDrawer } from 'src/components';
 import { API_URL, APP_VERSION } from 'src/constants';
@@ -166,6 +166,16 @@ const Route = () => {
     }
   };
 
+  function UnmountOnBlur({ children }) {
+    const isFocused = useIsFocused();
+
+    if (!isFocused) {
+      return null;
+    }
+
+    return children;
+  }
+
   const handleLogout = () => {
     setToken(null);
     storage.remove('token');
@@ -249,7 +259,6 @@ const Route = () => {
       <TabBarButton
         {...props}
         label={route.name}
-        focused={props?.accessibilityState?.selected || false}
         navigation={navigation}
       />
     ),
@@ -262,6 +271,7 @@ const Route = () => {
     },
     cardStyle: { backgroundColor: 'white' },
     unmountOnBlur: true,
+    popToTopOnBlur: true,
     gestureEnabled: Platform.OS === 'ios' ? true : false,
     lazy: false
   });
@@ -522,7 +532,10 @@ const Route = () => {
           </ScreenStack.Navigator>
         )}
       </BottomTab.Screen>
-      <BottomTab.Screen name={NAVIGATION_PAGES.MENU_DRAWER}>
+      <BottomTab.Screen
+        name={NAVIGATION_PAGES.MENU_DRAWER}
+        layout={({ children }) => <UnmountOnBlur>{children}</UnmountOnBlur>}
+      >
         {() => (
           <ScreenStack.Navigator screenOptions={screenOptions}>
             <ScreenStack.Screen

+ 6 - 1
app.config.ts

@@ -21,12 +21,13 @@ export default ({ config }: ConfigContext): ExpoConfig => ({
   ...config,
   name: 'NomadMania',
   slug: 'nomadmania-app',
+  newArchEnabled: true,
   owner: 'nomadmaniaou',
   scheme: 'nm',
   // Should be updated after every production release (deploy to AppStore/PlayMarket)
   version: '2.0.42',
   // Should be updated after every dependency change
-  runtimeVersion: '1.6',
+  runtimeVersion: '1.7',
   orientation: 'portrait',
   icon: './assets/icon-logo.png',
   userInterfaceStyle: 'light',
@@ -155,6 +156,10 @@ export default ({ config }: ConfigContext): ExpoConfig => ({
     [
       'expo-build-properties',
       {
+        ios: {
+          newArchEnabled: true,
+          deploymentTarget: '15.1'
+        },
         android: {
           minSdkVersion: 24,
           targetSdkVersion: 35

+ 2 - 4
babel.config.js

@@ -1,9 +1,7 @@
-module.exports = function(api) {
+module.exports = function (api) {
   api.cache(true);
   return {
     presets: ['babel-preset-expo'],
-    plugins: [
-      'react-native-reanimated/plugin',
-    ],
+    plugins: ['react-native-worklets/plugin']
   };
 };

+ 1 - 0
declarations.d.ts

@@ -4,3 +4,4 @@ declare module '*.svg' {
   const content: React.FC<SvgProps>;
   export default content;
 }
+declare module '@turf/turf';

+ 15 - 9
metro.config.js

@@ -1,20 +1,26 @@
-// const { getDefaultConfig } = require('expo/metro-config');
+const { getDefaultConfig } = require('expo/metro-config');
 const { getSentryExpoConfig } = require('@sentry/react-native/metro');
 
 module.exports = (() => {
-  // const config = getDefaultConfig(__dirname);
-  const config = getSentryExpoConfig(__dirname);
+  const baseConfig = getDefaultConfig(__dirname);
 
-  const { transformer, resolver } = config;
+  const config = getSentryExpoConfig(__dirname, baseConfig);
 
   config.transformer = {
-    ...transformer,
-    babelTransformerPath: require.resolve('react-native-svg-transformer')
+    ...config.transformer,
+    babelTransformerPath: require.resolve('react-native-svg-transformer'),
+    minifierConfig: {
+      keep_fnames: true,
+      mangle: {
+        keep_fnames: true
+      }
+    }
   };
+
   config.resolver = {
-    ...resolver,
-    assetExts: [...resolver.assetExts.filter((ext) => ext !== 'svg'), 'db'],
-    sourceExts: [...resolver.sourceExts, 'svg']
+    ...config.resolver,
+    assetExts: config.resolver.assetExts.filter((ext) => ext !== 'svg').concat(['db']),
+    sourceExts: [...config.resolver.sourceExts, 'svg']
   };
 
   return config;

+ 72 - 69
package.json

@@ -12,99 +12,102 @@
     "postinstall": "patch-package"
   },
   "dependencies": {
-    "@expo/config-plugins": "^8.0.8",
-    "@maplibre/maplibre-react-native": "^10.2.0",
-    "@react-native-clipboard/clipboard": "^1.14.2",
-    "@react-native-community/datetimepicker": "8.0.1",
-    "@react-native-community/netinfo": "11.3.1",
-    "@react-native-picker/picker": "2.7.5",
-    "@react-navigation/bottom-tabs": "^6.5.11",
-    "@react-navigation/drawer": "^6.6.15",
-    "@react-navigation/material-top-tabs": "^6.6.5",
-    "@react-navigation/native": "^6.1.9",
-    "@react-navigation/native-stack": "^6.9.17",
-    "@react-navigation/stack": "^6.3.20",
-    "@sentry/react-native": "~5.33.2",
-    "@shopify/flash-list": "1.6.4",
-    "@tanstack/react-query": "latest",
-    "@turf/turf": "^6.5.0",
-    "axios": "^1.6.1",
+    "@maplibre/maplibre-react-native": "^10.2.1",
+    "@react-native-clipboard/clipboard": "^1.16.3",
+    "@react-native-community/datetimepicker": "^8.4.4",
+    "@react-native-community/netinfo": "11.4.1",
+    "@react-native-documents/picker": "^10.1.7",
+    "@react-native-picker/picker": "^2.11.1",
+    "@react-navigation/bottom-tabs": "^7.4.7",
+    "@react-navigation/drawer": "^7.5.8",
+    "@react-navigation/material-top-tabs": "^7.3.7",
+    "@react-navigation/native": "^7.1.17",
+    "@react-navigation/native-stack": "^7.3.26",
+    "@react-navigation/stack": "^7.4.8",
+    "@sentry/react-native": "~6.20.0",
+    "@shopify/flash-list": "2.0.2",
+    "@tanstack/react-query": "^5.89.0",
+    "@turf/turf": "^7.2.0",
+    "axios": "^1.12.2",
     "better-react-native-image-viewing": "^0.2.7",
-    "dotenv": "^16.3.1",
-    "expo": "^51.0.9",
-    "expo-asset": "~10.0.10",
-    "expo-av": "^14.0.7",
-    "expo-blur": "~13.0.3",
-    "expo-build-properties": "~0.12.5",
-    "expo-checkbox": "~3.0.0",
-    "expo-constants": "~16.0.2",
-    "expo-dev-client": "~4.0.26",
-    "expo-file-system": "~17.0.1",
-    "expo-font": "~12.0.10",
-    "expo-image": "~1.13.0",
-    "expo-image-picker": "~15.1.0",
-    "expo-location": "~17.0.1",
-    "expo-media-library": "~16.0.5",
-    "expo-notifications": "~0.28.19",
-    "expo-splash-screen": "~0.27.5",
-    "expo-sqlite": "^14.0.6",
-    "expo-status-bar": "~1.12.1",
-    "expo-task-manager": "~11.8.2",
-    "expo-updates": "~0.25.24",
-    "formik": "^2.4.5",
-    "moment": "^2.29.4",
+    "dotenv": "^16.6.1",
+    "expo": "^54.0.9",
+    "expo-asset": "~12.0.9",
+    "expo-av": "~16.0.7",
+    "expo-blur": "~15.0.7",
+    "expo-build-properties": "~1.0.9",
+    "expo-checkbox": "~5.0.7",
+    "expo-constants": "~18.0.9",
+    "expo-dev-client": "~6.0.12",
+    "expo-file-system": "~19.0.14",
+    "expo-font": "~14.0.8",
+    "expo-image": "~3.0.8",
+    "expo-image-picker": "~17.0.8",
+    "expo-location": "~19.0.7",
+    "expo-media-library": "~18.2.0",
+    "expo-notifications": "~0.32.11",
+    "expo-splash-screen": "~31.0.10",
+    "expo-sqlite": "~16.0.8",
+    "expo-status-bar": "~3.0.8",
+    "expo-task-manager": "~14.0.7",
+    "expo-updates": "~29.0.11",
+    "formik": "^2.4.6",
+    "moment": "^2.30.1",
     "patch-package": "^8.0.0",
     "promise": "^8.3.0",
-    "react": "18.2.0",
-    "react-native": "0.74.5",
+    "react": "19.1.0",
+    "react-native": "0.81.4",
     "react-native-actions-sheet": "^0.9.7",
     "react-native-animated-pagination-dot": "^0.4.0",
     "react-native-calendars": "1.1304.1",
-    "react-native-color-matrix-image-filters": "^7.0.1",
+    "react-native-collapsible-tab-view": "^8.0.1",
     "react-native-device-detection": "^0.2.1",
-    "react-native-document-picker": "^9.3.1",
     "react-native-emoji-selector": "^0.2.0",
     "react-native-file-viewer": "^2.1.5",
-    "react-native-gesture-handler": "~2.16.1",
+    "react-native-gesture-handler": "^2.28.0",
     "react-native-get-random-values": "^1.11.0",
-    "react-native-gifted-chat": "~2.6.5",
+    "react-native-gifted-chat": "^2.8.1",
     "react-native-google-places-autocomplete": "^2.5.7",
-    "react-native-haptic-feedback": "^2.3.2",
+    "react-native-haptic-feedback": "^2.3.3",
     "react-native-image-viewing": "^0.2.2",
     "react-native-keyboard-aware-scroll-view": "^0.9.5",
+    "react-native-keyboard-controller": "1.18.5",
     "react-native-linear-gradient": "^2.8.3",
-    "react-native-map-clustering": "^3.4.2",
-    "react-native-maps": "1.14.0",
-    "react-native-mmkv": "^2.11.0",
-    "react-native-modal": "^13.0.1",
-    "react-native-pager-view": "6.3.0",
-    "react-native-paper": "^5.12.3",
+    "react-native-map-clustering": "^4.0.0",
+    "react-native-maps": "1.20.1",
+    "react-native-mmkv": "^3.3.1",
+    "react-native-modal": "^14.0.0-rc.1",
+    "react-native-pager-view": "^6.9.1",
+    "react-native-paper": "^5.14.5",
     "react-native-pell-rich-editor": "^1.10.0",
     "react-native-progress": "^5.0.1",
-    "react-native-reanimated": "~3.10.1",
-    "react-native-reanimated-carousel": "^3.5.1",
+    "react-native-reanimated": "~4.1.0",
+    "react-native-reanimated-carousel": "^4.0.3",
     "react-native-render-html": "^6.3.4",
-    "react-native-safe-area-context": "4.10.5",
-    "react-native-screens": "3.31.1",
-    "react-native-searchable-dropdown-kj": "^1.9.1",
-    "react-native-share": "^10.2.1",
-    "react-native-svg": "15.2.0",
-    "react-native-tab-view": "^3.5.2",
+    "react-native-safe-area-context": "~5.6.0",
+    "react-native-screens": "~4.16.0",
+    "react-native-searchable-dropdown-kj": "^1.9.3",
+    "react-native-share": "^12.2.0",
+    "react-native-svg": "15.12.1",
     "react-native-url-polyfill": "^2.0.0",
-    "react-native-view-shot": "^3.7.0",
+    "react-native-view-shot": "4.0.3",
     "react-native-walkthrough-tooltip": "^1.6.0",
-    "react-native-webview": "13.8.6",
-    "react-native-wheel-pick": "^1.2.2",
-    "uuid": "^10.0.0",
-    "yup": "^1.3.3",
-    "zustand": "^4.4.7"
+    "react-native-webview": "13.15.0",
+    "react-native-wheel-pick": "^1.2.6",
+    "react-native-worklets": "^0.5.1",
+    "uuid": "^13.0.0",
+    "yup": "^1.7.1",
+    "zustand": "^5.0.8"
   },
   "devDependencies": {
     "@babel/core": "^7.25.2",
-    "@types/react": "~18.2.14",
+    "@react-native-community/cli": "latest",
+    "@types/react": "~19.1.10",
+    "babel-preset-expo": "^54.0.2",
+    "metro-react-native-babel-transformer": "^0.77.0",
     "prettier": "^3.1.0",
     "react-native-svg-transformer": "^1.5.0",
-    "typescript": "~5.3.3"
+    "typescript": "~5.9.2"
   },
   "private": true
 }

+ 0 - 13
patches/react-native-tab-view+3.5.2.patch

@@ -1,13 +0,0 @@
-diff --git a/node_modules/react-native-tab-view/src/TabBar.tsx b/node_modules/react-native-tab-view/src/TabBar.tsx
-index e8d0b4c..cd5a424 100644
---- a/node_modules/react-native-tab-view/src/TabBar.tsx
-+++ b/node_modules/react-native-tab-view/src/TabBar.tsx
-@@ -405,6 +405,8 @@ export function TabBar<T extends Route>({
-                 // When we have measured widths for all of the tabs, we should updates the state
-                 // We avoid doing separate setState for each layout since it triggers multiple renders and slows down app
-                 setTabWidths({ ...measuredTabWidths.current });
-+              } else {
-+                setTabWidths({ ...measuredTabWidths?.current });
-               }
-             }
-           : undefined,

+ 18 - 2
src/components/AvatarWithInitials/index.tsx

@@ -1,5 +1,6 @@
 import React from 'react';
 import { View, Text, Image } from 'react-native';
+import { FilterImage } from 'react-native-svg/filter-image';
 
 import { styles } from './styles';
 import { Colors } from 'src/theme';
@@ -10,7 +11,8 @@ export const AvatarWithInitials = ({
   size,
   borderColor = Colors.FILL_LIGHT,
   fontSize = 22,
-  borderWidth = 2
+  borderWidth = 2,
+  grayscale = false
 }: {
   text: string;
   flag: string;
@@ -18,6 +20,7 @@ export const AvatarWithInitials = ({
   borderColor?: string;
   fontSize?: number;
   borderWidth?: number;
+  grayscale?: boolean;
 }) => {
   const shadowProps = [
     { textShadowOffset: { width: -0.5, height: -0.5 } },
@@ -41,7 +44,20 @@ export const AvatarWithInitials = ({
         { width: size, height: size, borderRadius: size / 2, borderColor, borderWidth }
       ]}
     >
-      <Image style={styles.avatar} source={{ uri: flag }} />
+      {grayscale ? (
+        <FilterImage
+          source={{ uri: flag }}
+          style={[
+            styles.avatar,
+            {
+              filter: 'grayscale(100%)'
+            }
+          ]}
+        />
+      ) : (
+        <Image style={styles.avatar} source={{ uri: flag }} />
+      )}
+
       <View style={styles.initialsContainer}>
         {shadowProps.map((props, index) => (
           <Text key={index} style={[styles.initials, props, { fontSize }]}>

+ 97 - 60
src/components/HorizontalTabView/index.tsx

@@ -1,7 +1,6 @@
 import React from 'react';
-import { StyleProp, Text, View, ViewStyle } from 'react-native';
-import { Route, TabBar, TabView } from 'react-native-tab-view';
-
+import { View, Text, Pressable, StyleProp, ViewStyle } from 'react-native';
+import { Tabs, MaterialTabBar } from 'react-native-collapsible-tab-view';
 import { styles } from './styles';
 import { Colors } from 'src/theme';
 
@@ -9,76 +8,114 @@ import MarkToUpIcon from '../../../assets/icons/mark-to-up.svg';
 import BanIcon from 'assets/icons/messages/ban.svg';
 import MessagesDot from '../MessagesDot';
 
-export const HorizontalTabView = ({
-  index,
-  setIndex,
-  routes,
-  renderScene,
-  withMark,
-  onDoubleClick,
-  lazy = false,
-  withNotification = 0,
-  maxTabHeight,
-  tabBarStyle = {}
-}: {
+interface Route {
+  key: string;
+  title?: string;
+  icon?: string;
+}
+
+type Props = {
   index: number;
   setIndex: React.Dispatch<React.SetStateAction<number>>;
   routes: Route[];
-  renderScene: (props: any) => React.ReactNode;
+  renderScene: (props: { route: any }) => React.ReactNode;
   withMark?: boolean;
-  onDoubleClick?: () => void;
   lazy?: boolean;
   withNotification?: number;
   maxTabHeight?: number;
   tabBarStyle?: StyleProp<ViewStyle>;
+  sceneStyles?: StyleProp<ViewStyle>;
+};
+
+export const HorizontalTabView: React.FC<Props> = ({
+  index,
+  setIndex,
+  routes,
+  renderScene,
+  withMark,
+  lazy = false,
+  withNotification = 0,
+  maxTabHeight,
+  tabBarStyle = {},
+  sceneStyles = {}
 }) => {
-  const renderTabBar = (props: any) => (
-    <TabBar
-      {...props}
-      renderLabel={({ route, focused }) => (
-        <>
-          <View style={[styles.tabLabelContainer, focused ? styles.tabLabelFocused : null]}>
-            {route.icon === 'ban' ? (
-              <BanIcon width={15} height={15} fill={focused ? Colors.WHITE : Colors.DARK_BLUE} />
-            ) : (
-              <Text style={[styles.label, focused ? styles.labelFocused : null]}>
-                {route.title}
-              </Text>
-            )}
-            {withMark ? (
-              <MarkToUpIcon
-                height={16}
-                width={16}
-                style={styles.icon}
-                stroke={focused ? Colors.WHITE : Colors.DARK_BLUE}
-              />
-            ) : null}
-          </View>
-          {withNotification > 0 && route.key === 'received' ? (
-            <MessagesDot messagesCount={withNotification} />
+  const TabItemComponent = ({ name, index: tabIndex, onPress, label, style, onLayout }: any) => {
+    const fullRoute = routes.find((r) => r.key === name);
+
+    return (
+      <Pressable
+        onPress={() => onPress(name)}
+        onLayout={onLayout}
+        style={[styles.tabLabelWrapper, style]}
+      >
+        <View
+          style={[
+            styles.tabLabelContainer,
+            maxTabHeight ? { maxHeight: maxTabHeight } : {},
+            tabIndex === index ? { backgroundColor: Colors.ORANGE, borderColor: Colors.ORANGE } : {}
+          ]}
+        >
+          {fullRoute?.icon === 'ban' ? (
+            <BanIcon
+              width={15}
+              height={15}
+              fill={tabIndex === index ? Colors.WHITE : Colors.DARK_BLUE}
+            />
+          ) : (
+            <Text style={[styles.label, tabIndex === index ? { color: Colors.WHITE } : {}]}>
+              {fullRoute?.title ?? label}
+            </Text>
+          )}
+          {withMark ? (
+            <MarkToUpIcon height={16} width={16} style={styles.icon} stroke={undefined} />
           ) : null}
-        </>
-      )}
-      scrollEnabled={true}
-      indicatorStyle={styles.indicator}
-      style={[styles.tabBar, tabBarStyle]}
-      activeColor={Colors.ORANGE}
-      inactiveColor={Colors.DARK_BLUE}
-      tabStyle={[styles.tabStyle, maxTabHeight ? { maxHeight: maxTabHeight } : {}]}
-      pressColor={'transparent'}
-    />
-  );
+        </View>
+
+        {withNotification > 0 && fullRoute?.key === 'received' ? (
+          <View style={styles.notificationContainer}>
+            <MessagesDot messagesCount={withNotification} />
+          </View>
+        ) : null}
+      </Pressable>
+    );
+  };
 
   return (
-    <TabView
-      navigationState={{ index, routes }}
-      renderScene={renderScene}
+    <Tabs.Container
+      renderTabBar={(props) => (
+        <MaterialTabBar
+          {...props}
+          scrollEnabled
+          indicatorStyle={{ height: 0 }}
+          activeColor={Colors.ORANGE}
+          inactiveColor={Colors.DARK_BLUE}
+          keepActiveTabCentered
+          contentContainerStyle={{ paddingHorizontal: 0 }}
+          style={[styles.tabBar, tabBarStyle]}
+          tabStyle={[styles.tabStyle, maxTabHeight ? { maxHeight: maxTabHeight } : {}]}
+          TabItemComponent={TabItemComponent}
+        />
+      )}
       onIndexChange={setIndex}
-      animationEnabled={true}
-      swipeEnabled={true}
-      style={styles.tabView}
-      renderTabBar={renderTabBar}
+      initialTabName={routes[index]?.key}
       lazy={lazy}
-    />
+      headerHeight={0}
+      headerContainerStyle={styles.tabBar}
+      containerStyle={[tabBarStyle]}
+      tabBarHeight={40}
+    >
+      {routes.map((route) => (
+        <Tabs.Tab key={route.key} name={route.key} label={route.title}>
+          <View
+            style={[
+              { flex: 1, width: '100%', paddingTop: 40, paddingHorizontal: '4%' },
+              sceneStyles
+            ]}
+          >
+            {renderScene({ route })}
+          </View>
+        </Tabs.Tab>
+      ))}
+    </Tabs.Container>
   );
 };

+ 44 - 11
src/components/HorizontalTabView/styles.tsx

@@ -1,7 +1,12 @@
-import { StyleSheet } from 'react-native';
+import { StyleSheet, Dimensions } from 'react-native';
 import { Colors } from 'src/theme';
 
+const { width: screenWidth } = Dimensions.get('window');
+
 export const styles = StyleSheet.create({
+  container: {
+    flex: 1
+  },
   tabBar: {
     backgroundColor: 'transparent',
     elevation: 0,
@@ -9,23 +14,37 @@ export const styles = StyleSheet.create({
     marginTop: 0
   },
   tabView: {
-    marginTop: -5
+    flex: 1
   },
   tabStyle: {
-    width: 'auto',
     padding: 0,
     marginHorizontal: 2,
-    backgroundColor: 'transparent'
+    backgroundColor: 'transparent',
+    alignItems: 'center',
+    justifyContent: 'center',
+    width: 'auto'
+  },
+  tabLabelWrapper: {
+    alignItems: 'center',
+    justifyContent: 'center',
+    position: 'relative'
+  },
+  tabContent: {
+    alignItems: 'center',
+    justifyContent: 'center',
+    position: 'relative',
+    width: screenWidth
   },
   tabLabelContainer: {
     borderRadius: 20,
     paddingHorizontal: 12,
-    paddingVertical: 10,
-    backgroundColor: 'transparent',
-    borderColor: 'rgba(15, 63, 79, 0.3)',
+    paddingVertical: 8,
     borderWidth: 1,
-    display: 'flex',
+    borderColor: 'rgba(15, 63, 79, 0.3)',
+    backgroundColor: 'transparent',
     flexDirection: 'row',
+    alignItems: 'center',
+    justifyContent: 'center',
     gap: 5
   },
   tabLabelFocused: {
@@ -36,16 +55,30 @@ export const styles = StyleSheet.create({
     fontSize: 12,
     textTransform: 'none',
     color: Colors.DARK_BLUE,
-    fontWeight: '600'
+    fontWeight: '600',
+    textAlign: 'center'
   },
   labelFocused: {
     color: 'white'
   },
   indicator: {
-    height: 0
+    height: 0,
+    backgroundColor: 'transparent'
   },
   icon: {
-    display: 'flex',
     alignSelf: 'center'
+  },
+  notificationContainer: {
+    position: 'absolute',
+    top: -5,
+    right: -5
+  },
+
+  gridContainer: {
+    flex: 1
+  },
+  contentContainer: {
+    flexDirection: 'row',
+    height: '100%'
   }
 });

+ 8 - 2
src/components/TabBarButton/index.tsx

@@ -18,6 +18,7 @@ import { WarningModal } from '../WarningModal';
 import { useState } from 'react';
 import { useMessagesStore } from 'src/stores/unreadMessagesStore';
 import { useFriendsNotificationsStore } from 'src/stores/friendsNotificationsStore';
+import { useNavigationState, useRoute } from '@react-navigation/native';
 
 const getTabIcon = (routeName: string) => {
   switch (routeName) {
@@ -41,12 +42,10 @@ const getTabIcon = (routeName: string) => {
 const TabBarButton = ({
   label,
   onPress,
-  focused,
   navigation
 }: {
   label: string;
   onPress: () => void;
-  focused: boolean;
   navigation: any;
 }) => {
   const IconComponent: React.FC<SvgProps> | null = getTabIcon(label);
@@ -55,6 +54,13 @@ const TabBarButton = ({
   const unreadMessagesCount = useMessagesStore((state) => state.unreadMessagesCount);
   const [isWarningModalVisible, setIsWarningModalVisible] = useState(false);
 
+  const route = useRoute();
+
+  const focused = useNavigationState((state) => {
+    const currentRoute = state.routes[state.index];
+    return currentRoute.name === route.name;
+  });
+
   let currentColor = focused ? Colors.DARK_BLUE : Colors.LIGHT_GRAY;
 
   return (

+ 147 - 0
src/components/TabViewWrapper/index.tsx

@@ -0,0 +1,147 @@
+import React, { useEffect, useState } from 'react';
+import { View, Text, Pressable } from 'react-native';
+import { Tabs, MaterialTabBar, MaterialTabItemProps } from 'react-native-collapsible-tab-view';
+import { Colors } from 'src/theme';
+
+interface RouteType {
+  key: string;
+  title: string;
+}
+
+interface TabViewWrapperProps {
+  routes: RouteType[];
+  renderScene: ({ route }: { route: RouteType }) => React.ReactNode;
+  setIndex: (index: number) => void;
+  selectedIndex: number;
+  lazy?: boolean;
+  renderBadge?: (props: { route: RouteType }) => React.ReactNode;
+}
+
+const CustomTabItem = ({
+  name,
+  label,
+  onLayout,
+  onPress,
+  style,
+  index,
+  renderBadge,
+  selectedIndex,
+  route
+}: MaterialTabItemProps<any> & {
+  renderBadge?: (props: { route: RouteType }) => React.ReactNode;
+  selectedIndex: number;
+  route: RouteType;
+}) => {
+  const isFocused = index === selectedIndex;
+
+  return (
+    <Pressable
+      onPress={() => onPress(name)}
+      onLayout={onLayout}
+      style={() => [
+        style,
+        {
+          opacity: isFocused ? 1 : 0.4,
+          alignItems: 'center',
+          justifyContent: 'center',
+          flex: 1,
+          position: 'relative'
+        }
+      ]}
+    >
+      <Text
+        style={{
+          color: Colors.DARK_BLUE,
+          textAlign: 'center',
+          fontSize: 14,
+          fontWeight: '600',
+          paddingHorizontal: 4
+        }}
+        numberOfLines={2}
+        adjustsFontSizeToFit
+        minimumFontScale={0.8}
+      >
+        {typeof label === 'string' ? label : ''}
+      </Text>
+
+      {renderBadge && (
+        <View style={{ position: 'absolute', top: 4, right: 10 }}>{renderBadge({ route })}</View>
+      )}
+    </Pressable>
+  );
+};
+
+const TabViewWrapper: React.FC<TabViewWrapperProps> = ({
+  routes,
+  renderScene,
+  setIndex,
+  selectedIndex,
+  lazy = false,
+  renderBadge
+}) => {
+  const [hasMounted, setHasMounted] = useState(false);
+
+  useEffect(() => {
+    if (!hasMounted) setHasMounted(true);
+  }, []);
+
+  return (
+    <Tabs.Container
+      initialTabName={!hasMounted ? routes[selectedIndex].key : undefined}
+      headerHeight={0}
+      containerStyle={{ flex: 1 }}
+      headerContainerStyle={{
+        elevation: 0,
+        shadowOpacity: 0,
+        borderBottomWidth: 0
+      }}
+      tabBarHeight={48}
+      onIndexChange={setIndex}
+      lazy={lazy}
+      renderTabBar={(props) => (
+        <MaterialTabBar
+          {...props}
+          TabItemComponent={(tabItemProps) => (
+            <CustomTabItem
+              {...tabItemProps}
+              renderBadge={renderBadge}
+              selectedIndex={selectedIndex}
+              route={routes[tabItemProps.index]}
+            />
+          )}
+          style={{
+            backgroundColor: '#fff',
+            elevation: 0,
+            shadowOpacity: 0,
+            borderBottomWidth: 0
+          }}
+          tabStyle={{
+            minHeight: 48,
+            justifyContent: 'center'
+          }}
+          indicatorStyle={{
+            backgroundColor: Colors.DARK_BLUE,
+            height: 2
+          }}
+        />
+      )}
+    >
+      {routes.map((route) => (
+        <Tabs.Tab key={route.key} name={route.key} label={route.title}>
+          <View
+            style={{
+              flex: 1,
+              width: '100%',
+              paddingTop: 48,
+              paddingHorizontal: '4%'
+            }}
+          >
+            {renderScene({ route })}
+          </View>
+        </Tabs.Tab>
+      ))}
+    </Tabs.Container>
+  );
+};
+
+export default TabViewWrapper;

+ 1 - 0
src/components/index.ts

@@ -22,3 +22,4 @@ export * from './MessagesDot';
 export * from './MapButton';
 export * from './ScaleBar';
 export * from './MultiSelectorCountries';
+export * from './TabViewWrapper';

+ 1 - 1
src/constants/constants.ts

@@ -1,5 +1,5 @@
 import { Platform, StatusBar } from 'react-native';
-import * as FileSystem from 'expo-file-system';
+import * as FileSystem from 'expo-file-system/legacy';
 
 const NOTCH_HEIGHT = 44;
 export const statusBarHeight =

+ 1 - 1
src/database/cacheService/index.ts

@@ -1,4 +1,4 @@
-import * as FileSystem from 'expo-file-system';
+import * as FileSystem from 'expo-file-system/legacy';
 import {
   CACHE_EXPIRATION_DAYS,
   CACHE_MAX_SIZE_MB,

+ 1 - 1
src/database/flagsService/index.ts

@@ -1,4 +1,4 @@
-import * as FileSystem from 'expo-file-system';
+import * as FileSystem from 'expo-file-system/legacy';
 import { API_HOST } from 'src/constants';
 
 const downloadFlag = async (flagId: number) => {

+ 1 - 1
src/database/tilesService/index.ts

@@ -1,4 +1,4 @@
-import * as FileSystem from 'expo-file-system';
+import * as FileSystem from 'expo-file-system/legacy';
 import * as MapLibreRN from '@maplibre/maplibre-react-native';
 
 import { VECTOR_MAP_HOST } from 'src/constants';

+ 4 - 4
src/db/index.ts

@@ -1,5 +1,5 @@
-import * as SQLite from 'expo-sqlite/legacy';
-import * as FileSystem from 'expo-file-system';
+import * as SQLite from 'expo-sqlite';
+import * as FileSystem from 'expo-file-system/legacy';
 import { Asset } from 'expo-asset';
 import { API_HOST } from 'src/constants';
 import { storage } from 'src/storage';
@@ -98,7 +98,7 @@ export async function openDatabases() {
       );
     }
 
-    const openDatabase = (dbName: string) => SQLite.openDatabase(dbName);
+    const openDatabase = (dbName: string) => SQLite.openDatabaseSync(dbName);
     db1 = openDatabase(nmRegionsDBname);
     db2 = openDatabase(darePlacesDBname);
     db3 = openDatabase(countriesDBname);
@@ -146,7 +146,7 @@ export const getCountriesDatabase = async () => {
   return db3;
 };
 
-const openDatabase = (dbName: string) => SQLite.openDatabase(dbName);
+const openDatabase = (dbName: string) => SQLite.openDatabaseSync(dbName);
 
 async function refreshNmDatabase() {
   try {

+ 31 - 48
src/modules/map/regionData.ts

@@ -1,52 +1,35 @@
-import { SQLiteDatabase } from 'expo-sqlite/legacy';
+import { SQLiteDatabase } from 'expo-sqlite';
 
 export const getData = async (
-  db: SQLiteDatabase | null, 
-  regionId: number, 
-  name: string, 
+  db: SQLiteDatabase | null,
+  regionId: number,
+  name: string,
   callback: (data: any, avatars: string[]) => void
-) => {
-  return new Promise<void>((resolve, reject) => {
-    db?.transaction(tx => {
-      tx.executeSql(
-        `SELECT * FROM ${name} WHERE id = ${regionId};`,
-        [],
-        (_, { rows }) => {
-          const regionData = rows._array[0];
-  
-          const avatarPromises = regionData?.visitors_avatars
-            ? JSON.parse(regionData.visitors_avatars)?.map((avatarId: number) => {
-              return new Promise<string>((resolveAvatar, rejectAvatar) => {
-                tx.executeSql(
-                  `SELECT * FROM avatars WHERE id = ${avatarId};`,
-                  [],
-                  (_, { rows }) => resolveAvatar(rows._array[0].data),
-                  (_, error) => {
-                    console.error('error', error);
-                    rejectAvatar(error);
-                    return false;
-                  }
-                );
-              });
-            })
-            : [];
-  
-          Promise.all(avatarPromises)
-            .then(avatars => {
-              callback(regionData, avatars);
-              resolve();
-            })
-            .catch(error => {
-              console.error('Error processing avatars', error);
-              reject(error);
-            });
-        },
-        (_, error) => {
-          console.error('error', error);
-          reject(error);
-          return false;
-        }
-      );
-    });
-  });
+): Promise<void> => {
+  if (!db) throw new Error('Database is null');
+
+  try {
+    const regionRows = await db.getAllAsync<any>(`SELECT * FROM ${name} WHERE id = ?;`, [regionId]);
+
+    const regionData = regionRows[0] ?? null;
+
+    const avatarIds: number[] = regionData?.visitors_avatars
+      ? JSON.parse(regionData.visitors_avatars)
+      : [];
+
+    const avatars: string[] = [];
+    for (const avatarId of avatarIds) {
+      const avatarRows = await db.getAllAsync<any>(`SELECT * FROM avatars WHERE id = ?;`, [
+        avatarId
+      ]);
+      if (avatarRows.length > 0) {
+        avatars.push(avatarRows[0].data);
+      }
+    }
+
+    setTimeout(() => callback(regionData, avatars), 0);
+  } catch (error) {
+    console.error('Error in getData:', error);
+    throw error;
+  }
 };

+ 21 - 19
src/screens/InAppScreens/MapScreen/CountryViewScreen/index.tsx

@@ -6,7 +6,6 @@ import { Button, HorizontalTabView, Loading, Modal as ReactModal } from 'src/com
 import { CommonActions, useFocusEffect } from '@react-navigation/native';
 import { Colors } from 'src/theme';
 import { ScrollView } from 'react-native-gesture-handler';
-import { TabView, TabBar } from 'react-native-tab-view';
 
 import { styles as ButtonStyles } from 'src/components/RegionPopup/style';
 import { usePostSetToggleItem } from '@api/series';
@@ -312,9 +311,9 @@ const CountryViewScreen: FC<Props> = ({ navigation, route }) => {
           </View>
         </TouchableOpacity>
 
-        <View style={styles.wrapper}>
+        <View style={{ gap: 16 }}>
           {regionData?.visited && !disabled && (
-            <View style={styles.durationContainer}>
+            <View style={[styles.durationContainer, styles.margin]}>
               <View style={styles.durationItem}>
                 {renderDurationIcon(regionData.slow11)}
                 <Text style={styles.visitDuration}>11+ days</Text>
@@ -329,7 +328,7 @@ const CountryViewScreen: FC<Props> = ({ navigation, route }) => {
               </View>
             </View>
           )}
-          <View style={styles.nameContainer}>
+          <View style={[styles.nameContainer, styles.margin]}>
             <Text style={styles.title}>{name}</Text>
             <View style={ButtonStyles.btnContainer}>
               {/* {regionData?.visited && !disabled ? (
@@ -386,9 +385,9 @@ const CountryViewScreen: FC<Props> = ({ navigation, route }) => {
             </View>
           </View>
 
-          <View style={styles.divider} />
+          <View style={[styles.divider, styles.margin]} />
 
-          <View style={styles.stats}>
+          <View style={[styles.stats, styles.margin]}>
             {(data?.data.users_from_country_count ?? 0 > 0) ? (
               <TouchableOpacity
                 style={[styles.statItem, { justifyContent: 'flex-start' }]}
@@ -487,7 +486,7 @@ const CountryViewScreen: FC<Props> = ({ navigation, route }) => {
             )}
           </View>
 
-          <View style={[styles.divider, { marginBottom: 8 }]} />
+          <View style={[styles.divider, styles.margin, { marginBottom: 8 }]} />
 
           {/* <TabView
             navigationState={{ index, routes: countryRoutes }}
@@ -639,23 +638,26 @@ const CountryViewScreen: FC<Props> = ({ navigation, route }) => {
             <> */}
           {series.length > 0 ? (
             <>
-              <Text style={styles.travelSeriesTitle}>TRAVEL SERIES</Text>
+              <Text style={[styles.travelSeriesTitle, styles.margin]}>TRAVEL SERIES</Text>
               <HorizontalTabView
                 index={indexSeries}
                 setIndex={setIndexSeries}
                 routes={routes}
-                renderScene={({ route }: { route: SeriesGroup }) => <View style={{ height: 0 }} />}
-              />
-              <TravelSeriesList
-                series={series}
-                indexSeries={indexSeries}
-                routes={routes}
-                handleCheckboxChange={handleCheckboxChange}
-                setIsInfoModalVisible={setIsInfoModalVisible}
-                setInfoItem={setInfoItem}
-                disabled={disabled}
-                includeAll={false}
+                renderScene={({ route }: { route: any }) => <View style={{ height: 0 }} />}
+                tabBarStyle={{ paddingLeft: '5%' }}
               />
+              <View style={styles.margin}>
+                <TravelSeriesList
+                  series={series}
+                  indexSeries={indexSeries}
+                  routes={routes}
+                  handleCheckboxChange={handleCheckboxChange}
+                  setIsInfoModalVisible={setIsInfoModalVisible}
+                  setInfoItem={setInfoItem}
+                  disabled={disabled}
+                  includeAll={false}
+                />
+              </View>
             </>
           ) : null}
           {/* </>

+ 2 - 1
src/screens/InAppScreens/MapScreen/RegionViewScreen/TravelSeriesList/styles.tsx

@@ -46,7 +46,8 @@ export const styles = StyleSheet.create({
     justifyContent: 'center'
   },
   travelSeriesList: {
-    paddingBottom: 16
+    paddingBottom: 16,
+    paddingTop: 40
   },
   travelSeriesText: {
     fontSize: 12,

+ 23 - 18
src/screens/InAppScreens/MapScreen/RegionViewScreen/index.tsx

@@ -404,8 +404,10 @@ const RegionViewScreen: FC<Props> = ({ navigation, route }) => {
           </View>
         </TouchableOpacity>
 
-        <View style={styles.wrapper}>
-          <View style={{ flexDirection: 'row', gap: 8, justifyContent: 'flex-end' }}>
+        <View style={{ gap: 16 }}>
+          <View
+            style={[styles.margin, { flexDirection: 'row', gap: 8, justifyContent: 'flex-end' }]}
+          >
             {regionData?.visited && regionData?.first_visit_year > 1 && !disabled && (
               <View style={styles.infoContent}>
                 <CalendarSvg height={18} width={18} fill={Colors.DARK_BLUE} />
@@ -422,7 +424,7 @@ const RegionViewScreen: FC<Props> = ({ navigation, route }) => {
               </View>
             )}
           </View>
-          <View style={styles.nameContainer}>
+          <View style={[styles.nameContainer, styles.margin]}>
             <Text style={styles.title}>{name[0]}</Text>
             <View style={ButtonStyles.btnContainer}>
               {regionData?.visited &&
@@ -470,11 +472,11 @@ const RegionViewScreen: FC<Props> = ({ navigation, route }) => {
             </View>
           </View>
 
-          <Text style={styles.subtitle}>{name[1]}</Text>
+          <Text style={[styles.subtitle, styles.margin]}>{name[1]}</Text>
 
-          <View style={styles.divider} />
+          <View style={[styles.divider, styles.margin]} />
 
-          <View style={styles.stats}>
+          <View style={[styles.stats, styles.margin]}>
             {(data?.data.users_from_region_count ?? 0 > 0) ? (
               <TouchableOpacity
                 style={[styles.statItem, { justifyContent: 'flex-start' }]}
@@ -573,27 +575,30 @@ const RegionViewScreen: FC<Props> = ({ navigation, route }) => {
             )}
           </View>
 
-          <View style={[styles.divider, { marginBottom: 8 }]} />
+          <View style={[styles.divider, styles.margin, { marginBottom: 8 }]} />
 
           {series.length > 0 ? (
             <>
-              <Text style={styles.travelSeriesTitle}>TRAVEL SERIES</Text>
+              <Text style={[styles.travelSeriesTitle, styles.margin]}>TRAVEL SERIES</Text>
               <HorizontalTabView
                 index={indexSeries}
                 setIndex={setIndexSeries}
                 routes={routes}
-                renderScene={({ route }: { route: SeriesGroup }) => <View style={{ height: 0 }} />}
+                renderScene={({ route }: { route: any }) => <View style={{ height: 0 }} />}
                 maxTabHeight={50}
+                tabBarStyle={{ paddingLeft: '5%' }}
               />
-              <TravelSeriesList
-                series={series}
-                indexSeries={indexSeries}
-                routes={routes}
-                handleCheckboxChange={handleCheckboxChange}
-                setIsInfoModalVisible={setIsInfoModalVisible}
-                setInfoItem={setInfoItem}
-                disabled={disabled}
-              />
+              <View style={styles.margin}>
+                <TravelSeriesList
+                  series={series}
+                  indexSeries={indexSeries}
+                  routes={routes}
+                  handleCheckboxChange={handleCheckboxChange}
+                  setIsInfoModalVisible={setIsInfoModalVisible}
+                  setInfoItem={setInfoItem}
+                  disabled={disabled}
+                />
+              </View>
             </>
           ) : null}
         </View>

+ 4 - 0
src/screens/InAppScreens/MapScreen/RegionViewScreen/styles.tsx

@@ -61,6 +61,10 @@ export const styles = StyleSheet.create({
     marginRight: '5%',
     gap: 16
   },
+  margin: {
+    marginLeft: '5%',
+    marginRight: '5%'
+  },
   divider: {
     height: 1,
     width: '100%',

+ 17 - 35
src/screens/InAppScreens/MapScreen/UniversalSearch/index.tsx

@@ -1,16 +1,15 @@
 import React, { useCallback, useEffect, useRef, useState } from 'react';
 import { View, Text, TouchableOpacity, Image, Dimensions } from 'react-native';
-import { TabView, TabBar } from 'react-native-tab-view';
 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, WarningModal } from 'src/components';
 import MessagesDot from 'src/components/MessagesDot';
 import ActionSheet, { ActionSheetRef } from 'react-native-actions-sheet';
+import TabViewWrapper from 'src/components/TabViewWrapper';
 
 export function TabViewDelayed({
   children,
@@ -180,7 +179,6 @@ const SearchModal = ({
               itemVisiblePercentThreshold: 50,
               minimumViewTime: 1000
             }}
-            estimatedItemSize={45}
             data={searchData?.[route.key]}
             renderItem={renderItem}
             keyExtractor={(item) => item.id.toString()}
@@ -215,38 +213,22 @@ const SearchModal = ({
       defaultOverlayOpacity={0.5}
     >
       <View style={[styles.modalContainer, { height: modalHeight }]}>
-        <TabViewDelayed waitBeforeShow={300}>
-          <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>
-                )}
-                renderBadge={({ route }) =>
-                  searchData?.[route.key] && searchData?.[route.key].length > 0 ? (
-                    <MessagesDot messagesCount={searchData?.[route.key].length} top={4} />
-                  ) : null
-                }
-              />
-            )}
-          />
-        </TabViewDelayed>
+        {searchVisible && (
+          <TabViewDelayed waitBeforeShow={300}>
+            <TabViewWrapper
+              routes={routes}
+              renderScene={renderScene}
+              setIndex={setIndex}
+              selectedIndex={index}
+              renderBadge={({ route }) =>
+                searchData?.[route.key] && searchData?.[route.key].length > 0 ? (
+                  <MessagesDot messagesCount={searchData?.[route.key].length} top={4} />
+                ) : null
+              }
+              lazy={false}
+            />
+          </TabViewDelayed>
+        )}
       </View>
     </ActionSheet>
   );

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

@@ -56,7 +56,6 @@ export const styles = StyleSheet.create({
   modalContainer: {
     backgroundColor: Colors.WHITE,
     borderRadius: 15,
-    paddingHorizontal: '4%',
     gap: 12,
     paddingVertical: 16,
     height: '80%'

+ 7 - 2
src/screens/InAppScreens/MapScreen/index.tsx

@@ -34,6 +34,7 @@ import { AvatarWithInitials, EditNmModal, WarningModal } from 'src/components';
 import { API_HOST, VECTOR_MAP_HOST } from 'src/constants';
 import { NAVIGATION_PAGES } from 'src/types';
 import Animated, {
+  configureReanimatedLogger,
   Easing,
   useAnimatedStyle,
   useSharedValue,
@@ -47,13 +48,13 @@ import {
   refreshDatabases
 } from 'src/db';
 import { fetchUserData, fetchUserDataDare, useGetListRegionsQuery } from '@api/regions';
-import { SQLiteDatabase } from 'expo-sqlite/legacy';
+import { SQLiteDatabase } from 'expo-sqlite';
 import { useFocusEffect } from '@react-navigation/native';
 import { useGetUniversalSearch } from '@api/search';
 import { fetchCountryUserData, useGetListCountriesQuery } from '@api/countries';
 import SearchModal from './UniversalSearch';
 import EditModal from '../TravelsScreen/Components/EditSlowModal';
-import * as FileSystem from 'expo-file-system';
+import * as FileSystem from 'expo-file-system/legacy';
 
 import CheckSvg from 'assets/icons/mark.svg';
 import moment from 'moment';
@@ -95,6 +96,10 @@ import { SheetManager } from 'react-native-actions-sheet';
 import MultipleSeriesModal from './MultipleSeriesModal';
 import { useSubscription } from 'src/screens/OfflineMapsScreen/useSubscription';
 
+configureReanimatedLogger({
+  strict: false
+});
+
 const clusteredUsersIcon = require('assets/icons/icon-clustered-users.png');
 const defaultUserAvatar = require('assets/icon-user-share-location-solid.png');
 const logo = require('assets/logo-world.png');

+ 10 - 10
src/screens/InAppScreens/MessagesScreen/ChatScreen/index.tsx

@@ -70,8 +70,8 @@ import { usePushNotification } from 'src/contexts/PushNotificationContext';
 import ReactionsListModal from '../Components/ReactionsListModal';
 import { dismissChatNotifications, isMessageEdited } from '../utils';
 import { useMessagesStore } from 'src/stores/unreadMessagesStore';
-import FileViewer from 'react-native-file-viewer';
-import * as FileSystem from 'expo-file-system';
+// import FileViewer from 'react-native-file-viewer';
+import * as FileSystem from 'expo-file-system/legacy';
 import ImageView from 'better-react-native-image-viewing';
 import * as MediaLibrary from 'expo-media-library';
 
@@ -513,10 +513,10 @@ const ChatScreen = ({ route }: { route: any }) => {
 
       const fileExists = await FileSystem.getInfoAsync(fileUri);
       if (fileExists.exists && fileExists.size > 1024) {
-        await FileViewer.open(fileUri, {
-          showOpenWithDialog: true,
-          showAppsSuggestions: true
-        });
+        // await FileViewer.open(fileUri, {
+        //   showOpenWithDialog: true,
+        //   showAppsSuggestions: true
+        // });
 
         return;
       }
@@ -525,10 +525,10 @@ const ChatScreen = ({ route }: { route: any }) => {
         headers: { Nmtoken: token, 'App-Version': APP_VERSION, Platform: Platform.OS }
       });
 
-      await FileViewer.open(localUri, {
-        showOpenWithDialog: true,
-        showAppsSuggestions: true
-      });
+      // await FileViewer.open(localUri, {
+      //   showOpenWithDialog: true,
+      //   showAppsSuggestions: true
+      // });
     } catch (err) {
       console.warn('openFileInApp error:', err);
       Alert.alert('Cannot open file', 'No application found to open this file.');

+ 49 - 27
src/screens/InAppScreens/MessagesScreen/Components/AttachmentsModal.tsx

@@ -7,7 +7,7 @@ import { WarningProps } from '../types';
 import { useSafeAreaInsets } from 'react-native-safe-area-context';
 import { usePostReportConversationMutation } from '@api/chat';
 import * as ImagePicker from 'expo-image-picker';
-import * as DocumentPicker from 'react-native-document-picker';
+// import * as DocumentPicker from 'react-native-document-picker';
 
 import { MaterialCommunityIcons } from '@expo/vector-icons';
 import RouteB from './RouteB';
@@ -16,7 +16,9 @@ import MegaphoneIcon from 'assets/icons/messages/megaphone.svg';
 import LocationIcon from 'assets/icons/messages/location.svg';
 import CameraIcon from 'assets/icons/messages/camera.svg';
 import ImagesIcon from 'assets/icons/messages/images.svg';
+import PollIcon from 'assets/icons/messages/poll.svg';
 import { storage, StoreType } from 'src/storage';
+import RouteC from './RouteC';
 
 const AttachmentsModal = () => {
   const insets = useSafeAreaInsets();
@@ -124,32 +126,32 @@ const AttachmentsModal = () => {
     if (!chatData) return;
 
     try {
-      const res = await DocumentPicker.pick({
-        type: [DocumentPicker.types.allFiles],
-        allowMultiSelection: false
-      });
-
-      let file = {
-        uri: res[0].uri,
-        name: res[0].name,
-        type: res[0].type
-      };
-
-      if ((file.name && !file.name.includes('.')) || !file.type) {
-        file = {
-          ...file,
-          type: file.type || 'application/octet-stream'
-        };
-      }
-
-      if (chatData.onSendFile) {
-        chatData.onSendFile([file]);
-      }
+      // const res = await DocumentPicker.pick({
+      //   type: [DocumentPicker.types.allFiles],
+      //   allowMultiSelection: false
+      // });
+
+      // let file = {
+      //   uri: res[0].uri,
+      //   name: res[0].name,
+      //   type: res[0].type
+      // };
+
+      // if ((file.name && !file.name.includes('.')) || !file.type) {
+      //   file = {
+      //     ...file,
+      //     type: file.type || 'application/octet-stream'
+      //   };
+      // }
+
+      // if (chatData.onSendFile) {
+      //   chatData.onSendFile([file]);
+      // }
     } catch (err) {
-      if (DocumentPicker.isCancel(err)) {
-      } else {
-        console.warn('DocumentPicker error:', err);
-      }
+      // if (DocumentPicker.isCancel(err)) {
+      // } else {
+      //   console.warn('DocumentPicker error:', err);
+      // }
     }
 
     SheetManager.hide('chat-attachments');
@@ -195,6 +197,20 @@ const AttachmentsModal = () => {
             <Text style={styles.optionLabel}>Live</Text>
           </TouchableOpacity> */}
 
+          {
+            chatDataRef.current?.isGroup ? (
+              <TouchableOpacity
+              style={styles.optionItem}
+              onPress={() => {
+                router?.navigate('route-c');
+              }}
+            >
+              <PollIcon height={36} />
+              <Text style={styles.optionLabel}>Poll</Text>
+            </TouchableOpacity>
+            ) : null
+          }
+
           {!chatDataRef.current?.isGroup ? (
             <TouchableOpacity style={styles.optionItem} onPress={handleReport}>
               <MegaphoneIcon fill={Colors.RED} width={36} height={36} />
@@ -218,7 +234,12 @@ const AttachmentsModal = () => {
     {
       name: 'route-b',
       component: RouteB,
-      params: { onSendLocation: data?.onSendLocation, insetsBottom: insets.bottom }
+      params: { onSendLocation: data?.onSendLocation, insetsBottom: insets.bottom } as any
+    },
+    {
+      name: 'route-c',
+      component: RouteC,
+      params: { onSendPoll: data?.onSendLocation, insetsBottom: insets.bottom } as any
     }
   ];
 
@@ -229,6 +250,7 @@ const AttachmentsModal = () => {
         backgroundColor: Colors.FILL_LIGHT
       }}
       enableRouterBackNavigation={true}
+      keyboardHandlerEnabled={false}
       routes={routes}
       initialRoute="route-a"
       defaultOverlayOpacity={0}

+ 1 - 1
src/screens/InAppScreens/MessagesScreen/Components/RenderMessageImage.tsx

@@ -1,6 +1,6 @@
 import React, { useState, useEffect } from 'react';
 import { View, ActivityIndicator, TouchableOpacity, Platform, Image } from 'react-native';
-import * as FileSystem from 'expo-file-system';
+import * as FileSystem from 'expo-file-system/legacy';
 import { Colors } from 'src/theme';
 import { CACHED_ATTACHMENTS_DIR } from 'src/constants/constants';
 import { API_HOST, APP_VERSION } from 'src/constants';

+ 1 - 1
src/screens/InAppScreens/MessagesScreen/Components/renderMessageVideo.tsx

@@ -2,7 +2,7 @@ import React, { useState, useEffect, useRef } from 'react';
 import { View, ActivityIndicator, TouchableOpacity, Platform } from 'react-native';
 import { ResizeMode, Video } from 'expo-av';
 import { MaterialCommunityIcons } from '@expo/vector-icons';
-import * as FileSystem from 'expo-file-system';
+import * as FileSystem from 'expo-file-system/legacy';
 import { Colors } from 'src/theme';
 import { CACHED_ATTACHMENTS_DIR } from 'src/constants/constants';
 import { API_HOST, APP_VERSION } from 'src/constants';

+ 10 - 10
src/screens/InAppScreens/MessagesScreen/GroupChatScreen/index.tsx

@@ -78,8 +78,8 @@ import { usePushNotification } from 'src/contexts/PushNotificationContext';
 import ReactionsListModal from '../Components/ReactionsListModal';
 import { dismissChatNotifications, isMessageEdited } from '../utils';
 import { useMessagesStore } from 'src/stores/unreadMessagesStore';
-import FileViewer from 'react-native-file-viewer';
-import * as FileSystem from 'expo-file-system';
+// import FileViewer from 'react-native-file-viewer';
+import * as FileSystem from 'expo-file-system/legacy';
 import ImageView from 'better-react-native-image-viewing';
 import * as MediaLibrary from 'expo-media-library';
 
@@ -528,10 +528,10 @@ const GroupChatScreen = ({ route }: { route: any }) => {
 
       const fileExists = await FileSystem.getInfoAsync(fileUri);
       if (fileExists.exists && fileExists.size > 1024) {
-        await FileViewer.open(fileUri, {
-          showOpenWithDialog: true,
-          showAppsSuggestions: true
-        });
+        // await FileViewer.open(fileUri, {
+        //   showOpenWithDialog: true,
+        //   showAppsSuggestions: true
+        // });
 
         return;
       }
@@ -540,10 +540,10 @@ const GroupChatScreen = ({ route }: { route: any }) => {
         headers: { Nmtoken: token, 'App-Version': APP_VERSION, Platform: Platform.OS }
       });
 
-      await FileViewer.open(localUri, {
-        showOpenWithDialog: true,
-        showAppsSuggestions: true
-      });
+      // await FileViewer.open(localUri, {
+      //   showOpenWithDialog: true,
+      //   showAppsSuggestions: true
+      // });
     } catch (err) {
       console.warn('openFileInApp error:', err);
       Alert.alert('Cannot open file', 'No application found to open this file.');

+ 3 - 3
src/screens/InAppScreens/MessagesScreen/index.tsx

@@ -571,7 +571,9 @@ const MessagesScreen = () => {
         index={index}
         setIndex={setIndex}
         routes={routes}
-        tabBarStyle={{ paddingHorizontal: '4%' }}
+        tabBarStyle={{ paddingLeft: '5%' }}
+        sceneStyles={{ paddingHorizontal: 0 }}
+        maxTabHeight={50}
         renderScene={({ route }: { route: { key: keyof typeof filteredChats } }) =>
           route.key === 'blocked' ? (
             <FlashList
@@ -583,7 +585,6 @@ const MessagesScreen = () => {
               data={filteredChats[route.key]}
               renderItem={renderBlockedItem}
               keyExtractor={(item, index) => `${item.id}-${index}`}
-              estimatedItemSize={50}
             />
           ) : (
             <FlashList
@@ -595,7 +596,6 @@ const MessagesScreen = () => {
               data={filteredChats[route.key]}
               renderItem={renderChatItem}
               keyExtractor={(item, index) => `${item.uid}-${index}`}
-              estimatedItemSize={78}
               extraData={typingUsers}
             />
           )

+ 13 - 8
src/screens/InAppScreens/ProfileScreen/MyFriendsScreen/index.tsx

@@ -1,6 +1,6 @@
 import { NavigationProp, useFocusEffect } from '@react-navigation/native';
 import React, { FC, useCallback, useEffect, useState } from 'react';
-import { ActivityIndicator } from 'react-native';
+import { ActivityIndicator, View } from 'react-native';
 import { Header, HorizontalTabView, Loading, PageWrapper, WarningModal } from 'src/components';
 
 import { Colors } from 'src/theme';
@@ -16,6 +16,7 @@ import {
 } from '@api/friends';
 import { FriendsProfile } from './FriendsProfile';
 import { useFriendsNotificationsStore } from 'src/stores/friendsNotificationsStore';
+import { SafeAreaView } from 'react-native-safe-area-context';
 
 type Props = {
   navigation: NavigationProp<any>;
@@ -218,16 +219,21 @@ const MyFriendsScreen: FC<Props> = ({ navigation }) => {
     isLoading ? <ActivityIndicator size="large" color={Colors.DARK_BLUE} /> : null;
 
   return (
-    <PageWrapper>
-      <Header
-        label={'Friends'}
-        rightElement={<FilterButton onPress={() => setModalVisible(!isModalVisible)} />}
-      />
+    <SafeAreaView style={{ height: '100%' }} edges={['top']}>
+      <View style={{ marginLeft: '5%', marginRight: '5%' }}>
+        <Header
+          label={'Friends'}
+          rightElement={<FilterButton onPress={() => setModalVisible(!isModalVisible)} />}
+        />
+      </View>
+
       <HorizontalTabView
         index={index}
         setIndex={setIndex}
         routes={routes}
         withNotification={isNotificationActive}
+        tabBarStyle={{ paddingLeft: '5%' }}
+        maxTabHeight={50}
         renderScene={({ route }: { route: Routes }) => (
           <>
             <FlashList
@@ -236,7 +242,6 @@ const MyFriendsScreen: FC<Props> = ({ navigation }) => {
                 itemVisiblePercentThreshold: 50,
                 minimumViewTime: 1000
               }}
-              estimatedItemSize={50}
               data={users[route.key]}
               renderItem={({ item, index }) => (
                 <FriendsProfile
@@ -316,7 +321,7 @@ const MyFriendsScreen: FC<Props> = ({ navigation }) => {
         }}
         countriesData={masterCountries}
       />
-    </PageWrapper>
+    </SafeAreaView>
   );
 };
 

+ 34 - 26
src/screens/InAppScreens/TravellersScreen/Components/Profile.tsx

@@ -4,7 +4,7 @@ import { Image } from 'expo-image';
 import { useNavigation } from '@react-navigation/native';
 import * as FileSystem from 'expo-file-system';
 import Tooltip from 'react-native-walkthrough-tooltip';
-import { Grayscale } from 'react-native-color-matrix-image-filters';
+import { FilterImage } from 'react-native-svg/filter-image';
 
 import { ProfileStyles, ScoreStyles, TBTStyles } from './styles';
 
@@ -252,20 +252,22 @@ export const Profile: FC<Props> = ({
             <View style={ProfileStyles.avatarContainer}>
               <View style={{ position: 'relative' }}>
                 {avatar && avatarBaseUri ? (
-                  <Grayscale amount={index === -1 ? 1 : 0}>
-                    <Image
-                      style={adaptiveStyle(ProfileStyles.profileAvatar, {})}
-                      source={{ uri: avatarBaseUri + avatar }}
-                    />
-                  </Grayscale>
+                  <FilterImage
+                    source={{ uri: avatarBaseUri + avatar }}
+                    style={[
+                      adaptiveStyle(ProfileStyles.profileAvatar, {}),
+                      {
+                        filter: index === -1 ? 'grayscale(100%)' : 'grayscale(0)'
+                      }
+                    ]}
+                  />
                 ) : homebase_flag ? (
-                  <Grayscale amount={index === -1 ? 1 : 0}>
-                    <AvatarWithInitials
-                      text={`${first_name[0] ?? ''}${last_name[0] ?? ''}`}
-                      flag={flagBaseUri + homebase_flag}
-                      size={48}
-                    />
-                  </Grayscale>
+                  <AvatarWithInitials
+                    text={`${first_name[0] ?? ''}${last_name[0] ?? ''}`}
+                    flag={flagBaseUri + homebase_flag}
+                    size={48}
+                    grayscale={index === -1}
+                  />
                 ) : null}
                 <View
                   style={{
@@ -295,20 +297,26 @@ export const Profile: FC<Props> = ({
                     Age: {date_of_birth ?? ''}
                   </Text>
                   {homebase_flag && (
-                    <Grayscale amount={index === -1 ? 1 : 0}>
-                      <Image
-                        source={{ uri: flagBaseUri + homebase_flag }}
-                        style={adaptiveStyle(ProfileStyles.countryFlag, {})}
-                      />
-                    </Grayscale>
+                    <FilterImage
+                      source={{ uri: flagBaseUri + homebase_flag }}
+                      style={[
+                        adaptiveStyle(ProfileStyles.countryFlag, {}),
+                        {
+                          filter: index === -1 ? 'grayscale(100%)' : 'grayscale(0)'
+                        }
+                      ]}
+                    />
                   )}
                   {homebase2_flag && homebase2_flag !== homebase_flag ? (
-                    <Grayscale amount={index === -1 ? 1 : 0}>
-                      <Image
-                        source={{ uri: flagBaseUri + homebase2_flag }}
-                        style={adaptiveStyle([ProfileStyles.countryFlag, { marginLeft: -15 }], {})}
-                      />
-                    </Grayscale>
+                    <FilterImage
+                      source={{ uri: flagBaseUri + homebase2_flag }}
+                      style={[
+                        adaptiveStyle([ProfileStyles.countryFlag, { marginLeft: -15 }], {}),
+                        {
+                          filter: index === -1 ? 'grayscale(100%)' : 'grayscale(0)'
+                        }
+                      ]}
+                    />
                   ) : null}
                   <View style={adaptiveStyle(ProfileStyles.badgesWrapper, {})}>
                     {auth ? <TickIcon isBlackAndWhite={index === -1} /> : null}

+ 1 - 1
src/screens/InAppScreens/TravellersScreen/Components/SeriesRankingItem.tsx

@@ -1,6 +1,6 @@
 import React from 'react';
 import { Text, TouchableOpacity, View, Image, Dimensions } from 'react-native';
-import * as FileSystem from 'expo-file-system';
+import * as FileSystem from 'expo-file-system/legacy';
 
 import { AvatarWithInitials } from '../../../../components';
 

+ 1 - 1
src/screens/InAppScreens/TravellersScreen/Components/TriumphItem.tsx

@@ -1,6 +1,6 @@
 import React, { useCallback } from 'react';
 import { View, Image, TouchableOpacity, Text } from 'react-native';
-import * as FileSystem from 'expo-file-system';
+import * as FileSystem from 'expo-file-system/legacy';
 
 import { AvatarWithInitials } from 'src/components';
 import { API_HOST } from 'src/constants';

+ 12 - 5
src/screens/InAppScreens/TravellersScreen/SeriesRankingListScreen/index.tsx

@@ -1,5 +1,5 @@
 import React, { Dispatch, SetStateAction, useCallback, useEffect, useState } from 'react';
-import { ActivityIndicator } from 'react-native';
+import { ActivityIndicator, View } from 'react-native';
 import { useNavigation } from '@react-navigation/native';
 import { FlashList } from '@shopify/flash-list';
 
@@ -18,6 +18,7 @@ import { NAVIGATION_PAGES } from 'src/types';
 import { StoreType, storage } from 'src/storage';
 import { Colors } from 'src/theme';
 import { SeriesRanking } from '../utils/types';
+import { SafeAreaView } from 'react-native-safe-area-context';
 
 const SeriesRankingListScreen = ({ route }: { route: any }) => {
   const name = route.params?.name;
@@ -43,25 +44,31 @@ const SeriesRankingListScreen = ({ route }: { route: any }) => {
   if (loading) return <Loading />;
 
   return (
-    <PageWrapper style={{ flex: 1 }}>
-      <Header label={name} />
+    <SafeAreaView style={{ height: '100%', flex: 1 }} edges={['top']}>
+      <View style={{ marginLeft: '5%', marginRight: '5%' }}>
+        <Header label={name} />
+      </View>
       {series ? (
         <HorizontalTabView
           index={index}
           setIndex={setIndex}
           routes={routes}
+          tabBarStyle={{ paddingLeft: '5%' }}
+          maxTabHeight={50}
           renderScene={({ route }: { route: { key: string; title: string } }) => (
             <SeriesList groupId={+route.key} setModalType={setModalType} />
           )}
           lazy={true}
         />
       ) : (
-        <SeriesList groupId={id} setModalType={setModalType} />
+        <View style={{ marginLeft: '5%', marginRight: '5%' }}>
+          <SeriesList groupId={id} setModalType={setModalType} />
+        </View>
       )}
       {modalType && (
         <WarningModal type={modalType} isVisible={true} onClose={() => setModalType(null)} />
       )}
-    </PageWrapper>
+    </SafeAreaView>
   );
 };
 

+ 8 - 3
src/screens/InAppScreens/TravellersScreen/UNMasters/index.tsx

@@ -25,6 +25,7 @@ import { getFontSize } from '../../../../utils';
 import { useNavigation } from '@react-navigation/native';
 import { NAVIGATION_PAGES } from 'src/types';
 import { FlashList } from '@shopify/flash-list';
+import { SafeAreaView } from 'react-native-safe-area-context';
 
 const UNMastersScreen = () => {
   const [index, setIndex] = useState(0);
@@ -51,18 +52,22 @@ const UNMastersScreen = () => {
   if (loading) return <Loading />;
 
   return (
-    <PageWrapper>
-      <Header label={'UN Masters'} />
+    <SafeAreaView style={{ height: '100%' }} edges={['top']}>
+      <View style={{ marginLeft: '5%', marginRight: '5%' }}>
+        <Header label={'UN Masters'} />
+      </View>
       <HorizontalTabView
         index={index}
         setIndex={setIndex}
         lazy={true}
         routes={routes}
+        tabBarStyle={{ paddingLeft: '5%' }}
+        maxTabHeight={50}
         renderScene={({ route }: { route: { key: string; title: string } }) => (
           <UNMastersList type={route.key} navigation={navigation} />
         )}
       />
-    </PageWrapper>
+    </SafeAreaView>
   );
 };
 

+ 56 - 56
src/screens/InAppScreens/TravelsScreen/EventScreen/index.tsx

@@ -14,9 +14,9 @@ import {
 import { styles } from './styles';
 import { CommonActions, useFocusEffect, useNavigation } from '@react-navigation/native';
 import { Colors } from 'src/theme';
-import FileViewer from 'react-native-file-viewer';
-import * as FileSystem from 'expo-file-system';
-import * as DocumentPicker from 'react-native-document-picker';
+// import FileViewer from 'react-native-file-viewer';
+import * as FileSystem from 'expo-file-system/legacy';
+// import * as DocumentPicker from 'react-native-document-picker';
 import * as ImagePicker from 'expo-image-picker';
 
 import { ScrollView } from 'react-native-gesture-handler';
@@ -298,10 +298,10 @@ const EventScreen = ({ route }: { route: any }) => {
 
       const fileExists = await FileSystem.getInfoAsync(fileUri);
       if (fileExists.exists && fileExists.size > 1024) {
-        await FileViewer.open(fileUri, {
-          showOpenWithDialog: true,
-          showAppsSuggestions: true
-        });
+        // await FileViewer.open(fileUri, {
+        //   showOpenWithDialog: true,
+        //   showAppsSuggestions: true
+        // });
 
         return;
       }
@@ -321,10 +321,10 @@ const EventScreen = ({ route }: { route: any }) => {
         headers: { Nmtoken: token, 'App-Version': APP_VERSION, Platform: Platform.OS }
       });
 
-      await FileViewer.open(localUri, {
-        showOpenWithDialog: true,
-        showAppsSuggestions: true
-      });
+      // await FileViewer.open(localUri, {
+      //   showOpenWithDialog: true,
+      //   showAppsSuggestions: true
+      // });
     } catch (error) {
       console.error('Error previewing document:', error);
     } finally {
@@ -338,53 +338,53 @@ const EventScreen = ({ route }: { route: any }) => {
 
   const handleUploadFile = useCallback(async () => {
     try {
-      const response = await DocumentPicker.pick({
-        type: [DocumentPicker.types.allFiles],
-        allowMultiSelection: true
-      });
+      // const response = await DocumentPicker.pick({
+      //   type: [DocumentPicker.types.allFiles],
+      //   allowMultiSelection: true
+      // });
 
       setIsUploading(true);
-      for (const res of response) {
-        let file: any = {
-          uri: res.uri,
-          name: res.name,
-          type: res.type
-        };
-
-        if ((file.name && !file.name.includes('.')) || !file.type) {
-          file = {
-            ...file,
-            type: file.type || 'application/octet-stream'
-          };
-        }
-
-        await uploadTempFile(
-          {
-            token,
-            file,
-            onUploadProgress: (progressEvent) => {
-              // if (progressEvent.lengthComputable) {
-              //   const progress = Math.round(
-              //     (progressEvent.loaded / (progressEvent.total ?? 100)) * 100
-              //   );
-              //   setUploadProgress((prev) => ({ ...prev, [file!.uri]: progress }));
-              // }
-            }
-          },
-          {
-            onSuccess: (result) => {
-              setMyTempFiles((prev) => [
-                { ...result, type: 1, description: '', isSending: false },
-                ...prev
-              ]);
-              setIsUploading(false);
-            },
-            onError: (error) => {
-              console.error('Upload error:', error);
-            }
-          }
-        );
-      }
+      // for (const res of response) {
+      //   let file: any = {
+      //     uri: res.uri,
+      //     name: res.name,
+      //     type: res.type
+      //   };
+
+      //   if ((file.name && !file.name.includes('.')) || !file.type) {
+      //     file = {
+      //       ...file,
+      //       type: file.type || 'application/octet-stream'
+      //     };
+      //   }
+
+      //   await uploadTempFile(
+      //     {
+      //       token,
+      //       file,
+      //       onUploadProgress: (progressEvent) => {
+      //         // if (progressEvent.lengthComputable) {
+      //         //   const progress = Math.round(
+      //         //     (progressEvent.loaded / (progressEvent.total ?? 100)) * 100
+      //         //   );
+      //         //   setUploadProgress((prev) => ({ ...prev, [file!.uri]: progress }));
+      //         // }
+      //       }
+      //     },
+      //     {
+      //       onSuccess: (result) => {
+      //         setMyTempFiles((prev) => [
+      //           { ...result, type: 1, description: '', isSending: false },
+      //           ...prev
+      //         ]);
+      //         setIsUploading(false);
+      //       },
+      //       onError: (error) => {
+      //         console.error('Upload error:', error);
+      //       }
+      //     }
+      //   );
+      // }
     } catch {
       setIsUploading(false);
     } finally {

+ 57 - 81
src/screens/InAppScreens/TravelsScreen/EventsScreen/index.tsx

@@ -1,13 +1,5 @@
 import React, { useCallback, useEffect, useRef, useState } from 'react';
-import {
-  View,
-  Text,
-  Image,
-  TouchableOpacity,
-  ScrollView,
-  LayoutAnimation,
-  Modal
-} from 'react-native';
+import { View, Text, Image, TouchableOpacity, LayoutAnimation, Modal } from 'react-native';
 import { FlashList } from '@shopify/flash-list';
 import { CommonActions, useFocusEffect, useNavigation } from '@react-navigation/native';
 import { popupStyles, styles } from './styles';
@@ -19,6 +11,7 @@ import Animated, {
   interpolate,
   Extrapolation
 } from 'react-native-reanimated';
+import { FilterImage } from 'react-native-svg/filter-image';
 
 import { NAVIGATION_PAGES } from 'src/types';
 import { StoreType, storage } from 'src/storage';
@@ -42,12 +35,12 @@ import {
 } from '@api/events';
 import moment from 'moment';
 import { API_HOST } from 'src/constants';
-import { Grayscale } from 'react-native-color-matrix-image-filters';
 import { renderSpotsText } from './utils';
 import ChevronIcon from 'assets/icons/chevron-left.svg';
 import Tooltip from 'react-native-walkthrough-tooltip';
-import { TabBar, TabView } from 'react-native-tab-view';
 import InfoIcon from 'assets/icons/info-solid.svg';
+import { SafeAreaView } from 'react-native-safe-area-context';
+import TabViewWrapper from 'src/components/TabViewWrapper';
 
 function TabViewDelayed({
   children,
@@ -329,7 +322,7 @@ const EventsScreen = () => {
       : API_HOST + staticImgUrl;
 
     return (
-      <Grayscale amount={item.active === 0 ? 1 : 0}>
+      <View>
         <TouchableOpacity
           style={[
             styles.card,
@@ -343,11 +336,23 @@ const EventsScreen = () => {
           disabled={item.active === 0}
         >
           <View style={styles.imageWrapper}>
-            <Image
-              source={{ uri: photo, cache: 'reload' }}
-              style={styles.image}
-              resizeMode="cover"
-            />
+            {item.active === 0 ? (
+              <FilterImage
+                source={{ uri: photo }}
+                style={[
+                  styles.image,
+                  {
+                    filter: 'grayscale(100%)'
+                  }
+                ]}
+              />
+            ) : (
+              <Image
+                source={{ uri: photo, cache: 'reload' }}
+                style={styles.image}
+                resizeMode="cover"
+              />
+            )}
 
             {item.star === 1 && (
               <View style={styles.iconOverlay}>
@@ -445,7 +450,7 @@ const EventsScreen = () => {
             </View>
           ) : null}
         </TouchableOpacity>
-      </Grayscale>
+      </View>
     );
   };
 
@@ -553,72 +558,43 @@ const EventsScreen = () => {
   );
 
   return (
-    <PageWrapper>
-      <Header
-        label="Events"
-        rightElement={
-          canAddEvent?.can ? (
-            <TouchableOpacity
-              ref={buttonRef}
-              onPress={handleAddButtonPress}
-              style={{ width: 30 }}
-              hitSlop={{ top: 10, bottom: 10, left: 10, right: 10 }}
-            >
-              <CalendarPlusIcon fill={Colors.DARK_BLUE} />
-            </TouchableOpacity>
-          ) : null
-        }
-      />
-
-      {/* <ScrollView
-        ref={scrollViewRef}
-        nestedScrollEnabled
-        showsVerticalScrollIndicator={false}
-        style={{ flex: 1, height: '100%' }}
-      > */}
-      <Animated.View style={[styles.searchContainer, searchContainerAnimatedStyle]}>
-        <Input
-          inputMode={'search'}
-          placeholder={'Search'}
-          onChange={(text) => handleSearch(text)}
-          value={searchQuery}
-          icon={<SearchIcon fill={'#C8C8C8'} width={14} height={14} />}
-          height={38}
+    <SafeAreaView style={{ height: '100%' }} edges={['top']}>
+      <View style={{ marginLeft: '5%', marginRight: '5%' }}>
+        <Header
+          label="Events"
+          rightElement={
+            canAddEvent?.can ? (
+              <TouchableOpacity
+                ref={buttonRef}
+                onPress={handleAddButtonPress}
+                style={{ width: 30 }}
+                hitSlop={{ top: 10, bottom: 10, left: 10, right: 10 }}
+              >
+                <CalendarPlusIcon fill={Colors.DARK_BLUE} />
+              </TouchableOpacity>
+            ) : null
+          }
         />
-      </Animated.View>
 
-      {/* <TabViewDelayed waitBeforeShow={300}> */}
-      <TabView
-        navigationState={{ index, routes }}
-        renderScene={renderScene}
-        onIndexChange={handleIndexChange}
-        lazy={false}
-        swipeEnabled={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, textAlign: 'center' }
-                ]}
-                numberOfLines={2}
-                adjustsFontSizeToFit={true}
-                minimumFontScale={0.8}
-              >
-                {route.title}
-              </Text>
-            )}
+        <Animated.View style={[styles.searchContainer, searchContainerAnimatedStyle]}>
+          <Input
+            inputMode={'search'}
+            placeholder={'Search'}
+            onChange={(text) => handleSearch(text)}
+            value={searchQuery}
+            icon={<SearchIcon fill={'#C8C8C8'} width={14} height={14} />}
+            height={38}
           />
-        )}
+        </Animated.View>
+      </View>
+
+      <TabViewWrapper
+        routes={routes}
+        renderScene={renderScene as never}
+        setIndex={setIndex}
+        lazy={false}
+        selectedIndex={index}
       />
-      {/* </TabViewDelayed> */}
-      {/* </ScrollView> */}
 
       <Modal
         visible={showPopup}
@@ -658,7 +634,7 @@ const EventsScreen = () => {
           </View>
         </TouchableOpacity>
       </Modal>
-    </PageWrapper>
+    </SafeAreaView>
   );
 };
 

+ 24 - 18
src/screens/InAppScreens/TravelsScreen/Series/index.tsx

@@ -11,6 +11,7 @@ import { StoreType, storage } from 'src/storage';
 import { styles } from './styles';
 import { NAVIGATION_PAGES } from 'src/types';
 import AddSvg from 'assets/icons/travels-section/add.svg';
+import { SafeAreaView } from 'react-native-safe-area-context';
 
 interface SeriesGroup {
   key: string;
@@ -74,27 +75,32 @@ const SeriesScreen = () => {
   if (isLoading) return <Loading />;
 
   return (
-    <PageWrapper>
-      <Header
-        label={'Series'}
-        rightElement={
-          <TouchableOpacity
-            onPress={() => {
-              if (token) {
-                navigation.navigate(NAVIGATION_PAGES.SUGGEST_SERIES as never);
-              } else {
-                setWarningVisible(true);
-              }
-            }}
-          >
-            <AddSvg />
-          </TouchableOpacity>
-        }
-      />
+    <SafeAreaView style={{ height: '100%' }} edges={['top']}>
+      <View style={{ marginLeft: '5%', marginRight: '5%' }}>
+        <Header
+          label={'Series'}
+          rightElement={
+            <TouchableOpacity
+              onPress={() => {
+                if (token) {
+                  navigation.navigate(NAVIGATION_PAGES.SUGGEST_SERIES as never);
+                } else {
+                  setWarningVisible(true);
+                }
+              }}
+            >
+              <AddSvg />
+            </TouchableOpacity>
+          }
+        />
+      </View>
+
       <HorizontalTabView
         index={index}
         setIndex={setIndex}
         routes={routes}
+        tabBarStyle={{ paddingLeft: '5%' }}
+        maxTabHeight={50}
         renderScene={({ route }: { route: SeriesGroup }) => (
           <SeriesList groupId={route.key} navigation={navigation} />
         )}
@@ -104,7 +110,7 @@ const SeriesScreen = () => {
         isVisible={warningVisible}
         onClose={() => setWarningVisible(false)}
       />
-    </PageWrapper>
+    </SafeAreaView>
   );
 };
 

+ 23 - 37
src/screens/InAppScreens/TravelsScreen/SeriesItemScreen/index.tsx

@@ -2,7 +2,6 @@ import React, { useCallback, useEffect, useState } from 'react';
 import { useFocusEffect } from '@react-navigation/native';
 import { View, Text, FlatList } from 'react-native';
 import { Button, Header, Input, Loading, Modal, PageWrapper } from 'src/components';
-import { TabView, TabBar } from 'react-native-tab-view';
 
 import SearchIcon from '../../../../../assets/icons/search.svg';
 import { fetchItemsForSeries, usePostSetToggleItem } from '@api/series';
@@ -11,6 +10,8 @@ import { ButtonVariants } from 'src/types/components';
 import { AccordionListItem } from '../Components/AccordionListItem';
 
 import { styles } from './styles';
+import TabViewWrapper from 'src/components/TabViewWrapper';
+import { SafeAreaView } from 'react-native-safe-area-context';
 
 interface SeriesItem {
   series_id: number;
@@ -84,7 +85,7 @@ export const SeriesItemScreen = ({ route }: { route: any }) => {
     { key: 'all', title: 'All items' },
     { key: 'checked', title: 'Ticked' },
     { key: 'unchecked', title: 'Unticked' },
-    { key: 'new', title: 'New' },
+    { key: 'new', title: 'New' }
   ]);
 
   const handleIndexChange = (index: number) => {
@@ -224,8 +225,17 @@ export const SeriesItemScreen = ({ route }: { route: any }) => {
   );
 
   return (
-    <PageWrapper>
-      <Header label={name} />
+    <SafeAreaView style={{ height: '100%' }} edges={['top']}>
+      <View style={{ marginLeft: '5%', marginRight: '5%' }}>
+        <Header label={name} />
+        <Input
+          inputMode={'search'}
+          placeholder={'Search'}
+          onChange={(text) => setSearch(text)}
+          value={search}
+          icon={<SearchIcon fill={'#C8C8C8'} width={14} height={14} />}
+        />
+      </View>
       <Modal
         visible={isInfoModalVisible}
         children={
@@ -247,44 +257,20 @@ export const SeriesItemScreen = ({ route }: { route: any }) => {
         headerTitle={'Info'}
         visibleInPercent={'auto'}
       />
-      <Input
-        inputMode={'search'}
-        placeholder={'Search'}
-        onChange={(text) => setSearch(text)}
-        value={search}
-        icon={<SearchIcon fill={'#C8C8C8'} width={14} height={14} />}
-      />
+
       {token ? (
-        <TabView
-          navigationState={{ index, routes }}
-          renderScene={renderScene}
-          onIndexChange={(i) => {
-            handleIndexChange(i);
-            setIndex(i);
-          }}
-          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>
-              )}
-            />
-          )}
+        <TabViewWrapper
+          routes={routes}
+          renderScene={renderScene as never}
+          setIndex={setIndex}
+          lazy={false}
+          selectedIndex={index}
         />
       ) : (
-        <View style={{paddingTop: 8, flex: 1}}>
+        <View style={{ paddingTop: 8, flex: 1, marginHorizontal: '5%' }}>
           {renderScene({ route: routes[index] })}
         </View>
       )}
-    </PageWrapper>
+    </SafeAreaView>
   );
 };

+ 1 - 1
src/screens/InAppScreens/TravelsScreen/utils/useRegionData.ts

@@ -3,7 +3,7 @@ import { useState, useEffect } from 'react';
 import { getFirstDatabase, getSecondDatabase, refreshDatabases } from 'src/db';
 import { getData } from 'src/modules/map/regionData';
 import { DbRegion } from './types';
-import { SQLiteDatabase } from 'expo-sqlite/legacy';
+import { SQLiteDatabase } from 'expo-sqlite';
 
 const useRegionData = (regionId: number, dare: boolean = false) => {
   const [regionData, setRegionData] = useState<DbRegion | null>(null);

+ 2 - 1
tsconfig.json

@@ -6,5 +6,6 @@
     "paths": {
       "@api/*": ["src/modules/api/*"]
     }
-  }
+  },
+  "include": ["src", "declarations.d.ts"]
 }