Browse Source

Merge remote-tracking branch 'origin/master'

Oleksandr Honcharov 1 năm trước cách đây
mục cha
commit
53ce9c59de

+ 42 - 40
Route.tsx

@@ -1,11 +1,7 @@
-import React, { useEffect } from 'react';
+import React, { useEffect, useState } from 'react';
 import { useFonts } from 'expo-font';
 import * as SplashScreen from 'expo-splash-screen';
-
-import GlobeIcon from './assets/icons/globe.svg';
-import PeoplesIcon from './assets/icons/peoples.svg';
-import ProfileIcon from './assets/icons/profile.svg';
-import MapIcon from './assets/icons/map.svg';
+import { Platform } from 'react-native';
 
 import { createStackNavigator } from '@react-navigation/stack';
 import { createBottomTabNavigator } from '@react-navigation/bottom-tabs';
@@ -17,13 +13,17 @@ import ResetPasswordDeepScreen from './src/screens/ResetPasswordDeepScreen';
 import JoinUsScreen from './src/screens/RegisterScreen/JoinUs';
 import EditAccount from './src/screens/RegisterScreen/EditAccount';
 
+import MapScreen from './src/screens/InAppScreens/MapScreen';
 import TravelsScreen from './src/screens/InAppScreens/TravelsScreen';
 import ProfileScreen from './src/screens/InAppScreens/ProfileScreen';
-import MapScreen from './src/screens/InAppScreens/MapScreen';
 import TravellersScreen from './src/screens/InAppScreens/TravellersScreen';
 
 import { NAVIGATION_PAGES } from './src/types';
 import { storageGet } from './src/storage';
+import { openDatabases } from './src/db';
+
+import TabBarButton from './src/components/TabBarButton';
+import { ParamListBase, RouteProp } from '@react-navigation/native';
 
 const ScreenStack = createStackNavigator();
 const BottomTab = createBottomTabNavigator();
@@ -36,16 +36,26 @@ const Route = () => {
     'redhat-700': require('./assets/fonts/RedHatDisplay-Bold-700.ttf'),
     'redhat-600': require('./assets/fonts/RedHatDisplay-SemiBold-600.ttf')
   });
+  const [dbLoaded, setDbLoaded] = useState(false);
 
+  useEffect(() => {
+    const prepareApp = async () => {
+      await openDatabases();
+      setDbLoaded(true);
+    };
+  
+    prepareApp();
+  }, []);
+  
   useEffect(() => {
     const hideSplashScreen = async () => {
-      if (fontsLoaded) {
+      if (fontsLoaded && dbLoaded) {
         await SplashScreen.hideAsync();
       }
     };
 
     hideSplashScreen();
-  }, [fontsLoaded]);
+  }, [fontsLoaded, dbLoaded]); 
 
   if (!fontsLoaded) {
     return null;
@@ -54,6 +64,24 @@ const Route = () => {
   let token: string | null = '';
   storageGet('token').then((data) => (token = data));
 
+  const screenOptions = ({ route }: { route: RouteProp<ParamListBase, string>; navigation: any; }) => ({
+    headerShown: false,
+    tabBarButton: (props: any) => (
+      <TabBarButton
+        {...props}
+        label={route.name}
+        focused={props.accessibilityState.selected}
+      />
+    ),
+    tabBarStyle: {
+      ...Platform.select({
+        android: {
+          height: 58,
+        },
+      }),
+    },
+  });
+
   return (
     <ScreenStack.Navigator
       screenOptions={{ headerShown: false }}
@@ -71,38 +99,12 @@ const Route = () => {
       <ScreenStack.Screen name={NAVIGATION_PAGES.IN_APP}>
         {() => (
           <BottomTab.Navigator
-            screenOptions={() => ({
-              headerShown: false
-            })}
+            screenOptions={screenOptions}
           >
-            <BottomTab.Screen
-              options={{
-                tabBarIcon: ({ color }) => <MapIcon fill={color} />
-              }}
-              name={NAVIGATION_PAGES.MAP_TAB}
-              component={MapScreen}
-            />
-            <BottomTab.Screen
-              options={{
-                tabBarIcon: ({ color }) => <PeoplesIcon fill={color} />
-              }}
-              name={NAVIGATION_PAGES.TRAVELLERS_TAB}
-              component={TravellersScreen}
-            />
-            <BottomTab.Screen
-              options={{
-                tabBarIcon: ({ color }) => <GlobeIcon fill={color} />
-              }}
-              name={NAVIGATION_PAGES.TRAVELS_TAB}
-              component={TravelsScreen}
-            />
-            <BottomTab.Screen
-              options={{
-                tabBarIcon: ({ color }) => <ProfileIcon fill={color} />
-              }}
-              name={NAVIGATION_PAGES.PROFILE_TAB}
-              component={ProfileScreen}
-            />
+            <BottomTab.Screen name={NAVIGATION_PAGES.MAP_TAB} component={MapScreen} />
+            <BottomTab.Screen name={NAVIGATION_PAGES.TRAVELLERS_TAB} component={TravellersScreen} />
+            <BottomTab.Screen name={NAVIGATION_PAGES.TRAVELS_TAB} component={TravelsScreen} />
+            <BottomTab.Screen name={NAVIGATION_PAGES.PROFILE_TAB} component={ProfileScreen} />
           </BottomTab.Navigator>
         )}
       </ScreenStack.Screen>

BIN
assets/db/darePlaces.db


BIN
assets/db/nmRegions.db


Những thai đổi đã bị hủy bỏ vì nó quá lớn
+ 0 - 0
assets/geojson/mqp.json


Những thai đổi đã bị hủy bỏ vì nó quá lớn
+ 0 - 0
assets/geojson/nm2022.json


+ 0 - 0
assets/icons/globe.svg → assets/icons/bottom-navigation/globe.svg


+ 4 - 0
assets/icons/bottom-navigation/map.svg

@@ -0,0 +1,4 @@
+<svg width="19" height="22" viewBox="0 0 19 22" xmlns="http://www.w3.org/2000/svg">
+<path fill-rule="evenodd" clip-rule="evenodd" d="M9.2093 4.86047C6.94879 4.86047 5.11628 6.69297 5.11628 8.95349C5.11628 11.214 6.94879 13.0465 9.2093 13.0465C11.4698 13.0465 13.3023 11.214 13.3023 8.95349C13.3023 6.69297 11.4698 4.86047 9.2093 4.86047ZM7.16279 8.95349C7.16279 7.82323 8.07905 6.90698 9.2093 6.90698C10.3396 6.90698 11.2558 7.82323 11.2558 8.95349C11.2558 10.0837 10.3396 11 9.2093 11C8.07905 11 7.16279 10.0837 7.16279 8.95349Z"/>
+<path fill-rule="evenodd" clip-rule="evenodd" d="M18.4186 9.2093C18.4186 14.8372 11.2558 22 9.2093 22C7.16279 22 0 14.8372 0 9.2093C0 4.12315 4.12315 0 9.2093 0C14.2955 0 18.4186 4.12315 18.4186 9.2093ZM16.3721 9.2093C16.3721 11.2914 14.9657 14.0204 12.9993 16.417C12.0547 17.5682 11.0699 18.5324 10.2414 19.1883C9.82504 19.5179 9.48419 19.741 9.23696 19.8715L9.2093 19.886L9.18165 19.8715C8.93442 19.741 8.59357 19.5179 8.17725 19.1883C7.34867 18.5324 6.3639 17.5682 5.41931 16.417C3.45291 14.0204 2.04651 11.2914 2.04651 9.2093C2.04651 5.2534 5.2534 2.04651 9.2093 2.04651C13.1652 2.04651 16.3721 5.2534 16.3721 9.2093Z"/>
+</svg>

+ 0 - 0
assets/icons/profile.svg → assets/icons/bottom-navigation/profile.svg


+ 2 - 2
assets/icons/peoples.svg → assets/icons/bottom-navigation/travellers.svg

@@ -1,6 +1,6 @@
 <svg width="22" height="22" viewBox="0 0 22 22" fill="none" xmlns="http://www.w3.org/2000/svg">
-<path fill-rule="evenodd" clip-rule="evenodd" d="M7.64491 1.54857C5.53402 1.54857 3.83543 3.25521 3.83543 5.35805C3.83543 7.40528 5.43467 9.06634 7.4538 9.15602C7.57625 9.1475 7.70561 9.14708 7.82765 9.15593C9.84479 9.0653 11.445 7.40534 11.4544 5.35647C11.4535 3.25549 9.74609 1.54857 7.64491 1.54857ZM2.28687 5.35805C2.28687 2.40223 4.6765 0 7.64491 0C10.6019 0 13.003 2.40109 13.003 5.35805L13.003 5.36127C12.9909 8.25234 10.7121 10.6087 7.83611 10.7053C7.80172 10.7065 7.76729 10.7054 7.73305 10.7019C7.68327 10.697 7.60887 10.6963 7.53951 10.7026C7.50756 10.7055 7.47546 10.7064 7.4434 10.7053C4.56801 10.6087 2.28687 8.25219 2.28687 5.35805Z" />
+<path fill-rule="evenodd" clip-rule="evenodd" d="M7.64491 1.54857C5.53402 1.54857 3.83543 3.25521 3.83543 5.35805C3.83543 7.40528 5.43467 9.06634 7.4538 9.15602C7.57625 9.1475 7.70561 9.14708 7.82765 9.15593C9.84479 9.0653 11.445 7.40534 11.4544 5.35647C11.4535 3.25549 9.74609 1.54857 7.64491 1.54857ZM2.28687 5.35805C2.28687 2.40223 4.6765 0 7.64491 0C10.6019 0 13.003 2.40109 13.003 5.35805L13.003 5.36127C12.9909 8.25234 10.7121 10.6087 7.83611 10.7053C7.80172 10.7065 7.76729 10.7054 7.73305 10.7019C7.68327 10.697 7.60887 10.6963 7.53951 10.7026C7.50756 10.7055 7.47546 10.7064 7.4434 10.7053C4.56801 10.6087 2.28687 8.25219 2.28687 5.35805Z"/>
 <path fill-rule="evenodd" clip-rule="evenodd" d="M14.5203 2.83898C14.5203 2.41136 14.8669 2.0647 15.2945 2.0647C17.7264 2.0647 19.6822 4.03363 19.6822 6.45231C19.6822 8.82209 17.8021 10.7516 15.4577 10.8394C15.416 10.8409 15.3742 10.8391 15.3327 10.8339C15.3172 10.832 15.2857 10.8308 15.2458 10.8352C14.8208 10.8824 14.438 10.5762 14.3908 10.1511C14.3436 9.72613 14.6498 9.34331 15.0748 9.29609C15.1964 9.28258 15.3238 9.27945 15.4495 9.2896C16.943 9.20773 18.1336 7.96815 18.1336 6.45231C18.1336 4.886 16.8683 3.61327 15.2945 3.61327C14.8669 3.61327 14.5203 3.26661 14.5203 2.83898Z"/>
 <path fill-rule="evenodd" clip-rule="evenodd" d="M7.82285 11.5498C9.81349 11.5498 11.8434 12.0494 13.4122 13.0971C14.8223 14.0357 15.6302 15.3514 15.6302 16.7672C15.6302 18.1829 14.8224 19.5009 13.4129 20.4445L13.4127 20.4446C11.8392 21.4975 9.80666 22.0001 7.81511 22.0001C5.82395 22.0001 3.79184 21.4977 2.2184 20.4452C0.808052 19.5067 0 18.1909 0 16.7749C0 15.3592 0.807777 14.0412 2.21733 13.0976L2.22017 13.0957L2.22018 13.0957C3.7981 12.0495 5.83197 11.5498 7.82285 11.5498ZM3.07735 14.3854C1.98959 15.1141 1.54857 15.9946 1.54857 16.7749C1.54857 17.5551 1.9894 18.4329 3.07693 19.1564L3.07863 19.1576C4.3441 20.0043 6.05912 20.4515 7.81511 20.4515C9.57103 20.4515 11.286 20.0043 12.5514 19.1577C13.6403 18.4288 14.0817 17.5479 14.0817 16.7672C14.0817 15.987 13.6408 15.1092 12.5533 14.3857L12.5521 14.3849C11.2922 13.5434 9.57976 13.0984 7.82285 13.0984C6.06685 13.0984 4.34866 13.543 3.07735 14.3854Z"/>
-<path fill-rule="evenodd" clip-rule="evenodd" d="M16.5629 12.9924C16.6569 12.5753 17.0713 12.3133 17.4884 12.4073C18.3034 12.591 19.1033 12.9186 19.778 13.4331C20.7495 14.1628 21.2929 15.1799 21.2929 16.2546C21.2929 17.329 20.7498 18.3458 19.7788 19.0755C19.0959 19.5989 18.2838 19.9402 17.4452 20.1149C17.0265 20.2021 16.6165 19.9335 16.5292 19.5148C16.442 19.0962 16.7107 18.6861 17.1293 18.5989C17.7759 18.4642 18.3665 18.2076 18.8392 17.8446L18.8461 17.8393L18.8461 17.8393C19.4834 17.3613 19.7443 16.7791 19.7443 16.2546C19.7443 15.7301 19.4834 15.1479 18.8461 14.6699L18.8408 14.6659L18.8408 14.6659C18.3805 14.3143 17.7979 14.0644 17.148 13.918C16.7308 13.824 16.4689 13.4096 16.5629 12.9924Z" />
+<path fill-rule="evenodd" clip-rule="evenodd" d="M16.5629 12.9924C16.6569 12.5753 17.0713 12.3133 17.4884 12.4073C18.3034 12.591 19.1033 12.9186 19.778 13.4331C20.7495 14.1628 21.2929 15.1799 21.2929 16.2546C21.2929 17.329 20.7498 18.3458 19.7788 19.0755C19.0959 19.5989 18.2838 19.9402 17.4452 20.1149C17.0265 20.2021 16.6165 19.9335 16.5292 19.5148C16.442 19.0962 16.7107 18.6861 17.1293 18.5989C17.7759 18.4642 18.3665 18.2076 18.8392 17.8446L18.8461 17.8393L18.8461 17.8393C19.4834 17.3613 19.7443 16.7791 19.7443 16.2546C19.7443 15.7301 19.4834 15.1479 18.8461 14.6699L18.8408 14.6659L18.8408 14.6659C18.3805 14.3143 17.7979 14.0644 17.148 13.918C16.7308 13.824 16.4689 13.4096 16.5629 12.9924Z"/>
 </svg>

+ 2 - 2
assets/icons/close.svg

@@ -1,3 +1,3 @@
-<svg width="16" height="16" viewBox="0 0 16 16" fill="none" xmlns="http://www.w3.org/2000/svg">
-<path fill-rule="evenodd" clip-rule="evenodd" d="M2.41919 0.580712C1.91151 0.0730308 1.08839 0.0730308 0.580712 0.580712C0.0730308 1.08839 0.0730308 1.91151 0.580712 2.41919L6.16147 7.99995L0.580712 13.5807C0.073031 14.0884 0.0730313 14.9115 0.580712 15.4192C1.08839 15.9269 1.91151 15.9269 2.41919 15.4192L7.99995 9.83843L13.5807 15.4192C14.0884 15.9269 14.9115 15.9269 15.4192 15.4192C15.9269 14.9115 15.9269 14.0884 15.4192 13.5807L9.83843 7.99995L15.4192 2.41919C15.9269 1.91151 15.9269 1.08839 15.4192 0.580712C14.9115 0.0730308 14.0884 0.0730308 13.5807 0.580712L7.99995 6.16147L2.41919 0.580712Z" fill="#0F3F4F"/>
+<svg width="16" height="16" viewBox="0 0 16 16" fill="#0F3F4F" xmlns="http://www.w3.org/2000/svg">
+<path fill-rule="evenodd" clip-rule="evenodd" d="M2.41919 0.580712C1.91151 0.0730308 1.08839 0.0730308 0.580712 0.580712C0.0730308 1.08839 0.0730308 1.91151 0.580712 2.41919L6.16147 7.99995L0.580712 13.5807C0.073031 14.0884 0.0730313 14.9115 0.580712 15.4192C1.08839 15.9269 1.91151 15.9269 2.41919 15.4192L7.99995 9.83843L13.5807 15.4192C14.0884 15.9269 14.9115 15.9269 15.4192 15.4192C15.9269 14.9115 15.9269 14.0884 15.4192 13.5807L9.83843 7.99995L15.4192 2.41919C15.9269 1.91151 15.9269 1.08839 15.4192 0.580712C14.9115 0.0730308 14.0884 0.0730308 13.5807 0.580712L7.99995 6.16147L2.41919 0.580712Z"/>
 </svg>

+ 3 - 0
assets/icons/location.svg

@@ -0,0 +1,3 @@
+<svg width="18" height="18" viewBox="0 0 18 18" fill="none" xmlns="http://www.w3.org/2000/svg">
+<path fill-rule="evenodd" clip-rule="evenodd" d="M15.4997 2.50026L2.66353 7.43727L7.02571 9.42008C7.71463 9.73322 8.26678 10.2854 8.57992 10.9743L10.5627 15.3365L15.4997 2.50026ZM15.0756 0.148301C16.8129 -0.519892 18.5199 1.18711 17.8517 2.92442L12.583 16.6229C11.8991 18.4011 9.41079 18.4751 8.62244 16.7407L6.44288 11.9457C6.36459 11.7734 6.22655 11.6354 6.05432 11.5571L1.25928 9.37756C-0.475078 8.58921 -0.40108 6.10086 1.37705 5.41697L15.0756 0.148301Z" fill="#0F3F4F"/>
+</svg>

+ 0 - 4
assets/icons/map.svg

@@ -1,4 +0,0 @@
-<svg width="22" height="26" viewBox="0 0 22 26" fill="none" xmlns="http://www.w3.org/2000/svg">
-<path fill-rule="evenodd" clip-rule="evenodd" d="M11 6.29167C8.42267 6.29167 6.33333 8.381 6.33333 10.9583C6.33333 13.5357 8.42267 15.625 11 15.625C13.5773 15.625 15.6667 13.5357 15.6667 10.9583C15.6667 8.381 13.5773 6.29167 11 6.29167ZM8.66667 10.9583C8.66667 9.66967 9.71134 8.625 11 8.625C12.2887 8.625 13.3333 9.66967 13.3333 10.9583C13.3333 12.247 12.2887 13.2917 11 13.2917C9.71134 13.2917 8.66667 12.247 8.66667 10.9583Z" />
-<path fill-rule="evenodd" clip-rule="evenodd" d="M21.5 11.25C21.5 17.6667 13.3333 25.8333 11 25.8333C8.66667 25.8333 0.5 17.6667 0.5 11.25C0.5 5.45101 5.20101 0.75 11 0.75C16.799 0.75 21.5 5.45101 21.5 11.25ZM19.1667 11.25C19.1667 13.6239 17.5632 16.7354 15.3212 19.4678C14.2442 20.7804 13.1214 21.8797 12.1767 22.6276C11.702 23.0034 11.3134 23.2577 11.0315 23.4066L11 23.423L10.9685 23.4066C10.6866 23.2577 10.298 23.0034 9.82331 22.6276C8.8786 21.8797 7.75581 20.7804 6.67884 19.4678C4.43684 16.7354 2.83333 13.6239 2.83333 11.25C2.83333 6.73967 6.48967 3.08333 11 3.08333C15.5103 3.08333 19.1667 6.73967 19.1667 11.25Z" fill="#0F3F4F"/>
-</svg>

+ 3 - 0
assets/icons/menu.svg

@@ -0,0 +1,3 @@
+<svg width="20" height="16" viewBox="0 0 20 16" fill="none" xmlns="http://www.w3.org/2000/svg">
+<path fill-rule="evenodd" clip-rule="evenodd" d="M2.00005 0.800049C1.33731 0.800049 0.800049 1.33731 0.800049 2.00005C0.800049 2.66279 1.33731 3.20005 2.00005 3.20005H18C18.6628 3.20005 19.2001 2.66279 19.2001 2.00005C19.2001 1.33731 18.6628 0.800049 18 0.800049H2.00005ZM0.800049 8.00005C0.800049 7.33731 1.33731 6.80005 2.00005 6.80005H18C18.6628 6.80005 19.2001 7.33731 19.2001 8.00005C19.2001 8.66279 18.6628 9.20005 18 9.20005H2.00005C1.33731 9.20005 0.800049 8.66279 0.800049 8.00005ZM0.800049 14C0.800049 13.3373 1.33731 12.8 2.00005 12.8H18C18.6628 12.8 19.2001 13.3373 19.2001 14C19.2001 14.6628 18.6628 15.2 18 15.2H2.00005C1.33731 15.2 0.800049 14.6628 0.800049 14Z" fill="#0F3F4F"/>
+</svg>

+ 4 - 0
assets/icons/radar.svg

@@ -0,0 +1,4 @@
+<svg width="18" height="18" viewBox="0 0 18 18" fill="none" xmlns="http://www.w3.org/2000/svg">
+<path d="M3.60066 1.79951C5.10216 0.675628 6.97459 0 9 0C13.9682 0 18 4.03176 18 9C18 13.9682 13.9682 18 9 18C4.03176 18 0 13.9682 0 9C0 7.37257 0.432023 5.84066 1.1981 4.51579C1.43377 4.10823 1.95477 3.9682 2.36301 4.20269L9.42545 8.25931C9.83452 8.49428 9.97566 9.01638 9.74069 9.42545C9.50572 9.83452 8.98362 9.97566 8.57455 9.74069L2.29438 6.13339C1.91613 7.01353 1.70836 7.98185 1.70836 9C1.70836 13.0247 4.97526 16.2916 9 16.2916C13.0247 16.2916 16.2916 13.0247 16.2916 9C16.2916 4.97526 13.0247 1.70836 9 1.70836C7.35977 1.70836 5.84355 2.25461 4.62436 3.16718C4.24669 3.44987 3.71136 3.37287 3.42867 2.9952C3.14598 2.61752 3.22298 2.0822 3.60066 1.79951Z" fill="#0F3F4F"/>
+<path d="M6.55297 3.80689C7.29827 3.44876 8.1337 3.2583 9.00009 3.2583C12.1681 3.2583 14.7418 5.83196 14.7418 8.99997C14.7418 12.168 12.1681 14.7416 9.00009 14.7416C5.83208 14.7416 3.25842 12.168 3.25842 8.99997C3.25842 7.94001 3.54533 6.9421 4.05479 6.08659C4.29616 5.68127 4.70284 5.83964 5.10816 6.08101C5.51349 6.32239 5.76397 6.55536 5.5226 6.96068C5.16861 7.55512 4.96678 8.25155 4.96678 8.99997C4.96678 11.2245 6.77558 13.0333 9.00009 13.0333C11.2246 13.0333 13.0334 11.2245 13.0334 8.99997C13.0334 6.77546 11.2246 4.96666 9.00009 4.96666C8.38395 4.96666 7.80203 5.10204 7.29288 5.3467C6.86767 5.55103 6.35733 5.37196 6.15301 4.94675C5.94869 4.52155 6.12776 4.01121 6.55297 3.80689Z" fill="#0F3F4F"/>
+</svg>

+ 3 - 0
assets/icons/search.svg

@@ -0,0 +1,3 @@
+<svg width="20" height="20" viewBox="0 0 20 20" fill="none" xmlns="http://www.w3.org/2000/svg">
+<path fill-rule="evenodd" clip-rule="evenodd" d="M9.00005 2.70005C5.52065 2.70005 2.70005 5.52065 2.70005 9.00005C2.70005 12.4794 5.52065 15.3 9.00005 15.3C10.6547 15.3 12.1581 14.6637 13.2831 13.6202C14.5253 12.4681 15.3 10.8254 15.3 9.00005C15.3 5.52065 12.4794 2.70005 9.00005 2.70005ZM0.300049 9.00005C0.300049 4.19517 4.19517 0.300049 9.00005 0.300049C13.8049 0.300049 17.7001 4.19517 17.7001 9.00005C17.7001 11.0721 16.9746 12.976 15.7659 14.4697L18.9476 17.6515C19.4163 18.1201 19.4163 18.8799 18.9476 19.3486C18.479 19.8172 17.7192 19.8172 17.2506 19.3486L14.0132 16.1112C12.5964 17.1115 10.866 17.7001 9.00005 17.7001C4.19517 17.7001 0.300049 13.8049 0.300049 9.00005Z" fill="#0F3F4F"/>
+</svg>

+ 1 - 1
metro.config.js

@@ -11,7 +11,7 @@ module.exports = (() => {
   };
   config.resolver = {
     ...resolver,
-    assetExts: resolver.assetExts.filter((ext) => ext !== 'svg'),
+    assetExts: [...resolver.assetExts.filter((ext) => ext !== 'svg'), 'db'],
     sourceExts: [...resolver.sourceExts, 'svg']
   };
 

Những thai đổi đã bị hủy bỏ vì nó quá lớn
+ 1878 - 253
package-lock.json


+ 5 - 0
package.json

@@ -12,17 +12,21 @@
   "dependencies": {
     "@react-native-async-storage/async-storage": "1.18.2",
     "@react-native-community/datetimepicker": "7.2.0",
+    "@react-native-community/netinfo": "9.3.10",
     "@react-navigation/bottom-tabs": "^6.5.11",
     "@react-navigation/native": "^6.1.9",
     "@react-navigation/native-stack": "^6.9.17",
     "@react-navigation/stack": "^6.3.20",
     "@tanstack/react-query": "^5.8.3",
+    "@turf/turf": "^6.5.0",
     "axios": "^1.6.1",
     "dotenv": "^16.3.1",
     "expo": "~49.0.15",
     "expo-checkbox": "~2.4.0",
     "expo-image-picker": "~14.3.2",
+    "expo-location": "~16.1.0",
     "expo-splash-screen": "~0.20.5",
+    "expo-sqlite": "~11.3.3",
     "expo-status-bar": "~1.6.0",
     "formik": "^2.4.5",
     "moment": "^2.29.4",
@@ -31,6 +35,7 @@
     "react": "18.2.0",
     "react-native": "0.72.6",
     "react-native-calendar-picker": "^7.1.4",
+    "react-native-maps": "1.7.1",
     "react-native-safe-area-context": "4.6.3",
     "react-native-screens": "~3.22.0",
     "react-native-svg": "13.9.0",

+ 97 - 0
src/components/RegionPopup/index.tsx

@@ -0,0 +1,97 @@
+import { useEffect, useRef } from 'react';
+import {
+  Text,
+  TouchableOpacity,
+  View,
+  Image,
+  Animated
+} from 'react-native';
+
+import { styles } from './style';
+
+interface Region {
+  id: number;
+  name: string;
+  region_photos: string;
+  visitors_count: number;
+}
+
+interface RegionPopupProps {
+  region: Region;
+  userAvatars: string[];
+  onMarkVisited: () => void;
+}
+
+const RegionPopup: React.FC<RegionPopupProps> = ({ region, userAvatars, onMarkVisited }) => {
+  const fadeAnim = useRef(new Animated.Value(0)).current;
+
+  useEffect(() => {
+    Animated.timing(fadeAnim, {
+      toValue: 1,
+      duration: 300,
+      useNativeDriver: true,
+    }).start();
+  }, [fadeAnim]);
+  
+  const splitRegionName = (fullName: string) => {
+    const parts = fullName.split(/ – | - /);
+    return {
+      regionTitle: parts[0],
+      regionSubtitle: parts.length > 1 ? parts[1] : '',
+    };
+  };
+
+  const { regionTitle, regionSubtitle } = splitRegionName(region.name);
+  const regionImg = JSON.parse(region.region_photos)[0];
+
+  function formatNumber(number: number) {
+    if (number >= 1000) {
+      return (number / 1000).toFixed(1) + 'k';
+    }
+    return number.toString();
+  }
+  
+  const formattedCount = formatNumber(region.visitors_count);
+
+  return (
+    <Animated.View style={[styles.popupContainer, {opacity: fadeAnim}]}>
+      <View style={styles.regionInfoContainer}>
+        {regionImg && (
+          <Image source={{ uri: regionImg}} style={styles.regionImage} />
+        )}
+        <View style={styles.regionTextContainer}>
+          <Text style={styles.regionTitle}>{regionTitle}</Text>
+          <Text style={styles.regionSubtitle}>{regionSubtitle}</Text>
+        </View>
+      </View>
+
+      <View style={styles.separator} />
+
+      <View style={styles.bottomContainer}>
+        <View style={styles.userContainer}>
+          <View style={styles.userImageContainer}>
+            {userAvatars?.map((avatar, index) => (
+              <Image
+                key={index}
+                source={{ uri: avatar }}
+                style={styles.userImage}
+              />
+            ))}
+            <View style={styles.userCountContainer}>
+              <Text style={styles.userCount}>
+                {formattedCount}
+              </Text>
+            </View>
+          </View>
+        </View>
+        <TouchableOpacity style={styles.markVisitedButton} onPress={onMarkVisited}>
+          <Text style={styles.markVisitedText}>
+            Mark Visited
+          </Text>
+        </TouchableOpacity>
+      </View>
+    </Animated.View>
+  );
+};
+
+export default RegionPopup;

+ 109 - 0
src/components/RegionPopup/style.tsx

@@ -0,0 +1,109 @@
+import { StyleSheet } from 'react-native';
+import { Colors } from '../../theme';
+
+export const styles = StyleSheet.create({
+  popupContainer: {
+    position: 'absolute',
+    bottom: 22,
+    left: 0,
+    right: 0,
+    backgroundColor: Colors.WHITE,
+    padding: 16,
+    borderRadius: 8,
+    alignItems: 'center',
+    justifyContent: 'center',
+    height: 152,
+    marginHorizontal: 24,
+    shadowColor: 'rgba(33, 37, 41, 0.12)',
+    shadowOffset: { width: 0, height: 4 },
+    shadowRadius: 8,
+    elevation: 5,
+    zIndex: 2,
+  },
+  regionInfoContainer: {
+    flexDirection: 'row',
+    alignItems: 'center',
+    justifyContent: 'flex-start',
+    width: '100%',
+  },
+  regionImage: {
+    width: 60,
+    height: 60,
+    borderRadius: 6,
+    marginRight: 10,
+  },
+  regionTextContainer: {
+    justifyContent: 'center',
+    flex: 1,
+  },
+  regionTitle: {
+    fontSize: 16,
+    fontWeight: 'bold',
+    color: Colors.DARK_BLUE,
+  },
+  regionSubtitle: {
+    fontSize: 13,
+    color: Colors.TEXT_GRAY,
+  },
+  separator: {
+    borderBottomWidth: 1,
+    borderBottomColor: Colors.DARK_LIGHT,
+    width: '100%',
+    marginVertical: 16,
+  },
+  bottomContainer: {
+    flexDirection: 'row',
+    alignItems: 'center',
+    justifyContent: 'space-between',
+    width: '100%',
+  },
+  userContainer: {
+    flexDirection: 'row',
+    alignItems: 'center',
+    marginLeft: 6,
+  },
+  userImageContainer: {
+    flexDirection: 'row',
+    alignItems: 'center',
+    marginRight: 10,
+  },
+  userImage: {
+    width: 28,
+    height: 28,
+    borderRadius: 14,
+    marginLeft: -6,
+    borderWidth: 1,
+    borderColor: Colors.DARK_LIGHT,
+    resizeMode: 'cover',
+  },
+  userCountContainer: {
+    width: 28,
+    height: 28,
+    borderRadius: 14,
+    backgroundColor: Colors.DARK_LIGHT,
+    alignItems: 'center',
+    justifyContent: 'center',
+    marginLeft: -6,
+  },
+  userCount: {
+    fontSize: 12,
+    color: Colors.DARK_BLUE,
+    lineHeight: 24,
+  },
+  markVisitedButton: {
+    backgroundColor: Colors.ORANGE,
+    paddingHorizontal: 12,
+    paddingVertical: 8,
+    borderRadius: 6,
+    alignItems: 'center',
+    justifyContent: 'center',
+    height: 32,
+  },
+  markVisitedText: {
+    fontSize: 12,
+    color: Colors.WHITE,
+    fontWeight: '700',
+    letterSpacing: 0.01,
+    lineHeight: 16,
+  },
+});

+ 52 - 0
src/components/TabBarButton/index.tsx

@@ -0,0 +1,52 @@
+import { Text, View, TouchableWithoutFeedback } from 'react-native';
+import { SvgProps } from 'react-native-svg';
+
+import { NAVIGATION_PAGES } from '../../types';
+
+import MapIcon from '../../../assets/icons/bottom-navigation/map.svg';
+import TravellersIcon from '../../../assets/icons/bottom-navigation/travellers.svg';
+import GlobeIcon from '../../../assets/icons/bottom-navigation/globe.svg';
+import ProfileIcon from '../../../assets/icons/bottom-navigation/profile.svg';
+
+import { Colors } from '../../theme';
+import { styles } from './style';
+
+const getTabIcon = (routeName: string) => {
+  switch (routeName) {
+    case NAVIGATION_PAGES.MAP_TAB:
+      return MapIcon;
+    case NAVIGATION_PAGES.TRAVELLERS_TAB:
+      return TravellersIcon;
+    case NAVIGATION_PAGES.TRAVELS_TAB:
+      return GlobeIcon;
+    case NAVIGATION_PAGES.PROFILE_TAB:
+      return ProfileIcon;
+    default:
+      return null;
+  }
+};
+
+const TabBarButton = ({
+  label,
+  onPress,
+  focused,
+}: {
+  label: string;
+  onPress: () => void;
+  focused: boolean;
+}) => {
+  const IconComponent: React.FC<SvgProps> | null = getTabIcon(label);
+
+  let currentColor = focused ? Colors.DARK_BLUE : Colors.LIGHT_GRAY;
+
+  return (
+    <TouchableWithoutFeedback onPress={onPress}>
+      <View style={styles.buttonStyle}>
+        {IconComponent && <IconComponent width={24} height={24} fill={currentColor} />}
+        <Text style={[styles.labelStyle, { color: currentColor }]}>{label}</Text>
+      </View>
+    </TouchableWithoutFeedback>
+  );
+};
+
+export default TabBarButton;

+ 14 - 0
src/components/TabBarButton/style.tsx

@@ -0,0 +1,14 @@
+import { StyleSheet } from 'react-native';
+
+export const styles = StyleSheet.create({
+  buttonStyle: {
+    flex: 1,
+    alignItems: 'center',
+    justifyContent: 'center',
+    overflow: 'hidden',
+  },
+  labelStyle: {
+    marginTop: 4,
+    fontSize: 10,
+  },
+});

+ 53 - 0
src/db/index.ts

@@ -0,0 +1,53 @@
+import * as SQLite from 'expo-sqlite';
+import * as FileSystem from 'expo-file-system';
+import { Asset } from 'expo-asset';
+
+let db1: SQLite.SQLiteDatabase | null = null;
+let db2: SQLite.SQLiteDatabase | null = null;
+
+async function copyDatabaseFile(dbName: string, dbAsset: Asset) {
+  await dbAsset.downloadAsync();
+  await FileSystem.downloadAsync(
+    dbAsset.uri,
+    FileSystem.documentDirectory + "SQLite/" + dbName
+  );
+  
+  const dbUri = FileSystem.documentDirectory + `SQLite/${dbName}`;
+
+  await FileSystem.copyAsync({
+    from: dbAsset.localUri ?? '',
+    to: dbUri,
+  });
+
+  return dbUri;
+}
+
+export async function openDatabases() {
+  try {
+    const sqlDir = FileSystem.documentDirectory + "SQLite";
+    const fileInfo = await FileSystem.getInfoAsync(sqlDir);
+
+    if (!fileInfo.exists) {
+      await FileSystem.makeDirectoryAsync(sqlDir, { intermediates: true });
+      console.log('openDatabase - Downloading databases');
+      await copyDatabaseFile('nmRegions.db', Asset.fromModule(require('../../assets/db/nmRegions.db')));
+      await copyDatabaseFile('darePlaces.db', Asset.fromModule(require('../../assets/db/darePlaces.db')));
+
+      console.log('openDatabase - Databases downloaded');
+    }
+
+    const openDatabase = (dbName: string) => SQLite.openDatabase(dbName);
+    db1 = openDatabase("nmRegions.db");
+    db2 = openDatabase("darePlaces.db");
+  } catch (error) {
+    console.error('openDatabase - Error:', error);
+  }
+}
+
+export function getFirstDatabase() {
+  return db1;
+}
+
+export function getSecondDatabase() {
+  return db2;
+}

+ 50 - 0
src/modules/map/regionData.ts

@@ -0,0 +1,50 @@
+import { SQLiteDatabase } from 'expo-sqlite';
+
+export const getData = async (
+  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 = 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;
+        }
+      );
+    });
+  });
+};

+ 240 - 6
src/screens/InAppScreens/MapScreen/index.tsx

@@ -1,12 +1,246 @@
-import React from 'react';
-import { View, Text } from 'react-native';
+import {
+  View,
+  Platform,
+  TouchableOpacity,
+  Text
+} from 'react-native';
+import React, { useEffect, useState, useRef, useMemo } from 'react';
+import MapView, { UrlTile, Geojson } from 'react-native-maps';
+import * as turf from '@turf/turf';
+import * as FileSystem from 'expo-file-system';
+
+import MenuIcon from '../../../../assets/icons/menu.svg';
+import SearchIcon from '../../../../assets/icons/search.svg';
+import RadarIcon from '../../../../assets/icons/radar.svg';
+import LocationIcon from '../../../../assets/icons/location.svg';
+import CloseSvg from '../../../../assets/icons/close.svg';
+
+import regions from '../../../../assets/geojson/nm2022.json';
+import dareRegions from '../../../../assets/geojson/mqp.json';
+
+import NetInfo from "@react-native-community/netinfo";
+import { getFirstDatabase, getSecondDatabase } from '../../../db';
+import RegionPopup from '../../../components/RegionPopup';
+
+import { BottomTabNavigationProp } from '@react-navigation/bottom-tabs';
+import { styles } from './style';
+import { findRegionInDataset, calculateMapRegion } from '../../../utils/mapHelpers';
+import { getData } from '../../../modules/map/regionData';
+
+const tilesBaseURL = 'https://maps.nomadmania.com/tiles_osm';
+const localTileDir = `${FileSystem.cacheDirectory}tiles`;
+
+const gridUrl = 'https://maps.nomadmania.com/tiles_nm/grid';
+const localGridDir = `${FileSystem.cacheDirectory}tiles/grid`;
+
+const visitedTiles = 'https://maps.nomadmania.com/tiles_nm/user_visited/51363';
+const localVisitedDir = `${FileSystem.cacheDirectory}tiles/user_visited`;
+
+const dareTiles = 'https://maps.nomadmania.com/tiles_nm/regions_mqp';
+const localDareDir = `${FileSystem.cacheDirectory}tiles/regions_mqp`;
+
+interface Region {
+  id: number;
+  name: string;
+  region_photos: string;
+  visitors_count: number;
+}
+
+interface MapScreenProps {
+  navigation: BottomTabNavigationProp<any>;
+}
+
+const MapScreen: React.FC<MapScreenProps> = ({ navigation }) => {
+  const mapRef = useRef<MapView>(null);
+
+  const [isConnected, setIsConnected] = useState<boolean | null>(true);
+  const [selectedRegion, setSelectedRegion] = useState(null);
+  const [popupVisible, setPopupVisible] = useState<boolean | null>(false);
+  const [regionData, setRegionData] = useState<Region | null>(null);
+  const [userAvatars, setUserAvatars] = useState<string[]>([]);
+
+  useEffect(() => {
+    navigation.setOptions({
+      tabBarStyle: {
+        display: popupVisible ? 'none' : 'flex',
+        position: 'absolute',
+        ...Platform.select({
+          android: {
+            height: 58,
+          },
+        }),
+      }
+    });
+  }, [popupVisible, navigation]);
+
+  const handleRegionData = (regionData: Region, avatars: string[]) => {
+    setRegionData(regionData);
+    setUserAvatars(avatars);
+  };
+
+  const handleMapPress = async (event: { nativeEvent: { coordinate: { latitude: any; longitude: any; }; }; }) => {
+    const { latitude, longitude } = event.nativeEvent.coordinate;
+    const point = turf.point([longitude, latitude]);
+    setUserAvatars([]);
+
+    let db = getSecondDatabase();
+    let tableName = 'places';
+
+    let foundRegion = findRegionInDataset(dareRegions, point);
+
+    if (!foundRegion) {
+      foundRegion = findRegionInDataset(regions, point);
+      db = getFirstDatabase();
+      tableName = 'regions';
+    }
+
+    if (foundRegion) {
+      const id = foundRegion.properties?.id;
+
+      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, tableName, handleRegionData)
+      .then(() => {
+        setPopupVisible(true);
+      })
+      .catch(error => {
+        console.error("Error fetching data", error);
+      });
+
+      const bounds = turf.bbox(foundRegion);
+      const region = calculateMapRegion(bounds);
+
+      mapRef.current?.animateToRegion(region, 1000);
+    } else {
+      handleClosePopup();
+    }
+  };
+
+  useEffect(() => {
+    const unsubscribe = NetInfo.addEventListener(state => {
+      setIsConnected(state.isConnected);
+    });
+  
+    return () => unsubscribe();
+  }, []);
+
+  const renderMapTiles = (
+    url: string,
+    cacheDir: string,
+    zIndex: number,
+    opacity = 1
+  ) => (
+    <UrlTile
+      urlTemplate={`${url}/{z}/{x}/{y}`}
+      maximumZ={15}
+      maximumNativeZ={13}
+      tileCachePath={`${cacheDir}`}
+      shouldReplaceMapContent
+      minimumZ={0}
+      offlineMode={!isConnected}
+      opacity={opacity}
+      zIndex={zIndex}
+    />
+  );
+
+  function renderGeoJSON() {
+    if (!selectedRegion) return null;
+
+    return (
+        <Geojson
+          geojson={selectedRegion}
+          fillColor="rgba(57, 115, 172, 0.2)"
+          strokeColor="#3973ac"
+          strokeWidth={Platform.OS == 'android' ? 3 : 2}
+          zIndex={3}
+        />
+    );
+  };
+
+  const handleClosePopup = () => {
+    setPopupVisible(false);
+    setSelectedRegion(null);
+  }
+
+  const renderedGeoJSON = useMemo(() => renderGeoJSON(), [selectedRegion]);
 
-const MapScreen = () => {
   return (
-    <View>
-      <Text>Map Screen</Text>
+    <View style={styles.container}>
+      <MapView
+        ref={mapRef}
+        showsMyLocationButton={false}
+        showsCompass={false}
+        zoomControlEnabled={false}
+        onPress={handleMapPress}
+        style={styles.map}
+        mapType={Platform.OS == 'android' ? 'none' : 'standard'}
+        offlineMode={!isConnected}
+        maxZoomLevel={15}
+        minZoomLevel={0}
+      >
+        {renderedGeoJSON}
+        {renderMapTiles(tilesBaseURL, localTileDir, 1)}
+        {renderMapTiles(gridUrl, localGridDir, 2)}
+        {renderMapTiles(visitedTiles, localVisitedDir, 2, 0.5)}
+        {renderMapTiles(dareTiles, localDareDir, 2, 0.5)}
+      </MapView>
+
+      {popupVisible && regionData ? (
+        <>
+          <TouchableOpacity
+            style={[ styles.cornerButton, styles.topLeftButton, styles.closeLeftButton ]}
+            onPress={handleClosePopup}
+          >
+            <CloseSvg
+              fill="white"
+              width={13}
+              height={13}
+            />
+            <Text style={styles.textClose}>Close</Text>
+          </TouchableOpacity>
+
+          <TouchableOpacity style={[ styles.cornerButton, styles.topRightButton, styles.bottomButton ]}>
+            <LocationIcon />
+          </TouchableOpacity>
+
+          <RegionPopup 
+            region={regionData}
+            userAvatars={userAvatars}
+            onMarkVisited={() => console.log('Mark as visited')} 
+          />
+        </>
+      ) : (
+        <>
+          <TouchableOpacity style={[styles.cornerButton, styles.topLeftButton]}>
+            <MenuIcon />
+          </TouchableOpacity>
+
+          <TouchableOpacity style={[styles.cornerButton, styles.topRightButton]}>
+            <SearchIcon />
+          </TouchableOpacity>
+
+          <TouchableOpacity style={[styles.cornerButton, styles.bottomButton, styles.bottomLeftButton]}>
+            <RadarIcon />
+          </TouchableOpacity>
+
+          <TouchableOpacity style={[styles.cornerButton, styles.bottomButton, styles.bottomRightButton]}>
+            <LocationIcon />
+          </TouchableOpacity>
+        </>
+      )}
     </View>
   );
-};
+}
 
 export default MapScreen;

+ 63 - 0
src/screens/InAppScreens/MapScreen/style.tsx

@@ -0,0 +1,63 @@
+import { StyleSheet, Platform } from 'react-native';
+import { Colors } from '../../../theme';
+
+export const styles = StyleSheet.create({
+  container: {
+    ...StyleSheet.absoluteFillObject,
+    alignItems: 'center',
+    justifyContent: 'flex-end',
+  },
+  map: {
+    ...StyleSheet.absoluteFillObject,
+  },
+  cornerButton: {
+    position: 'absolute',
+    backgroundColor: Colors.WHITE,
+    padding: 12,
+    width: 48,
+    height: 48,
+    borderRadius: 24,
+    alignItems: 'center',
+    justifyContent: 'center',
+    shadowColor: 'rgba(33, 37, 41, 0.12)',
+    shadowOffset: { width: 0, height: 4 },
+    shadowRadius: 8,
+    elevation: 5,
+  },
+  topLeftButton: {
+    top: 44,
+    left: 16,
+  },
+  closeLeftButton: {
+    backgroundColor: 'rgba(33, 37, 41, 0.78)',
+    paddingHorizontal: 12,
+    paddingVertical: 8,
+    width: 81,
+    height: 36,
+    borderRadius: 18,
+    flexDirection: 'row',
+    gap: 6,
+  },
+  topRightButton: {
+    top: 44,
+    right: 16,
+  },
+  bottomButton: {
+    bottom: Platform.OS == 'android' ? 80 : 100,
+    width: 42,
+    height: 42,
+    borderRadius: 21,
+  },
+  bottomLeftButton: {
+    left: 16,
+  },
+  bottomRightButton: {
+    right: 16,
+  },
+  textClose: {
+    fontSize: 12,
+    color: 'white',
+    fontWeight: '500',
+    lineHeight: 14,
+  },
+});

+ 6 - 0
src/screens/WelcomeScreen/index.tsx

@@ -37,6 +37,12 @@ const WelcomeScreen: FC<Props> = ({ navigation }) => {
               >
                 Login
               </Button>
+              <Button
+                onPress={() => navigation.navigate(NAVIGATION_PAGES.IN_APP)}
+                variant={ButtonVariants.OPACITY}
+              >
+                Without registration
+              </Button>
             </View>
           </View>
         </SafeAreaView>

+ 2 - 0
src/theme.ts

@@ -4,4 +4,6 @@ export enum Colors {
   WHITE = '#FFF',
   DARK_LIGHT = '#E5E5E5',
   RED = '#EF5B5B',
+  LIGHT_GRAY = '#C8C8C8',
+  TEXT_GRAY = '#3E6471',
 }

+ 1 - 1
src/types/navigation.ts

@@ -7,7 +7,7 @@ export enum NAVIGATION_PAGES {
   RESET_PASSWORD_DEEP = 'resetPasswordDeep',
   IN_APP = 'inAppStack',
   MAP_TAB = 'Map',
-  TRAVELLERS_TAB = 'Travellers',
   TRAVELS_TAB = 'Travels',
+  TRAVELLERS_TAB = 'Travellers',
   PROFILE_TAB = 'Profile'
 }

+ 44 - 0
src/utils/mapHelpers.ts

@@ -0,0 +1,44 @@
+import * as turf from '@turf/turf';
+import { Feature } from '@turf/turf';
+
+interface RegionData {
+  type?: string;
+  name?: string;
+  crs?: {
+    type: string;
+    properties: { name: string; };
+  };
+  features?: any;
+}
+
+export const findRegionInDataset = (dataset: RegionData, point: turf.helpers.Position | turf.helpers.Point | turf.helpers.Feature<turf.helpers.Point, turf.helpers.Properties>): Feature | undefined => {
+  return dataset.features.find((region: any) => {
+    const coordinates = region?.geometry?.coordinates;
+    const type = region?.geometry?.type;
+
+    if (!Array.isArray(coordinates) || coordinates.length === 0) {
+      return false;
+    }
+
+    try {
+      const polygon: any = type === 'Polygon'
+        ? turf.polygon(coordinates)
+        : turf.multiPolygon(coordinates);
+
+      return turf.booleanPointInPolygon(point, polygon);
+    } catch (error) {
+      console.error('Error creating polygon:', error);
+      return false;
+    }
+  });
+};
+
+export const calculateMapRegion = (bounds: turf.BBox): any => {
+  const padding = 1;
+  return {
+    latitude: (bounds[1] + bounds[3]) / 2,
+    longitude: (bounds[0] + bounds[2]) / 2,
+    latitudeDelta: Math.abs(bounds[3] - bounds[1]) + padding,
+    longitudeDelta: Math.abs(bounds[2] - bounds[0]) + padding,
+  };
+};

Một số tệp đã không được hiển thị bởi vì quá nhiều tập tin thay đổi trong này khác