Ver Fonte

group chat base

Viktoriia há 4 meses atrás
pai
commit
eb8e499860
48 ficheiros alterados com 4616 adições e 788 exclusões
  1. 5 0
      Route.tsx
  2. 3 0
      assets/icons/events/calendar-check.svg
  3. 3 0
      assets/icons/events/calendar-plus.svg
  4. 1 1
      assets/icons/events/calendar-solid.svg
  5. 3 0
      assets/icons/events/file-solid.svg
  6. 3 0
      assets/icons/events/gift.svg
  7. 3 0
      assets/icons/events/image.svg
  8. 3 0
      assets/icons/events/shopping-cart.svg
  9. 1 1
      assets/icons/messages/camera.svg
  10. 11 0
      assets/icons/messages/group-chat.svg
  11. 1 1
      assets/icons/travels-screens/map-location.svg
  12. 1 1
      assets/icons/travels-section/earth.svg
  13. 0 36
      src/database/avatarsService/index.ts
  14. 14 0
      src/database/cacheService/index.ts
  15. 7 34
      src/database/index.ts
  16. 0 13
      src/modules/api/avatars/avatars-api.tsx
  17. 0 3
      src/modules/api/avatars/avatars-query-keys.tsx
  18. 0 3
      src/modules/api/avatars/index.ts
  19. 0 1
      src/modules/api/avatars/queries/index.ts
  20. 0 21
      src/modules/api/avatars/queries/use-post-get-avatars.tsx
  21. 105 2
      src/modules/api/chat/chat-api.ts
  22. 13 1
      src/modules/api/chat/chat-query-keys.tsx
  23. 7 0
      src/modules/api/chat/queries/index.ts
  24. 17 0
      src/modules/api/chat/queries/use-post-create-group.tsx
  25. 28 0
      src/modules/api/chat/queries/use-post-get-group-conversation.tsx
  26. 17 0
      src/modules/api/chat/queries/use-post-group-messages-read.tsx
  27. 17 0
      src/modules/api/chat/queries/use-post-group-messages-received.tsx
  28. 17 0
      src/modules/api/chat/queries/use-post-react-to-group-message.tsx
  29. 21 0
      src/modules/api/chat/queries/use-post-send-group-message.tsx
  30. 17 0
      src/modules/api/chat/queries/use-post-unreact-to-group-message.tsx
  31. 4 4
      src/screens/InAppScreens/MapScreen/index.tsx
  32. 10 6
      src/screens/InAppScreens/MessagesScreen/Components/AttachmentsModal.tsx
  33. 64 28
      src/screens/InAppScreens/MessagesScreen/Components/ReactionsListModal.tsx
  34. 412 0
      src/screens/InAppScreens/MessagesScreen/Components/RouteAddGroup.tsx
  35. 347 0
      src/screens/InAppScreens/MessagesScreen/Components/RouteAddUsers.tsx
  36. 168 0
      src/screens/InAppScreens/MessagesScreen/Components/RouteSearch.tsx
  37. 30 144
      src/screens/InAppScreens/MessagesScreen/Components/SearchUsersModal.tsx
  38. 249 6
      src/screens/InAppScreens/MessagesScreen/FullMapScreen/index.tsx
  39. 1956 0
      src/screens/InAppScreens/MessagesScreen/GroupChatScreen/index.tsx
  40. 37 18
      src/screens/InAppScreens/MessagesScreen/index.tsx
  41. 527 0
      src/screens/InAppScreens/TravelsScreen/EventScreen/index.tsx
  42. 216 0
      src/screens/InAppScreens/TravelsScreen/EventScreen/styles.tsx
  43. 155 266
      src/screens/InAppScreens/TravelsScreen/EventsScreen/index.tsx
  44. 61 186
      src/screens/InAppScreens/TravelsScreen/EventsScreen/styles.tsx
  45. 0 8
      src/screens/LocationSharingScreen/index.tsx
  46. 45 0
      src/stores/groupChatStore.ts
  47. 15 4
      src/types/api.ts
  48. 2 0
      src/types/navigation.ts

+ 5 - 0
Route.tsx

@@ -97,6 +97,8 @@ import { useFriendsNotificationsStore } from 'src/stores/friendsNotificationsSto
 import EventsScreen from 'src/screens/InAppScreens/TravelsScreen/EventsScreen';
 import { NavigationProvider } from 'src/contexts/NavigationContext';
 import FullMapScreen from 'src/screens/InAppScreens/MessagesScreen/FullMapScreen';
+import EventScreen from 'src/screens/InAppScreens/TravelsScreen/EventScreen';
+import GroupChatScreen from 'src/screens/InAppScreens/MessagesScreen/GroupChatScreen';
 
 enableScreens();
 
@@ -308,6 +310,7 @@ const Route = () => {
             <ScreenStack.Screen name={NAVIGATION_PAGES.FIXERS} component={FixersScreen} />
             <ScreenStack.Screen name={NAVIGATION_PAGES.ADD_FIXER} component={AddNewFixerScreen} />
             <ScreenStack.Screen name={NAVIGATION_PAGES.EVENTS} component={EventsScreen} />
+            <ScreenStack.Screen name={NAVIGATION_PAGES.EVENT} component={EventScreen} />
             <ScreenStack.Screen
               name={NAVIGATION_PAGES.FIXERS_COMMENTS}
               component={FixersCommentsScreen}
@@ -416,6 +419,8 @@ const Route = () => {
           <ScreenStack.Navigator screenOptions={screenOptions}>
             <ScreenStack.Screen name={NAVIGATION_PAGES.CHATS_LIST} component={MessagesScreen} />
             <ScreenStack.Screen name={NAVIGATION_PAGES.CHAT} component={ChatScreen} />
+            <ScreenStack.Screen name={NAVIGATION_PAGES.GROUP_CHAT} component={GroupChatScreen} />
+
             <ScreenStack.Screen name={NAVIGATION_PAGES.FULL_MAP_VIEW} component={FullMapScreen} />
             <ScreenStack.Screen
               name={NAVIGATION_PAGES.PUBLIC_PROFILE_VIEW}

+ 3 - 0
assets/icons/events/calendar-check.svg

@@ -0,0 +1,3 @@
+<svg width="14" height="16" viewBox="0 0 14 16" fill="none" xmlns="http://www.w3.org/2000/svg">
+<path d="M4 0C4.41563 0 4.75 0.334375 4.75 0.75V2H9.25V0.75C9.25 0.334375 9.58438 0 10 0C10.4156 0 10.75 0.334375 10.75 0.75V2H12C13.1031 2 14 2.89687 14 4V4.5V6V14C14 15.1031 13.1031 16 12 16H2C0.896875 16 0 15.1031 0 14V6V4.5V4C0 2.89687 0.896875 2 2 2H3.25V0.75C3.25 0.334375 3.58437 0 4 0ZM12.5 6H1.5V14C1.5 14.275 1.725 14.5 2 14.5H12C12.275 14.5 12.5 14.275 12.5 14V6ZM10.2812 9.28125L6.78125 12.7812C6.4875 13.075 6.0125 13.075 5.72188 12.7812L3.72187 10.7812C3.42812 10.4875 3.42812 10.0125 3.72187 9.72188C4.01562 9.43125 4.49062 9.42813 4.78125 9.72188L6.25 11.1906L9.21875 8.22188C9.5125 7.92813 9.9875 7.92813 10.2781 8.22188C10.5687 8.51562 10.5719 8.99063 10.2781 9.28125H10.2812Z" fill="white"/>
+</svg>

+ 3 - 0
assets/icons/events/calendar-plus.svg

@@ -0,0 +1,3 @@
+<svg width="18" height="20" viewBox="0 0 18 20" fill="none" xmlns="http://www.w3.org/2000/svg">
+<path d="M3.75 1.25V2.5H1.875C0.839844 2.5 0 3.33984 0 4.375V6.25H17.5V4.375C17.5 3.33984 16.6602 2.5 15.625 2.5H13.75V1.25C13.75 0.558594 13.1914 0 12.5 0C11.8086 0 11.25 0.558594 11.25 1.25V2.5H6.25V1.25C6.25 0.558594 5.69141 0 5 0C4.30859 0 3.75 0.558594 3.75 1.25ZM17.5 7.5H0V18.125C0 19.1602 0.839844 20 1.875 20H15.625C16.6602 20 17.5 19.1602 17.5 18.125V7.5ZM8.75 9.6875C9.26953 9.6875 9.6875 10.1055 9.6875 10.625V12.8125H11.875C12.3945 12.8125 12.8125 13.2305 12.8125 13.75C12.8125 14.2695 12.3945 14.6875 11.875 14.6875H9.6875V16.875C9.6875 17.3945 9.26953 17.8125 8.75 17.8125C8.23047 17.8125 7.8125 17.3945 7.8125 16.875V14.6875H5.625C5.10547 14.6875 4.6875 14.2695 4.6875 13.75C4.6875 13.2305 5.10547 12.8125 5.625 12.8125H7.8125V10.625C7.8125 10.1055 8.23047 9.6875 8.75 9.6875Z" fill="#0F3F4F"/>
+</svg>

+ 1 - 1
assets/icons/events/calendar-solid.svg

@@ -1,6 +1,6 @@
 <svg width="29" height="32" viewBox="0 0 29 32" fill="none" xmlns="http://www.w3.org/2000/svg">
 <g clip-path="url(#clip0_4524_47052)">
-<path d="M8.25 0C9.35625 0 10.25 0.89375 10.25 2V4H18.25V2C18.25 0.89375 19.1437 0 20.25 0C21.3563 0 22.25 0.89375 22.25 2V4H25.25C26.9062 4 28.25 5.34375 28.25 7V10H0.25V7C0.25 5.34375 1.59375 4 3.25 4H6.25V2C6.25 0.89375 7.14375 0 8.25 0ZM0.25 12H28.25V29C28.25 30.6562 26.9062 32 25.25 32H3.25C1.59375 32 0.25 30.6562 0.25 29V12ZM4.25 17V19C4.25 19.55 4.7 20 5.25 20H7.25C7.8 20 8.25 19.55 8.25 19V17C8.25 16.45 7.8 16 7.25 16H5.25C4.7 16 4.25 16.45 4.25 17ZM12.25 17V19C12.25 19.55 12.7 20 13.25 20H15.25C15.8 20 16.25 19.55 16.25 19V17C16.25 16.45 15.8 16 15.25 16H13.25C12.7 16 12.25 16.45 12.25 17ZM21.25 16C20.7 16 20.25 16.45 20.25 17V19C20.25 19.55 20.7 20 21.25 20H23.25C23.8 20 24.25 19.55 24.25 19V17C24.25 16.45 23.8 16 23.25 16H21.25ZM4.25 25V27C4.25 27.55 4.7 28 5.25 28H7.25C7.8 28 8.25 27.55 8.25 27V25C8.25 24.45 7.8 24 7.25 24H5.25C4.7 24 4.25 24.45 4.25 25ZM13.25 24C12.7 24 12.25 24.45 12.25 25V27C12.25 27.55 12.7 28 13.25 28H15.25C15.8 28 16.25 27.55 16.25 27V25C16.25 24.45 15.8 24 15.25 24H13.25ZM20.25 25V27C20.25 27.55 20.7 28 21.25 28H23.25C23.8 28 24.25 27.55 24.25 27V25C24.25 24.45 23.8 24 23.25 24H21.25C20.7 24 20.25 24.45 20.25 25Z" fill="#ED9334"/>
+<path d="M8.25 0C9.35625 0 10.25 0.89375 10.25 2V4H18.25V2C18.25 0.89375 19.1437 0 20.25 0C21.3563 0 22.25 0.89375 22.25 2V4H25.25C26.9062 4 28.25 5.34375 28.25 7V10H0.25V7C0.25 5.34375 1.59375 4 3.25 4H6.25V2C6.25 0.89375 7.14375 0 8.25 0ZM0.25 12H28.25V29C28.25 30.6562 26.9062 32 25.25 32H3.25C1.59375 32 0.25 30.6562 0.25 29V12ZM4.25 17V19C4.25 19.55 4.7 20 5.25 20H7.25C7.8 20 8.25 19.55 8.25 19V17C8.25 16.45 7.8 16 7.25 16H5.25C4.7 16 4.25 16.45 4.25 17ZM12.25 17V19C12.25 19.55 12.7 20 13.25 20H15.25C15.8 20 16.25 19.55 16.25 19V17C16.25 16.45 15.8 16 15.25 16H13.25C12.7 16 12.25 16.45 12.25 17ZM21.25 16C20.7 16 20.25 16.45 20.25 17V19C20.25 19.55 20.7 20 21.25 20H23.25C23.8 20 24.25 19.55 24.25 19V17C24.25 16.45 23.8 16 23.25 16H21.25ZM4.25 25V27C4.25 27.55 4.7 28 5.25 28H7.25C7.8 28 8.25 27.55 8.25 27V25C8.25 24.45 7.8 24 7.25 24H5.25C4.7 24 4.25 24.45 4.25 25ZM13.25 24C12.7 24 12.25 24.45 12.25 25V27C12.25 27.55 12.7 28 13.25 28H15.25C15.8 28 16.25 27.55 16.25 27V25C16.25 24.45 15.8 24 15.25 24H13.25ZM20.25 25V27C20.25 27.55 20.7 28 21.25 28H23.25C23.8 28 24.25 27.55 24.25 27V25C24.25 24.45 23.8 24 23.25 24H21.25C20.7 24 20.25 24.45 20.25 25Z"/>
 </g>
 <defs>
 <clipPath id="clip0_4524_47052">

+ 3 - 0
assets/icons/events/file-solid.svg

@@ -0,0 +1,3 @@
+<svg width="14" height="19" viewBox="0 0 14 19" fill="none" xmlns="http://www.w3.org/2000/svg">
+<path d="M11.75 16.8125C12.0594 16.8125 12.3125 16.5594 12.3125 16.25V6.125H9.5C8.87773 6.125 8.375 5.62227 8.375 5V2.1875H2.75C2.44062 2.1875 2.1875 2.44062 2.1875 2.75V16.25C2.1875 16.5594 2.44062 16.8125 2.75 16.8125H11.75ZM0.5 2.75C0.5 1.50898 1.50898 0.5 2.75 0.5H8.56836C9.16602 0.5 9.73906 0.735547 10.1609 1.15742L13.3426 4.33906C13.7645 4.76094 14 5.33398 14 5.93164V16.25C14 17.491 12.991 18.5 11.75 18.5H2.75C1.50898 18.5 0.5 17.491 0.5 16.25V2.75Z" fill="white"/>
+</svg>

+ 3 - 0
assets/icons/events/gift.svg

@@ -0,0 +1,3 @@
+<svg width="16" height="16" viewBox="0 0 16 16" fill="none" xmlns="http://www.w3.org/2000/svg">
+<path d="M5.95312 2.15L7.04063 4H7H4.75C4.05937 4 3.5 3.44062 3.5 2.75C3.5 2.05938 4.05937 1.5 4.75 1.5H4.81875C5.28437 1.5 5.71875 1.74688 5.95312 2.15ZM2 2.75C2 3.2 2.10938 3.625 2.3 4H1C0.446875 4 0 4.44688 0 5V7C0 7.55312 0.446875 8 1 8H15C15.5531 8 16 7.55312 16 7V5C16 4.44688 15.5531 4 15 4H13.7C13.8906 3.625 14 3.2 14 2.75C14 1.23125 12.7688 0 11.25 0H11.1812C10.1844 0 9.25938 0.528125 8.75313 1.3875L8 2.67188L7.24687 1.39062C6.74062 0.528125 5.81562 0 4.81875 0H4.75C3.23125 0 2 1.23125 2 2.75ZM12.5 2.75C12.5 3.44062 11.9406 4 11.25 4H9H8.95938L10.0469 2.15C10.2844 1.74688 10.7156 1.5 11.1812 1.5H11.25C11.9406 1.5 12.5 2.05938 12.5 2.75ZM1 9V14.5C1 15.3281 1.67188 16 2.5 16H7V9H1ZM9 16H13.5C14.3281 16 15 15.3281 15 14.5V9H9V16Z" fill="white"/>
+</svg>

+ 3 - 0
assets/icons/events/image.svg

@@ -0,0 +1,3 @@
+<svg width="18" height="16" viewBox="0 0 18 16" fill="none" xmlns="http://www.w3.org/2000/svg">
+<path d="M15.75 1.8125C16.0594 1.8125 16.3125 2.06562 16.3125 2.375V13.618L16.1367 13.3895L11.3555 7.20195C11.1973 6.99453 10.9477 6.875 10.6875 6.875C10.4273 6.875 10.1812 6.99453 10.0195 7.20195L7.10156 10.9777L6.0293 9.47656C5.87109 9.25508 5.61797 9.125 5.34375 9.125C5.06953 9.125 4.81641 9.25508 4.6582 9.48008L1.8457 13.4176L1.6875 13.6355V13.625V2.375C1.6875 2.06562 1.94062 1.8125 2.25 1.8125H15.75ZM2.25 0.125C1.00898 0.125 0 1.13398 0 2.375V13.625C0 14.866 1.00898 15.875 2.25 15.875H15.75C16.991 15.875 18 14.866 18 13.625V2.375C18 1.13398 16.991 0.125 15.75 0.125H2.25ZM5.0625 6.875C5.28411 6.875 5.50354 6.83135 5.70828 6.74655C5.91302 6.66174 6.09904 6.53744 6.25574 6.38074C6.41244 6.22404 6.53674 6.03802 6.62155 5.83328C6.70635 5.62854 6.75 5.40911 6.75 5.1875C6.75 4.96589 6.70635 4.74646 6.62155 4.54172C6.53674 4.33698 6.41244 4.15096 6.25574 3.99426C6.09904 3.83756 5.91302 3.71326 5.70828 3.62845C5.50354 3.54365 5.28411 3.5 5.0625 3.5C4.84089 3.5 4.62146 3.54365 4.41672 3.62845C4.21198 3.71326 4.02596 3.83756 3.86926 3.99426C3.71256 4.15096 3.58826 4.33698 3.50345 4.54172C3.41865 4.74646 3.375 4.96589 3.375 5.1875C3.375 5.40911 3.41865 5.62854 3.50345 5.83328C3.58826 6.03802 3.71256 6.22404 3.86926 6.38074C4.02596 6.53744 4.21198 6.66174 4.41672 6.74655C4.62146 6.83135 4.84089 6.875 5.0625 6.875Z" fill="white"/>
+</svg>

+ 3 - 0
assets/icons/events/shopping-cart.svg

@@ -0,0 +1,3 @@
+<svg width="12" height="12" viewBox="0 0 12 12" fill="none" xmlns="http://www.w3.org/2000/svg">
+<path d="M0 1.16699C0 0.889909 0.222917 0.666992 0.5 0.666992H1.44792C1.90625 0.666992 2.3125 0.933659 2.50208 1.33366H11.0646C11.6125 1.33366 12.0125 1.85449 11.8688 2.38366L11.0146 5.55658C10.8375 6.21074 10.2438 6.66699 9.56667 6.66699H3.55625L3.66875 7.26074C3.71458 7.49616 3.92083 7.66699 4.16042 7.66699H10.1667C10.4438 7.66699 10.6667 7.88991 10.6667 8.16699C10.6667 8.44408 10.4438 8.66699 10.1667 8.66699H4.16042C3.43958 8.66699 2.82083 8.15449 2.6875 7.44824L1.6125 1.80241C1.59792 1.72324 1.52917 1.66699 1.44792 1.66699H0.5C0.222917 1.66699 0 1.44408 0 1.16699ZM2.66667 10.3337C2.66667 10.2023 2.69253 10.0723 2.74279 9.95097C2.79304 9.82965 2.8667 9.71941 2.95956 9.62655C3.05242 9.53369 3.16266 9.46003 3.28398 9.40978C3.40531 9.35952 3.53534 9.33366 3.66667 9.33366C3.79799 9.33366 3.92802 9.35952 4.04935 9.40978C4.17068 9.46003 4.28092 9.53369 4.37377 9.62655C4.46663 9.71941 4.54029 9.82965 4.59055 9.95097C4.6408 10.0723 4.66667 10.2023 4.66667 10.3337C4.66667 10.465 4.6408 10.595 4.59055 10.7163C4.54029 10.8377 4.46663 10.9479 4.37377 11.0408C4.28092 11.1336 4.17068 11.2073 4.04935 11.2575C3.92802 11.3078 3.79799 11.3337 3.66667 11.3337C3.53534 11.3337 3.40531 11.3078 3.28398 11.2575C3.16266 11.2073 3.05242 11.1336 2.95956 11.0408C2.8667 10.9479 2.79304 10.8377 2.74279 10.7163C2.69253 10.595 2.66667 10.465 2.66667 10.3337ZM9.66667 9.33366C9.93188 9.33366 10.1862 9.43902 10.3738 9.62655C10.5613 9.81409 10.6667 10.0684 10.6667 10.3337C10.6667 10.5989 10.5613 10.8532 10.3738 11.0408C10.1862 11.2283 9.93188 11.3337 9.66667 11.3337C9.40145 11.3337 9.1471 11.2283 8.95956 11.0408C8.77202 10.8532 8.66667 10.5989 8.66667 10.3337C8.66667 10.0684 8.77202 9.81409 8.95956 9.62655C9.1471 9.43902 9.40145 9.33366 9.66667 9.33366Z" fill="white"/>
+</svg>

+ 1 - 1
assets/icons/messages/camera.svg

@@ -1,6 +1,6 @@
 <svg width="37" height="36" viewBox="0 0 37 36" fill="none" xmlns="http://www.w3.org/2000/svg">
 <g clip-path="url(#clip0_4486_39236)">
-<path d="M10.9836 4.55625L10.2523 6.75H5C2.51797 6.75 0.5 8.76797 0.5 11.25V29.25C0.5 31.732 2.51797 33.75 5 33.75H32C34.482 33.75 36.5 31.732 36.5 29.25V11.25C36.5 8.76797 34.482 6.75 32 6.75H26.7477L26.0164 4.55625C25.5594 3.17813 24.2727 2.25 22.8172 2.25H14.1828C12.7273 2.25 11.4406 3.17813 10.9836 4.55625ZM18.5 13.5C20.2902 13.5 22.0071 14.2112 23.273 15.477C24.5388 16.7429 25.25 18.4598 25.25 20.25C25.25 22.0402 24.5388 23.7571 23.273 25.023C22.0071 26.2888 20.2902 27 18.5 27C16.7098 27 14.9929 26.2888 13.727 25.023C12.4612 23.7571 11.75 22.0402 11.75 20.25C11.75 18.4598 12.4612 16.7429 13.727 15.477C14.9929 14.2112 16.7098 13.5 18.5 13.5Z" fill="#ED9334"/>
+<path d="M10.9836 4.55625L10.2523 6.75H5C2.51797 6.75 0.5 8.76797 0.5 11.25V29.25C0.5 31.732 2.51797 33.75 5 33.75H32C34.482 33.75 36.5 31.732 36.5 29.25V11.25C36.5 8.76797 34.482 6.75 32 6.75H26.7477L26.0164 4.55625C25.5594 3.17813 24.2727 2.25 22.8172 2.25H14.1828C12.7273 2.25 11.4406 3.17813 10.9836 4.55625ZM18.5 13.5C20.2902 13.5 22.0071 14.2112 23.273 15.477C24.5388 16.7429 25.25 18.4598 25.25 20.25C25.25 22.0402 24.5388 23.7571 23.273 25.023C22.0071 26.2888 20.2902 27 18.5 27C16.7098 27 14.9929 26.2888 13.727 25.023C12.4612 23.7571 11.75 22.0402 11.75 20.25C11.75 18.4598 12.4612 16.7429 13.727 15.477C14.9929 14.2112 16.7098 13.5 18.5 13.5Z"/>
 </g>
 <defs>
 <clipPath id="clip0_4486_39236">

+ 11 - 0
assets/icons/messages/group-chat.svg

@@ -0,0 +1,11 @@
+<svg width="28" height="28" viewBox="0 0 28 28" fill="none" xmlns="http://www.w3.org/2000/svg">
+<rect width="28" height="28" rx="14" fill="#F4F4F4"/>
+<g clip-path="url(#clip0_4804_40844)">
+<path d="M8.5 6C9.16304 6 9.79893 6.26339 10.2678 6.73223C10.7366 7.20107 11 7.83696 11 8.5C11 9.16304 10.7366 9.79893 10.2678 10.2678C9.79893 10.7366 9.16304 11 8.5 11C7.83696 11 7.20107 10.7366 6.73223 10.2678C6.26339 9.79893 6 9.16304 6 8.5C6 7.83696 6.26339 7.20107 6.73223 6.73223C7.20107 6.26339 7.83696 6 8.5 6ZM20 6C20.663 6 21.2989 6.26339 21.7678 6.73223C22.2366 7.20107 22.5 7.83696 22.5 8.5C22.5 9.16304 22.2366 9.79893 21.7678 10.2678C21.2989 10.7366 20.663 11 20 11C19.337 11 18.7011 10.7366 18.2322 10.2678C17.7634 9.79893 17.5 9.16304 17.5 8.5C17.5 7.83696 17.7634 7.20107 18.2322 6.73223C18.7011 6.26339 19.337 6 20 6ZM4 15.3344C4 13.4937 5.49375 12 7.33437 12H8.66875C9.16562 12 9.6375 12.1094 10.0625 12.3031C10.0219 12.5281 10.0031 12.7625 10.0031 13C10.0031 14.1937 10.5281 15.2656 11.3562 16C11.35 16 11.3437 16 11.3344 16H4.66562C4.3 16 4 15.7 4 15.3344ZM16.6656 16C16.6594 16 16.6531 16 16.6438 16C17.475 15.2656 17.9969 14.1937 17.9969 13C17.9969 12.7625 17.975 12.5312 17.9375 12.3031C18.3625 12.1062 18.8344 12 19.3313 12H20.6656C22.5062 12 24 13.4937 24 15.3344C24 15.7031 23.7 16 23.3344 16H16.6687H16.6656ZM11 13C11 12.2044 11.3161 11.4413 11.8787 10.8787C12.4413 10.3161 13.2044 10 14 10C14.7956 10 15.5587 10.3161 16.1213 10.8787C16.6839 11.4413 17 12.2044 17 13C17 13.7956 16.6839 14.5587 16.1213 15.1213C15.5587 15.6839 14.7956 16 14 16C13.2044 16 12.4413 15.6839 11.8787 15.1213C11.3161 14.5587 11 13.7956 11 13ZM8 21.1656C8 18.8656 9.86562 17 12.1656 17H15.8313C18.1344 17 20 18.8656 20 21.1656C20 21.625 19.6281 22 19.1656 22H8.83125C8.37188 22 7.99688 21.6281 7.99688 21.1656H8Z" fill="#0F3F4F"/>
+</g>
+<defs>
+<clipPath id="clip0_4804_40844">
+<rect width="20" height="16" fill="white" transform="translate(4 6)"/>
+</clipPath>
+</defs>
+</svg>

+ 1 - 1
assets/icons/travels-screens/map-location.svg

@@ -1,6 +1,6 @@
 <svg width="20" height="18" viewBox="0 0 20 18" fill="none" xmlns="http://www.w3.org/2000/svg">
 <g clip-path="url(#clip0_3810_38142)">
-<path d="M14.1667 4.16667C14.1667 6.0625 11.6285 9.44097 10.5139 10.8333C10.2465 11.1667 9.75 11.1667 9.48611 10.8333C8.37153 9.44097 5.83333 6.0625 5.83333 4.16667C5.83333 1.86458 7.69792 0 10 0C12.3021 0 14.1667 1.86458 14.1667 4.16667ZM14.4444 6.95833C14.566 6.71875 14.6771 6.47917 14.7778 6.24306C14.7951 6.20139 14.8125 6.15625 14.8299 6.11458L18.8576 4.50347C19.4062 4.28472 20 4.6875 20 5.27778V14.6806C20 15.0208 19.7917 15.3264 19.4757 15.4549L14.4444 17.4653V6.95833ZM4.77778 4.80208C4.86111 5.29167 5.02778 5.78472 5.22222 6.24306C5.32292 6.47917 5.43403 6.71875 5.55556 6.95833V15.6875L1.14236 17.4549C0.59375 17.6736 0 17.2708 0 16.6806V7.27778C0 6.9375 0.208333 6.63194 0.524306 6.50347L4.78125 4.80208H4.77778ZM11.3819 11.5278C11.8646 10.9236 12.6215 9.94097 13.3333 8.85417V17.5104L6.66667 15.6042V8.85417C7.37847 9.94097 8.13542 10.9236 8.61806 11.5278C9.32986 12.4167 10.6701 12.4167 11.3819 11.5278ZM10 5.27778C10.3684 5.27778 10.7216 5.13145 10.9821 4.87098C11.2426 4.61051 11.3889 4.25725 11.3889 3.88889C11.3889 3.52053 11.2426 3.16726 10.9821 2.9068C10.7216 2.64633 10.3684 2.5 10 2.5C9.63164 2.5 9.27837 2.64633 9.01791 2.9068C8.75744 3.16726 8.61111 3.52053 8.61111 3.88889C8.61111 4.25725 8.75744 4.61051 9.01791 4.87098C9.27837 5.13145 9.63164 5.27778 10 5.27778Z" fill="white"/>
+<path d="M14.1667 4.16667C14.1667 6.0625 11.6285 9.44097 10.5139 10.8333C10.2465 11.1667 9.75 11.1667 9.48611 10.8333C8.37153 9.44097 5.83333 6.0625 5.83333 4.16667C5.83333 1.86458 7.69792 0 10 0C12.3021 0 14.1667 1.86458 14.1667 4.16667ZM14.4444 6.95833C14.566 6.71875 14.6771 6.47917 14.7778 6.24306C14.7951 6.20139 14.8125 6.15625 14.8299 6.11458L18.8576 4.50347C19.4062 4.28472 20 4.6875 20 5.27778V14.6806C20 15.0208 19.7917 15.3264 19.4757 15.4549L14.4444 17.4653V6.95833ZM4.77778 4.80208C4.86111 5.29167 5.02778 5.78472 5.22222 6.24306C5.32292 6.47917 5.43403 6.71875 5.55556 6.95833V15.6875L1.14236 17.4549C0.59375 17.6736 0 17.2708 0 16.6806V7.27778C0 6.9375 0.208333 6.63194 0.524306 6.50347L4.78125 4.80208H4.77778ZM11.3819 11.5278C11.8646 10.9236 12.6215 9.94097 13.3333 8.85417V17.5104L6.66667 15.6042V8.85417C7.37847 9.94097 8.13542 10.9236 8.61806 11.5278C9.32986 12.4167 10.6701 12.4167 11.3819 11.5278ZM10 5.27778C10.3684 5.27778 10.7216 5.13145 10.9821 4.87098C11.2426 4.61051 11.3889 4.25725 11.3889 3.88889C11.3889 3.52053 11.2426 3.16726 10.9821 2.9068C10.7216 2.64633 10.3684 2.5 10 2.5C9.63164 2.5 9.27837 2.64633 9.01791 2.9068C8.75744 3.16726 8.61111 3.52053 8.61111 3.88889C8.61111 4.25725 8.75744 4.61051 9.01791 4.87098C9.27837 5.13145 9.63164 5.27778 10 5.27778Z"/>
 </g>
 <defs>
 <clipPath id="clip0_3810_38142">

+ 1 - 1
assets/icons/travels-section/earth.svg

@@ -1,6 +1,6 @@
 <svg width="33" height="32" viewBox="0 0 33 32" fill="none" xmlns="http://www.w3.org/2000/svg">
 <g clip-path="url(#clip0_2651_21785)">
-<path d="M4.35625 12.0625L4.94375 13.0875C5.4625 13.9937 6.3125 14.6625 7.31875 14.95L10.9375 15.9812C12.0125 16.2875 12.75 17.2688 12.75 18.3875V20.8813C12.75 21.5688 13.1375 22.1938 13.75 22.5C14.3625 22.8062 14.75 23.4312 14.75 24.1187V26.5562C14.75 27.5312 15.6812 28.2375 16.6187 27.9688C17.625 27.6812 18.4062 26.875 18.6625 25.8563L18.8375 25.1562C19.1 24.1 19.7875 23.1938 20.7313 22.6562L21.2375 22.3687C22.175 21.8375 22.75 20.8375 22.75 19.7625V19.2437C22.75 18.45 22.4312 17.6875 21.8687 17.125L21.625 16.8813C21.0625 16.3188 20.3 16 19.5063 16H16.8125C16.1187 16 15.4312 15.8188 14.825 15.475L12.6687 14.2437C12.4 14.0875 12.1938 13.8375 12.0938 13.5437C11.8938 12.9437 12.1625 12.2937 12.7312 12.0125L13.1 11.825C13.5125 11.6187 13.9937 11.5813 14.4312 11.7312L15.8813 12.2125C16.3938 12.3812 16.9562 12.1875 17.25 11.7437C17.5438 11.3062 17.5125 10.725 17.175 10.3188L16.325 9.3C15.7 8.55 15.7063 7.45625 16.3438 6.71875L17.325 5.575C17.875 4.93125 17.9625 4.0125 17.5438 3.28125L17.3937 3.01875C17.175 3.00625 16.9625 3 16.7437 3C10.9437 3 6.025 6.80625 4.35625 12.0625ZM29.75 16C29.75 13.7 29.15 11.5375 28.1 9.65625L26.5 10.3C25.5187 10.6938 25.0125 11.7875 25.3438 12.7875L26.4 15.9563C26.6187 16.6063 27.15 17.1 27.8125 17.2625L29.6313 17.7188C29.7063 17.1562 29.7437 16.5812 29.7437 16H29.75ZM0.75 16C0.75 11.7565 2.43571 7.68687 5.43629 4.68629C8.43687 1.68571 12.5065 0 16.75 0C20.9935 0 25.0631 1.68571 28.0637 4.68629C31.0643 7.68687 32.75 11.7565 32.75 16C32.75 20.2435 31.0643 24.3131 28.0637 27.3137C25.0631 30.3143 20.9935 32 16.75 32C12.5065 32 8.43687 30.3143 5.43629 27.3137C2.43571 24.3131 0.75 20.2435 0.75 16Z" fill="#ED9334"/>
+<path d="M4.35625 12.0625L4.94375 13.0875C5.4625 13.9937 6.3125 14.6625 7.31875 14.95L10.9375 15.9812C12.0125 16.2875 12.75 17.2688 12.75 18.3875V20.8813C12.75 21.5688 13.1375 22.1938 13.75 22.5C14.3625 22.8062 14.75 23.4312 14.75 24.1187V26.5562C14.75 27.5312 15.6812 28.2375 16.6187 27.9688C17.625 27.6812 18.4062 26.875 18.6625 25.8563L18.8375 25.1562C19.1 24.1 19.7875 23.1938 20.7313 22.6562L21.2375 22.3687C22.175 21.8375 22.75 20.8375 22.75 19.7625V19.2437C22.75 18.45 22.4312 17.6875 21.8687 17.125L21.625 16.8813C21.0625 16.3188 20.3 16 19.5063 16H16.8125C16.1187 16 15.4312 15.8188 14.825 15.475L12.6687 14.2437C12.4 14.0875 12.1938 13.8375 12.0938 13.5437C11.8938 12.9437 12.1625 12.2937 12.7312 12.0125L13.1 11.825C13.5125 11.6187 13.9937 11.5813 14.4312 11.7312L15.8813 12.2125C16.3938 12.3812 16.9562 12.1875 17.25 11.7437C17.5438 11.3062 17.5125 10.725 17.175 10.3188L16.325 9.3C15.7 8.55 15.7063 7.45625 16.3438 6.71875L17.325 5.575C17.875 4.93125 17.9625 4.0125 17.5438 3.28125L17.3937 3.01875C17.175 3.00625 16.9625 3 16.7437 3C10.9437 3 6.025 6.80625 4.35625 12.0625ZM29.75 16C29.75 13.7 29.15 11.5375 28.1 9.65625L26.5 10.3C25.5187 10.6938 25.0125 11.7875 25.3438 12.7875L26.4 15.9563C26.6187 16.6063 27.15 17.1 27.8125 17.2625L29.6313 17.7188C29.7063 17.1562 29.7437 16.5812 29.7437 16H29.75ZM0.75 16C0.75 11.7565 2.43571 7.68687 5.43629 4.68629C8.43687 1.68571 12.5065 0 16.75 0C20.9935 0 25.0631 1.68571 28.0637 4.68629C31.0643 7.68687 32.75 11.7565 32.75 16C32.75 20.2435 31.0643 24.3131 28.0637 27.3137C25.0631 30.3143 20.9935 32 16.75 32C12.5065 32 8.43687 30.3143 5.43629 27.3137C2.43571 24.3131 0.75 20.2435 0.75 16Z"/>
 </g>
 <defs>
 <clipPath id="clip0_2651_21785">

+ 0 - 36
src/database/avatarsService/index.ts

@@ -1,36 +0,0 @@
-import { fetchUpdatedAvatars } from '@api/avatars';
-import * as FileSystem from 'expo-file-system';
-import { storage } from 'src/storage';
-import { API_HOST } from 'src/constants';
-
-const dirPath = `${FileSystem.documentDirectory}avatars`;
-
-async function downloadAvatar(userId: number) {
-  try {
-    const avatarUrl = `${API_HOST}/img/avatars/small/${userId}.webp`;
-    let fileUri = `${dirPath}/${userId}.webp`;
-
-    await FileSystem.downloadAsync(avatarUrl, fileUri);
-  } catch (error) {
-    console.error('Error downloading avatars: ', error);
-  }
-}
-
-export async function updateAvatars(lastUpdateDate: string) {
-  const avatars = await fetchUpdatedAvatars(lastUpdateDate);
-  const currentDate = new Date().toISOString().split('T')[0];
-
-  if (avatars && avatars.updated) {
-    const dirInfo = await FileSystem.getInfoAsync(dirPath);
-
-    if (!dirInfo.exists) {
-      await FileSystem.makeDirectoryAsync(dirPath, { intermediates: true });
-    }
-
-    for (const avatar of avatars.updated) {
-      await downloadAvatar(avatar);
-    }
-
-    storage.set('lastUpdateDate', currentDate);
-  }
-}

+ 14 - 0
src/database/cacheService/index.ts

@@ -5,6 +5,8 @@ import {
   CACHED_ATTACHMENTS_DIR
 } from 'src/constants/constants';
 
+const dirPath = `${FileSystem.documentDirectory}avatars`;
+
 export const cleanCache = async () => {
   try {
     const dirInfo = await FileSystem.getInfoAsync(CACHED_ATTACHMENTS_DIR);
@@ -36,3 +38,15 @@ export const cleanCache = async () => {
     console.error('Error cleaning cache:', error);
   }
 };
+
+export async function deleteAvatarsDirectory() {
+  try {
+    const dirInfo = await FileSystem.getInfoAsync(dirPath);
+
+    if (dirInfo.exists) {
+      await FileSystem.deleteAsync(dirPath, { idempotent: true });
+    }
+  } catch (error) {
+    console.error('Error deleting avatars directory: ', error);
+  }
+}

+ 7 - 34
src/database/index.ts

@@ -1,37 +1,19 @@
-import * as SQLite from 'expo-sqlite/legacy';
 import NetInfo, { NetInfoState } from '@react-native-community/netinfo';
 import { StoreType, storage } from 'src/storage';
 import { fetchLimitedRanking, fetchLpi, fetchInHistory, fetchInMemoriam } from '@api/ranking';
 import { initOfflineSetup } from './tilesService';
 import { downloadFlags } from './flagsService';
 import { fetchAndSaveAllTypesAndMasters } from './unMastersService';
-import { updateAvatars } from './avatarsService';
 import { fetchAndSaveStatistics } from './statisticsService';
 import { saveTriumphsData } from './triumphsService';
 import { saveSeriesRankingData } from './seriesRankingService';
 import { updateDarePlacesDb, updateNmRegionsDb } from 'src/db';
-import { cleanCache } from './cacheService';
+import { cleanCache, deleteAvatarsDirectory } from './cacheService';
 
-const db = SQLite.openDatabase('nomadManiaDb.db');
-const lastUpdateDate = storage.get('lastUpdateDate', StoreType.STRING) as string || '1990-01-01';
-const lastUpdateNmRegions = storage.get('lastUpdateNmRegions', StoreType.STRING) as string || '1990-01-01';
-const lastUpdateDarePlaces = storage.get('lastUpdateDarePlaces', StoreType.STRING) as string || '1990-01-01';
-
-export const initializeDatabase = (): Promise<void> => {
-  return new Promise<void>((resolve, reject) => {
-    db.transaction(tx => {
-      createSyncTriggers('tableName');
-
-      resolve();
-    }, error => {
-      console.error('DB initialization error: ', error);
-      reject(error);
-    });
-  });
-};
-
-const createSyncTriggers = (tableName: string): void => {
-};
+const lastUpdateNmRegions =
+  (storage.get('lastUpdateNmRegions', StoreType.STRING) as string) || '1990-01-01';
+const lastUpdateDarePlaces =
+  (storage.get('lastUpdateDarePlaces', StoreType.STRING) as string) || '1990-01-01';
 
 export const checkInternetConnection = async (): Promise<NetInfoState> => {
   const state = await NetInfo.fetch();
@@ -42,31 +24,22 @@ export const syncDataWithServer = async (): Promise<void> => {
   const { isConnected } = await checkInternetConnection();
 
   if (isConnected) {
-    processSyncQueue();
     await updateDarePlacesDb(lastUpdateDarePlaces);
     await updateMasterRanking();
     await updateNmRegionsDb(lastUpdateNmRegions);
     await downloadFlags();
     await initOfflineSetup();
-    // await updateAvatars(lastUpdateDate);
   }
 };
 
-const processSyncQueue = (): void => {
-};
-
-export const updateStaticGeoJSON = async (): Promise<void> => {
-};
-
 export const setupDatabaseAndSync = async (): Promise<void> => {
-  await initializeDatabase();
   await syncDataWithServer();
-  await updateStaticGeoJSON();
   cleanCache();
+  deleteAvatarsDirectory();
 };
 
 export const updateMasterRanking = async () => {
-  const token = storage.get('token', StoreType.STRING) as string || '';
+  const token = (storage.get('token', StoreType.STRING) as string) || '';
   const dataLimitedRanking = await fetchLimitedRanking();
 
   if (dataLimitedRanking && dataLimitedRanking?.data) {

+ 0 - 13
src/modules/api/avatars/avatars-api.tsx

@@ -1,13 +0,0 @@
-import { request } from '../../../utils';
-import { API } from '../../../types';
-import { ResponseType } from '../response-type';
-
-export interface PostGetAvatars extends ResponseType {
-  empty: number[];
-  updated: number[]
-}
-
-export const avatarsApi = {
-  getUpdatedAvatars: (date: string) =>
-    request.postForm<PostGetAvatars>(API.GET_UPDATED_AVATARS, { date })
-};

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

@@ -1,3 +0,0 @@
-export const avatarsQueryKeys = {
-  getUpdatedAvatars: (date: string) => ['getUpdatedAvatars', { date }] as const,
-};

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

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

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

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

+ 0 - 21
src/modules/api/avatars/queries/use-post-get-avatars.tsx

@@ -1,21 +0,0 @@
-import { avatarsQueryKeys } from '../avatars-query-keys';
-import { type PostGetAvatars, avatarsApi } from '../avatars-api';
-import { queryClient } from 'src/utils/queryClient';
-
-export const fetchUpdatedAvatars = async (date: string) => {
-  try {
-    const data: PostGetAvatars = await queryClient.fetchQuery({
-      queryKey: avatarsQueryKeys.getUpdatedAvatars(date),
-      queryFn: async () => {
-        const response = await avatarsApi.getUpdatedAvatars(date);
-        return response.data;
-      },
-      gcTime: 0,
-      staleTime: 0
-    });
-    
-    return data;
-  } catch (error) {
-    console.error('Failed to fetch updated avatars:', error);
-  }
-};

+ 105 - 2
src/modules/api/chat/chat-api.ts

@@ -14,7 +14,8 @@ export interface PostSearchUsersReturn extends ResponseType {
 // status: 1 -sent; 2 -received; 3 -read; 4 - deleted
 export interface PostGetChatsListReturn extends ResponseType {
   conversations: {
-    uid: number;
+    uid: number | null;
+    group_chat_token: string | null;
     name: string;
     avatar: string | null;
     short: string;
@@ -75,6 +76,14 @@ export interface PostSendMessage {
   attachment?: { uri: string; type: string; name?: string } | -1;
 }
 
+export interface PostSendGroupMessage {
+  token: string;
+  to_group_token: string;
+  text: string;
+  reply_to_id?: number;
+  attachment?: { uri: string; type: string; name?: string } | -1;
+}
+
 export interface PostSendMessageReturn extends ResponseType {
   message_id: number;
   attachment?: any;
@@ -86,6 +95,12 @@ export interface PostMessagesReceivedOrRead {
   messages_id: number[];
 }
 
+export interface PostGroupMessagesReceivedOrRead {
+  token: string;
+  group_token: string;
+  messages_id: number[];
+}
+
 export interface PostReactToMessage {
   token: string;
   message_id: number;
@@ -99,6 +114,19 @@ export interface PostUnreactToMessage {
   conversation_with_user: number;
 }
 
+export interface PostReactToGroupMessage {
+  token: string;
+  message_id: number;
+  reaction: string;
+  group_token: string;
+}
+
+export interface PostUnreactToGroupMessage {
+  token: string;
+  message_id: number;
+  group_token: string;
+}
+
 export interface PostDeleteMessage {
   token: string;
   message_id: number;
@@ -134,6 +162,18 @@ export interface PostGetUnreadCountReturn extends ResponseType {
   unread_conversations: number;
 }
 
+export interface PostCreateGroup {
+  token: string;
+  users: number[];
+  name: string;
+  admins: number[] | [];
+  description?: string;
+  group_avatar?: { uri: string; type: string; name?: string };
+  members_can_edit_settings?: 0 | 1;
+  members_can_add_new_members?: 0 | 1;
+  members_can_send_messages?: 0 | 1;
+}
+
 export const chatApi = {
   searchUsers: (token: string, search: string) =>
     request.postForm<PostSearchUsersReturn>(API.SEARCH_USERS, { token, search }),
@@ -191,5 +231,68 @@ export const chatApi = {
       token
     }),
   reportConversation: (data: PostReportConversation) =>
-    request.postForm<ResponseType>(API.REPORT_CONVERSATION, data)
+    request.postForm<ResponseType>(API.REPORT_CONVERSATION, data),
+  createGroup: (data: PostCreateGroup) => {
+    const formData = new FormData();
+
+    formData.append('token', data.token);
+    formData.append('users', JSON.stringify(data.users));
+    formData.append('name', data.name);
+
+    if (data.description) {
+      formData.append('description', data.description);
+    }
+
+    formData.append('admins', JSON.stringify(data.admins));
+
+    if (data.group_avatar) {
+      const { uri, type, name } = data.group_avatar;
+      formData.append('attachment', {
+        uri,
+        type: type === 'image' ? type + '/' + uri.split('.').pop()! : type,
+        name: name || 'file'
+      } as unknown as Blob);
+    }
+
+    return request.postForm<ResponseType>(API.CREATE_GROUP, formData);
+  },
+  getGroupChat: (
+    token: string,
+    group_token: string,
+    no_of_messages: number,
+    previous_than_message_id: number
+  ) =>
+    request.postForm<PostGetChatWithReturn>(API.GET_GROUP_CHAT, {
+      token,
+      group_token,
+      no_of_messages,
+      previous_than_message_id
+    }),
+  sendGroupMessage: (data: PostSendGroupMessage) => {
+    const formData = new FormData();
+    formData.append('token', data.token);
+    formData.append('to_group_token', data.to_group_token);
+    formData.append('text', data.text);
+    if (data.reply_to_id && data.reply_to_id !== undefined) {
+      formData.append('reply_to_id', data.reply_to_id.toString());
+    }
+    if (data.attachment && data.attachment !== -1) {
+      const { uri, type, name } = data.attachment;
+      formData.append('attachment', {
+        uri,
+        type: type === 'image' ? type + '/' + uri.split('.').pop()! : type,
+        name: name || 'file'
+      } as unknown as Blob);
+    }
+
+    return request.postForm<PostSendMessageReturn>(API.SEND_GROUP_MESSAGE, formData);
+  },
+  groupMessagesReceived: (data: PostGroupMessagesReceivedOrRead) =>
+    request.postForm<ResponseType>(API.GROUP_MESSAGES_RECEIVED, data),
+  groupMessagesRead: (data: PostGroupMessagesReceivedOrRead) =>
+    request.postForm<ResponseType>(API.GROUP_MESSAGES_READ, data),
+  reactToGroupMessage: (data: PostReactToGroupMessage) =>
+    request.postForm<ResponseType>(API.REACT_TO_GROUP_MESSAGE, data),
+  unreactToGroupMessage: (data: PostUnreactToGroupMessage) =>
+    request.postForm<ResponseType>(API.UNREACT_TO_GROUP_MESSAGE, data)
 };

+ 13 - 1
src/modules/api/chat/chat-query-keys.tsx

@@ -20,5 +20,17 @@ export const chatQueryKeys = {
   unreactToMessage: () => ['unreactToMessage'] as const,
   getBlocked: (token: string) => ['getBlocked', token] as const,
   getUnreadMessagesCount: (token: string) => ['getUnreadMessagesCount', token] as const,
-  reportConversation: () => ['reportConversation'] as const
+  reportConversation: () => ['reportConversation'] as const,
+  createGroup: () => ['createGroup'] as const,
+  getGroupChat: (
+    token: string,
+    group_token: string,
+    no_of_messages: number,
+    previous_than_message_id: number
+  ) => ['getChatWith', token, group_token, no_of_messages, previous_than_message_id] as const,
+  sendGroupMessage: () => ['sendGroupMessage'] as const,
+  groupMessagesReceived: () => ['groupMessagesReceived'] as const,
+  groupMessagesRead: () => ['groupMessagesRead'] as const,
+  reactToGroupMessage: () => ['reactToGroupMessage'] as const,
+  unreactToGroupMessage: () => ['unreactToGroupMessage'] as const
 };

+ 7 - 0
src/modules/api/chat/queries/index.ts

@@ -15,3 +15,10 @@ export * from './use-post-unreact-to-message';
 export * from './use-post-get-blocked';
 export * from './use-post-get-new-messages-present';
 export * from './use-post-report-conversation';
+export * from './use-post-create-group';
+export * from './use-post-get-group-conversation';
+export * from './use-post-send-group-message';
+export * from './use-post-group-messages-received';
+export * from './use-post-group-messages-read';
+export * from './use-post-react-to-group-message';
+export * from './use-post-unreact-to-group-message';

+ 17 - 0
src/modules/api/chat/queries/use-post-create-group.tsx

@@ -0,0 +1,17 @@
+import { useMutation } from '@tanstack/react-query';
+
+import { chatQueryKeys } from '../chat-query-keys';
+import { chatApi, type PostCreateGroup } from '../chat-api';
+
+import type { BaseAxiosError } from '../../../../types';
+import { ResponseType } from '@api/response-type';
+
+export const usePostCreateGroupMutation = () => {
+  return useMutation<ResponseType, BaseAxiosError, PostCreateGroup, ResponseType>({
+    mutationKey: chatQueryKeys.createGroup(),
+    mutationFn: async (data) => {
+      const response = await chatApi.createGroup(data);
+      return response.data;
+    }
+  });
+};

+ 28 - 0
src/modules/api/chat/queries/use-post-get-group-conversation.tsx

@@ -0,0 +1,28 @@
+import { useQuery } from '@tanstack/react-query';
+
+import { chatQueryKeys } from '../chat-query-keys';
+import { chatApi, type PostGetChatWithReturn } from '../chat-api';
+
+import type { BaseAxiosError } from '../../../../types';
+
+export const usePostGetGroupChatQuery = (
+  token: string,
+  group_token: string,
+  no_of_messages: number,
+  previous_than_message_id: number,
+  enabled: boolean
+) => {
+  return useQuery<PostGetChatWithReturn, BaseAxiosError>({
+    queryKey: chatQueryKeys.getGroupChat(token, group_token, no_of_messages, previous_than_message_id),
+    queryFn: async () => {
+      const response = await chatApi.getGroupChat(
+        token,
+        group_token,
+        no_of_messages,
+        previous_than_message_id
+      );
+      return response.data;
+    },
+    enabled
+  });
+};

+ 17 - 0
src/modules/api/chat/queries/use-post-group-messages-read.tsx

@@ -0,0 +1,17 @@
+import { useMutation } from '@tanstack/react-query';
+
+import { chatQueryKeys } from '../chat-query-keys';
+import { chatApi, type PostGroupMessagesReceivedOrRead } from '../chat-api';
+
+import type { BaseAxiosError } from '../../../../types';
+import { ResponseType } from '@api/response-type';
+
+export const usePostGroupMessagesReadMutation = () => {
+  return useMutation<ResponseType, BaseAxiosError, PostGroupMessagesReceivedOrRead, ResponseType>({
+    mutationKey: chatQueryKeys.groupMessagesRead(),
+    mutationFn: async (data) => {
+      const response = await chatApi.groupMessagesRead(data);
+      return response.data;
+    }
+  });
+};

+ 17 - 0
src/modules/api/chat/queries/use-post-group-messages-received.tsx

@@ -0,0 +1,17 @@
+import { useMutation } from '@tanstack/react-query';
+
+import { chatQueryKeys } from '../chat-query-keys';
+import { chatApi, type PostGroupMessagesReceivedOrRead } from '../chat-api';
+
+import type { BaseAxiosError } from '../../../../types';
+import { ResponseType } from '@api/response-type';
+
+export const usePostGroupMessagesReceivedMutation = () => {
+  return useMutation<ResponseType, BaseAxiosError, PostGroupMessagesReceivedOrRead, ResponseType>({
+    mutationKey: chatQueryKeys.groupMessagesReceived(),
+    mutationFn: async (data) => {
+      const response = await chatApi.groupMessagesReceived(data);
+      return response.data;
+    }
+  });
+};

+ 17 - 0
src/modules/api/chat/queries/use-post-react-to-group-message.tsx

@@ -0,0 +1,17 @@
+import { useMutation } from '@tanstack/react-query';
+
+import { chatQueryKeys } from '../chat-query-keys';
+import { chatApi, type PostReactToGroupMessage } from '../chat-api';
+
+import type { BaseAxiosError } from '../../../../types';
+import { ResponseType } from '@api/response-type';
+
+export const usePostReactToGroupMessageMutation = () => {
+  return useMutation<ResponseType, BaseAxiosError, PostReactToGroupMessage, ResponseType>({
+    mutationKey: chatQueryKeys.reactToGroupMessage(),
+    mutationFn: async (data) => {
+      const response = await chatApi.reactToGroupMessage(data);
+      return response.data;
+    }
+  });
+};

+ 21 - 0
src/modules/api/chat/queries/use-post-send-group-message.tsx

@@ -0,0 +1,21 @@
+import { useMutation } from '@tanstack/react-query';
+
+import { chatQueryKeys } from '../chat-query-keys';
+import { chatApi, type PostSendGroupMessage, type PostSendMessageReturn } from '../chat-api';
+
+import type { BaseAxiosError } from '../../../../types';
+
+export const usePostSendGroupMessageMutation = () => {
+  return useMutation<
+    PostSendMessageReturn,
+    BaseAxiosError,
+    PostSendGroupMessage,
+    PostSendMessageReturn
+  >({
+    mutationKey: chatQueryKeys.sendGroupMessage(),
+    mutationFn: async (data) => {
+      const response = await chatApi.sendGroupMessage(data);
+      return response.data;
+    }
+  });
+};

+ 17 - 0
src/modules/api/chat/queries/use-post-unreact-to-group-message.tsx

@@ -0,0 +1,17 @@
+import { useMutation } from '@tanstack/react-query';
+
+import { chatQueryKeys } from '../chat-query-keys';
+import { chatApi, type PostUnreactToGroupMessage } from '../chat-api';
+
+import type { BaseAxiosError } from '../../../../types';
+import { ResponseType } from '@api/response-type';
+
+export const usePostUnreactToGroupMessageMutation = () => {
+  return useMutation<ResponseType, BaseAxiosError, PostUnreactToGroupMessage, ResponseType>({
+    mutationKey: chatQueryKeys.unreactToGroupMessage(),
+    mutationFn: async (data) => {
+      const response = await chatApi.unreactToGroupMessage(data);
+      return response.data;
+    }
+  });
+};

+ 4 - 4
src/screens/InAppScreens/MapScreen/index.tsx

@@ -661,14 +661,14 @@ const MapScreen: any = ({ navigation, route }: { navigation: any; route: any })
         });
         setLocation(currentLocation.coords);
 
-        if (showNomads && token) {
+        if (locationSettings && locationSettings.sharing === 1 && token) {
           updateLocation({
             token,
             lat: currentLocation.coords.latitude,
             lng: currentLocation.coords.longitude
           });
 
-          refetchUsersLocation();
+          showNomads && refetchUsersLocation();
         }
       } catch (error) {
         console.error('Error fetching user location:', error);
@@ -960,14 +960,14 @@ const MapScreen: any = ({ navigation, route }: { navigation: any; route: any })
         );
       }
 
-      if (showNomads && token) {
+      if (locationSettings && locationSettings.sharing === 1 && token) {
         updateLocation({
           token,
           lat: currentLocation.coords.latitude,
           lng: currentLocation.coords.longitude
         });
 
-        refetchUsersLocation();
+        showNomads && refetchUsersLocation();
       }
 
       handleClosePopup();

+ 10 - 6
src/screens/InAppScreens/MessagesScreen/Components/AttachmentsModal.tsx

@@ -171,7 +171,7 @@ const AttachmentsModal = () => {
           </TouchableOpacity>
 
           <TouchableOpacity style={styles.optionItem} onPress={handleOpenCamera}>
-            <CameraIcon height={36} />
+            <CameraIcon height={36} fill={Colors.ORANGE} />
             <Text style={styles.optionLabel}>Camera</Text>
           </TouchableOpacity>
 
@@ -195,10 +195,15 @@ const AttachmentsModal = () => {
             <Text style={styles.optionLabel}>Live</Text>
           </TouchableOpacity> */}
 
-          <TouchableOpacity style={styles.optionItem} onPress={handleReport}>
-            <MegaphoneIcon fill={Colors.RED} width={36} height={36} />
-            <Text style={styles.optionLabel}>Report</Text>
-          </TouchableOpacity>
+          {!chatDataRef.current?.isGroup ? (
+            <TouchableOpacity style={styles.optionItem} onPress={handleReport}>
+              <MegaphoneIcon fill={Colors.RED} width={36} height={36} />
+              <Text style={styles.optionLabel}>Report</Text>
+            </TouchableOpacity>
+          ) : (
+            <View style={styles.optionItem}></View>
+          )}
+
           <View style={styles.optionItem}></View>
         </View>
       </View>
@@ -220,7 +225,6 @@ const AttachmentsModal = () => {
   return (
     <ActionSheet
       id="chat-attachments"
-      // gestureEnabled={true}
       containerStyle={{
         backgroundColor: Colors.FILL_LIGHT
       }}

+ 64 - 28
src/screens/InAppScreens/MessagesScreen/Components/ReactionsListModal.tsx

@@ -1,7 +1,7 @@
 import React, { useState } from 'react';
 import { View, Text, TouchableOpacity, FlatList, StyleSheet } from 'react-native';
 import ActionSheet, { SheetManager } from 'react-native-actions-sheet';
-import { usePostUnreactToMessageMutation } from '@api/chat';
+import { usePostUnreactToGroupMessageMutation, usePostUnreactToMessageMutation } from '@api/chat';
 import { getFontSize } from 'src/utils';
 import { Colors } from 'src/theme';
 import { CustomMessage } from '../types';
@@ -15,9 +15,12 @@ const ReactionsListModal = () => {
     conversation_with_user: number;
     setMessages: (messages: any) => void;
     sendWebSocketMessage: (action: string, data: any) => void;
+    isGroup?: boolean;
+    groupToken?: string;
   } | null>(null);
 
   const { mutateAsync: unreactToMessage } = usePostUnreactToMessageMutation();
+  const { mutateAsync: unreactToGroupMessage } = usePostUnreactToGroupMessageMutation();
 
   const handleSheetOpen = (
     payload: {
@@ -28,6 +31,8 @@ const ReactionsListModal = () => {
       conversation_with_user: number;
       setMessages: (messages: any) => void;
       sendWebSocketMessage: (action: string, data: any) => void;
+      isGroup?: boolean;
+      groupToken?: string;
     } | null
   ) => {
     setReactionsData(payload);
@@ -35,34 +40,65 @@ const ReactionsListModal = () => {
 
   const handleUnreact = () => {
     if (reactionsData) {
-      unreactToMessage(
-        {
-          token: reactionsData.token,
-          message_id: reactionsData.messageId,
-          conversation_with_user: reactionsData.conversation_with_user
-        },
-        {
-          onSuccess: () => {
-            SheetManager.hide('reactions-list-modal');
-            reactionsData.setMessages((prevMessages: any) =>
-              prevMessages?.map((msg: any) => {
-                if (msg._id === reactionsData.messageId) {
-                  return {
-                    ...msg,
-                    reactions: Array.isArray(msg.reactions)
-                      ? msg.reactions.filter((r: any) => r.uid !== reactionsData.currentUserId)
-                      : []
-                  };
-                }
-                return msg;
-              })
-            );
-            reactionsData.sendWebSocketMessage('unreact', {
-              _id: reactionsData.messageId
-            } as unknown as CustomMessage);
+      if (reactionsData.isGroup) {
+        unreactToGroupMessage(
+          {
+            token: reactionsData.token,
+            message_id: reactionsData.messageId,
+            group_token: reactionsData.groupToken as string
+          },
+          {
+            onSuccess: () => {
+              SheetManager.hide('reactions-list-modal');
+              reactionsData.setMessages((prevMessages: any) =>
+                prevMessages?.map((msg: any) => {
+                  if (msg._id === reactionsData.messageId) {
+                    return {
+                      ...msg,
+                      reactions: Array.isArray(msg.reactions)
+                        ? msg.reactions.filter((r: any) => r.uid !== reactionsData.currentUserId)
+                        : []
+                    };
+                  }
+                  return msg;
+                })
+              );
+              reactionsData.sendWebSocketMessage('unreact', {
+                _id: reactionsData.messageId
+              } as unknown as CustomMessage);
+            }
+          }
+        );
+      } else {
+        unreactToMessage(
+          {
+            token: reactionsData.token,
+            message_id: reactionsData.messageId,
+            conversation_with_user: reactionsData.conversation_with_user
+          },
+          {
+            onSuccess: () => {
+              SheetManager.hide('reactions-list-modal');
+              reactionsData.setMessages((prevMessages: any) =>
+                prevMessages?.map((msg: any) => {
+                  if (msg._id === reactionsData.messageId) {
+                    return {
+                      ...msg,
+                      reactions: Array.isArray(msg.reactions)
+                        ? msg.reactions.filter((r: any) => r.uid !== reactionsData.currentUserId)
+                        : []
+                    };
+                  }
+                  return msg;
+                })
+              );
+              reactionsData.sendWebSocketMessage('unreact', {
+                _id: reactionsData.messageId
+              } as unknown as CustomMessage);
+            }
           }
-        }
-      );
+        );
+      }
     }
   };
 

+ 412 - 0
src/screens/InAppScreens/MessagesScreen/Components/RouteAddGroup.tsx

@@ -0,0 +1,412 @@
+import React, { useEffect, useState } from 'react';
+import {
+  View,
+  Text,
+  TouchableOpacity,
+  StyleSheet,
+  Image,
+  ActivityIndicator,
+  ScrollView
+} from 'react-native';
+import { useNavigation } from '@react-navigation/native';
+import { Colors } from 'src/theme';
+import { Input } from 'src/components';
+import { storage, StoreType } from 'src/storage';
+import { PostCreateGroup, usePostCreateGroupMutation } from '@api/chat';
+import { API_HOST } from 'src/constants';
+import { AvatarWithInitials } from 'src/components';
+import * as ImagePicker from 'expo-image-picker';
+import * as yup from 'yup';
+
+import { FlashList } from '@shopify/flash-list';
+import { useSheetRouter } from 'react-native-actions-sheet';
+import { getFontSize } from 'src/utils';
+
+import CloseIcon from 'assets/icons/close.svg';
+import CameraIcon from 'assets/icons/messages/camera.svg';
+import { Formik } from 'formik';
+import { useGroupChatStore } from 'src/stores/groupChatStore';
+import { NAVIGATION_PAGES } from 'src/types';
+
+const ProfileSchema = yup.object({
+  name: yup
+    .string()
+    .required('name is required')
+    .min(3, 'group name should be at least 3 characters'),
+  description: yup.string().optional().max(8000, 'description should not exceed 8000 characters'),
+  users: yup.array().min(2, 'select at least 2 members').required('members are required')
+});
+
+const RouteAddGroup = () => {
+  const router = useSheetRouter('search-modal');
+  const token = storage.get('token', StoreType.STRING) as string;
+  const navigation = useNavigation();
+
+  const {
+    selectedUsers,
+    removeUser,
+    image,
+    setImage,
+    groupName,
+    setGroupName,
+    description,
+    setDescription,
+    clearStore
+  } = useGroupChatStore();
+  const [isSubmitting, setIsSubmitting] = useState(false);
+
+  const { mutate: createGroup } = usePostCreateGroupMutation();
+
+  const pickImage = async () => {
+    let result = await ImagePicker.launchImageLibraryAsync({
+      mediaTypes: ImagePicker.MediaTypeOptions.Images,
+      allowsEditing: true,
+      aspect: [4, 3],
+      quality: 1
+    });
+
+    if (!result.canceled) {
+      setImage(result.assets[0]);
+    }
+  };
+
+  const renderUserItem = ({ item }: { item: any }) => {
+    return (
+      <View style={styles.selectedUserContainer}>
+        <View style={styles.userContainer}>
+          <TouchableOpacity onPress={() => removeUser(item.user_id)} style={styles.removeIcon}>
+            <CloseIcon width={10} height={10} fill={Colors.WHITE} />
+          </TouchableOpacity>
+          {item.avatar ? (
+            <Image source={{ uri: API_HOST + item.avatar }} style={styles.selectedAvatar} />
+          ) : (
+            <AvatarWithInitials
+              text={`${item.first_name[0] ?? ''}${item.last_name[0] ?? ''}`}
+              flag={API_HOST + item.flag1}
+              size={60}
+              fontSize={21}
+              borderColor={Colors.LIGHT_GRAY}
+              borderWidth={1}
+            />
+          )}
+        </View>
+      </View>
+    );
+  };
+
+  return (
+    <Formik
+      validationSchema={ProfileSchema}
+      initialValues={{
+        name: groupName,
+        description,
+        users: selectedUsers
+      }}
+      onSubmit={async (values) => {
+        setIsSubmitting(true);
+        const groupData: PostCreateGroup = {
+          token,
+          name: values.name,
+          description: values.description,
+          users: selectedUsers.map((user) => +user.user_id),
+          admins: []
+        };
+
+        if (image && image.uri) {
+          groupData.group_avatar = {
+            type: image.type || 'image',
+            uri: image.uri,
+            name: image.uri.split('/').pop()!
+          };
+        }
+
+        await createGroup(groupData, {
+          onSuccess: (res) => {
+            console.log('res', res);
+            setIsSubmitting(false);
+            navigation.navigate(
+              ...([
+                NAVIGATION_PAGES.CHAT,
+                {
+                  id: 8948,
+                  name: 'Test Name',
+                  avatar: null,
+                  userType: 'normal'
+                }
+              ] as never)
+            );
+            router?.close();
+          },
+          onError: (err) => {
+            console.log('err', err);
+            setIsSubmitting(false);
+          }
+        });
+      }}
+    >
+      {(props) => {
+        useEffect(() => {
+          props.setFieldValue('users', selectedUsers);
+        }, [selectedUsers]);
+
+        return (
+          <View style={styles.container}>
+            <View style={styles.header}>
+              <TouchableOpacity
+                onPress={() => {
+                  router?.goBack();
+                }}
+                style={{
+                  paddingTop: 16,
+                  paddingBottom: 6,
+                  paddingHorizontal: 6
+                }}
+              >
+                <Text style={styles.headerText}>Back</Text>
+              </TouchableOpacity>
+              {isSubmitting ? (
+                <View
+                  style={{
+                    paddingTop: 10,
+                    paddingHorizontal: 10
+                  }}
+                >
+                  <ActivityIndicator size="small" color={Colors.DARK_BLUE} />
+                </View>
+              ) : (
+                <TouchableOpacity
+                  style={{
+                    paddingTop: 16,
+                    paddingBottom: 6,
+                    paddingHorizontal: 6
+                  }}
+                  onPress={() => props.handleSubmit()}
+                >
+                  <Text style={styles.headerText}>Save</Text>
+                </TouchableOpacity>
+              )}
+            </View>
+
+            <ScrollView
+              showsVerticalScrollIndicator={false}
+              style={{ flex: 1 }}
+              contentContainerStyle={{ gap: 16 }}
+            >
+              <View style={styles.photoContainer}>
+                <TouchableOpacity style={styles.photoContainer} onPress={pickImage}>
+                  {!image && (
+                    <>
+                      <View style={[styles.groupPhoto, { backgroundColor: Colors.FILL_LIGHT }]}>
+                        <CameraIcon width={36} height={36} fill={Colors.LIGHT_GRAY} />
+                      </View>
+                      <Text style={styles.photoText}>Add photo</Text>
+                    </>
+                  )}
+                  {image && (
+                    <>
+                      <Image
+                        source={{ uri: image.uri }}
+                        style={{
+                          width: 80,
+                          height: 80,
+                          borderRadius: 40,
+                          borderWidth: 1,
+                          borderColor: Colors.FILL_LIGHT
+                        }}
+                      />
+                      <Text style={styles.photoText}>Change photo</Text>
+                    </>
+                  )}
+                </TouchableOpacity>
+              </View>
+
+              <Input
+                placeholder="Add group name"
+                value={props.values.name}
+                inputMode={'text'}
+                onChange={(text) => {
+                  props.handleChange('name')(text);
+                  setGroupName(text);
+                }}
+                onBlur={props.handleBlur('name')}
+                header="Group name"
+                formikError={props.touched.name && props.errors.name}
+              />
+
+              <Input
+                placeholder="Add group description"
+                value={props.values.description}
+                onChange={(text) => {
+                  props.handleChange('description')(text);
+                  setDescription(text);
+                }}
+                onBlur={props.handleBlur('description')}
+                header="Description"
+                multiline
+                height={58}
+                formikError={props.touched.description && props.errors.description}
+              />
+
+              {selectedUsers.length > 0 ? (
+                <View
+                  style={[
+                    styles.usersRow,
+                    props.errors.users ? { borderColor: Colors.RED, borderWidth: 1 } : {}
+                  ]}
+                >
+                  <FlashList
+                    viewabilityConfig={{
+                      waitForInteraction: true,
+                      itemVisiblePercentThreshold: 50,
+                      minimumViewTime: 1000
+                    }}
+                    scrollEnabled={false}
+                    data={selectedUsers}
+                    renderItem={renderUserItem}
+                    keyExtractor={(item) => item.user_id.toString()}
+                    estimatedItemSize={100}
+                    extraData={selectedUsers}
+                    showsVerticalScrollIndicator={false}
+                    contentContainerStyle={styles.selectedUsersList}
+                    numColumns={4}
+                  />
+                </View>
+              ) : null}
+              {props.errors.users && (
+                <Text
+                  style={[styles.textError, selectedUsers.length > 0 ? { marginTop: -32 } : {}]}
+                >
+                  select at least 2 members
+                </Text>
+              )}
+            </ScrollView>
+          </View>
+        );
+      }}
+    </Formik>
+  );
+};
+
+const styles = StyleSheet.create({
+  container: {
+    gap: 16,
+    height: '100%',
+    backgroundColor: Colors.WHITE
+  },
+  header: {
+    flexDirection: 'row',
+    justifyContent: 'space-between',
+    alignItems: 'center'
+  },
+  headerText: {
+    color: Colors.DARK_BLUE,
+    fontSize: getFontSize(14),
+    fontWeight: '700'
+  },
+  photoContainer: {
+    alignItems: 'center',
+    gap: 8
+  },
+  groupPhoto: {
+    width: 80,
+    height: 80,
+    borderRadius: 40,
+    alignItems: 'center',
+    justifyContent: 'center'
+  },
+  photoText: {
+    color: Colors.DARK_BLUE,
+    fontSize: 12,
+    fontWeight: '700'
+  },
+  input: {
+    marginBottom: 12
+  },
+  userItem: {
+    flexDirection: 'row',
+    alignItems: 'center',
+    paddingVertical: 8,
+    paddingHorizontal: 12,
+    backgroundColor: Colors.FILL_LIGHT,
+    gap: 8,
+    borderRadius: 8,
+    marginBottom: 6
+  },
+  avatar: {
+    width: 36,
+    height: 36,
+    borderRadius: 18,
+    borderWidth: 1,
+    borderColor: Colors.LIGHT_GRAY
+  },
+  userName: {
+    color: Colors.DARK_BLUE,
+    fontSize: getFontSize(14),
+    fontFamily: 'montserrat-700'
+  },
+  userSubtitle: {
+    color: Colors.DARK_BLUE,
+    fontSize: 14,
+    fontFamily: 'montserrat-500'
+  },
+  userNM: {
+    color: Colors.DARK_BLUE,
+    fontSize: 14,
+    fontFamily: 'montserrat-700',
+    marginRight: 12
+  },
+  unselectedCircle: {
+    width: 20,
+    height: 20,
+    borderRadius: 10,
+    borderWidth: 1,
+    borderColor: Colors.LIGHT_GRAY,
+    justifyContent: 'center',
+    alignItems: 'center'
+  },
+  selectedUsersList: {
+    paddingTop: 12
+  },
+  selectedUserContainer: {
+    position: 'relative',
+    width: '100%',
+    alignItems: 'center',
+    paddingBottom: 12
+  },
+  userContainer: {},
+  selectedAvatar: {
+    width: 60,
+    height: 60,
+    borderRadius: 30,
+    borderWidth: 1,
+    borderColor: Colors.LIGHT_GRAY
+  },
+  removeIcon: {
+    position: 'absolute',
+    top: -4,
+    right: -4,
+    width: 22,
+    height: 22,
+    borderRadius: 11,
+    borderWidth: 1,
+    borderColor: Colors.WHITE,
+    backgroundColor: Colors.RED,
+    justifyContent: 'center',
+    alignItems: 'center',
+    zIndex: 1
+  },
+  usersRow: {
+    flex: 1,
+    backgroundColor: Colors.FILL_LIGHT,
+    borderRadius: 8,
+    marginBottom: 24
+  },
+  textError: {
+    color: Colors.RED,
+    fontSize: getFontSize(12),
+    fontFamily: 'redhat-600',
+    marginTop: 5
+  }
+});
+
+export default RouteAddGroup;

+ 347 - 0
src/screens/InAppScreens/MessagesScreen/Components/RouteAddUsers.tsx

@@ -0,0 +1,347 @@
+import React, { useCallback, useEffect, useState } from 'react';
+import { View, Text, TouchableOpacity, StyleSheet, Image, ActivityIndicator } from 'react-native';
+import { Colors } from 'src/theme';
+import { Input } from 'src/components';
+import { storage, StoreType } from 'src/storage';
+import { usePostSearchUsers } from '@api/chat';
+import { API_HOST } from 'src/constants';
+import { AvatarWithInitials } from 'src/components';
+
+import SearchIcon from 'assets/icons/search.svg';
+import CheckSvg from 'assets/icons/travels-screens/circle-check.svg';
+import { FlashList } from '@shopify/flash-list';
+import { useSheetRouter } from 'react-native-actions-sheet';
+import { getFontSize } from 'src/utils';
+
+import CloseIcon from 'assets/icons/close.svg';
+import { useGetMyFriendsMutation } from '@api/friends';
+import { useGroupChatStore } from 'src/stores/groupChatStore';
+
+const RouteAddUsers = () => {
+  const router = useSheetRouter('search-modal');
+  const token = storage.get('token', StoreType.STRING) as string;
+
+  const { selectedUsers, addUser, removeUser, clearStore } = useGroupChatStore();
+
+  const [searchQuery, setSearchQuery] = useState('');
+  const [friends, setFriends] = useState<any[]>([]);
+  const [page, setPage] = useState(0);
+  const [isLoadingMore, setIsLoadingMore] = useState(false);
+  const [maxPages, setMaxPages] = useState(1);
+
+  const { mutateAsync: getMyFriends } = useGetMyFriendsMutation();
+
+  const { data: searchResult, isFetching } = usePostSearchUsers(
+    token,
+    searchQuery,
+    searchQuery.length > 1
+  );
+
+  useEffect(() => {
+    getMyFriends(
+      {
+        token,
+        type: 'friends',
+        page: 0
+      },
+      {
+        onSuccess: (data) => {
+          setFriends(data.friends.users);
+          setMaxPages(data.friends.max_pages);
+        }
+      }
+    );
+  }, []);
+
+  useEffect(() => {
+    const getNextPage = async () => {
+      await getMyFriends(
+        {
+          token,
+          type: 'friends',
+          page
+        },
+        {
+          onSuccess: (data) => {
+            setIsLoadingMore(false);
+            setFriends((prevState) => [...prevState, ...data['friends'].users]);
+          }
+        }
+      );
+    };
+    if (page !== 0) {
+      getNextPage();
+    }
+  }, [page]);
+
+  const handleEndReached = useCallback(() => {
+    if (friends && page < maxPages && !isLoadingMore && maxPages > 1) {
+      setIsLoadingMore(true);
+      setPage((prevPage) => prevPage + 1);
+    }
+  }, [friends, page]);
+
+  const toggleUserSelection = (user: any) => {
+    const isSelected = selectedUsers.some((selected) => selected.user_id === user.user_id);
+    if (isSelected) {
+      removeUser(user.user_id);
+    } else {
+      addUser(user);
+    }
+  };
+
+  const renderUserItem = ({ item }: { item: any }) => {
+    const isSelected = selectedUsers.some((selected) => selected.user_id === item.user_id);
+    return (
+      <TouchableOpacity style={styles.userItem} onPress={() => toggleUserSelection(item)}>
+        {item.avatar ? (
+          <Image source={{ uri: API_HOST + item.avatar }} style={styles.avatar} />
+        ) : (
+          <AvatarWithInitials
+            text={`${item.first_name[0] ?? ''}${item.last_name[0] ?? ''}`}
+            flag={API_HOST + item.flag1}
+            size={36}
+            fontSize={16}
+            borderColor={Colors.LIGHT_GRAY}
+            borderWidth={1}
+          />
+        )}
+        <View style={styles.userDetails}>
+          <Text style={styles.userName}>
+            {item.first_name} {item.last_name}
+          </Text>
+        </View>
+        <View style={styles.unselectedCircle}>
+          {isSelected && <CheckSvg fill={Colors.DARK_BLUE} height={20} width={20} />}
+        </View>
+      </TouchableOpacity>
+    );
+  };
+
+  const renderSelectedUser = ({ item }: { item: any }) => (
+    <View style={styles.selectedUserContainer}>
+      <TouchableOpacity onPress={() => removeUser(item.user_id)} style={styles.removeIcon}>
+        <CloseIcon width={10} height={10} fill={Colors.WHITE} />
+      </TouchableOpacity>
+      {item.avatar ? (
+        <Image source={{ uri: API_HOST + item.avatar }} style={styles.selectedAvatar} />
+      ) : (
+        <AvatarWithInitials
+          text={`${item.first_name[0] ?? ''}${item.last_name[0] ?? ''}`}
+          flag={API_HOST + item.flag1}
+          size={60}
+          fontSize={21}
+          borderColor={Colors.LIGHT_GRAY}
+          borderWidth={1}
+        />
+      )}
+    </View>
+  );
+
+  return (
+    <View style={styles.container}>
+      <View style={styles.header}>
+        <TouchableOpacity
+          onPress={() => {
+            clearStore();
+            router?.goBack();
+          }}
+          style={{
+            paddingTop: 16,
+            paddingBottom: 6,
+            paddingHorizontal: 6
+          }}
+        >
+          <Text style={styles.headerText}>Cancel</Text>
+        </TouchableOpacity>
+        <TouchableOpacity
+          onPress={() => router?.navigate('route-add-group', { users: selectedUsers })}
+          style={{
+            paddingTop: 16,
+            paddingBottom: 6,
+            paddingHorizontal: 6
+          }}
+        >
+          <Text style={styles.headerText}>Next</Text>
+        </TouchableOpacity>
+      </View>
+
+      <Input
+        inputMode={'search'}
+        placeholder={'Search nomads'}
+        onChange={(text) => {
+          setSearchQuery(text);
+        }}
+        value={searchQuery}
+        icon={<SearchIcon fill={'#C8C8C8'} width={14} height={14} />}
+      />
+
+      {selectedUsers.length > 0 && (
+        <View style={styles.usersRow}>
+          <FlashList
+            horizontal
+            data={selectedUsers}
+            renderItem={renderSelectedUser}
+            keyExtractor={(item) => item.user_id.toString()}
+            showsHorizontalScrollIndicator={false}
+            contentContainerStyle={styles.selectedUsersList}
+            estimatedItemSize={50}
+          />
+        </View>
+      )}
+
+      {isFetching ? (
+        <ActivityIndicator size="large" color={Colors.DARK_BLUE} />
+      ) : searchQuery ? (
+        <FlashList
+          viewabilityConfig={{
+            waitForInteraction: true,
+            itemVisiblePercentThreshold: 50,
+            minimumViewTime: 1000
+          }}
+          data={searchResult?.data || []}
+          renderItem={renderUserItem}
+          keyExtractor={(item) => item.user_id.toString()}
+          estimatedItemSize={100}
+          extraData={selectedUsers}
+          showsVerticalScrollIndicator={false}
+          refreshing={isFetching}
+          contentContainerStyle={{ paddingBottom: 16 }}
+        />
+      ) : (
+        <FlashList
+          viewabilityConfig={{
+            waitForInteraction: true,
+            itemVisiblePercentThreshold: 50,
+            minimumViewTime: 1000
+          }}
+          data={friends || []}
+          renderItem={renderUserItem}
+          keyExtractor={(item) => item.user_id.toString()}
+          estimatedItemSize={100}
+          extraData={selectedUsers}
+          showsVerticalScrollIndicator={false}
+          refreshing={isFetching}
+          onEndReached={handleEndReached}
+          onEndReachedThreshold={0.2}
+          contentContainerStyle={{ paddingBottom: 16 }}
+        />
+      )}
+    </View>
+  );
+};
+
+const styles = StyleSheet.create({
+  container: {
+    gap: 16,
+    height: '100%',
+    backgroundColor: Colors.WHITE
+  },
+  header: {
+    flexDirection: 'row',
+    justifyContent: 'space-between',
+    alignItems: 'center'
+  },
+  headerText: {
+    color: Colors.DARK_BLUE,
+    fontSize: getFontSize(14),
+    fontWeight: '700'
+  },
+  photoContainer: {
+    alignItems: 'center',
+    gap: 8
+  },
+  groupPhoto: {
+    width: 80,
+    height: 80,
+    borderRadius: 40
+  },
+  photoText: {
+    color: Colors.DARK_BLUE,
+    fontSize: 14,
+    fontFamily: 'montserrat-600'
+  },
+  input: {
+    marginBottom: 12
+  },
+  userItem: {
+    flexDirection: 'row',
+    alignItems: 'center',
+    paddingVertical: 8,
+    paddingHorizontal: 12,
+    backgroundColor: Colors.FILL_LIGHT,
+    gap: 8,
+    borderRadius: 8,
+    marginBottom: 6
+  },
+  avatar: {
+    width: 36,
+    height: 36,
+    borderRadius: 18,
+    borderWidth: 1,
+    borderColor: Colors.LIGHT_GRAY
+  },
+  userDetails: {
+    flex: 1
+  },
+  userName: {
+    color: Colors.DARK_BLUE,
+    fontSize: getFontSize(14),
+    fontFamily: 'montserrat-700'
+  },
+  userSubtitle: {
+    color: Colors.DARK_BLUE,
+    fontSize: 14,
+    fontFamily: 'montserrat-500'
+  },
+  userNM: {
+    color: Colors.DARK_BLUE,
+    fontSize: 14,
+    fontFamily: 'montserrat-700',
+    marginRight: 12
+  },
+  unselectedCircle: {
+    width: 20,
+    height: 20,
+    borderRadius: 10,
+    borderWidth: 1,
+    borderColor: Colors.LIGHT_GRAY,
+    justifyContent: 'center',
+    alignItems: 'center'
+  },
+  usersRow: {
+    backgroundColor: Colors.FILL_LIGHT,
+    borderRadius: 8
+  },
+  selectedUsersList: {
+    paddingHorizontal: 12,
+    paddingVertical: 10
+  },
+  selectedUserContainer: {
+    position: 'relative',
+    marginRight: 12
+  },
+  selectedAvatar: {
+    width: 60,
+    height: 60,
+    borderRadius: 30,
+    borderWidth: 1,
+    borderColor: Colors.LIGHT_GRAY
+  },
+  removeIcon: {
+    position: 'absolute',
+    top: -4,
+    right: -4,
+    width: 22,
+    height: 22,
+    borderRadius: 11,
+    borderWidth: 1,
+    borderColor: Colors.WHITE,
+    backgroundColor: Colors.RED,
+    justifyContent: 'center',
+    alignItems: 'center',
+    zIndex: 1
+  }
+});
+
+export default RouteAddUsers;

+ 168 - 0
src/screens/InAppScreens/MessagesScreen/Components/RouteSearch.tsx

@@ -0,0 +1,168 @@
+import React, { useEffect, useState } from 'react';
+import { View, Text, Image, TouchableOpacity, StyleSheet, ActivityIndicator } from 'react-native';
+import { FlashList } from '@shopify/flash-list';
+import { storage, StoreType } from 'src/storage';
+import { usePostSearchUsers } from '@api/chat';
+import { API_HOST } from 'src/constants';
+import { Colors } from 'src/theme';
+import { useNavigation } from '@react-navigation/native';
+import { NAVIGATION_PAGES } from 'src/types';
+import { getFontSize } from 'src/utils';
+import { AvatarWithInitials, Input } from 'src/components';
+
+import SearchIcon from 'assets/icons/search.svg';
+import NomadsIcon from 'assets/icons/bottom-navigation/travellers.svg';
+import { SheetManager, useSheetRouter } from 'react-native-actions-sheet';
+
+const RouteSearch = () => {
+  const router = useSheetRouter('search-modal');
+  const navigation = useNavigation();
+  const token = storage.get('token', StoreType.STRING) as string;
+  const [searchQuery, setSearchQuery] = useState('');
+
+  const { data: searchResult, isFetching } = usePostSearchUsers(
+    token,
+    searchQuery,
+    searchQuery.length > 1
+  );
+
+  useEffect(() => {}, [searchResult]);
+
+  const renderItem = ({ item }: { item: any }) => (
+    <TouchableOpacity
+      style={styles.itemContainer}
+      onPress={() => {
+        SheetManager.hide('search-modal').then(() => {
+          navigation.navigate(
+            ...([
+              NAVIGATION_PAGES.CHAT,
+              {
+                id: item.user_id,
+                name: item.first_name + ' ' + item.last_name,
+                avatar: item.avatar,
+                userType: 'normal'
+              }
+            ] as never)
+          );
+        });
+      }}
+    >
+      {item.avatar ? (
+        <Image source={{ uri: API_HOST + item.avatar }} style={styles.avatar} />
+      ) : (
+        <AvatarWithInitials
+          text={`${item.first_name[0] ?? ''}${item.last_name[0] ?? ''}`}
+          flag={API_HOST + item.homebase_flag}
+          size={30}
+          fontSize={12}
+        />
+      )}
+
+      <View style={styles.textContainer}>
+        <Text style={styles.name}>
+          {item.first_name} {item.last_name}
+        </Text>
+      </View>
+    </TouchableOpacity>
+  );
+
+  return (
+    <View style={styles.container}>
+      <TouchableOpacity style={styles.header} onPress={() => router?.goBack()}>
+        <Text style={styles.cancelText}>Cancel</Text>
+      </TouchableOpacity>
+
+      <Input
+        inputMode={'search'}
+        placeholder={'Search nomads'}
+        onChange={(text) => {
+          setSearchQuery(text);
+        }}
+        value={searchQuery}
+        icon={<SearchIcon fill={'#C8C8C8'} width={14} height={14} />}
+      />
+
+      <TouchableOpacity style={styles.newGroup} onPress={() => router?.navigate('route-add-users')}>
+        <Text style={styles.text}>New group chat</Text>
+        <NomadsIcon fill={Colors.DARK_BLUE} width={20} height={16} />
+      </TouchableOpacity>
+
+      {isFetching ? (
+        <ActivityIndicator size="large" color={Colors.DARK_BLUE} />
+      ) : (
+        <View style={{ flex: 1 }}>
+          <FlashList
+            viewabilityConfig={{
+              waitForInteraction: true,
+              itemVisiblePercentThreshold: 50,
+              minimumViewTime: 1000
+            }}
+            data={searchResult?.data || []}
+            renderItem={renderItem}
+            keyExtractor={(item) => item.user_id.toString()}
+            estimatedItemSize={100}
+            extraData={searchResult}
+            showsVerticalScrollIndicator={false}
+            refreshing={isFetching}
+            contentContainerStyle={{ paddingBottom: 16 }}
+          />
+        </View>
+      )}
+    </View>
+  );
+};
+
+const styles = StyleSheet.create({
+  container: {
+    backgroundColor: 'white',
+    gap: 16,
+    height: '100%'
+  },
+  header: {
+    paddingTop: 16,
+    paddingHorizontal: 6,
+    width: 68
+  },
+  cancelText: {
+    color: Colors.DARK_BLUE,
+    fontSize: getFontSize(14),
+    fontWeight: '700'
+  },
+  itemContainer: {
+    flexDirection: 'row',
+    alignItems: 'center',
+    paddingBottom: 24,
+    gap: 8
+  },
+  avatar: {
+    width: 30,
+    height: 30,
+    borderRadius: 15,
+    borderWidth: 1,
+    borderColor: Colors.FILL_LIGHT
+  },
+  textContainer: {
+    flex: 1
+  },
+  name: {
+    fontSize: getFontSize(14),
+    color: Colors.DARK_BLUE,
+    fontFamily: 'montserrat-700'
+  },
+  newGroup: {
+    flexDirection: 'row',
+    justifyContent: 'space-between',
+    alignItems: 'center',
+    padding: 12,
+    backgroundColor: Colors.FILL_LIGHT,
+    borderRadius: 8,
+    height: 44
+  },
+  text: {
+    fontSize: getFontSize(12),
+    color: Colors.DARK_BLUE,
+    fontWeight: '700'
+  }
+});
+
+export default RouteSearch;

+ 30 - 144
src/screens/InAppScreens/MessagesScreen/Components/SearchUsersModal.tsx

@@ -1,162 +1,48 @@
-import React, { useEffect, useState } from 'react';
-import {
-  View,
-  Text,
-  Image,
-  TouchableOpacity,
-  StyleSheet,
-  ActivityIndicator,
-  Platform
-} from 'react-native';
-import ActionSheet, { SheetManager } from 'react-native-actions-sheet';
-import { FlashList } from '@shopify/flash-list';
-import { storage, StoreType } from 'src/storage';
-import { usePostSearchUsers } from '@api/chat';
-import { API_HOST } from 'src/constants';
-import { Colors } from 'src/theme';
-import { useNavigation } from '@react-navigation/native';
-import { NAVIGATION_PAGES } from 'src/types';
-import { getFontSize } from 'src/utils';
-import { AvatarWithInitials, Input } from 'src/components';
-
-import SearchIcon from 'assets/icons/search.svg';
+import React from 'react';
+import { View, Platform, StyleSheet } from 'react-native';
+import ActionSheet, { Route } from 'react-native-actions-sheet';
+import RouteSearch from './RouteSearch';
+import RouteAddGroup from './RouteAddGroup';
+import RouteAddUsers from './RouteAddUsers';
+import { useGroupChatStore } from 'src/stores/groupChatStore';
 
 const SearchModal = () => {
-  const navigation = useNavigation();
-  const token = storage.get('token', StoreType.STRING) as string;
-  const [searchQuery, setSearchQuery] = useState('');
-  const { data: searchResult, isFetching } = usePostSearchUsers(
-    token,
-    searchQuery,
-    searchQuery.length > 1
-  );
-
-  useEffect(() => {}, [searchResult]);
-
-  const renderItem = ({ item }: { item: any }) => (
-    <TouchableOpacity
-      style={styles.itemContainer}
-      onPress={() => {
-        SheetManager.hide('search-modal').then(() => {
-          navigation.navigate(
-            ...([
-              NAVIGATION_PAGES.CHAT,
-              {
-                id: item.user_id,
-                name: item.first_name + ' ' + item.last_name,
-                avatar: item.avatar,
-                userType: 'normal'
-              }
-            ] as never)
-          );
-        });
-      }}
-    >
-      {item.avatar ? (
-        <Image source={{ uri: API_HOST + item.avatar }} style={styles.avatar} />
-      ) : (
-        <AvatarWithInitials
-          text={`${item.first_name[0] ?? ''}${item.last_name[0] ?? ''}`}
-          flag={API_HOST + item.homebase_flag}
-          size={30}
-          fontSize={12}
-        />
-      )}
-
-      <View style={styles.textContainer}>
-        <Text style={styles.name}>
-          {item.first_name} {item.last_name}
-        </Text>
-      </View>
-    </TouchableOpacity>
-  );
+  const { clearStore } = useGroupChatStore();
+  const routes: Route[] = [
+    {
+      name: 'route-search',
+      component: RouteSearch
+    },
+    {
+      name: 'route-add-users',
+      component: RouteAddUsers
+    },
+    {
+      name: 'route-add-group',
+      component: RouteAddGroup
+    }
+  ];
 
   return (
     <ActionSheet
       id="search-modal"
-      gestureEnabled={Platform.OS === 'ios'}
-      onClose={() => setSearchQuery('')}
       containerStyle={styles.sheetContainer}
       defaultOverlayOpacity={0.5}
-      indicatorStyle={{ backgroundColor: 'transparent' }}
-    >
-      <View style={styles.container}>
-        <Input
-          inputMode={'search'}
-          placeholder={'Search nomads'}
-          onChange={(text) => {
-            setSearchQuery(text);
-          }}
-          value={searchQuery}
-          icon={<SearchIcon fill={'#C8C8C8'} width={14} height={14} />}
-        />
-
-        {isFetching ? (
-          <ActivityIndicator size="large" color={Colors.DARK_BLUE} />
-        ) : (
-          <View style={{ flex: 1 }}>
-            <FlashList
-              viewabilityConfig={{
-                waitForInteraction: true,
-                itemVisiblePercentThreshold: 50,
-                minimumViewTime: 1000
-              }}
-              data={searchResult?.data || []}
-              renderItem={renderItem}
-              keyExtractor={(item) => item.user_id.toString()}
-              estimatedItemSize={100}
-              extraData={searchResult}
-              showsVerticalScrollIndicator={false}
-              refreshing={isFetching}
-            />
-          </View>
-        )}
-      </View>
-    </ActionSheet>
+      routes={routes}
+      initialRoute="route-search"
+      closeOnTouchBackdrop={false}
+      keyboardHandlerEnabled={false}
+      onClose={() => clearStore()}
+    />
   );
 };
 
 const styles = StyleSheet.create({
   sheetContainer: {
-    height: '80%',
+    height: '95%',
     borderTopLeftRadius: 15,
     borderTopRightRadius: 15,
-    paddingHorizontal: 16,
-    paddingVertical: 12
-  },
-  container: {
-    backgroundColor: 'white',
-    gap: 12,
-    height: '100%'
-  },
-  searchInput: {
-    height: 40,
-    borderColor: '#ccc',
-    borderWidth: 1,
-    borderRadius: 8,
-    paddingHorizontal: 10,
-    marginBottom: 10
-  },
-  itemContainer: {
-    flexDirection: 'row',
-    alignItems: 'center',
-    paddingVertical: 12,
-    gap: 8
-  },
-  avatar: {
-    width: 30,
-    height: 30,
-    borderRadius: 15,
-    borderWidth: 1,
-    borderColor: Colors.FILL_LIGHT
-  },
-  textContainer: {
-    flex: 1
-  },
-  name: {
-    fontSize: getFontSize(14),
-    color: Colors.DARK_BLUE,
-    fontFamily: 'montserrat-700'
+    paddingHorizontal: 16
   }
 });
 

+ 249 - 6
src/screens/InAppScreens/MessagesScreen/FullMapScreen/index.tsx

@@ -1,18 +1,142 @@
-import React, { useRef } from 'react';
-import { View, StyleSheet, StatusBar, TouchableOpacity } from 'react-native';
+import React, { useEffect, useRef, useState } from 'react';
+import {
+  View,
+  StyleSheet,
+  StatusBar,
+  TouchableOpacity,
+  ActivityIndicator,
+  Platform,
+  Linking
+} from 'react-native';
 import * as MapLibreRN from '@maplibre/maplibre-react-native';
+import * as Location from 'expo-location';
+import { useBottomTabBarHeight } from '@react-navigation/bottom-tabs';
+import { SafeAreaView, useSafeAreaInsets } from 'react-native-safe-area-context';
+import { useNavigation } from '@react-navigation/native';
+import _ from 'lodash';
+
 import { VECTOR_MAP_HOST } from 'src/constants';
+import { WarningModal } from 'src/components';
 import { Colors } from 'src/theme';
-import { SafeAreaView } from 'react-native-safe-area-context';
-import { useNavigation } from '@react-navigation/native';
+import ScaleBar from 'src/components/ScaleBar';
+
 import ChevronLeft from 'assets/icons/chevron-left.svg';
+import LocationIcon from 'assets/icons/location.svg';
+import MapSvg from 'assets/icons/travels-screens/map-location.svg';
 
 const FullMapScreen = ({ route }: { route: any }) => {
   const { lat, lng } = route.params;
+  const tabBarHeight = useBottomTabBarHeight();
+  const insets = useSafeAreaInsets();
+
   const navigation = useNavigation();
   const mapRef = useRef<MapLibreRN.MapViewRef>(null);
   const cameraRef = useRef<MapLibreRN.CameraRef>(null);
 
+  const [isLocationLoading, setIsLocationLoading] = useState(false);
+  const [location, setLocation] = useState<any | null>(null);
+
+  const [zoom, setZoom] = useState(0);
+  const [center, setCenter] = useState<number[] | null>(null);
+  const [isZooming, setIsZooming] = useState(true);
+
+  const [askLocationVisible, setAskLocationVisible] = useState<boolean>(false);
+  const [openSettingsVisible, setOpenSettingsVisible] = useState<boolean>(false);
+
+  const hideTimer = useRef<ReturnType<typeof setTimeout> | null>(null);
+
+  useEffect(() => {
+    (async () => {
+      let { status } = await Location.getForegroundPermissionsAsync();
+      const isServicesEnabled = await Location.hasServicesEnabledAsync();
+
+      if (status !== 'granted' || !isServicesEnabled) {
+        return;
+      }
+
+      try {
+        let currentLocation = await Location.getCurrentPositionAsync({
+          accuracy: Location.Accuracy.Balanced
+        });
+        setLocation(currentLocation.coords);
+      } catch (error) {
+        console.error('Error fetching user location:', error);
+      }
+    })();
+  }, []);
+
+  const handleMapChange = async () => {
+    if (!mapRef.current) return;
+    if (hideTimer.current) clearTimeout(hideTimer.current);
+
+    setIsZooming(true);
+
+    const currentZoom = await mapRef.current.getZoom();
+    const currentCenter = await mapRef.current.getCenter();
+
+    setZoom(currentZoom);
+    setCenter(currentCenter);
+  };
+
+  const handleGetLocation = async () => {
+    setIsLocationLoading(true);
+    try {
+      let { status, canAskAgain } = await Location.getForegroundPermissionsAsync();
+      const isServicesEnabled = await Location.hasServicesEnabledAsync();
+
+      if (status === 'granted' && isServicesEnabled) {
+        await getLocation();
+      } else if (!canAskAgain || !isServicesEnabled) {
+        setOpenSettingsVisible(true);
+      } else {
+        setAskLocationVisible(true);
+      }
+    } finally {
+      setIsLocationLoading(false);
+    }
+  };
+
+  const getLocation = async () => {
+    try {
+      let currentLocation = await Location.getCurrentPositionAsync({
+        accuracy: Location.Accuracy.Balanced
+      });
+      setLocation(currentLocation.coords);
+
+      if (currentLocation.coords) {
+        cameraRef.current?.flyTo(
+          [currentLocation.coords.longitude, currentLocation.coords.latitude],
+          1000
+        );
+      }
+    } catch (error) {
+      console.error('Error fetching user location:', error);
+    }
+  };
+
+  const handleAcceptPermission = async () => {
+    setAskLocationVisible(false);
+    let { status, canAskAgain } = await Location.requestForegroundPermissionsAsync();
+    const isServicesEnabled = await Location.hasServicesEnabledAsync();
+
+    if (status === 'granted' && isServicesEnabled) {
+      getLocation();
+    } else if (!canAskAgain || !isServicesEnabled) {
+      setOpenSettingsVisible(true);
+    }
+  };
+
+  const openInExternalMaps = async (latitude: number, longitude: number) => {
+    const appleMapsURL = `http://maps.apple.com/?q=${latitude},${longitude}`;
+    const defaultGeoURL = `geo:${latitude},${longitude}?q=${latitude},${longitude}`;
+
+    if (Platform.OS === 'ios') {
+      await Linking.openURL(appleMapsURL);
+    } else {
+      await Linking.openURL(defaultGeoURL);
+    }
+  };
+
   return (
     <SafeAreaView style={{ height: '100%' }}>
       <StatusBar translucent backgroundColor="transparent" />
@@ -23,6 +147,13 @@ const FullMapScreen = ({ route }: { route: any }) => {
         mapStyle={VECTOR_MAP_HOST + '/nomadmania-maps.json'}
         rotateEnabled={false}
         attributionEnabled={false}
+        onRegionDidChange={() => {
+          hideTimer.current = setTimeout(() => {
+            setIsZooming(false);
+          }, 2000);
+        }}
+        onRegionIsChanging={handleMapChange}
+        onRegionWillChange={_.debounce(handleMapChange, 200)}
       >
         <MapLibreRN.Camera
           ref={cameraRef}
@@ -31,17 +162,99 @@ const FullMapScreen = ({ route }: { route: any }) => {
         <MapLibreRN.MarkerView coordinate={[lng, lat]}>
           <View style={styles.marker} />
         </MapLibreRN.MarkerView>
+
+        {location && (
+          <MapLibreRN.UserLocation
+            animated={true}
+            showsUserHeadingIndicator={true}
+            onPress={async () => {
+              const currentZoom = await mapRef.current?.getZoom();
+              const newZoom = (currentZoom || 0) + 2;
+
+              cameraRef.current?.setCamera({
+                centerCoordinate: [location.longitude, location.latitude],
+                zoomLevel: newZoom,
+                animationDuration: 500,
+                animationMode: 'flyTo'
+              });
+            }}
+          ></MapLibreRN.UserLocation>
+        )}
       </MapLibreRN.MapView>
       <TouchableOpacity
         onPress={() => {
           navigation.goBack();
         }}
-        style={styles.backButtonContainer}
+        style={[
+          styles.backButtonContainer,
+          { top: Platform.OS === 'android' ? insets.top + 20 : insets.top }
+        ]}
       >
         <View style={styles.backButton}>
           <ChevronLeft fill={Colors.WHITE} />
         </View>
       </TouchableOpacity>
+      <TouchableOpacity
+        onPress={handleGetLocation}
+        style={[
+          styles.cornerButton,
+          styles.bottomButton,
+          styles.bottomRightButton,
+          { bottom: tabBarHeight + insets.bottom + 20 }
+        ]}
+      >
+        {isLocationLoading ? (
+          <ActivityIndicator size="small" color={Colors.DARK_BLUE} />
+        ) : (
+          <LocationIcon />
+        )}
+      </TouchableOpacity>
+
+      <TouchableOpacity
+        onPress={() => openInExternalMaps(lat, lng)}
+        style={[
+          styles.cornerButton,
+          styles.bottomButton,
+          styles.topRightButton,
+          { top: Platform.OS === 'android' ? insets.top + 24 : insets.top + 4 }
+        ]}
+      >
+        <MapSvg fill={Colors.DARK_BLUE} />
+      </TouchableOpacity>
+
+      {center ? (
+        <ScaleBar
+          zoom={zoom}
+          latitude={center[1]}
+          isVisible={isZooming}
+          bottom={tabBarHeight + insets.bottom + 38}
+        />
+      ) : null}
+
+      <WarningModal
+        type={'success'}
+        isVisible={askLocationVisible}
+        onClose={() => setAskLocationVisible(false)}
+        action={handleAcceptPermission}
+        message="To use this feature we need your permission to access your location. If you press OK your system will ask you to approve location sharing with NomadMania app."
+      />
+      <WarningModal
+        type={'success'}
+        isVisible={openSettingsVisible}
+        onClose={() => setOpenSettingsVisible(false)}
+        action={async () => {
+          const isServicesEnabled = await Location.hasServicesEnabledAsync();
+
+          if (!isServicesEnabled) {
+            Platform.OS === 'ios'
+              ? Linking.openURL('app-settings:')
+              : Linking.sendIntent('android.settings.LOCATION_SOURCE_SETTINGS');
+          } else {
+            Platform.OS === 'ios' ? Linking.openURL('app-settings:') : Linking.openSettings();
+          }
+        }}
+        message="NomadMania app needs location permissions to function properly. Open settings?"
+      />
     </SafeAreaView>
   );
 };
@@ -55,7 +268,7 @@ const styles = StyleSheet.create({
     width: 50,
     height: 50,
     top: 50,
-    left: 10,
+    left: 12,
     justifyContent: 'center',
     alignItems: 'center',
     zIndex: 2
@@ -75,6 +288,36 @@ const styles = StyleSheet.create({
     backgroundColor: Colors.ORANGE,
     borderWidth: 2,
     borderColor: Colors.WHITE
+  },
+  cornerButton: {
+    position: 'absolute',
+    backgroundColor: Colors.WHITE,
+    padding: 12,
+    width: 48,
+    height: 48,
+    borderRadius: 24,
+    alignItems: 'center',
+    justifyContent: 'center',
+    shadowColor: '#000',
+    shadowOffset: {
+      width: 0,
+      height: 1
+    },
+    shadowOpacity: 0.25,
+    shadowRadius: 1.5,
+    elevation: 2
+  },
+  bottomButton: {
+    width: 42,
+    height: 42,
+    borderRadius: 21
+  },
+  bottomRightButton: {
+    right: 16
+  },
+  topRightButton: {
+    top: 54,
+    right: 16
   }
 });
 

+ 1956 - 0
src/screens/InAppScreens/MessagesScreen/GroupChatScreen/index.tsx

@@ -0,0 +1,1956 @@
+import React, { useState, useCallback, useEffect, useRef } from 'react';
+import {
+  View,
+  TouchableOpacity,
+  Image,
+  Text,
+  FlatList,
+  Dimensions,
+  Alert,
+  ScrollView,
+  Linking,
+  ActivityIndicator,
+  AppState,
+  AppStateStatus,
+  TextInput
+} from 'react-native';
+import {
+  GiftedChat,
+  Bubble,
+  InputToolbar,
+  IMessage,
+  Send,
+  BubbleProps,
+  Composer,
+  TimeProps,
+  MessageProps,
+  Actions,
+  isSameUser,
+  isSameDay
+} from 'react-native-gifted-chat';
+import { MaterialCommunityIcons } from '@expo/vector-icons';
+import { GestureHandlerRootView, Swipeable } from 'react-native-gesture-handler';
+import { AvatarWithInitials, Header, WarningModal } from 'src/components';
+import { Colors } from 'src/theme';
+import { useFocusEffect, useNavigation } from '@react-navigation/native';
+import { Audio } from 'expo-av';
+import ChatMessageBox from '../Components/ChatMessageBox';
+import ReplyMessageBar from '../Components/ReplyMessageBar';
+import { useSharedValue, withTiming } from 'react-native-reanimated';
+import { BlurView } from 'expo-blur';
+import { SafeAreaView, useSafeAreaInsets } from 'react-native-safe-area-context';
+import Clipboard from '@react-native-clipboard/clipboard';
+import { trigger } from 'react-native-haptic-feedback';
+import ReactModal from 'react-native-modal';
+import { storage, StoreType } from 'src/storage';
+import {
+  usePostDeleteMessageMutation,
+  usePostGetGroupChatQuery,
+  usePostSendGroupMessageMutation,
+  usePostReactToGroupMessageMutation,
+  usePostGroupMessagesReadMutation
+} from '@api/chat';
+import { CustomMessage, Message, Reaction } from '../types';
+import { API_HOST, WEBSOCKET_URL } from 'src/constants';
+import ReactionBar from '../Components/ReactionBar';
+import OptionsMenu from '../Components/OptionsMenu';
+import EmojiSelectorModal from '../Components/EmojiSelectorModal';
+import { styles } from '../ChatScreen/styles';
+import SendIcon from 'assets/icons/messages/send.svg';
+import { SheetManager } from 'react-native-actions-sheet';
+import { NAVIGATION_PAGES } from 'src/types';
+import { usePushNotification } from 'src/contexts/PushNotificationContext';
+import ReactionsListModal from '../Components/ReactionsListModal';
+import { dismissChatNotifications } from '../utils';
+import { useMessagesStore } from 'src/stores/unreadMessagesStore';
+import FileViewer from 'react-native-file-viewer';
+import * as FileSystem from 'expo-file-system';
+import ImageView from 'better-react-native-image-viewing';
+import * as MediaLibrary from 'expo-media-library';
+
+import BanIcon from 'assets/icons/messages/ban.svg';
+import AttachmentsModal from '../Components/AttachmentsModal';
+import RenderMessageVideo from '../Components/renderMessageVideo';
+import MessageLocation from '../Components/MessageLocation';
+import GroupIcon from 'assets/icons/messages/group-chat.svg';
+import { CACHED_ATTACHMENTS_DIR } from 'src/constants/constants';
+
+const options = {
+  enableVibrateFallback: true,
+  ignoreAndroidSystemSettings: false
+};
+
+const reactionEmojis = ['👍', '❤️', '😂', '😮', '😭'];
+
+const GroupChatScreen = ({ route }: { route: any }) => {
+  const token = storage.get('token', StoreType.STRING) as string;
+  const {
+    group_token,
+    name,
+    avatar,
+    userType = 'normal'
+  }: {
+    group_token: string;
+    name: string;
+    avatar: string | null;
+    userType: 'normal' | 'not_exist' | 'blocked';
+  } = route.params;
+  const groupName =
+    userType === 'blocked'
+      ? 'Account is blocked'
+      : userType === 'not_exist'
+        ? 'Account does not exist'
+        : name;
+
+  const currentUserId = storage.get('uid', StoreType.STRING) as number;
+  const insets = useSafeAreaInsets();
+  const [groupAvatar, setGroupAvatar] = useState<string | null>(null);
+  const [messages, setMessages] = useState<CustomMessage[] | null>();
+  const navigation = useNavigation();
+  const [prevThenMessageId, setPrevThenMessageId] = useState<number>(-1);
+  const {
+    data: chatData,
+    refetch: refetch,
+    isFetching: isFetching
+  } = usePostGetGroupChatQuery(token, group_token, 50, prevThenMessageId, true);
+  const { mutateAsync: sendMessage } = usePostSendGroupMessageMutation();
+
+  const swipeableRowRef = useRef<Swipeable | null>(null);
+  const messageContainerRef = useRef<FlatList<IMessage> | null>(null);
+  const [selectedMedia, setSelectedMedia] = useState<any>(null);
+
+  const [replyMessage, setReplyMessage] = useState<CustomMessage | null>(null);
+  const [modalInfo, setModalInfo] = useState({
+    visible: false,
+    type: 'confirm',
+    message: '',
+    action: () => {},
+    buttonTitle: '',
+    title: ''
+  });
+
+  const [selectedMessage, setSelectedMessage] = useState<BubbleProps<CustomMessage> | null>(null);
+  const [emojiSelectorVisible, setEmojiSelectorVisible] = useState(false);
+  const [messagePosition, setMessagePosition] = useState<{
+    x: number;
+    y: number;
+    width: number;
+    height: number;
+    isMine: boolean;
+  } | null>(null);
+
+  const [isModalVisible, setIsModalVisible] = useState(false);
+  const [unreadMessageIndex, setUnreadMessageIndex] = useState<number | null>(null);
+  const { mutateAsync: markMessagesAsRead } = usePostGroupMessagesReadMutation();
+  const { mutateAsync: deleteMessage } = usePostDeleteMessageMutation();
+  const { mutateAsync: reactToMessage } = usePostReactToGroupMessageMutation();
+
+  const [highlightedMessageId, setHighlightedMessageId] = useState<number | null>(null);
+  const [isRerendering, setIsRerendering] = useState<boolean>(false);
+  const [isTyping, setIsTyping] = useState<boolean>(false);
+
+  const messageRefs = useRef<{ [key: string]: any }>({});
+  const flatList = useRef<FlatList | null>(null);
+  const scrollY = useSharedValue(0);
+  const { isSubscribed } = usePushNotification();
+  const [isLoadingEarlier, setIsLoadingEarlier] = useState(false);
+  const [hasMoreMessages, setHasMoreMessages] = useState(true);
+  const updateUnreadMessagesCount = useMessagesStore((state) => state.updateUnreadMessagesCount);
+
+  const appState = useRef(AppState.currentState);
+  const textInputRef = useRef<TextInput>(null);
+
+  const socket = useRef<WebSocket | null>(null);
+
+  const closeModal = () => {
+    setModalInfo({ ...modalInfo, visible: false });
+  };
+
+  useEffect(() => {
+    Audio.setAudioModeAsync({
+      allowsRecordingIOS: false,
+      staysActiveInBackground: false,
+      playsInSilentModeIOS: true,
+      shouldDuckAndroid: true,
+      playThroughEarpieceAndroid: false
+    });
+  }, []);
+
+  const onSendMedia = useCallback(
+    async (files: { uri: string; type: 'image' | 'video' }[]) => {
+      for (const file of files) {
+        const tempMessage: CustomMessage = {
+          _id: Date.now() + Math.random(),
+          text: '',
+          createdAt: new Date(),
+          user: { _id: +currentUserId, name: 'Me' },
+          reactions: {},
+          deleted: false,
+          attachment: {
+            id: -1,
+            filename: file.type,
+            filetype: file.type,
+            attachment_link: file.uri
+          },
+          pending: true,
+          isSending: true,
+          image: file.type === 'image' ? file.uri : undefined,
+          video: file.type === 'video' ? file.uri : undefined
+        };
+
+        if (replyMessage) {
+          tempMessage.replyMessage = {
+            text: replyMessage.text,
+            id: replyMessage._id,
+            name: replyMessage.user._id !== +currentUserId ? replyMessage.user.name : 'Me'
+          };
+        }
+
+        setMessages((previousMessages) => GiftedChat.append(previousMessages ?? [], [tempMessage]));
+
+        const messageData = {
+          token,
+          to_group_token: group_token,
+          text: '',
+          reply_to_id: replyMessage ? (replyMessage._id as number) : -1,
+          attachment: {
+            uri: file.uri,
+            type: file.type,
+            name: file.uri.split('/').pop()
+          }
+        };
+
+        const res = await sendMessage(messageData, {
+          onSuccess: (res) => {
+            const { attachment, message_id } = res;
+
+            const newMessage = {
+              _id: message_id,
+              text: '',
+              attachment,
+              replyMessage: { ...tempMessage.replyMessage, sender: replyMessage?.user?._id },
+              image: file.type === 'image' ? API_HOST + attachment.attachment_full_url : undefined,
+              video: file.type === 'video' ? file.uri : undefined
+            };
+
+            setMessages((previousMessages) =>
+              (previousMessages ?? []).map((msg) =>
+                msg._id === tempMessage._id
+                  ? {
+                      ...msg,
+                      _id: res.message_id,
+                      attachment: res.attachment,
+                      isSending: false,
+                      image:
+                        res.attachment?.attachment_small_url && file.type === 'image'
+                          ? API_HOST + res.attachment.attachment_small_url
+                          : undefined,
+                      video: res.attachment?.attachment_link
+                        ? API_HOST + res.attachment.attachment_link
+                        : undefined
+                    }
+                  : msg
+              )
+            );
+
+            sendWebSocketMessage('new_message', newMessage as unknown as CustomMessage);
+          }
+        });
+
+        clearReplyMessage();
+      }
+    },
+    [replyMessage]
+  );
+
+  const onSendLocation = useCallback(
+    async (coords: { latitude: number; longitude: number }) => {
+      const tempMessage: CustomMessage = {
+        _id: Date.now() + Math.random(),
+        text: '',
+        createdAt: new Date(),
+        user: { _id: +currentUserId, name: 'Me' },
+        pending: true,
+        deleted: false,
+        reactions: {},
+        attachment: {
+          id: -1,
+          filename: 'location.json',
+          filetype: 'nomadmania/location',
+          lat: coords.latitude,
+          lng: coords.longitude
+        }
+      };
+
+      if (replyMessage) {
+        tempMessage.replyMessage = {
+          text: replyMessage.text,
+          id: replyMessage._id,
+          name: replyMessage.user._id !== +currentUserId ? replyMessage.user.name : 'Me'
+        };
+      }
+
+      setMessages((previousMessages) => GiftedChat.append(previousMessages ?? [], [tempMessage]));
+
+      const locationData = JSON.stringify({ lat: coords.latitude, lng: coords.longitude });
+      const fileUri = FileSystem.documentDirectory + 'location.json';
+      await FileSystem.writeAsStringAsync(fileUri, locationData);
+
+      const locationFile = {
+        uri: fileUri,
+        type: 'application/json',
+        name: 'location.json'
+      };
+
+      const messageData = {
+        token,
+        to_group_token: group_token,
+        text: tempMessage.text,
+        reply_to_id: replyMessage ? (replyMessage._id as number) : -1,
+        attachment: locationFile
+      };
+
+      sendMessage(messageData, {
+        onSuccess: async (res) => {
+          const { attachment, message_id } = res;
+
+          const newMessage = {
+            _id: message_id,
+            text: '',
+            attachment,
+            replyMessage: { ...tempMessage.replyMessage, sender: replyMessage?.user?._id }
+          };
+
+          setMessages((previousMessages) =>
+            (previousMessages ?? []).map((msg) =>
+              msg._id === tempMessage._id ? { ...msg, _id: res.message_id } : msg
+            )
+          );
+
+          sendWebSocketMessage('new_message', newMessage as unknown as CustomMessage);
+          await FileSystem.deleteAsync(fileUri);
+        },
+        onError: async (err) => {
+          await FileSystem.deleteAsync(fileUri);
+        }
+      });
+
+      clearReplyMessage();
+    },
+    [replyMessage]
+  );
+
+  const onSendFile = useCallback(
+    (files: { uri: string; type: string; name?: string }[]) => {
+      const newMsgs = files.map((file) => {
+        const msg: CustomMessage = {
+          _id: Date.now() + Math.random(),
+          text: '',
+          createdAt: new Date(),
+          user: { _id: +currentUserId, name: 'Me' },
+          deleted: false,
+          reactions: {},
+          isSending: true,
+          attachment: {
+            id: -1,
+            filename: file.name ?? 'File',
+            filetype: file.type,
+            attachment_link: file.uri
+          }
+        };
+
+        if (replyMessage) {
+          msg.replyMessage = {
+            text: replyMessage.text,
+            id: replyMessage._id,
+            name: replyMessage.user._id !== +currentUserId ? replyMessage.user.name : 'Me'
+          };
+        }
+
+        if (file.type.includes('image')) {
+          msg.image = file.uri;
+        } else if (file.type.includes('video')) {
+          msg.video = file.uri;
+        }
+
+        setMessages((previousMessages) => GiftedChat.append(previousMessages ?? [], [msg]));
+
+        const messageData = {
+          token,
+          to_group_token: group_token,
+          text: '',
+          reply_to_id: replyMessage ? (replyMessage._id as number) : -1,
+          attachment: {
+            uri: file.uri,
+            type: file.type,
+            name: file.name || file.uri.split('/').pop()
+          }
+        };
+
+        sendMessage(messageData, {
+          onSuccess: (res) => {
+            const { attachment, message_id } = res;
+
+            const newMessage = {
+              _id: message_id,
+              text: '',
+              attachment,
+              replyMessage: { ...msg.replyMessage, sender: replyMessage?.user?._id },
+              image: file.type === 'image' ? API_HOST + attachment.attachment_full_url : undefined,
+              video: file.type === 'video' ? file.uri : undefined
+            };
+
+            setMessages((previousMessages) =>
+              (previousMessages ?? []).map((prevMsg) =>
+                prevMsg._id === msg._id
+                  ? {
+                      ...prevMsg,
+                      _id: res.message_id,
+                      attachment: res.attachment,
+                      isSending: false,
+                      image:
+                        res.attachment?.attachment_small_url && file.type?.startsWith('image')
+                          ? API_HOST + res.attachment.attachment_small_url
+                          : undefined,
+                      video:
+                        res.attachment?.attachment_link && file.type?.startsWith('video')
+                          ? API_HOST + res.attachment.attachment_link
+                          : undefined
+                    }
+                  : prevMsg
+              )
+            );
+
+            sendWebSocketMessage('new_message', newMessage as unknown as CustomMessage);
+          }
+        });
+
+        return msg;
+      });
+
+      clearReplyMessage();
+    },
+    [replyMessage]
+  );
+
+  async function openFileInApp(uri: string, fileName: string) {
+    try {
+      const dirExist = await FileSystem.getInfoAsync(CACHED_ATTACHMENTS_DIR);
+      if (!dirExist.exists) {
+        await FileSystem.makeDirectoryAsync(CACHED_ATTACHMENTS_DIR, { intermediates: true });
+      }
+
+      const fileUri = `${CACHED_ATTACHMENTS_DIR}${fileName}`;
+
+      const fileExists = await FileSystem.getInfoAsync(fileUri);
+      if (fileExists.exists) {
+        await FileViewer.open(fileUri, {
+          showOpenWithDialog: true,
+          showAppsSuggestions: true
+        });
+
+        return;
+      }
+
+      const { uri: localUri } = await FileSystem.downloadAsync(uri, fileUri, {
+        headers: { Nmtoken: token }
+      });
+
+      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.');
+    }
+  }
+
+  async function downloadFileToDevice(currentMessage: CustomMessage) {
+    if (!currentMessage.image && !currentMessage.video) {
+      return;
+    }
+
+    const fileUrl = currentMessage.video
+      ? currentMessage.video
+      : API_HOST + currentMessage.attachment?.attachment_full_url;
+    const fileType = currentMessage.attachment?.filetype || '';
+    const fileExt = fileType.split('/').pop() || '';
+    const fileName = currentMessage.attachment?.filename?.split('.')[0] || 'file';
+    const fileUri = `${FileSystem.cacheDirectory}${fileName}.${fileExt}`;
+
+    try {
+      const { status } = await MediaLibrary.requestPermissionsAsync();
+      if (status !== 'granted') {
+        return;
+      }
+
+      const downloadOptions = currentMessage.video ? { headers: { Nmtoken: token } } : undefined;
+      const { uri } = await FileSystem.downloadAsync(fileUrl, fileUri, downloadOptions);
+
+      await MediaLibrary.createAssetAsync(uri);
+
+      Alert.alert(
+        'Success',
+        `${fileType.startsWith('video') ? 'Video' : 'Image'} saved to gallery.`
+      );
+    } catch (error) {
+      Alert.alert('Error', 'Failed to download the file.');
+    }
+  }
+  const renderMessageFile = (props: BubbleProps<CustomMessage>) => {
+    const { currentMessage } = props;
+    const leftMessage = currentMessage?.user?._id !== +currentUserId;
+    if (!currentMessage?.attachment) return null;
+
+    const { attachment_link, filename } = currentMessage.attachment;
+    const fileName = filename ?? 'Attachment';
+    const uri = API_HOST + attachment_link;
+
+    return (
+      <TouchableOpacity
+        style={[
+          styles.fileContainer,
+          { backgroundColor: leftMessage ? 'rgba(15, 63, 79, 0.2)' : 'rgba(244, 244, 244, 0.2)' }
+        ]}
+        onPress={() => {
+          openFileInApp(uri, fileName);
+        }}
+        onLongPress={() => handleLongPress(currentMessage, props)}
+        disabled={currentMessage?.isSending}
+      >
+        {currentMessage?.isSending ? (
+          <ActivityIndicator
+            size="small"
+            color={leftMessage ? Colors.DARK_BLUE : Colors.FILL_LIGHT}
+          />
+        ) : (
+          <MaterialCommunityIcons
+            name="file"
+            size={32}
+            color={leftMessage ? Colors.DARK_BLUE : Colors.FILL_LIGHT}
+          />
+        )}
+        <Text
+          style={[
+            styles.fileNameText,
+            { color: leftMessage ? Colors.DARK_BLUE : Colors.FILL_LIGHT }
+          ]}
+        >
+          {fileName}
+        </Text>
+      </TouchableOpacity>
+    );
+  };
+
+  const renderMessageLocation = (props: BubbleProps<CustomMessage>) => {
+    const { currentMessage } = props;
+    if (!currentMessage?.attachment) return null;
+
+    const { lat, lng } = currentMessage.attachment;
+    if (!lat || !lng) return null;
+
+    return (
+      <View
+        style={[
+          {
+            alignItems: 'center',
+            borderRadius: 8,
+            marginVertical: 6,
+            marginHorizontal: 6,
+            width: 220
+          }
+        ]}
+      >
+        <MessageLocation props={props} lat={lat} lng={lng} onLongPress={handleLongPress} />
+      </View>
+    );
+  };
+
+  const onShareLiveLocation = useCallback(() => {}, []);
+
+  useEffect(() => {
+    let unsubscribe: any;
+
+    const setupNotificationHandler = async () => {
+      // todo: implement dismissChatNotifications
+      unsubscribe = await dismissChatNotifications(
+        group_token,
+        isSubscribed,
+        setModalInfo,
+        navigation
+      );
+    };
+
+    setupNotificationHandler();
+
+    return () => {
+      if (unsubscribe) unsubscribe();
+      updateUnreadMessagesCount();
+    };
+  }, [group_token]);
+
+  useEffect(() => {
+    socket.current = new WebSocket(WEBSOCKET_URL);
+
+    socket.current.onopen = () => {
+      socket.current?.send(JSON.stringify({ token }));
+    };
+
+    socket.current.onmessage = (event) => {
+      const data = JSON.parse(event.data);
+      handleWebSocketMessage(data);
+    };
+
+    socket.current.onclose = () => {
+      console.log('WebSocket connection closed chat screen');
+    };
+
+    return () => {
+      if (socket.current) {
+        socket.current.close();
+        socket.current = null;
+      }
+    };
+  }, [token]);
+
+  useEffect(() => {
+    const handleAppStateChange = async (nextAppState: AppStateStatus) => {
+      if (appState.current.match(/inactive|background/) && nextAppState === 'active') {
+        if (!socket.current || socket.current.readyState === WebSocket.CLOSED) {
+          socket.current = new WebSocket(WEBSOCKET_URL);
+          socket.current.onopen = () => {
+            socket.current?.send(JSON.stringify({ token }));
+          };
+          socket.current.onmessage = (event) => {
+            const data = JSON.parse(event.data);
+            handleWebSocketMessage(data);
+          };
+        }
+
+        // todo: implement dismissChatNotifications
+        await dismissChatNotifications(group_token, isSubscribed, setModalInfo, navigation);
+      }
+    };
+
+    const subscription = AppState.addEventListener('change', handleAppStateChange);
+
+    return () => {
+      subscription.remove();
+      if (socket.current) {
+        socket.current.close();
+        socket.current = null;
+      }
+    };
+  }, [token]);
+
+  const handleWebSocketMessage = (data: any) => {
+    switch (data.action) {
+      case 'new_message':
+        if (data.conversation_with === group_token && data.message) {
+          const newMessage = mapApiMessageToGiftedMessage(data.message);
+          setMessages((previousMessages) => {
+            const messageExists =
+              previousMessages && previousMessages.some((msg) => msg._id === newMessage._id);
+            if (!messageExists) {
+              return GiftedChat.append(previousMessages ?? [], [newMessage]);
+            }
+            return previousMessages;
+          });
+        }
+        break;
+
+      case 'new_reaction':
+        if (data.conversation_with === group_token && data.reaction) {
+          updateMessageWithReaction(data.reaction);
+        }
+        break;
+
+      case 'unreact':
+        if (data.conversation_with === group_token && data.unreacted_message_id) {
+          removeReactionFromMessage(data.unreacted_message_id);
+        }
+        break;
+
+      case 'delete_message':
+        if (data.conversation_with === group_token && data.deleted_message_id) {
+          removeDeletedMessage(data.deleted_message_id);
+        }
+        break;
+
+      case 'is_typing':
+        if (data.conversation_with === group_token) {
+          setIsTyping(true);
+        }
+        break;
+
+      case 'stopped_typing':
+        if (data.conversation_with === group_token) {
+          setIsTyping(false);
+        }
+        break;
+
+      case 'messages_read':
+        if (data.conversation_with === group_token && data.read_messages_ids) {
+          setMessages(
+            (prevMessages) =>
+              prevMessages?.map((msg) => {
+                if (data.read_messages_ids.includes(msg._id)) {
+                  return { ...msg, received: true };
+                }
+                return msg;
+              }) ?? []
+          );
+        }
+        break;
+
+      default:
+        break;
+    }
+  };
+
+  const updateMessageWithReaction = (reactionData: any) => {
+    setMessages(
+      (prevMessages) =>
+        prevMessages?.map((msg) => {
+          if (msg._id === reactionData.message_id) {
+            const updatedReactions = [
+              ...(Array.isArray(msg.reactions)
+                ? msg.reactions?.filter((r: any) => r.uid !== reactionData.uid)
+                : []),
+              reactionData
+            ];
+            return { ...msg, reactions: updatedReactions };
+          }
+          return msg;
+        }) ?? []
+    );
+  };
+
+  const removeReactionFromMessage = (messageId: number) => {
+    setMessages(
+      (prevMessages) =>
+        prevMessages?.map((msg) => {
+          if (msg._id === messageId) {
+            const updatedReactions = Array.isArray(msg.reactions)
+              ? msg.reactions?.filter((r: any) => r.uid === +currentUserId)
+              : [];
+            return { ...msg, reactions: updatedReactions };
+          }
+          return msg;
+        }) ?? []
+    );
+  };
+
+  const removeDeletedMessage = (messageId: number) => {
+    setMessages(
+      (prevMessages) =>
+        prevMessages?.map((msg) => {
+          if (msg._id === messageId) {
+            return {
+              ...msg,
+              deleted: true,
+              text: 'This message was deleted',
+              pending: false,
+              sent: false,
+              received: false
+            };
+          }
+          return msg;
+        }) ?? []
+    );
+  };
+
+  useEffect(() => {
+    const pingInterval = setInterval(() => {
+      if (socket.current && socket.current.readyState === WebSocket.OPEN) {
+        socket.current.send(JSON.stringify({ action: 'ping', conversation_with: group_token }));
+      } else {
+        socket.current = new WebSocket(WEBSOCKET_URL);
+        socket.current.onopen = () => {
+          socket.current?.send(JSON.stringify({ token }));
+        };
+        socket.current.onmessage = (event) => {
+          const data = JSON.parse(event.data);
+          handleWebSocketMessage(data);
+        };
+
+        return () => {
+          if (socket.current) {
+            socket.current.close();
+            socket.current = null;
+          }
+        };
+      }
+    }, 50000);
+
+    return () => clearInterval(pingInterval);
+  }, []);
+
+  const sendWebSocketMessage = (
+    action: string,
+    message: CustomMessage | null = null,
+    reaction: string | null = null,
+    readMessagesIds: number[] | null = null
+  ) => {
+    if (socket.current && socket.current.readyState === WebSocket.OPEN) {
+      const data: any = {
+        action,
+        conversation_with: group_token
+      };
+
+      if (action === 'new_message' && message) {
+        data.message = {
+          id: message._id,
+          text: message.text,
+          sender: +currentUserId,
+          sent_datetime: new Date().toISOString().replace('T', ' ').substring(0, 19),
+          reply_to_id: message.replyMessage?.id ?? -1,
+          reply_to: message.replyMessage ?? null,
+          reactions: message.reactions ?? '{}',
+          status: 2,
+          attachement: message.attachment ? message.attachment : -1
+        };
+      }
+
+      if (action === 'new_reaction' && message && reaction) {
+        data.reaction = {
+          message_id: message._id,
+          reaction,
+          uid: +currentUserId,
+          datetime: new Date().toISOString()
+        };
+      }
+
+      if (action === 'unreact' && message) {
+        data.message_id = message._id;
+      }
+
+      if (action === 'delete_message' && message) {
+        data.message_id = message._id;
+      }
+
+      if (action === 'messages_read' && readMessagesIds) {
+        data.messages_ids = readMessagesIds;
+      }
+
+      socket.current.send(JSON.stringify(data));
+    }
+  };
+
+  const handleTyping = (isTyping: boolean) => {
+    if (isTyping) {
+      sendWebSocketMessage('is_typing');
+    } else {
+      sendWebSocketMessage('stopped_typing');
+    }
+  };
+
+  const mapApiMessageToGiftedMessage = (message: Message): CustomMessage => {
+    return {
+      _id: message.id,
+      text: message.text,
+      createdAt: new Date(message.sent_datetime + 'Z'),
+      user: {
+        _id: message.sender,
+        // todo: sender_name
+        name: message.sender !== +currentUserId ? message.sender_name : 'Me',
+        avatar:
+          message.sender !== +currentUserId && message.sender_avatar
+            ? API_HOST + message.sender_avatar
+            : message.sender === +currentUserId
+              ? (null as never)
+              : undefined
+      },
+      replyMessage:
+        message.reply_to_id !== -1
+          ? {
+              text: message.reply_to.text,
+              id: message.reply_to.id,
+              name:
+                message.reply_to.sender !== +currentUserId ? message.reply_to?.sender_name : 'Me'
+            }
+          : null,
+      reactions: JSON.parse(message.reactions || '{}'),
+      attachment: message.attachement !== -1 ? message.attachement : null,
+      pending: message.status === 1,
+      sent: message.status === 2,
+      received: message.status === 3,
+      deleted: message.status === 4,
+      isSending: false,
+      video:
+        message.attachement !== -1 && message.attachement?.filetype?.startsWith('video')
+          ? API_HOST + message.attachement?.attachment_link
+          : null,
+      image:
+        message.attachement !== -1 && message.attachement?.filetype?.startsWith('image')
+          ? API_HOST + message.attachement?.attachment_small_url
+          : null
+    };
+  };
+
+  useFocusEffect(
+    useCallback(() => {
+      refetch();
+    }, [])
+  );
+
+  useFocusEffect(
+    useCallback(() => {
+      if (chatData?.groupAvatar) {
+        setGroupAvatar(API_HOST + chatData.groupAvatar);
+      }
+
+      if (chatData?.messages) {
+        const mappedMessages = chatData.messages.map(mapApiMessageToGiftedMessage);
+
+        if (unreadMessageIndex === null && !isFetching) {
+          const firstUnreadIndex = mappedMessages.findLastIndex(
+            (msg) => !msg.received && !msg?.deleted && msg.user._id !== +currentUserId
+          );
+
+          if (firstUnreadIndex !== -1) {
+            setUnreadMessageIndex(firstUnreadIndex);
+
+            const unreadMarker: any = {
+              _id: 'unreadMarker',
+              text: 'Unread messages',
+              system: true
+            };
+
+            mappedMessages.splice(firstUnreadIndex + 1, 0, unreadMarker);
+            setTimeout(() => {
+              if (flatList.current) {
+                flatList.current.scrollToIndex({
+                  index: firstUnreadIndex,
+                  animated: true,
+                  viewPosition: 0.5
+                });
+              }
+            }, 500);
+          } else {
+            setUnreadMessageIndex(0);
+          }
+        }
+
+        setMessages((previousMessages) => {
+          const newMessages = mappedMessages.filter(
+            (newMsg) => !previousMessages?.some((oldMsg) => oldMsg._id === newMsg._id)
+          );
+          return prevThenMessageId !== -1 && previousMessages
+            ? GiftedChat.prepend(previousMessages, newMessages)
+            : mappedMessages;
+        });
+
+        if (mappedMessages.length < 50) {
+          setHasMoreMessages(false);
+        }
+
+        if (mappedMessages.length === 0 && !modalInfo.visible) {
+          setTimeout(() => {
+            textInputRef.current?.focus();
+          }, 500);
+        }
+
+        setIsLoadingEarlier(false);
+      }
+    }, [chatData])
+  );
+
+  useEffect(() => {
+    if (messages?.length === 0 && !modalInfo.visible) {
+      setTimeout(() => {
+        textInputRef.current?.focus();
+      }, 500);
+    }
+  }, [modalInfo]);
+
+  const loadEarlierMessages = async () => {
+    if (!hasMoreMessages || isLoadingEarlier || !messages) return;
+
+    setIsLoadingEarlier(true);
+
+    const previousMessageId = messages[messages.length - 1]._id;
+
+    setPrevThenMessageId(previousMessageId);
+  };
+
+  const sentToServer = useRef<Set<number>>(new Set());
+
+  const handleViewableItemsChanged = ({ viewableItems }: { viewableItems: any[] }) => {
+    const newViewableUnreadMessages = viewableItems
+      .filter(
+        (item) =>
+          !item.item.received &&
+          !item.item.deleted &&
+          !item.item.system &&
+          item.item.user._id !== +currentUserId &&
+          !sentToServer.current.has(item.item._id)
+      )
+      .map((item) => item.item._id);
+
+    if (newViewableUnreadMessages.length > 0) {
+      markMessagesAsRead(
+        {
+          token,
+          group_token,
+          messages_id: newViewableUnreadMessages
+        },
+        {
+          onSuccess: (res) => {
+            newViewableUnreadMessages.forEach((id) => sentToServer.current.add(id));
+            sendWebSocketMessage('messages_read', null, null, newViewableUnreadMessages);
+          }
+        }
+      );
+    }
+  };
+
+  const renderSystemMessage = (props: any) => {
+    if (props.currentMessage._id === 'unreadMarker') {
+      return (
+        <View style={styles.unreadMessagesContainer}>
+          <Text style={styles.unreadMessagesText}>{props.currentMessage.text}</Text>
+        </View>
+      );
+    }
+    return null;
+  };
+
+  const clearReplyMessage = () => setReplyMessage(null);
+
+  const handleLongPress = (message: CustomMessage, props: BubbleProps<CustomMessage>) => {
+    const messageRef = messageRefs.current[message._id];
+
+    setSelectedMessage(props);
+    trigger('impactMedium', options);
+
+    const isMine = message.user._id === +currentUserId;
+
+    if (messageRef) {
+      messageRef.measureInWindow((x: number, y: number, width: number, height: number) => {
+        const screenHeight = Dimensions.get('window').height;
+        const spaceAbove = y - insets.top;
+        const spaceBelow = screenHeight - (y + height) - insets.bottom * 2;
+
+        let finalY = y;
+        scrollY.value = 0;
+
+        if (isNaN(y) || isNaN(height)) {
+          console.error("Invalid measurement values for 'y' or 'height'", { y, height });
+          return;
+        }
+
+        if (spaceBelow < 160) {
+          const extraShift = 160 - spaceBelow;
+          finalY -= extraShift;
+        }
+
+        if (spaceAbove < 50) {
+          const extraShift = 50 - spaceAbove;
+          finalY += extraShift;
+        }
+
+        if (spaceBelow < 160 || spaceAbove < 50) {
+          const targetY = screenHeight / 2 - height / 2;
+          scrollY.value = withTiming(finalY - finalY);
+        }
+
+        if (height > Dimensions.get('window').height - 200) {
+          finalY = 100;
+        }
+
+        finalY = isNaN(finalY) ? 0 : finalY;
+
+        setMessagePosition({ x, y: finalY, width, height, isMine });
+        setIsModalVisible(true);
+      });
+    }
+  };
+
+  const openEmojiSelector = () => {
+    SheetManager.show('emoji-selector');
+    trigger('impactLight', options);
+  };
+
+  const closeEmojiSelector = () => {
+    SheetManager.hide('emoji-selector');
+  };
+
+  const handleReactionPress = (emoji: string, messageId: number) => {
+    addReaction(messageId, emoji);
+  };
+
+  // todo: delete api
+  const handleDeleteMessage = (messageId: number) => {
+    deleteMessage(
+      {
+        token,
+        message_id: messageId,
+        conversation_with_user: group_token
+      },
+      {
+        onSuccess: () => {
+          setMessages(
+            (prevMessages) =>
+              prevMessages?.map((msg) => {
+                if (msg._id === messageId) {
+                  return {
+                    ...msg,
+                    deleted: true,
+                    text: 'This message was deleted',
+                    pending: false,
+                    sent: false,
+                    received: false,
+                    attachment: null,
+                    image: undefined,
+                    video: undefined
+                  };
+                }
+                return msg;
+              }) ?? []
+          );
+          const messageToDelete = messages?.find((msg) => msg._id === messageId);
+          if (messageToDelete) {
+            sendWebSocketMessage('delete_message', messageToDelete, null, null);
+          }
+        }
+      }
+    );
+  };
+
+  const handleOptionPress = (option: string) => {
+    if (!selectedMessage) return;
+
+    switch (option) {
+      case 'reply':
+        setReplyMessage(selectedMessage.currentMessage);
+        setIsModalVisible(false);
+        break;
+      case 'copy':
+        Clipboard.setString(selectedMessage?.currentMessage?.text ?? '');
+        setIsModalVisible(false);
+        Alert.alert('Copied');
+        break;
+      case 'delete':
+        handleDeleteMessage(selectedMessage.currentMessage?._id);
+        setIsModalVisible(false);
+        break;
+      case 'download':
+        downloadFileToDevice(selectedMessage.currentMessage);
+        setIsModalVisible(false);
+        break;
+      default:
+        break;
+    }
+    closeEmojiSelector();
+  };
+
+  const openReactionList = (
+    reactions: { uid: number; name: string; reaction: string }[],
+    messageId: number
+  ) => {
+    SheetManager.show('reactions-list-modal', {
+      payload: {
+        users: reactions,
+        currentUserId: +currentUserId,
+        token,
+        messageId,
+        conversation_with_user: group_token,
+        setMessages,
+        sendWebSocketMessage,
+        isGroup: true,
+        groupToken: group_token
+      } as any
+    });
+  };
+
+  const renderTimeContainer = (time: TimeProps<CustomMessage>) => {
+    const createdAt = new Date(time.currentMessage.createdAt);
+
+    const formattedTime = createdAt.toLocaleTimeString([], {
+      hour: '2-digit',
+      minute: '2-digit',
+      hour12: true
+    });
+
+    const hasReactions =
+      time.currentMessage.reactions &&
+      Array.isArray(time.currentMessage.reactions) &&
+      time.currentMessage.reactions.length > 0;
+
+    return (
+      <View
+        style={[
+          styles.bottomContainer,
+          {
+            justifyContent: hasReactions ? 'space-between' : 'flex-end'
+          }
+        ]}
+      >
+        {hasReactions && (
+          <TouchableOpacity
+            style={[
+              styles.bottomCustomContainer,
+              {
+                backgroundColor:
+                  time.position === 'left' ? 'rgba(0, 0, 0, 0.2)' : 'rgba(255, 255, 255, 0.2)'
+              }
+            ]}
+            onPress={() =>
+              Array.isArray(time.currentMessage.reactions) &&
+              openReactionList(
+                time.currentMessage.reactions.map((reaction) => ({
+                  ...reaction,
+                  name: reaction.uid !== +currentUserId ? reaction?.name : 'Me'
+                })),
+                time.currentMessage._id
+              )
+            }
+          >
+            {Object.entries(
+              (Array.isArray(time.currentMessage.reactions)
+                ? time.currentMessage.reactions
+                : []
+              ).reduce(
+                (acc: Record<string, { count: number }>, { reaction }: { reaction: string }) => {
+                  if (!acc[reaction]) {
+                    acc[reaction] = { count: 0 };
+                  }
+                  acc[reaction].count += 1;
+                  return acc;
+                },
+                {}
+              )
+            ).map(([emoji, { count }]: any) => {
+              return (
+                <View key={emoji}>
+                  <Text style={{}}>
+                    {emoji}
+                    {(count as number) > 1 ? ` ${count}` : ''}
+                  </Text>
+                </View>
+              );
+            })}
+          </TouchableOpacity>
+        )}
+        <View style={styles.timeContainer}>
+          <Text style={styles.timeText}>{formattedTime}</Text>
+          {renderTicks(time.currentMessage)}
+        </View>
+      </View>
+    );
+  };
+
+  const renderSelectedMessage = () =>
+    selectedMessage && (
+      <View
+        style={{
+          maxHeight: '80%',
+          width: messagePosition?.width,
+          position: 'absolute',
+          top: messagePosition?.y,
+          left: messagePosition?.x
+        }}
+      >
+        <ScrollView>
+          <Bubble
+            {...selectedMessage}
+            wrapperStyle={{
+              right: { backgroundColor: Colors.DARK_BLUE },
+              left: { backgroundColor: Colors.FILL_LIGHT }
+            }}
+            textStyle={{
+              right: { color: Colors.WHITE },
+              left: { color: Colors.DARK_BLUE }
+            }}
+            renderTicks={() => null}
+            renderTime={renderTimeContainer}
+            renderCustomView={() =>
+              selectedMessage.currentMessage.attachment?.filetype === 'nomadmania/location'
+                ? renderMessageLocation(selectedMessage)
+                : selectedMessage.currentMessage.attachment &&
+                    !selectedMessage.currentMessage.image &&
+                    !selectedMessage.currentMessage.video
+                  ? renderMessageFile(selectedMessage)
+                  : renderReplyMessageView(selectedMessage)
+            }
+          />
+        </ScrollView>
+      </View>
+    );
+
+  const handleBackgroundPress = () => {
+    setIsModalVisible(false);
+    setSelectedMessage(null);
+    closeEmojiSelector();
+  };
+
+  useFocusEffect(
+    useCallback(() => {
+      navigation?.getParent()?.setOptions({
+        tabBarStyle: {
+          display: 'none'
+        }
+      });
+    }, [navigation])
+  );
+
+  const onSend = useCallback(
+    (newMessages: CustomMessage[] = []) => {
+      if (replyMessage) {
+        newMessages[0].replyMessage = {
+          text: replyMessage.text,
+          id: replyMessage._id,
+          name: replyMessage.user._id !== +currentUserId ? replyMessage.user.name : 'Me'
+        };
+      }
+      const user = {
+        _id: +currentUserId,
+        name: 'Me',
+        avatar: null
+      };
+      const message = { ...newMessages[0], pending: true, isSending: true, user };
+
+      setMessages((previousMessages) => GiftedChat.append(previousMessages ?? [], [message]));
+
+      sendMessage(
+        {
+          token,
+          to_group_token: group_token,
+          text: message.text,
+          reply_to_id: replyMessage ? (replyMessage._id as number) : -1
+        },
+        {
+          onSuccess: (res) => {
+            const newMessage = {
+              _id: res.message_id,
+              text: message.text,
+              replyMessage: { ...message.replyMessage, sender: replyMessage?.user?._id }
+            };
+
+            setMessages((previousMessages) =>
+              (previousMessages ?? []).map((msg) =>
+                msg._id === message._id ? { ...msg, _id: res.message_id, isSending: false } : msg
+              )
+            );
+            sendWebSocketMessage('new_message', newMessage as unknown as CustomMessage);
+          }
+        }
+      );
+
+      clearReplyMessage();
+    },
+    [replyMessage]
+  );
+
+  const addReaction = (messageId: number, reaction: string) => {
+    if (!messages) return;
+
+    const updatedMessages = messages.map((msg: any) => {
+      if (msg._id === messageId) {
+        const updatedReactions: Reaction[] = [
+          ...(Array.isArray(msg.reactions)
+            ? msg.reactions?.filter((r: Reaction) => r.uid !== +currentUserId)
+            : []),
+          { datetime: new Date().toISOString(), reaction: reaction, uid: +currentUserId }
+        ];
+
+        return {
+          ...msg,
+          reactions: updatedReactions
+        };
+      }
+      return msg;
+    });
+
+    setMessages(updatedMessages);
+
+    reactToMessage(
+      { token, message_id: messageId, reaction: reaction, group_token: group_token },
+      {
+        onSuccess: () => {
+          const message = messages.find((msg) => msg._id === messageId);
+          if (message) {
+            sendWebSocketMessage('new_reaction', message, reaction);
+          }
+        }
+      }
+    );
+
+    setIsModalVisible(false);
+  };
+
+  const updateRowRef = useCallback(
+    (ref: any) => {
+      if (
+        ref &&
+        replyMessage &&
+        ref.props.children.props.currentMessage?._id === replyMessage._id
+      ) {
+        swipeableRowRef.current = ref;
+      }
+    },
+    [replyMessage]
+  );
+
+  const renderReplyMessageView = (props: BubbleProps<CustomMessage>) => {
+    if (!props.currentMessage) {
+      return null;
+    }
+    const { currentMessage } = props;
+
+    if (!currentMessage || !currentMessage?.replyMessage) {
+      return null;
+    }
+
+    return (
+      <TouchableOpacity
+        style={[
+          styles.replyMessageContainer,
+          {
+            backgroundColor:
+              currentMessage.user._id !== +currentUserId
+                ? 'rgba(255, 255, 255, 0.7)'
+                : 'rgba(0, 0, 0, 0.2)',
+            borderColor:
+              currentMessage.user._id !== +currentUserId ? Colors.DARK_BLUE : Colors.WHITE
+          }
+        ]}
+        onPress={() => {
+          if (currentMessage?.replyMessage?.id) {
+            scrollToMessage(currentMessage.replyMessage.id);
+          }
+        }}
+      >
+        <View style={styles.replyContent}>
+          <Text
+            style={[
+              styles.replyAuthorName,
+              {
+                color: currentMessage.user._id !== +currentUserId ? Colors.DARK_BLUE : Colors.WHITE
+              }
+            ]}
+          >
+            {currentMessage.replyMessage.name}
+          </Text>
+
+          <Text
+            numberOfLines={1}
+            style={[
+              styles.replyMessageText,
+              {
+                color: currentMessage.user._id !== +currentUserId ? Colors.DARK_BLUE : Colors.WHITE
+              }
+            ]}
+          >
+            {currentMessage.replyMessage.text}
+          </Text>
+        </View>
+      </TouchableOpacity>
+    );
+  };
+
+  const scrollToMessage = (messageId: number) => {
+    if (!messages) return;
+
+    const messageIndex = messages.findIndex((message) => message._id === messageId);
+
+    if (messageIndex !== -1 && flatList.current) {
+      flatList.current.scrollToIndex({
+        index: messageIndex,
+        animated: true,
+        viewPosition: 0.5
+      });
+
+      setHighlightedMessageId(messageId);
+    }
+  };
+
+  useEffect(() => {
+    if (highlightedMessageId && isRerendering) {
+      setTimeout(() => {
+        setHighlightedMessageId(null);
+        setIsRerendering(false);
+      }, 1500);
+    }
+  }, [highlightedMessageId, isRerendering]);
+
+  useEffect(() => {
+    if (replyMessage && swipeableRowRef.current) {
+      swipeableRowRef.current.close();
+      swipeableRowRef.current = null;
+    }
+  }, [replyMessage]);
+
+  const renderMessageImage = (props: any) => {
+    const { currentMessage } = props;
+    const leftMessage = currentMessage?.user?._id !== +currentUserId;
+
+    return (
+      <TouchableOpacity
+        onPress={() => setSelectedMedia(API_HOST + currentMessage.attachment.attachment_full_url)}
+        onLongPress={() => handleLongPress(currentMessage, props)}
+        style={styles.imageContainer}
+        disabled={currentMessage.isSending}
+      >
+        <Image source={{ uri: currentMessage.image }} style={styles.chatImage} resizeMode="cover" />
+        {currentMessage.isSending && (
+          <View
+            style={{
+              position: 'absolute',
+              top: 0,
+              left: 0,
+              right: 0,
+              bottom: 0,
+              justifyContent: 'center',
+              alignItems: 'center'
+            }}
+          >
+            <ActivityIndicator
+              size="large"
+              color={leftMessage ? Colors.DARK_BLUE : Colors.FILL_LIGHT}
+            />
+          </View>
+        )}
+      </TouchableOpacity>
+    );
+  };
+
+  const renderTicks = (message: CustomMessage) => {
+    if (message.user._id !== +currentUserId) return null;
+
+    if (message.isSending) {
+      return (
+        <View>
+          <ActivityIndicator
+            size={16}
+            color={Colors.LIGHT_GRAY}
+            style={{ transform: 'scale(0.8)' }}
+          />
+        </View>
+      );
+    }
+
+    return message.received ? (
+      <View>
+        <MaterialCommunityIcons name="check-all" size={16} color={Colors.WHITE} />
+      </View>
+    ) : message.sent ? (
+      <View>
+        <MaterialCommunityIcons name="check" size={16} color={Colors.WHITE} />
+      </View>
+    ) : message.pending ? (
+      <View>
+        <MaterialCommunityIcons name="check" size={16} color={Colors.LIGHT_GRAY} />
+      </View>
+    ) : null;
+  };
+
+  const renderBubble = (props: BubbleProps<CustomMessage>) => {
+    const { currentMessage } = props;
+
+    if (currentMessage.deleted) {
+      const text = currentMessage.text.length
+        ? props.currentMessage.text
+        : 'This message was deleted';
+
+      return (
+        <View>
+          <Bubble
+            {...props}
+            renderTime={() => null}
+            currentMessage={{
+              ...props.currentMessage,
+              text: text
+            }}
+            renderMessageText={() => (
+              <View style={{ paddingHorizontal: 12, paddingVertical: 6 }}>
+                <Text style={{ color: Colors.LIGHT_GRAY, fontStyle: 'italic', fontSize: 12 }}>
+                  {text}
+                </Text>
+              </View>
+            )}
+            wrapperStyle={{
+              right: {
+                backgroundColor: Colors.DARK_BLUE
+              },
+              left: {
+                backgroundColor: Colors.FILL_LIGHT
+              }
+            }}
+            textStyle={{
+              left: {
+                color: Colors.DARK_BLUE
+              },
+              right: {
+                color: Colors.WHITE
+              }
+            }}
+          />
+        </View>
+      );
+    }
+
+    const isHighlighted = currentMessage._id === highlightedMessageId;
+    const backgroundColor = isHighlighted
+      ? Colors.ORANGE
+      : currentMessage.user._id === +currentUserId
+        ? Colors.DARK_BLUE
+        : Colors.FILL_LIGHT;
+
+    const messageToCompare = props.previousMessage;
+
+    const showUserName =
+      props.position === 'left' &&
+      currentMessage &&
+      messageToCompare &&
+      (!isSameUser(currentMessage, messageToCompare) ||
+        !isSameDay(currentMessage, messageToCompare));
+
+    return (
+      <View
+        key={`${currentMessage._id}-${isHighlighted ? 'highlighted' : 'normal'}`}
+        ref={(ref) => {
+          if (ref && currentMessage) {
+            messageRefs.current[currentMessage._id] = ref;
+          }
+        }}
+        collapsable={false}
+      >
+        <Bubble
+          {...props}
+          wrapperStyle={{
+            right: {
+              backgroundColor: backgroundColor
+            },
+            left: {
+              backgroundColor: backgroundColor
+            }
+          }}
+          textStyle={{
+            left: {
+              color: Colors.DARK_BLUE
+            },
+            right: {
+              color: Colors.FILL_LIGHT
+            }
+          }}
+          onLongPress={() => handleLongPress(currentMessage, props)}
+          renderTicks={() => null}
+          renderTime={renderTimeContainer}
+          renderCustomView={() => {
+            return (
+              <View>
+                {showUserName ? (
+                  <Text
+                    style={{
+                      color: Colors.BLACK,
+                      fontWeight: '600',
+                      fontSize: 13,
+                      paddingHorizontal: 10,
+                      paddingTop: 8,
+                      paddingBottom: 2
+                    }}
+                  >
+                    {/* {'~ '} */}
+                    {props.currentMessage.user.name}
+                  </Text>
+                ) : null}
+                {currentMessage.attachment?.filetype === 'nomadmania/location'
+                  ? renderMessageLocation(props)
+                  : currentMessage.attachment && !currentMessage.image && !currentMessage.video
+                    ? renderMessageFile(props)
+                    : renderReplyMessageView(props)}
+              </View>
+            );
+          }}
+        />
+      </View>
+    );
+  };
+
+  const openAttachmentsModal = () => {
+    SheetManager.show('chat-attachments', {
+      payload: {
+        name: groupName,
+        uid: group_token,
+        setModalInfo,
+        closeOptions: () => {},
+        onSendMedia,
+        onSendLocation,
+        onShareLiveLocation,
+        onSendFile,
+        isGroup: true
+      } as any
+    });
+  };
+
+  const renderInputToolbar = (props: any) => (
+    <InputToolbar
+      {...props}
+      renderActions={() =>
+        userType === 'normal' ? (
+          <Actions
+            icon={() => <MaterialCommunityIcons name="plus" size={28} color={Colors.DARK_BLUE} />}
+            onPressActionButton={openAttachmentsModal}
+          />
+        ) : null
+      }
+      containerStyle={{
+        backgroundColor: Colors.FILL_LIGHT
+      }}
+    />
+  );
+
+  const renderScrollToBottom = () => {
+    return (
+      <TouchableOpacity
+        style={styles.scrollToBottom}
+        onPress={() => {
+          if (flatList.current) {
+            flatList.current.scrollToIndex({ index: 0, animated: true });
+          }
+        }}
+      >
+        <MaterialCommunityIcons name="chevron-down" size={24} color={Colors.WHITE} />
+      </TouchableOpacity>
+    );
+  };
+
+  const shouldUpdateMessage = (
+    props: MessageProps<IMessage>,
+    nextProps: MessageProps<IMessage>
+  ) => {
+    setIsRerendering(true);
+    const currentId = nextProps.currentMessage._id;
+    return currentId === highlightedMessageId;
+  };
+
+  return (
+    <SafeAreaView
+      edges={['top']}
+      style={{
+        height: '100%'
+      }}
+    >
+      <View style={{ paddingHorizontal: '5%' }}>
+        <Header
+          label={groupName}
+          textColor={userType !== 'normal' ? Colors.RED : Colors.DARK_BLUE}
+          rightElement={
+            <TouchableOpacity
+              // todo: change to settings
+              onPress={
+                () => {}
+                // navigation.navigate(
+                //   ...([NAVIGATION_PAGES.PUBLIC_PROFILE_VIEW, { userId: 57363 }] as never)
+                // )
+              }
+              disabled={userType !== 'normal'}
+            >
+              {groupAvatar && userType === 'normal' ? (
+                <Image source={{ uri: groupAvatar }} style={styles.avatar} />
+              ) : userType === 'normal' ? (
+                <GroupIcon fill={Colors.DARK_BLUE} width={30} height={30} />
+              ) : (
+                <BanIcon fill={Colors.RED} width={30} height={30} />
+              )}
+            </TouchableOpacity>
+          }
+        />
+      </View>
+
+      <GestureHandlerRootView style={styles.container}>
+        {messages ? (
+          <GiftedChat
+            messages={messages as CustomMessage[]}
+            listViewProps={{
+              ref: flatList,
+              showsVerticalScrollIndicator: false,
+              initialNumToRender: 30,
+              onViewableItemsChanged: handleViewableItemsChanged,
+              viewabilityConfig: { itemVisiblePercentThreshold: 50 },
+              onScrollToIndexFailed: (info: any) => {
+                const wait = new Promise((resolve) => setTimeout(resolve, 300));
+                wait.then(() => {
+                  flatList.current?.scrollToIndex({
+                    index: info.index,
+                    animated: true,
+                    viewPosition: 0.5
+                  });
+                });
+              }
+            }}
+            renderSystemMessage={renderSystemMessage}
+            onSend={(newMessages: CustomMessage[]) => onSend(newMessages)}
+            user={{ _id: +currentUserId, name: 'Me' }}
+            renderBubble={renderBubble}
+            renderMessageImage={renderMessageImage}
+            showUserAvatar={true}
+            onPressAvatar={(user) => {
+              navigation.navigate(
+                ...([NAVIGATION_PAGES.PUBLIC_PROFILE_VIEW, { userId: user._id }] as never)
+              );
+            }}
+            renderInputToolbar={renderInputToolbar}
+            renderCustomView={renderReplyMessageView}
+            isCustomViewBottom={false}
+            messageContainerRef={messageContainerRef}
+            minComposerHeight={34}
+            onInputTextChanged={(text) => handleTyping(text.length > 0)}
+            textInputRef={textInputRef}
+            isTyping={isTyping}
+            renderSend={(props) => (
+              <View style={styles.sendBtn}>
+                {props.text?.trim() && (
+                  <Send
+                    {...props}
+                    containerStyle={{
+                      justifyContent: 'center'
+                    }}
+                  >
+                    <SendIcon fill={Colors.DARK_BLUE} />
+                  </Send>
+                )}
+                {!props.text?.trim() && <SendIcon fill={Colors.LIGHT_GRAY} />}
+              </View>
+            )}
+            renderMessageVideo={(props) => (
+              <RenderMessageVideo
+                props={props}
+                token={token}
+                currentUserId={+currentUserId}
+                onLongPress={handleLongPress}
+              />
+            )}
+            textInputProps={{
+              ...styles.composer,
+              selectionColor: Colors.LIGHT_GRAY
+            }}
+            placeholder=""
+            renderMessage={(props) => (
+              <ChatMessageBox
+                {...(props as MessageProps<CustomMessage>)}
+                updateRowRef={updateRowRef}
+                setReplyOnSwipeOpen={setReplyMessage}
+              />
+            )}
+            renderChatFooter={() => (
+              <ReplyMessageBar clearReply={clearReplyMessage} message={replyMessage} />
+            )}
+            maxComposerHeight={100}
+            renderComposer={(props) => <Composer {...props} />}
+            keyboardShouldPersistTaps="handled"
+            renderChatEmpty={() => (
+              <View style={styles.emptyChat}>
+                <Text
+                  style={styles.emptyChatText}
+                >{`No messages yet.\nFeel free to start the conversation.`}</Text>
+              </View>
+            )}
+            shouldUpdateMessage={shouldUpdateMessage}
+            scrollToBottom={true}
+            scrollToBottomComponent={renderScrollToBottom}
+            scrollToBottomStyle={{ backgroundColor: 'transparent' }}
+            parsePatterns={(linkStyle) => [
+              {
+                type: 'url',
+                style: { color: Colors.ORANGE, textDecorationLine: 'underline' },
+                onPress: (url: string) => Linking.openURL(url),
+                onLongPress: (url: string) => {
+                  Clipboard.setString(url ?? '');
+                  Alert.alert('Link copied');
+                }
+              }
+            ]}
+            infiniteScroll={true}
+            loadEarlier={hasMoreMessages}
+            isLoadingEarlier={isLoadingEarlier}
+            onLoadEarlier={loadEarlierMessages}
+            renderLoadEarlier={() => (
+              <View style={{ paddingVertical: 20 }}>
+                <ActivityIndicator size="small" color={Colors.DARK_BLUE} />
+              </View>
+            )}
+          />
+        ) : (
+          <ActivityIndicator size="large" color={Colors.DARK_BLUE} />
+        )}
+
+        <ImageView
+          images={[{ uri: selectedMedia, cache: 'force-cache' }]}
+          imageIndex={0}
+          visible={!!selectedMedia}
+          onRequestClose={() => setSelectedMedia(null)}
+          backgroundColor={Colors.DARK_BLUE}
+        />
+
+        <ReactModal
+          isVisible={isModalVisible}
+          onBackdropPress={handleBackgroundPress}
+          style={styles.reactModalContainer}
+          animationIn="fadeIn"
+          animationOut="fadeOut"
+          useNativeDriver
+          backdropColor="transparent"
+        >
+          <BlurView
+            intensity={80}
+            style={styles.modalBackground}
+            experimentalBlurMethod="dimezisBlurView"
+          >
+            <TouchableOpacity
+              style={styles.modalBackground}
+              activeOpacity={1}
+              onPress={handleBackgroundPress}
+            >
+              <ReactionBar
+                messagePosition={messagePosition}
+                selectedMessage={selectedMessage}
+                reactionEmojis={reactionEmojis}
+                handleReactionPress={handleReactionPress}
+                openEmojiSelector={openEmojiSelector}
+              />
+              {renderSelectedMessage()}
+              <OptionsMenu
+                selectedMessage={selectedMessage}
+                handleOptionPress={handleOptionPress}
+                messagePosition={messagePosition}
+              />
+              <EmojiSelectorModal
+                visible={emojiSelectorVisible}
+                selectedMessage={selectedMessage}
+                addReaction={addReaction}
+                closeEmojiSelector={closeEmojiSelector}
+              />
+            </TouchableOpacity>
+          </BlurView>
+        </ReactModal>
+
+        <WarningModal
+          isVisible={modalInfo.visible}
+          onClose={closeModal}
+          type={modalInfo.type}
+          message={modalInfo.message}
+          buttonTitle={modalInfo.buttonTitle}
+          title={modalInfo.title}
+          action={() => {
+            modalInfo.action();
+            closeModal();
+          }}
+        />
+        <AttachmentsModal />
+        <ReactionsListModal />
+      </GestureHandlerRootView>
+      <View
+        style={{
+          height: insets.bottom,
+          backgroundColor: Colors.FILL_LIGHT
+        }}
+      />
+    </SafeAreaView>
+  );
+};
+
+export default GroupChatScreen;

+ 37 - 18
src/screens/InAppScreens/MessagesScreen/index.tsx

@@ -39,6 +39,7 @@ import BanIcon from 'assets/icons/messages/ban.svg';
 import SwipeableBlockedRow from './Components/SwipeableBlockedRow';
 import { useMessagesStore } from 'src/stores/unreadMessagesStore';
 import { useSafeAreaInsets } from 'react-native-safe-area-context';
+import GroupIcon from 'assets/icons/messages/group-chat.svg';
 
 const TypingIndicator = () => {
   const [dots, setDots] = useState('');
@@ -290,7 +291,7 @@ const MessagesScreen = () => {
           pin: item.pin,
           archive: item.archive,
           muted: item.muted,
-          userType: item.user_type
+          userType: item.user_type ?? 'normal'
         }}
         token={token}
         onRowOpen={handleRowOpen}
@@ -300,35 +301,51 @@ const MessagesScreen = () => {
         <TouchableHighlight
           key={`${item.uid}-${typingUsers[item.uid]}`}
           activeOpacity={0.8}
-          onPress={() =>
-            navigation.navigate(
-              ...([
-                NAVIGATION_PAGES.CHAT,
-                {
-                  id: item.uid,
-                  name: item.name,
-                  avatar: item.avatar,
-                  userType: item.user_type
-                }
-              ] as never)
-            )
-          }
+          onPress={() => {
+            if (!item.uid) {
+              navigation.navigate(
+                ...([
+                  NAVIGATION_PAGES.GROUP_CHAT,
+                  {
+                    group_token: item.group_chat_token,
+                    name: item.name,
+                    avatar: item.avatar,
+                    userType: item.user_type
+                  }
+                ] as never)
+              );
+            } else {
+              navigation.navigate(
+                ...([
+                  NAVIGATION_PAGES.CHAT,
+                  {
+                    id: item.uid,
+                    name: item.name,
+                    avatar: item.avatar,
+                    userType: item.user_type
+                  }
+                ] as never)
+              );
+            }
+          }}
           underlayColor={Colors.FILL_LIGHT}
         >
           <View style={styles.chatItem}>
-            {item.avatar && item.user_type === 'normal' ? (
+            {item.avatar && (item.user_type === 'normal' || !item.user_type) ? (
               <Image source={{ uri: API_HOST + item.avatar }} style={styles.avatar} />
-            ) : item.user_type === 'normal' ? (
+            ) : item.uid && (item.user_type === 'normal' || !item.user_type) ? (
               <AvatarWithInitials
                 text={
                   item.name
-                    .split(/ (.+)/)
+                    ?.split(/ (.+)/)
                     .map((n) => n[0])
                     .join('') ?? ''
                 }
                 flag={API_HOST + item?.flag}
                 size={54}
               />
+            ) : item.user_type === 'normal' || !item.user_type ? (
+              <GroupIcon fill={Colors.DARK_BLUE} width={54} height={54} />
             ) : (
               <BanIcon fill={Colors.RED} width={54} height={54} />
             )}
@@ -338,7 +355,9 @@ const MessagesScreen = () => {
                 <Text
                   style={[
                     styles.chatName,
-                    item.user_type !== 'normal' ? { color: Colors.RED } : {}
+                    item.user_type === 'not_exist' || item.user_type === 'blocked'
+                      ? { color: Colors.RED }
+                      : {}
                   ]}
                 >
                   {name}

+ 527 - 0
src/screens/InAppScreens/TravelsScreen/EventScreen/index.tsx

@@ -0,0 +1,527 @@
+import React, { FC, useCallback, useEffect, useState } from 'react';
+import { View, Text, Image, TouchableOpacity, Linking, Dimensions, FlatList } from 'react-native';
+import ImageView from 'better-react-native-image-viewing';
+import { styles } from './styles';
+import { CommonActions, useFocusEffect, useNavigation } from '@react-navigation/native';
+import { Colors } from 'src/theme';
+import { styles as ButtonStyles } from 'src/components/RegionPopup/style';
+
+import { ScrollView } from 'react-native-gesture-handler';
+import { NAVIGATION_PAGES } from 'src/types';
+import { API_HOST } from 'src/constants';
+import { StoreType, storage } from 'src/storage';
+import { ButtonVariants } from 'src/types/components';
+import formatNumber from '../../TravelsScreen/utils/formatNumber';
+import { MaterialCommunityIcons } from '@expo/vector-icons';
+
+import ChevronLeft from 'assets/icons/chevron-left.svg';
+import MapSvg from 'assets/icons/travels-screens/map-location.svg';
+import AddImgSvg from 'assets/icons/travels-screens/add-img.svg';
+import ShareIcon from 'assets/icons/share.svg';
+import GigtIcon from 'assets/icons/events/gift.svg';
+import CalendarCheckIcon from 'assets/icons/events/calendar-check.svg';
+import CalendarIcon from 'assets/icons/events/calendar-solid.svg';
+import EarthIcon from 'assets/icons/travels-section/earth.svg';
+import NomadsIcon from 'assets/icons/bottom-navigation/travellers.svg';
+import LocationIcon from 'assets/icons/bottom-navigation/map.svg';
+import FileIcon from 'assets/icons/events/file-solid.svg';
+import ImageIcon from 'assets/icons/events/image.svg';
+import { getFontSize } from 'src/utils';
+
+const testData = [
+  {
+    id: 1,
+    title: 'Attachment 1',
+    url: ''
+  },
+  {
+    id: 2,
+    title: 'Attachment 2 dgfgs sgfbfsg tsrgrsz sgb',
+    url: ''
+  },
+  {
+    id: 3,
+    title: 'Attachment 3',
+    url: ''
+  },
+  {
+    id: 4,
+    title: 'Attachment 4',
+    url: ''
+  },
+  {
+    id: 5,
+    title: 'Attachment 5',
+    url: ''
+  },
+  {
+    id: 6,
+    title: 'Attachment 6',
+    url: ''
+  },
+  {
+    id: 7,
+    title: 'Attachment 7',
+    url: ''
+  },
+  {
+    id: 8,
+    title: 'Attachment 8',
+    url: ''
+  },
+  {
+    id: 9,
+    title: 'Attachment 9',
+    url: ''
+  }
+];
+
+const fileWidth = Dimensions.get('window').width / 5;
+
+const EventScreen = () => {
+  const token = (storage.get('token', StoreType.STRING) as string) ?? null;
+  const navigation = useNavigation();
+  const [isLoading, setIsLoading] = useState(true);
+  const [isExpanded, setIsExpanded] = useState(false);
+
+  const renderItem = ({ item, index }) => {
+    const totalItems = testData.length;
+    if (!isExpanded && index === 7 && totalItems > 8) {
+      return (
+        <TouchableOpacity
+          style={{
+            width: fileWidth,
+            alignItems: 'center',
+            gap: 4
+          }}
+          onPress={() => setIsExpanded(true)}
+        >
+          <View
+            style={{
+              backgroundColor: Colors.FILL_LIGHT,
+              borderRadius: 8,
+              alignItems: 'center',
+              justifyContent: 'center',
+              height: fileWidth,
+              width: fileWidth
+            }}
+          >
+            <MaterialCommunityIcons name="dots-horizontal" size={36} color={Colors.DARK_BLUE} />
+          </View>
+        </TouchableOpacity>
+      );
+    }
+
+    return (
+      <TouchableOpacity
+        style={{
+          width: fileWidth,
+          alignItems: 'center',
+          gap: 4
+        }}
+        onPress={() => {
+          // Linking.openURL(item.url);
+        }}
+      >
+        <View
+          style={{
+            backgroundColor: Colors.FILL_LIGHT,
+            borderRadius: 8,
+            alignItems: 'center',
+            justifyContent: 'center',
+            height: fileWidth,
+            width: fileWidth
+          }}
+        >
+          <MaterialCommunityIcons name="file" size={36} color={Colors.DARK_BLUE} />
+        </View>
+        <Text
+          style={{ fontSize: 12, fontWeight: '600', color: Colors.DARK_BLUE }}
+          numberOfLines={2}
+        >
+          {item.title}
+        </Text>
+      </TouchableOpacity>
+    );
+  };
+
+  return (
+    <View style={styles.container}>
+      <TouchableOpacity
+        onPress={() => {
+          navigation.goBack();
+        }}
+        style={styles.backButton}
+      >
+        <View style={styles.chevronWrapper}>
+          <ChevronLeft fill={Colors.WHITE} />
+        </View>
+      </TouchableOpacity>
+
+      <ScrollView
+        contentContainerStyle={{}}
+        nestedScrollEnabled={true}
+        showsVerticalScrollIndicator={false}
+      >
+        <View style={styles.emptyImage}>
+          <Image
+            source={require('../../../../../assets/images/logo-opacity.png')}
+            style={{ width: 100, height: 100 }}
+          />
+          <Text style={styles.emptyImageText}>No image available at this location</Text>
+        </View>
+
+        <TouchableOpacity
+          onPress={() => {
+            // route.params?.isTravelsScreen || route.params?.isProfileScreen
+            //   ? navigation.dispatch(
+            //       CommonActions.reset({
+            //         index: 1,
+            //         routes: [
+            //           {
+            //             name: NAVIGATION_PAGES.IN_APP_MAP_TAB,
+            //             state: {
+            //               routes: [
+            //                 {
+            //                   name: NAVIGATION_PAGES.MAP_TAB,
+            //                   params: { id: regionId, type: type === 'nm' ? 'regions' : 'places' }
+            //                 }
+            //               ]
+            //             }
+            //           }
+            //         ]
+            //       })
+            //     )
+            //   : navigation.goBack();
+          }}
+          style={styles.goToMapBtn}
+        >
+          <View style={styles.chevronWrapper}>
+            <MapSvg fill={Colors.WHITE} />
+          </View>
+        </TouchableOpacity>
+
+        <View
+          style={{
+            position: 'absolute',
+            width: 71,
+            height: 31,
+            top: 170,
+            left: 0,
+            justifyContent: 'center',
+            alignItems: 'center',
+            zIndex: 2,
+            backgroundColor: Colors.ORANGE,
+            borderTopRightRadius: 4,
+            borderBottomRightRadius: 4,
+            paddingRight: 10,
+            paddingLeft: 16
+          }}
+        >
+          <Text
+            style={{
+              textTransform: 'uppercase',
+              fontSize: getFontSize(16),
+              fontWeight: '700',
+              color: Colors.WHITE
+            }}
+          >
+            Tour
+          </Text>
+        </View>
+
+        <View style={styles.wrapper}>
+          <View style={styles.nameContainer}>
+            <Text style={styles.title}>Meeting in San Clemente</Text>
+            <TouchableOpacity
+              onPress={() => {}}
+              style={{
+                alignItems: 'center',
+                justifyContent: 'center',
+                paddingLeft: 8
+              }}
+            >
+              <ShareIcon
+                width={20}
+                height={20}
+                fill={Colors.DARK_BLUE}
+                style={{ alignSelf: 'center' }}
+              />
+            </TouchableOpacity>
+          </View>
+
+          <TouchableOpacity
+            style={{
+              flexDirection: 'row',
+              alignItems: 'center',
+              justifyContent: 'center',
+              paddingVertical: 8,
+              paddingHorizontal: 12,
+              borderRadius: 20,
+              backgroundColor: Colors.ORANGE,
+              gap: 6
+            }}
+          >
+            {true ? (
+              <CalendarCheckIcon fill={Colors.WHITE} width={16} height={16} />
+            ) : (
+              <GigtIcon fill={Colors.WHITE} width={16} height={16} />
+            )}
+            <Text
+              style={{
+                color: Colors.WHITE,
+                fontSize: getFontSize(14),
+                fontFamily: 'montserrat-700',
+                textTransform: 'uppercase'
+              }}
+            >
+              Join - 2000$
+            </Text>
+          </TouchableOpacity>
+
+          <View style={styles.divider} />
+
+          <View style={{ flexDirection: 'row', alignItems: 'center', gap: 4 }}>
+            <View style={{ flexDirection: 'row', alignItems: 'center', gap: 4, flex: 1 }}>
+              <CalendarIcon fill={Colors.DARK_BLUE} height={20} width={20} />
+              <Text
+                style={{
+                  fontSize: getFontSize(12),
+                  fontWeight: '600',
+                  color: Colors.DARK_BLUE,
+                  flex: 1
+                }}
+              >
+                13 January, 2025
+              </Text>
+            </View>
+
+            <View style={{ flexDirection: 'row', alignItems: 'center', gap: 4, flex: 1 }}>
+              <EarthIcon fill={Colors.DARK_BLUE} height={20} width={20} />
+              <Text
+                style={{
+                  fontSize: getFontSize(12),
+                  fontWeight: '600',
+                  color: Colors.DARK_BLUE,
+                  flex: 1
+                }}
+              >
+                San Clemente, USA
+              </Text>
+            </View>
+          </View>
+
+          <View style={{ flexDirection: 'row', alignItems: 'center', gap: 4 }}>
+            <View style={{ flexDirection: 'row', alignItems: 'center', gap: 4, flex: 1 }}>
+              <LocationIcon fill={Colors.DARK_BLUE} height={20} width={20} />
+              <Text
+                style={{
+                  fontSize: getFontSize(12),
+                  fontWeight: '600',
+                  color: Colors.DARK_BLUE,
+                  flex: 1
+                }}
+              >
+                301 N El Camino Real
+              </Text>
+            </View>
+
+            <View style={{ flexDirection: 'row', alignItems: 'center', gap: 4, flex: 1 }}>
+              <NomadsIcon fill={Colors.DARK_BLUE} height={20} width={20} />
+              <Text
+                style={{
+                  fontSize: getFontSize(12),
+                  fontWeight: '600',
+                  color: Colors.DARK_BLUE,
+                  flex: 1
+                }}
+              >
+                <Text style={{ fontWeight: '700' }}>164/300</Text> Spots Available
+              </Text>
+            </View>
+          </View>
+
+          <View style={styles.stats}>
+            {true ? (
+              <View style={{ gap: 8, flex: 1 }}>
+                <Text style={[styles.travelSeriesTitle, { fontSize: 12 }]}>Host</Text>
+                <TouchableOpacity
+                  style={[styles.statItem, { justifyContent: 'flex-start' }]}
+                  onPress={() =>
+                    navigation.navigate(
+                      ...([
+                        NAVIGATION_PAGES.USERS_LIST,
+                        {
+                          id: 720,
+                          isFromHere: true,
+                          type: 'nm'
+                        }
+                      ] as never)
+                    )
+                  }
+                >
+                  <View style={styles.userImageContainer}>
+                    <Image
+                      source={{ uri: API_HOST }}
+                      style={[styles.userImage, { marginLeft: 0 }]}
+                    />
+                    <View style={{ justifyContent: 'space-between' }}>
+                      <Text
+                        style={{
+                          fontFamily: 'montserrat-700',
+                          fontSize: 12,
+                          color: Colors.DARK_BLUE
+                        }}
+                      >
+                        Harry Mitsidis
+                      </Text>
+                      <Text style={{ fontWeight: '600', fontSize: 12, color: Colors.DARK_BLUE }}>
+                        NM: <Text style={{ fontFamily: 'montserrat-700' }}>1284</Text> / UN:{' '}
+                        <Text style={{ fontFamily: 'montserrat-700' }}>193</Text>
+                      </Text>
+                    </View>
+                  </View>
+                </TouchableOpacity>
+              </View>
+            ) : (
+              <View style={[styles.statItem, { justifyContent: 'flex-start' }]} />
+            )}
+
+            {true ? (
+              <View style={{ gap: 8, flex: 1 }}>
+                <Text style={[styles.travelSeriesTitle, { fontSize: 12 }]}>Participants</Text>
+
+                <TouchableOpacity
+                  style={[styles.statItem, { justifyContent: 'flex-end' }]}
+                  onPress={() =>
+                    navigation.navigate(
+                      ...([
+                        NAVIGATION_PAGES.USERS_LIST,
+                        {
+                          id: 720,
+                          isFromHere: false,
+                          type: 'nm'
+                        }
+                      ] as never)
+                    )
+                  }
+                >
+                  <View style={styles.userImageContainer}>
+                    {/* {data?.data.users_who_visited_region &&
+                    data?.data.users_who_visited_region.length > 0 &&
+                    data?.data.users_who_visited_region?.map((user, index) => (
+                      <Image
+                        key={index}
+                        source={{ uri: API_HOST + user }}
+                        style={[styles.userImage]}
+                      />
+                    ))} */}
+                    <View style={styles.userCountContainer}>
+                      <Text style={styles.userCount}>
+                        {/* {formatNumber(data?.data.users_who_visited_region_count ?? 0)} */}
+                      </Text>
+                    </View>
+                  </View>
+                </TouchableOpacity>
+              </View>
+            ) : (
+              <View style={[styles.statItem, { justifyContent: 'flex-end' }]} />
+            )}
+          </View>
+
+          <View style={[styles.divider]} />
+
+          <View style={{ gap: 16 }}>
+            <Text style={styles.travelSeriesTitle}>Details</Text>
+            <Text style={{ fontWeight: '600', fontSize: 13, color: Colors.DARK_BLUE }}>
+              Lorem Ipsum is simply dummy text of the printing and typesetting industry. Lorem Ipsum
+              has been the industry's standard dummy text ever since the 1500s, when an unknown
+              printer took a galley of type and scrambled it to make a type specimen book. It has
+              survived not only five centuries, but also the leap into electronic typesetting,
+              remaining essentially unchanged. It was popularised in the 1960s with the release of
+              Letraset sheets containing Lorem Ipsum passages, and more recently with desktop
+              publishing software like Aldus PageMaker including versions of Lorem Ipsum.
+            </Text>
+          </View>
+
+          <View style={{ gap: 16 }}>
+            <View
+              style={{
+                flexDirection: 'row',
+                justifyContent: 'space-between',
+                alignItems: 'center'
+              }}
+            >
+              <Text style={styles.travelSeriesTitle}>Attachments</Text>
+              <TouchableOpacity
+                style={{
+                  flexDirection: 'row',
+                  backgroundColor: Colors.ORANGE,
+                  gap: 6,
+                  alignItems: 'center',
+                  justifyContent: 'center',
+                  paddingVertical: 7,
+                  paddingHorizontal: 12,
+                  borderRadius: 20
+                }}
+              >
+                <Text style={{ fontSize: getFontSize(13), fontWeight: '700', color: Colors.WHITE }}>
+                  Add
+                </Text>
+                <FileIcon fill={Colors.WHITE} height={18} />
+              </TouchableOpacity>
+            </View>
+            <FlatList
+              data={isExpanded ? testData : testData.slice(0, 8)}
+              renderItem={renderItem}
+              style={{ flex: 1 }}
+              keyExtractor={(item) => item.id.toString()}
+              numColumns={4}
+              columnWrapperStyle={{
+                justifyContent: 'flex-start',
+                gap: 12
+              }}
+              contentContainerStyle={{
+                gap: 8,
+                alignSelf: 'center'
+              }}
+              showsVerticalScrollIndicator={false}
+              scrollEnabled={false}
+            />
+          </View>
+
+          <View style={{ gap: 16 }}>
+            <View
+              style={{
+                flexDirection: 'row',
+                justifyContent: 'space-between',
+                alignItems: 'center'
+              }}
+            >
+              <Text style={styles.travelSeriesTitle}>Photos</Text>
+              <TouchableOpacity
+                style={{
+                  flexDirection: 'row',
+                  backgroundColor: Colors.ORANGE,
+                  gap: 6,
+                  alignItems: 'center',
+                  justifyContent: 'center',
+                  paddingVertical: 7,
+                  paddingHorizontal: 12,
+                  borderRadius: 20
+                }}
+              >
+                <Text style={{ fontSize: getFontSize(13), fontWeight: '700', color: Colors.WHITE }}>
+                  Add
+                </Text>
+                <ImageIcon fill={Colors.WHITE} width={18} />
+              </TouchableOpacity>
+            </View>
+          </View>
+        </View>
+      </ScrollView>
+    </View>
+  );
+};
+
+export default EventScreen;

+ 216 - 0
src/screens/InAppScreens/TravelsScreen/EventScreen/styles.tsx

@@ -0,0 +1,216 @@
+import { StyleSheet } from 'react-native';
+import { Colors } from '../../../../theme';
+import { getFontSize } from 'src/utils';
+
+export const styles = StyleSheet.create({
+  container: {
+    flex: 1,
+    backgroundColor: 'white',
+    position: 'relative'
+  },
+  chevronWrapper: {
+    width: 42,
+    height: 42,
+    borderRadius: 21,
+    justifyContent: 'center',
+    alignItems: 'center',
+    backgroundColor: 'rgba(0, 0, 0, 0.3)'
+  },
+  backButton: {
+    position: 'absolute',
+    width: 50,
+    height: 50,
+    top: 50,
+    left: 10,
+    justifyContent: 'center',
+    alignItems: 'center',
+    zIndex: 2
+  },
+  emptyImage: {
+    height: 220,
+    width: '100%',
+    backgroundColor: Colors.FILL_LIGHT,
+    alignItems: 'center',
+    justifyContent: 'center',
+    gap: 16,
+    paddingTop: 24,
+    shadowColor: '#00000026',
+    shadowOffset: { width: 0, height: 0 },
+    shadowOpacity: 0.15,
+    shadowRadius: 8,
+    elevation: 8
+  },
+  emptyImageText: { fontWeight: '600', color: '#808080', fontSize: 12 },
+  imageFooter: { paddingBottom: 50, paddingHorizontal: 16, gap: 16 },
+  imageDescription: { color: Colors.WHITE, textAlign: 'center', fontWeight: '600' },
+  imageOwner: {
+    flexDirection: 'row',
+    alignItems: 'center',
+    justifyContent: 'center',
+    gap: 4,
+    backgroundColor: 'rgba(255, 255, 255, 0.8)',
+    paddingVertical: 8,
+    paddingHorizontal: 12,
+    borderRadius: 8,
+    alignSelf: 'center'
+  },
+  imageOwnerText: { color: Colors.DARK_BLUE, fontFamily: 'montserrat-700' },
+  wrapper: {
+    marginLeft: '5%',
+    marginRight: '5%',
+    gap: 16
+  },
+  divider: {
+    height: 1,
+    width: '100%',
+    backgroundColor: Colors.DARK_LIGHT
+  },
+  nameContainer: {
+    flexDirection: 'row',
+    justifyContent: 'space-between',
+    alignItems: 'center',
+    marginTop: 16
+  },
+  title: {
+    fontSize: 18,
+    fontFamily: 'montserrat-700',
+    color: Colors.DARK_BLUE,
+    flex: 1
+  },
+  subtitle: {
+    fontSize: 12,
+    fontWeight: '600',
+    color: Colors.DARK_BLUE
+  },
+  stats: {
+    flexDirection: 'row',
+    justifyContent: 'space-between'
+  },
+  icon: {
+    width: 28,
+    height: 28,
+    borderRadius: 14,
+    backgroundColor: Colors.DARK_BLUE,
+    alignItems: 'center',
+    justifyContent: 'center'
+  },
+  statItem: {
+    flexDirection: 'row',
+    alignItems: 'center',
+    gap: 4,
+    flex: 1
+  },
+  statText: {
+    fontSize: 14,
+    color: 'gray'
+  },
+  statNumber: {
+    fontSize: 16,
+    fontWeight: 'bold'
+  },
+  travelSeriesTitle: {
+    fontSize: 14,
+    fontFamily: 'montserrat-700',
+    textTransform: 'uppercase',
+    color: Colors.DARK_BLUE
+  },
+  userImageContainer: {
+    flexDirection: 'row',
+    alignItems: 'center',
+    gap: 4
+  },
+  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
+  },
+  modalView: {
+    paddingHorizontal: 8,
+    paddingVertical: 24,
+    alignItems: 'center'
+  },
+  infoTitle: {
+    color: Colors.DARK_BLUE,
+    fontSize: 16,
+    fontWeight: '700',
+    textAlign: 'center',
+    marginBottom: 16
+  },
+  infoText: {
+    color: Colors.DARK_BLUE,
+    fontSize: 14,
+    fontWeight: '400',
+    textAlign: 'left',
+    marginBottom: 24
+  },
+  btnContainer: {
+    borderColor: Colors.DARK_BLUE,
+    backgroundColor: Colors.WHITE,
+    width: '60%'
+  },
+  infoContent: { flexDirection: 'row', alignItems: 'center', gap: 4 },
+  visitedButtonText: {
+    color: Colors.DARK_BLUE,
+    fontWeight: 'bold',
+    fontSize: 13
+  },
+  durationContainer: {
+    flexDirection: 'row',
+    justifyContent: 'center',
+    gap: 12
+  },
+  durationItem: {
+    alignItems: 'center',
+    justifyContent: 'center',
+    gap: 4
+  },
+  durationIconActive: {
+    backgroundColor: Colors.WHITE
+  },
+  durationIconInactive: {
+    backgroundColor: Colors.LIGHT_GRAY
+  },
+  visitDuration: {
+    color: Colors.DARK_BLUE,
+    fontWeight: '600',
+    fontSize: getFontSize(11)
+  },
+  goToMapBtn: {
+    position: 'absolute',
+    width: 50,
+    height: 50,
+    top: 50,
+    right: 10,
+    justifyContent: 'center',
+    alignItems: 'center',
+    zIndex: 2
+  },
+  addPhotoButton: {
+    position: 'absolute',
+    width: 50,
+    height: 50,
+    top: 160,
+    right: 10,
+    justifyContent: 'center',
+    alignItems: 'center',
+    zIndex: 2
+  }
+});

+ 155 - 266
src/screens/InAppScreens/TravelsScreen/EventsScreen/index.tsx

@@ -1,298 +1,187 @@
 import React, { FC, useCallback, useEffect, useState } from 'react';
-import { View, Text, Image, TouchableOpacity, Linking, Dimensions, FlatList } from 'react-native';
-import ImageView from 'better-react-native-image-viewing';
-import { styles } from './styles';
+import { View, Text, Image, TouchableOpacity, Linking, Dimensions, TextInput } from 'react-native';
+import { FlashList } from '@shopify/flash-list';
 import { CommonActions, useFocusEffect, useNavigation } from '@react-navigation/native';
-import { Colors } from 'src/theme';
-import { styles as ButtonStyles } from 'src/components/RegionPopup/style';
+import { styles } from './styles';
 
-import { ScrollView } from 'react-native-gesture-handler';
 import { NAVIGATION_PAGES } from 'src/types';
-import { API_HOST } from 'src/constants';
 import { StoreType, storage } from 'src/storage';
-import { ButtonVariants } from 'src/types/components';
-import formatNumber from '../../TravelsScreen/utils/formatNumber';
-import { MaterialCommunityIcons } from '@expo/vector-icons';
 
-import ChevronLeft from 'assets/icons/chevron-left.svg';
-import MapSvg from 'assets/icons/travels-screens/map-location.svg';
 import AddImgSvg from 'assets/icons/travels-screens/add-img.svg';
-import ShareIcon from 'assets/icons/share.svg';
+import { Header, Input, PageWrapper } from 'src/components';
+import { Colors } from 'src/theme';
+
+import SearchIcon from 'assets/icons/search.svg';
+import CalendarIcon from 'assets/icons/events/calendar-solid.svg';
+import EarthIcon from 'assets/icons/travels-section/earth.svg';
+import NomadsIcon from 'assets/icons/bottom-navigation/travellers.svg';
+import CalendarPlusIcon from 'assets/icons/events/calendar-plus.svg';
+import ShoppingCartIcon from 'assets/icons/events/shopping-cart.svg';
 
 const testData = [
   {
-    id: 1,
-    title: 'Attachment 1',
-    url: ''
+    id: '1',
+    title: 'Meeting in San Clemente',
+    date: 'Early February, 2025',
+    location: 'San Clemente, California, USA',
+    spots: '164/300',
+    image: 'https://nomadmania.com/ajax/pic/27519',
+    isPaid: true,
+    status: null
   },
   {
-    id: 2,
-    title: 'Attachment 2',
-    url: ''
+    id: '2',
+    title: 'Meeting in Singapore',
+    date: '08 February, 2025',
+    location: 'Singapore',
+    spots: '250/250',
+    image: 'https://nomadmania.com/ajax/pic/27520',
+    isPaid: true,
+    status: 'FULL'
   },
   {
-    id: 3,
-    title: 'Attachment 3',
-    url: ''
+    id: '3',
+    title: 'Meeting in Patras',
+    date: '14 February, 2025',
+    location: 'Patras, Greece',
+    spots: '119/200',
+    image: 'https://nomadmania.com/ajax/pic/27521',
+    isPaid: false,
+    status: null
   },
   {
-    id: 4,
-    title: 'Attachment 4',
-    url: ''
+    id: '4',
+    title: 'Nepal (including the MusNepal (including the MusNepal (including the Mus',
+    date: 'March 27 – April 7, 2025',
+    location: 'Nepal',
+    spots: '71/150',
+    image: 'https://nomadmania.com/ajax/pic/27522',
+    isPaid: true,
+    status: 'TOUR'
+  },
+  {
+    id: '5',
+    title: 'Meeting in Nicosia, Cyprus',
+    date: '15 April, 2025',
+    location: 'Nicosia, Cyprus',
+    spots: null,
+    image: 'https://nomadmania.com/ajax/pic/27523',
+    isPaid: false,
+    status: null
   }
 ];
 
-const EventsScreen = () => {
-  const token = (storage.get('token', StoreType.STRING) as string) ?? null;
-  const navigation = useNavigation();
-  const [isLoading, setIsLoading] = useState(true);
-  const fileWidth = Dimensions.get('window').width / 5;
-
+const renderEventCard = ({ item }: { item: (typeof testData)[0] }) => {
   return (
-    <View style={styles.container}>
-      <TouchableOpacity
-        onPress={() => {
-          navigation.goBack();
-        }}
-        style={styles.backButton}
-      >
-        <View style={styles.chevronWrapper}>
-          <ChevronLeft fill={Colors.WHITE} />
+    <TouchableOpacity
+      style={[styles.card, item.status ? { backgroundColor: Colors.FILL_LIGHT } : {}]}
+    >
+      <View style={styles.imageWrapper}>
+        <Image source={{ uri: item.image }} style={styles.image} />
+        {item.isPaid && (
+          <View style={styles.iconOverlay}>
+            <ShoppingCartIcon fill={Colors.WHITE} width={12} />
+          </View>
+        )}
+      </View>
+
+      <View style={styles.info}>
+        <Text style={styles.title} numberOfLines={1}>
+          {item.title}
+        </Text>
+
+        <View style={styles.row}>
+          <CalendarIcon fill={Colors.DARK_BLUE} height={14} width={14} />
+          <Text style={styles.dateAndLocation} numberOfLines={1}>
+            {item.date}
+          </Text>
         </View>
-      </TouchableOpacity>
 
-      <ScrollView
-        contentContainerStyle={{}}
-        nestedScrollEnabled={true}
-        showsVerticalScrollIndicator={false}
-      >
-        <View style={styles.emptyImage}>
-          <Image
-            source={require('../../../../../assets/images/logo-opacity.png')}
-            style={{ width: 100, height: 100 }}
-          />
-          <Text style={styles.emptyImageText}>No image available at this location</Text>
+        <View style={styles.row}>
+          <EarthIcon fill={Colors.DARK_BLUE} height={14} width={14} />
+          <Text style={styles.dateAndLocation} numberOfLines={1}>
+            {item.location}
+          </Text>
         </View>
-        <TouchableOpacity onPress={() => {}} style={styles.addPhotoButton}>
-          <View style={styles.chevronWrapper}>
-            <AddImgSvg fill={Colors.WHITE} width={20} height={20} />
-          </View>
-        </TouchableOpacity>
-
-        <TouchableOpacity
-          onPress={() => {
-            // route.params?.isTravelsScreen || route.params?.isProfileScreen
-            //   ? navigation.dispatch(
-            //       CommonActions.reset({
-            //         index: 1,
-            //         routes: [
-            //           {
-            //             name: NAVIGATION_PAGES.IN_APP_MAP_TAB,
-            //             state: {
-            //               routes: [
-            //                 {
-            //                   name: NAVIGATION_PAGES.MAP_TAB,
-            //                   params: { id: regionId, type: type === 'nm' ? 'regions' : 'places' }
-            //                 }
-            //               ]
-            //             }
-            //           }
-            //         ]
-            //       })
-            //     )
-            //   : navigation.goBack();
-          }}
-          style={styles.goToMapBtn}
-        >
-          <View style={styles.chevronWrapper}>
-            <MapSvg fill={Colors.WHITE} />
-          </View>
-        </TouchableOpacity>
-
-        <View style={styles.wrapper}>
-          <View style={styles.nameContainer}>
-            <Text style={styles.title}>Meeting in San Clemente</Text>
-            <View style={[ButtonStyles.btnContainer, { gap: 2, flex: 0 }]}>
-              <TouchableOpacity
-                style={[ButtonStyles.btn, ButtonStyles.markVisitedButton]}
-                onPress={() => {}}
-              >
-                <Text style={[ButtonStyles.markVisitedText, { fontFamily: 'redhat-700' }]}>
-                  JOIN
-                </Text>
-              </TouchableOpacity>
-
-              <TouchableOpacity
-                onPress={() => {}}
-                style={{
-                  alignItems: 'center',
-                  justifyContent: 'center',
-                  padding: 8,
-                  paddingRight: 0
-                }}
-              >
-                <ShareIcon
-                  width={20}
-                  height={20}
-                  fill={Colors.DARK_BLUE}
-                  style={{ alignSelf: 'center' }}
-                />
-              </TouchableOpacity>
-            </View>
-          </View>
-
-          <View style={styles.divider} />
-
-          <View style={styles.stats}>
-            {true ? (
-              <View style={{ gap: 8, flex: 1 }}>
-                <Text style={[styles.travelSeriesTitle, { fontSize: 12 }]}>Host</Text>
-                <TouchableOpacity
-                  style={[styles.statItem, { justifyContent: 'flex-start' }]}
-                  onPress={() =>
-                    navigation.navigate(
-                      ...([
-                        NAVIGATION_PAGES.USERS_LIST,
-                        {
-                          id: 720,
-                          isFromHere: true,
-                          type: 'nm'
-                        }
-                      ] as never)
-                    )
-                  }
-                >
-                  <View style={styles.userImageContainer}>
-                    <Image
-                      source={{ uri: API_HOST }}
-                      style={[styles.userImage, { marginLeft: 0 }]}
-                    />
-                    <View style={{ justifyContent: 'space-between' }}>
-                      <Text
-                        style={{
-                          fontFamily: 'montserrat-700',
-                          fontSize: 12,
-                          color: Colors.DARK_BLUE
-                        }}
-                      >
-                        Harry Mitsidis
-                      </Text>
-                      <Text style={{ fontWeight: '600', fontSize: 12, color: Colors.DARK_BLUE }}>
-                        NM: <Text style={{ fontFamily: 'montserrat-700' }}>1284</Text> / UN:{' '}
-                        <Text style={{ fontFamily: 'montserrat-700' }}>193</Text>
-                      </Text>
-                    </View>
-                  </View>
-                </TouchableOpacity>
-              </View>
-            ) : (
-              <View style={[styles.statItem, { justifyContent: 'flex-start' }]} />
-            )}
 
-            {true ? (
-              <View style={{ gap: 8, flex: 1 }}>
-                <Text style={[styles.travelSeriesTitle, { fontSize: 12 }]}>Participants</Text>
-
-                <TouchableOpacity
-                  style={[styles.statItem, { justifyContent: 'flex-end' }]}
-                  onPress={() =>
-                    navigation.navigate(
-                      ...([
-                        NAVIGATION_PAGES.USERS_LIST,
-                        {
-                          id: 720,
-                          isFromHere: false,
-                          type: 'nm'
-                        }
-                      ] as never)
-                    )
-                  }
-                >
-                  <View style={styles.userImageContainer}>
-                    {/* {data?.data.users_who_visited_region &&
-                    data?.data.users_who_visited_region.length > 0 &&
-                    data?.data.users_who_visited_region?.map((user, index) => (
-                      <Image
-                        key={index}
-                        source={{ uri: API_HOST + user }}
-                        style={[styles.userImage]}
-                      />
-                    ))} */}
-                    <View style={styles.userCountContainer}>
-                      <Text style={styles.userCount}>
-                        {/* {formatNumber(data?.data.users_who_visited_region_count ?? 0)} */}
-                      </Text>
-                    </View>
-                  </View>
-                </TouchableOpacity>
-              </View>
-            ) : (
-              <View style={[styles.statItem, { justifyContent: 'flex-end' }]} />
-            )}
-          </View>
-
-          <View style={[styles.divider]} />
-
-          <View style={{ gap: 16 }}>
-            <Text style={styles.travelSeriesTitle}>Details</Text>
-            <Text style={{ fontWeight: '600', fontSize: 13, color: Colors.DARK_BLUE }}>
-              Lorem Ipsum is simply dummy text of the printing and typesetting industry. Lorem Ipsum
-              has been the industry's standard dummy text ever since the 1500s, when an unknown
-              printer took a galley of type and scrambled it to make a type specimen book. It has
-              survived not only five centuries, but also the leap into electronic typesetting,
-              remaining essentially unchanged. It was popularised in the 1960s with the release of
-              Letraset sheets containing Lorem Ipsum passages, and more recently with desktop
-              publishing software like Aldus PageMaker including versions of Lorem Ipsum.
+        {item.spots && (
+          <View style={styles.row}>
+            <NomadsIcon fill={Colors.DARK_BLUE} height={14} width={14} />
+            <Text style={styles.dateAndLocation} numberOfLines={1}>
+              <Text style={{ fontWeight: '700' }}>{item.spots}</Text> Spots Available
             </Text>
           </View>
+        )}
+      </View>
 
-          <View style={{ gap: 16 }}>
-            <Text style={styles.travelSeriesTitle}>Photos</Text>
-          </View>
-
-          <View style={{ gap: 16 }}>
-            <Text style={styles.travelSeriesTitle}>Attachments</Text>
-            <FlatList
-              data={testData}
-              renderItem={({ item }) => (
-                <TouchableOpacity
-                  style={{ gap: 4 }}
-                  onPress={() => {
-                    // Linking.openURL(item.url);
-                  }}
-                >
-                  <View
-                    style={{
-                      backgroundColor: Colors.FILL_LIGHT,
-                      borderRadius: 8,
-                      alignItems: 'center',
-                      justifyContent: 'center',
-                      height: fileWidth,
-                      width: fileWidth
-                    }}
-                  >
-                    <MaterialCommunityIcons name="file" size={36} color={Colors.DARK_BLUE} />
-                  </View>
-                  <Text style={{ fontSize: 12, fontWeight: '600', color: Colors.DARK_BLUE }}>
-                    {item.title}
-                  </Text>
-                </TouchableOpacity>
-              )}
-              style={{ flex: 1 }}
-              contentContainerStyle={{
-                flex: 1,
-                gap: 8
-              }}
-              keyExtractor={(item) => item.id.toString()}
-              numColumns={4}
-              columnWrapperStyle={{ justifyContent: 'space-between' }}
-              showsVerticalScrollIndicator={false}
-              scrollEnabled={false}
-            />
+      {item.status && (
+        <View
+          style={[styles.statusBadge, item.status === 'FULL' ? styles.fullBadge : styles.tourBadge]}
+        >
+          <View style={styles.rotatedContainer}>
+            <Text style={styles.statusText}>{item.status}</Text>
           </View>
         </View>
-      </ScrollView>
-    </View>
+      )}
+    </TouchableOpacity>
+  );
+};
+
+const EventsScreen = () => {
+  const token = (storage.get('token', StoreType.STRING) as string) ?? null;
+  const navigation = useNavigation();
+  const [searchQuery, setSearchQuery] = useState('');
+  const [filteredData, setFilteredData] = useState(testData);
+
+  const handleSearch = (text: string) => {
+    if (text) {
+      const searchData =
+        testData?.filter((item: any) => {
+          const itemData = item.title ? item.title.toLowerCase() : ''.toLowerCase();
+          const textData = text.toLowerCase();
+          return itemData.indexOf(textData) > -1;
+        }) ?? [];
+      setFilteredData(searchData);
+      setSearchQuery(text);
+    } else {
+      setFilteredData(testData);
+      setSearchQuery(text);
+    }
+  };
+
+  return (
+    <PageWrapper>
+      <Header
+        label="Events"
+        rightElement={
+          <TouchableOpacity
+            onPress={() => navigation.navigate(NAVIGATION_PAGES.EVENT as never)}
+            style={{ width: 30 }}
+          >
+            <CalendarPlusIcon fill={Colors.DARK_BLUE} />
+          </TouchableOpacity>
+        }
+      />
+
+      <View style={styles.searchContainer}>
+        <Input
+          inputMode={'search'}
+          placeholder={'Search'}
+          onChange={(text) => handleSearch(text)}
+          value={searchQuery}
+          icon={<SearchIcon fill={'#C8C8C8'} width={14} height={14} />}
+          height={38}
+        />
+      </View>
+
+      <FlashList
+        data={filteredData}
+        keyExtractor={(item) => item.id}
+        renderItem={renderEventCard}
+        estimatedItemSize={100}
+        contentContainerStyle={styles.listContainer}
+      />
+    </PageWrapper>
   );
 };
 

+ 61 - 186
src/screens/InAppScreens/TravelsScreen/EventsScreen/styles.tsx

@@ -1,216 +1,91 @@
-import { StyleSheet } from 'react-native';
+import { Dimensions, StyleSheet } from 'react-native';
 import { Colors } from '../../../../theme';
 import { getFontSize } from 'src/utils';
 
 export const styles = StyleSheet.create({
-  container: {
-    flex: 1,
-    backgroundColor: 'white',
-    position: 'relative'
-  },
-  chevronWrapper: {
-    width: 42,
-    height: 42,
-    borderRadius: 21,
-    justifyContent: 'center',
-    alignItems: 'center',
-    backgroundColor: 'rgba(0, 0, 0, 0.3)'
-  },
-  backButton: {
-    position: 'absolute',
-    width: 50,
-    height: 50,
-    top: 50,
-    left: 10,
-    justifyContent: 'center',
-    alignItems: 'center',
-    zIndex: 2
+  listContainer: {
+    paddingTop: 12
+    // paddingBottom: 20,
   },
-  emptyImage: {
-    height: 220,
-    width: '100%',
-    marginBottom: 12,
-    backgroundColor: Colors.FILL_LIGHT,
-    alignItems: 'center',
-    justifyContent: 'center',
-    gap: 16,
-    paddingTop: 24,
-    shadowColor: '#00000026',
-    shadowOffset: { width: 0, height: 0 },
-    shadowOpacity: 0.15,
-    shadowRadius: 8,
-    elevation: 8
+  searchContainer: {
+    paddingBottom: 6
   },
-  emptyImageText: { fontWeight: '600', color: '#808080', fontSize: 12 },
-  imageFooter: { paddingBottom: 50, paddingHorizontal: 16, gap: 16 },
-  imageDescription: { color: Colors.WHITE, textAlign: 'center', fontWeight: '600' },
-  imageOwner: {
+  card: {
+    borderRadius: 8,
+    marginBottom: 16,
     flexDirection: 'row',
     alignItems: 'center',
-    justifyContent: 'center',
-    gap: 4,
-    backgroundColor: 'rgba(255, 255, 255, 0.8)',
-    paddingVertical: 8,
-    paddingHorizontal: 12,
-    borderRadius: 8,
-    alignSelf: 'center'
+    gap: 10
   },
-  imageOwnerText: { color: Colors.DARK_BLUE, fontFamily: 'montserrat-700' },
-  wrapper: {
-    marginLeft: '5%',
-    marginRight: '5%',
-    gap: 16
+  imageWrapper: {
+    position: 'relative'
   },
-  divider: {
-    height: 1,
-    width: '100%',
-    backgroundColor: Colors.DARK_LIGHT
+  image: {
+    width: 86,
+    height: 86,
+    borderRadius: 8
   },
-  nameContainer: {
-    flexDirection: 'row',
-    justifyContent: 'space-between',
+  iconOverlay: {
+    position: 'absolute',
+    top: 0,
+    left: 0,
+    width: 20,
+    height: 20,
+    backgroundColor: Colors.ORANGE,
+    borderTopLeftRadius: 8,
+    borderBottomRightRadius: 8,
+    justifyContent: 'center',
     alignItems: 'center'
   },
+  info: {
+    flex: 1,
+    paddingVertical: 5,
+    gap: 5
+  },
   title: {
-    fontSize: 18,
+    fontSize: getFontSize(16),
     fontFamily: 'montserrat-700',
-    color: Colors.DARK_BLUE,
-    flex: 1
-  },
-  subtitle: {
-    fontSize: 12,
-    fontWeight: '600',
+    lineHeight: 16,
     color: Colors.DARK_BLUE
   },
-  stats: {
-    flexDirection: 'row',
-    justifyContent: 'space-between'
-  },
-  icon: {
-    width: 28,
-    height: 28,
-    borderRadius: 14,
-    backgroundColor: Colors.DARK_BLUE,
-    alignItems: 'center',
-    justifyContent: 'center'
-  },
-  statItem: {
-    flexDirection: 'row',
-    alignItems: 'center',
-    gap: 4,
-    flex: 1
-  },
-  statText: {
-    fontSize: 14,
-    color: 'gray'
-  },
-  statNumber: {
-    fontSize: 16,
-    fontWeight: 'bold'
-  },
-  travelSeriesTitle: {
-    fontSize: 14,
-    fontFamily: 'montserrat-700',
-    textTransform: 'uppercase',
+  dateAndLocation: {
+    fontSize: getFontSize(12),
+    fontWeight: '600',
     color: Colors.DARK_BLUE
   },
-  userImageContainer: {
-    flexDirection: 'row',
-    alignItems: 'center',
-    gap: 4
-  },
-  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
-  },
-  modalView: {
-    paddingHorizontal: 8,
-    paddingVertical: 24,
-    alignItems: 'center'
-  },
-  infoTitle: {
-    color: Colors.DARK_BLUE,
-    fontSize: 16,
-    fontWeight: '700',
-    textAlign: 'center',
-    marginBottom: 16
-  },
-  infoText: {
-    color: Colors.DARK_BLUE,
+  spots: {
     fontSize: 14,
-    fontWeight: '400',
-    textAlign: 'left',
-    marginBottom: 24
+    color: '#FF5E00',
+    marginTop: 2
   },
-  btnContainer: {
-    borderColor: Colors.DARK_BLUE,
-    backgroundColor: Colors.WHITE,
-    width: '60%'
-  },
-  infoContent: { flexDirection: 'row', alignItems: 'center', gap: 4 },
-  visitedButtonText: {
-    color: Colors.DARK_BLUE,
-    fontWeight: 'bold',
-    fontSize: 13
-  },
-  durationContainer: {
-    flexDirection: 'row',
+  statusBadge: {
     justifyContent: 'center',
-    gap: 12
-  },
-  durationItem: {
     alignItems: 'center',
-    justifyContent: 'center',
-    gap: 4
-  },
-  durationIconActive: {
-    backgroundColor: Colors.WHITE
+    backgroundColor: Colors.DARK_BLUE,
+    borderTopRightRadius: 8,
+    borderBottomRightRadius: 8,
+    width: 23,
+    height: '100%'
   },
-  durationIconInactive: {
-    backgroundColor: Colors.LIGHT_GRAY
+  fullBadge: {
+    backgroundColor: Colors.DARK_BLUE
   },
-  visitDuration: {
-    color: Colors.DARK_BLUE,
-    fontWeight: '600',
-    fontSize: getFontSize(11)
+  tourBadge: {
+    backgroundColor: Colors.ORANGE
   },
-  goToMapBtn: {
-    position: 'absolute',
-    width: 50,
-    height: 50,
-    top: 50,
-    right: 10,
+  rotatedContainer: {
+    transform: [{ rotate: '-90deg' }],
     justifyContent: 'center',
     alignItems: 'center',
-    zIndex: 2
+    height: 23,
+    width: 80
   },
-  addPhotoButton: {
-    position: 'absolute',
-    width: 50,
-    height: 50,
-    top: 160,
-    right: 10,
-    justifyContent: 'center',
-    alignItems: 'center',
-    zIndex: 2
-  }
+
+  statusText: {
+    color: Colors.WHITE,
+    fontSize: getFontSize(16),
+    fontFamily: 'montserrat-700',
+    textAlign: 'center'
+  },
+  row: { flexDirection: 'row', gap: 6, alignItems: 'center' }
 });

+ 0 - 8
src/screens/LocationSharingScreen/index.tsx

@@ -190,14 +190,6 @@ const LocationSharingScreen = ({ navigation }: { navigation: any }) => {
     <PageWrapper>
       <Header label="Location sharing" />
       <View style={textStyles.container}>
-        <Text style={textStyles.textWithIcon}>
-          Your location is shared each time you launch the app and whenever you press the{'  '}
-          <View style={textStyles.icon}>
-            <LocationIcon width={12} height={12} />
-          </View>
-          {'  '}
-          button.
-        </Text>
         <Text style={textStyles.text}>The location is shared with 1 km radius precision.</Text>
       </View>
       <TouchableOpacity

+ 45 - 0
src/stores/groupChatStore.ts

@@ -0,0 +1,45 @@
+import { create } from 'zustand';
+import { ImagePickerAsset } from 'expo-image-picker';
+
+interface User {
+  user_id: string;
+  first_name: string;
+  last_name: string;
+  avatar?: string;
+  flag1?: string;
+}
+
+interface GroupChatStore {
+  selectedUsers: User[] | [];
+  image: ImagePickerAsset | null;
+  groupName: string;
+  description: string;
+  addUser: (user: User) => void;
+  removeUser: (userId: string) => void;
+  setImage: (image: ImagePickerAsset | null) => void;
+  setGroupName: (name: string) => void;
+  setDescription: (desc: string) => void;
+  clearStore: () => void;
+}
+
+export const useGroupChatStore = create<GroupChatStore>((set) => ({
+  selectedUsers: [],
+  image: null,
+  groupName: '',
+  description: '',
+  addUser: (user) => set((state) => ({ selectedUsers: [user, ...state.selectedUsers] })),
+  removeUser: (userId) =>
+    set((state) => ({
+      selectedUsers: state.selectedUsers.filter((user) => user.user_id !== userId)
+    })),
+  setImage: (image) => set({ image }),
+  setGroupName: (name) => set({ groupName: name }),
+  setDescription: (desc) => set({ description: desc }),
+  clearStore: () =>
+    set({
+      selectedUsers: [],
+      image: null,
+      groupName: '',
+      description: ''
+    })
+}));

+ 15 - 4
src/types/api.ts

@@ -6,7 +6,6 @@ export enum API_ROUTE {
   SERIES = 'series',
   RANKING = 'ranking',
   UN_MASTERS = 'un-masters',
-  AVATARS = 'avatars',
   STATISTICS = 'statistics',
   KYE = 'kye',
   PHOTOS = 'photos',
@@ -25,7 +24,7 @@ export enum API_ROUTE {
   CHAT = 'chat',
   MAPS = 'maps',
   DARE = 'dare',
-  LOCATION = 'location',
+  LOCATION = 'location'
 }
 
 export enum API_ENDPOINT {
@@ -47,7 +46,6 @@ export enum API_ENDPOINT {
   GET_IN_MEMORIAM = 'get-app-in-memoriam',
   GET_UN_MASTERS_TYPES = 'get-types',
   GET_UN_MASTERS_TYPE = 'get-type',
-  GET_UPDATED_AVATARS = 'get-updates',
   GET_LIST = 'get-list',
   GET_STATISTIC = 'get-stat',
   SERIES_GROUPS = 'get-series-groups',
@@ -162,6 +160,13 @@ export enum API_ENDPOINT {
   REPORT_CONVERSATION = 'report-conversation',
   GET_USERS_COUNT = 'get-users-on-map-count',
   UPDATE_EMAIL = 'update-email',
+  CREATE_GROUP = 'create-group',
+  GET_GROUP_CHAT = 'get-group-conversation',
+  SEND_GROUP_MESSAGE = 'send-group-message',
+  GROUP_MESSAGES_RECEIVED = 'group-messages-received',
+  GROUP_MESSAGES_READ = 'group-messages-read',
+  REACT_TO_GROUP_MESSAGE = 'react-to-group-message',
+  UNREACT_TO_GROUP_MESSAGE = 'unreact-to-group-message'
 }
 
 export enum API {
@@ -182,7 +187,6 @@ export enum API {
   GET_IN_MEMORIAM = `${API_ROUTE.RANKING}/${API_ENDPOINT.GET_IN_MEMORIAM}`,
   GET_UN_MASTERS_TYPES = `${API_ROUTE.UN_MASTERS}/${API_ENDPOINT.GET_UN_MASTERS_TYPES}`,
   GET_UN_MASTERS_TYPE = `${API_ROUTE.UN_MASTERS}/${API_ENDPOINT.GET_UN_MASTERS_TYPE}`,
-  GET_UPDATED_AVATARS = `${API_ROUTE.AVATARS}/${API_ENDPOINT.GET_UPDATED_AVATARS}`,
   GET_LIST = `${API_ROUTE.STATISTICS}/${API_ENDPOINT.GET_LIST}`,
   GET_STATISTIC = `${API_ROUTE.STATISTICS}/${API_ENDPOINT.GET_STATISTIC}`,
   SERIES_GROUPS = `${API_ROUTE.SERIES}/${API_ENDPOINT.SERIES_GROUPS}`,
@@ -297,6 +301,13 @@ export enum API {
   REPORT_CONVERSATION = `${API_ROUTE.CHAT}/${API_ENDPOINT.REPORT_CONVERSATION}`,
   GET_USERS_COUNT = `${API_ROUTE.LOCATION}/${API_ENDPOINT.GET_USERS_COUNT}`,
   UPDATE_EMAIL = `${API_ROUTE.USER}/${API_ENDPOINT.UPDATE_EMAIL}`,
+  CREATE_GROUP = `${API_ROUTE.CHAT}/${API_ENDPOINT.CREATE_GROUP}`,
+  GET_GROUP_CHAT = `${API_ROUTE.CHAT}/${API_ENDPOINT.GET_GROUP_CHAT}`,
+  SEND_GROUP_MESSAGE = `${API_ROUTE.CHAT}/${API_ENDPOINT.SEND_GROUP_MESSAGE}`,
+  GROUP_MESSAGES_RECEIVED = `${API_ROUTE.CHAT}/${API_ENDPOINT.GROUP_MESSAGES_RECEIVED}`,
+  GROUP_MESSAGES_READ = `${API_ROUTE.CHAT}/${API_ENDPOINT.GROUP_MESSAGES_READ}`,
+  REACT_TO_GROUP_MESSAGE = `${API_ROUTE.CHAT}/${API_ENDPOINT.REACT_TO_GROUP_MESSAGE}`,
+  UNREACT_TO_GROUP_MESSAGE = `${API_ROUTE.CHAT}/${API_ENDPOINT.UNREACT_TO_GROUP_MESSAGE}`
 }
 
 export type BaseAxiosError = AxiosError;

+ 2 - 0
src/types/navigation.ts

@@ -70,7 +70,9 @@ export enum NAVIGATION_PAGES {
   IN_APP_MESSAGES_TAB = 'Messages',
   CHATS_LIST = 'inAppChatsList',
   CHAT = 'inAppChat',
+  GROUP_CHAT = 'inAppGroupChat',
   LOCATION_SHARING = 'inAppLocationSharing',
   EVENTS = 'inAppEvents',
+  EVENT = 'inAppEvent',
   FULL_MAP_VIEW = 'inAppFullMapView',
 }